Name

sn_em_tbac.EvtMgmtTagBasedDefinitionToACR

Description

Generates alert correlation rule from Alert Clustering Definition and Alert Clustering Tags

Script

var EvtMgmtTagBasedDefinitionToACR = Class.create();
EvtMgmtTagBasedDefinitionToACR.prototype = {
  type: 'EvtMgmtTagBasedDefinitionToACR',

  initialize: function() {
      this.tbacUtils = new sn_em_tbac.EvtMgmtTagBasedAlertClusteringUtils();

      this.ACR_NAME_PREFIX = '[Tag Based]' + ' '; // Prefix for referenced alert correlation rule for easy identification.

      this.ACR_DESCRIPTION_PREPEND = 'This alert correlation rule and its advanced mode script were created automatically by Tag Based Alert Clustering Engine.\n\n\
      !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\
      !!! ANY MANUAL MODIFICATION TO THIS SCRIPT MAY BE OVERRIDDEN !!!\n\
      !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n';
  },

  /**
   * On creation or update of Definition with Tags:
   * 1. Create alert correlation rule if it doesn't exist.
   * 2. Update the script of the alert correlation rule - whether it exists or being created.
   * @param definitionRecord - A Definition record. Reference from M2M table (current.alert_clustering_definition) or from Definitions table (current)
   * @returns {String} - sys_id of the created/updated alert correlation rule
   */
  upsertACR: function(definitionRecord) {
      // Get the alert correlation rule that's associated with the definition
      var acr = new GlideRecord('em_alert_correlation_rule');
      acr.get(definitionRecord.alert_correlation_rule);

      // Create alert correlation rule if it doesn't exist
      if (!acr.isValidRecord()) {
          acr.initialize();
          acr.setNewGuidValue(gs.generateGUID());
          acr.advanced = true; // Always advanced
          acr.relationship_type = "1"; // "1" - No relationship 
      }

      // When the virtual alerts feature is active, create a virtual alert
      acr.generate_virtual_alerts = this.tbacUtils.isVirtualAlertFeatureActive();

      // Update ACR fields according to Definition fields even if exists
      acr.active = definitionRecord.getValue('active');
      acr.name = this.ACR_NAME_PREFIX + definitionRecord.getValue('name');
      acr.description = this.getDescriptionForACR(definitionRecord.getUniqueValue(), definitionRecord.sys_domain.getDisplayValue(), definitionRecord.getValue('sys_domain'), definitionRecord.getValue('name'), definitionRecord.getValue('description'), definitionRecord.sys_overrides.getDisplayValue(), definitionRecord.getValue('sys_overrides'));
      acr.advanced_filter = definitionRecord.getValue('alert_filter');
      acr.order = definitionRecord.getValue('order');
      acr.time_difference = definitionRecord.getValue('time_difference');
      acr.sys_domain = definitionRecord.getValue('sys_domain');
      acr.rule_origin = "2"; // "2" - Tag Based Alert Clustering Engine
      acr.override_group_description = definitionRecord.getValue('override_group_description');
      acr.custom_group_description = definitionRecord.getValue('custom_group_description');

      var acrSysId = acr.getUniqueValue() ? acr.getUniqueValue() : String(definitionRecord.alert_correlation_rule);

      // Get an updated ACR script and set it to the ACR
      acr.script = this.getACRScript(
          acrSysId,
          definitionRecord.getUniqueValue(),
          definitionRecord.getValue('name'),
          definitionRecord.sys_domain.getDisplayValue(),
          definitionRecord.getValue('sys_domain'),
          definitionRecord.getValue('alert_filter'),
          definitionRecord.getValue('compared_alerts_limit'),
          definitionRecord.getValue('time_difference'),
          definitionRecord.sys_overrides.getDisplayValue(),
          definitionRecord.getValue('sys_overrides')
      );

      // acr.update() inserts a new ACR if it doesn't exist, or updates it if it does.
      return acr.update(); // Return the sys_id of the created/updated alert correlation rule
  },

  /* Returns description for the alert correlation rule that will be created.
   * This description includes:
   * 1. The Definition sys_id, sys_domain and name that created the ACR.
   * 2. The description of the Definition.
   * 3. The Tags that were used in the definition and for the ACR.
   */
  getDescriptionForACR: function(definitionSysId, definitionDomainName, definitionDomainId, definitionName, definitionDescription, definitionOverridesName, definitionOverridesId) {
      var definitionDetails = [
          'sys_id: ' + definitionSysId,
          'domain: ' + definitionDomainName + ' (sys_domain: ' + definitionDomainId + ')',
          'overrides: ' + definitionOverridesName + ' (sys_overrides: ' + definitionOverridesId + ')',
      ];
      var description = this.ACR_DESCRIPTION_PREPEND + 'From Definition: "' + definitionName + '", ' + definitionDetails.join(', ');

      if (definitionDescription) {
          description += '\n' + "Definition's description:\n" + definitionDescription;
      }

      var tagsData = this.tbacUtils.getTagsDataForDefinition(definitionSysId, definitionDomainId);
      if (tagsData.hasTags()) {
          var tags = tagsData.getAllTags();
          description += '\n\n' + 'Definition\'s Tags:';

          for (var i = 0; i < tags.length; i++) {
              var tag = tags[i];
              var tagDetails = [
                  'name and sys_id: ' + tag.name + ' (sys_id: ' + tag.sys_id + ')', // Tag name and sys_id
                  'domain: ' + tag.sys_domain_display_value + ' (sys_domain: ' + tag.sys_domain + ')', // Tag domain
                  'overrides: ' + tag.sys_overrides_display_value + ' (sys_overrides: ' + tag.sys_overrides + ')' // Tag overrides
              ];
              description += '\n' + (i + 1) + '. ' + tagDetails.join(', ');
          }
      }

      return description;
  },

  getACRScript: function(acrSysId, definitionSysId, definitionName, definitionDomainName, definitionDomainId, alertFilter, comparedAlertsLimit, timeDifference, domainOverridesName, domainOverridesId) {
      var tagsData = this.tbacUtils.getTagsDataForDefinition(definitionSysId, definitionDomainId);
      if (!tagsData.hasTags()) {
          // Definition without tags is invalid and can't exist, return empty string for its script.
          return '';
      }

      // Modify and enrich the base alert correlation rule script with every tag's unique data.
      var enrich = {
          beforeQuery: '',
          query: '',
          afterQuery: '',
          loopStart: '',
          comparison: ''
      };

      // The field to compare tags with source alert tags.
      var alertTagsField = this.tbacUtils.getAlertTagsField();

      var allTags = tagsData.tags;
      for (var tagsSource in allTags) {
          // classifiedTags - arrays of [em_alert tags], [cmdb_ci tags], [additional_info tags].
          var classifiedTags = allTags[tagsSource];

          // Skip empty arrays without tags.
          if (!classifiedTags.length) continue;

          var query = '';

          for (var i in classifiedTags) {
              // Get the Tag data (a TagEntity instance).
              var tagEntity = classifiedTags[i];

              // Add the name and sys_id of the tag for debugging purposes.
              var tagDebuggingComment = '\n// Tag enrichment step: "<step>" | From the tag named: "' + tagEntity.name + '", with sys_id: ' + tagEntity.sys_id + ' from domain: ' + tagEntity.sys_domain_display_value + ' (sys_domain: ' + tagEntity.sys_domain + ') and overrides: ' + tagEntity.sys_overrides_display_value + ' (sys_overrides: ' + tagEntity.sys_overrides + ')' + '\n';

              // Before query
              if (tagEntity.getBeforeQuery()) {
                  enrich.beforeQuery += tagDebuggingComment.replace('<step>', 'Before Query');
                  enrich.beforeQuery += tagEntity.getBeforeQuery();
              }

              // Query
              if (tagEntity.getQuery()) {
                  query += tagDebuggingComment.replace('<step>', 'Query');
                  query += tagEntity.getQuery();
              }

              // After query
              if (tagEntity.getAfterQuery()) {
                  enrich.afterQuery += tagDebuggingComment.replace('<step>', 'After Query');
                  enrich.afterQuery += (tagEntity.getAfterQuery() || '');
              }

              // Loop start
              if (tagEntity.getLoopStart()) {
                  enrich.loopStart += tagDebuggingComment.replace('<step>', 'Loop Start');
                  enrich.loopStart += tagEntity.getLoopStart();
              }

              // Comparison
              if (tagEntity.getComparison()) {
                  // tag enrichment debugging comment should be implemented in getComparison() according to the Tag's match method, field/key, etc.
                  // AND (&&) operator is the only supported operator at the moment.
                  enrich.comparison += tagEntity.getComparison() + ' && \n\n';
              }
          }

          if (query) {
              // Wrap queries in gs.getProperty that will allow to skip them when their value is false.
              // Properties are:
              // evt_mgmt.sn_em_tbac.use_additional_info_tags_in_query
              // evt_mgmt.sn_em_tbac.use_cmdb_ci_tags_in_alerts_query
              // evt_mgmt.sn_em_tbac.use_em_alert_tags_in_query
              // evt_mgmt.sn_em_tbac.use_alert_tags_tags_in_query
              enrich.query +=
                  "// Tags from the " + tagsSource + " source (source = " + tagsSource + ") \n\
                  if (gs.getProperty('evt_mgmt.sn_em_tbac.use_" + tagsSource + "_tags_in_query', 'true') === 'true') { \n\
                      " + query + "\
                  } \n\n";
          }
      }

      if (enrich.comparison.lastIndexOf('&&')) {
          // Remove the last && string that was appended.
          enrich.comparison = enrich.comparison.substring(0, enrich.comparison.lastIndexOf('&&'));
      }

      // Escape characters that may break the generated ACR script, see the attached image in DEF0229745 for example.
      definitionName = this.tbacUtils.stripAndEscapeChars(definitionName);

      // This is the final ACR script. Initialized with base script and being enriched with data from tags.
      var acrScript = "\
/** \n\
* IMPORTANT NOTICE: \n\
* This script is AUTO-GENERATED from Tag Based Alert Clustering Definition: '" + definitionName + "' with sys_id: " + definitionSysId + " from domain " + definitionDomainName + " sys_domain: " + definitionDomainId + " and overrides: " + domainOverridesName + " sys_overrides: " + domainOverridesId + "\n\
* /sn_em_tbac_alert_clustering_definitions.do?sys_id=" + definitionSysId + " \n\
* \n\
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! \n\
* !!! ANY MANUAL MODIFICATION TO THIS SCRIPT MAY BE OVERRIDDEN !!! \n\
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! \n\
*/ \n\
(function findCorrelatedAlerts(currentAlert){ \n\
  try {\n\
  	if (!currentAlert || !(currentAlert instanceof GlideRecord)) {\n\
  		throw('currentAlert argument is missing or invalid');\n\
  	}\n\
  	if (!currentAlert.getValue('initial_remote_time')) {\n\
  		throw('currentAlert with sys_id:' + currentAlert.getUniqueValue() + ' and number ' + currentAlert.getValue('number') + ' does not have a value for its \"initial_remote_time\" field.');\n\
  	}\n\
  	var t = {\n\
  		start: Date.now() // Measure milliseconds (ms) at the beginning of the script. \n\
  	};\n\n\
  	var acrUtils;\n\
  	if (typeof(EvtMgmtAlertCorrelationRulesUtils) != 'undefined') {\n\
  		acrUtils = new EvtMgmtAlertCorrelationRulesUtils();\n\
  	}\n\n\
  	var tagBasedComparators = new sn_em_tbac.EvtMgmtTagBasedComparators();\n";
  		if (tagsData.hasCMDBKeyTags()) {
  			acrScript += "// There are CMDB Key tags, so we must get the CMDB tags for this alert.\n\
  	var tbacCmdbKeyUtils = new sn_em_tbac.EvtMgmtTagBasedCmdbKeyUtils();\n\
  	var currentCiKeyValues = tbacCmdbKeyUtils.getCiCmdbKeyValues(currentAlert.getValue('cmdb_ci'));\n\n";
  		}
  		acrScript += "// Check whether the property of case sensitivity is set. It's true by default, which means the filter of the alert is case sensitive.\n\
  	var isCorrelationCaseSensitive = gs.getProperty('sa_analytics.correlation_case_sensitive', 'true');\n\n\
  	var alertManager = new SNC.AlertManager(); \n\n\
  	// acrScriptDebug collects debug information that will be logged if the value of the property '" + this.tbacUtils.LOG_ACR_DEBUG_PROPERTY + "' is set to true. \n\
  	var acrScriptDebug = '\"sa_analytics.correlation_case_sensitive\" property is set to: ' + isCorrelationCaseSensitive + '\\n\\n';\n\
  	acrScriptDebug += 'Alert correlation rule debug information for definition [code]<a href=\"sn_em_tbac_alert_clustering_definitions.do?sys_id=" + definitionSysId + "\" target=\"_blank\">" + definitionName + " (sys_id: " + definitionSysId + ")</a>[/code] from domain " + definitionDomainName + " (sys_domain: " + definitionDomainId + "):\\n';\n\
  	acrScriptDebug += 'Clustering timeframe is: " + timeDifference + " minutes (" + timeDifference * 1000 * 60 + " ms).\\n';\n\
  	acrScriptDebug += '\\nExamined (created or reopened) alert: [code]<a href=\"em_alert.do?sys_id=' + currentAlert.getUniqueValue() + '\" target=\"_blank\">' + currentAlert.getValue('number') + ', sys_id: ' + currentAlert.getUniqueValue() + '</a>[/code].\\n';\n\n\
  	// FROM DEFINITION CONFIG - Clustering timeframe: " + timeDifference + " minutes (" + timeDifference * 1000 * 60 + " ms). \n\
  	var alertsTimeDiff = new GlideDateTime(currentAlert.getValue('initial_remote_time')); \n\
  	alertsTimeDiff.subtract(" + timeDifference * 1000 * 60 + "); \n\n\
  	alertManager.addStep('Alert correlation rule advanced script - Before alerts query'); // Add slow steps before query \n\n\
  	" + enrich.beforeQuery + "\n\n\
  	// Query alerts to compare against. \n\
  	var gr = new GlideRecord('em_alert'); \n\
  	gr.addNotNullQuery('initial_remote_time'); \n\
  	gr.addQuery('initial_remote_time', '>=', alertsTimeDiff.getValue());\n\
  	gr.addQuery('sys_id', '!=', currentAlert.getUniqueValue());\n\
  	// FROM DEFINITION CONFIG - Encoded alert filter: " + alertFilter + ". \n\
  	gr.addEncodedQuery('" + alertFilter + "'); \n\n\
  	/* ADDITIONAL QUERIES FROM TAGS */ \n\
  	" + enrich.query + "\n\n\
  	// If virtual alerts feature is not active then use the legacy queries. \n\
  	if (!acrUtils || !acrUtils.isVirtualAlertFeatureActive()){\n\
  		acrScriptDebug += '\\nVirtual alerts for ACR feature is INACTIVE.\\n\\n';\n\
  		gr.addQuery('state', 'IN', 'Open,Reopen'); \n\
  		gr.addQuery('correlation_rule_group', 'IN', '0,1'); // 0 = None (potential parent) | 1 = Primary alert (parent) | 2 = Secondary \n\
  		gr.addQuery('group_source', 'IN', '2,6'); // 2 = Rules-Based, 6 = None \n\
  	}\n\
  	// Else, if virtual alerts feature is active, then query the child alerts of relevant primary alerts. \n\
  	else {\n\
  		acrScriptDebug += '\\nVirtual alerts for ACR feature is ACTIVE.\\n\\n';\n\
  		// 1. Init a shared query to be used for ungrouped alerts query and parent alerts query.\n\
  		var sharedEncodedQuery = gr.getEncodedQuery();\n\
  		\n\
  		// 2. Find OPEN or REOPEN ungrouped alerts\n\
  		var ungroupedAlertsGr = new GlideRecord('em_alert');\n\
  		ungroupedAlertsGr.addQuery('state', 'IN', 'Open,Reopen');\n\
  		ungroupedAlertsGr.addQuery('correlation_group', '0'); // 0 = None (potential parent)\n\
  		ungroupedAlertsGr.addEncodedQuery(sharedEncodedQuery); // Add the shared encoded query to the ungrouped alerts query.\n\
  		// Don't query ungroupedAlertsGr on purpose, we'd like to extract the encoded query and use it later\n\
  		acrScriptDebug += '\\nThe encoded query to retrieve ungrouped alerts is: \\n ' + ungroupedAlertsGr.getEncodedQuery() + '\\n\\n';\n\
  		\n\
  		// 3. Find virtual alerts to group to - but we can't query them directly because values in their fields change,\n\
  		// so tags comparison may fail for them.\n\
  		// Instead, find a representative secondary alert (childs) in their groups and compare the tags to it.\n\
  		var childGr;\n\
  		if (gs.getProperty('evt_mgmt.update_agg_group_record_with_main_alert', 'true') == 'true') {\n\
  			// If the 'evt_mgmt.update_agg_group_record_with_main_alert' property is enabled, it means \n\
  			// that the group representative (main alert) can be queried directly and to be used to compare \n\
  			// against the tags, but they need to be fetched first from em_agg_group \n\
  			var creationTimeToQueryGroupsInMinutesProperty = gs.getProperty('evt_mgmt.acr_groups_creation_time_to_query_from_in_minutes', '1440'); // 1440 minutes (one day) by default\n\
  			var creationTimeToQueryGroupsInMs = parseInt(creationTimeToQueryGroupsInMinutesProperty, 10) * 60 * 1000; // In milliseconds\n\
  			var creationTimeToQueryGroupsGdt = new GlideDateTime();\n\
  			creationTimeToQueryGroupsGdt.subtract(creationTimeToQueryGroupsInMs);\n\
  			// Get Tag Cluster groups that were created X time ago, depending on the property value\n\
  			var aggGroupGr = new GlideRecord('em_agg_group');\n\
  			aggGroupGr.addQuery('group_status', '1'); // Active \n\
  			aggGroupGr.addQuery('source_rule', '" + acrSysId + "');\n\
  			aggGroupGr.addQuery('source', '11'); // Tag Cluster groups\n\
  			aggGroupGr.addNotNullQuery('main_alert_id'); // Main alerts are defined\n\
  			aggGroupGr.addQuery('sys_created_on', '>=', creationTimeToQueryGroupsGdt.getValue());\n\
  			aggGroupGr.orderByDesc('sys_created_on'); // Latest first\n\
  			acrScriptDebug += '\\nThe em_agg_group query to retrieve main alerts of tag-cluster groups is: \\n ' + aggGroupGr.getEncodedQuery() + '\\n\\n';\n\
  			aggGroupGr.query();\n\
  			var mainAlertsIds = [];\n\
  			while (aggGroupGr.next()) {\n\
  				var mainAlertId = aggGroupGr.getValue('main_alert_id');\n\
  				mainAlertsIds.push(mainAlertId);\n\
  			}\n\
  			if (mainAlertsIds.length) {\n\
  				childGr = new GlideRecord('em_alert');\n\
  				childGr.addEncodedQuery(sharedEncodedQuery); // Add the shared encoded query to the child alerts query.\n\
  				childGr.addQuery('sys_id', 'IN', mainAlertsIds.join(','));\n\
  				// Don't query childGr on purpose, we'd like to extract the encoded query and use it later\n\
  			}\n\
  		} else {\n\
  			// When the 'evt_mgmt.update_agg_group_record_with_main_alert' property is disabled\n\
  			// then we must get a representative in a different way - aggregate groups and get a single alert from them.\n\
  			// GR for only OPEN parent alerts, we DON'T want to consider reopen parent alerts\n\
  			// Use GlideAggregate instead of GlideRecord for efficiency, and narrow results to a single alert within each group.\n\
  			gs.error('" + this.tbacUtils.logsPrefix + " - \"" + this.ACR_NAME_PREFIX + definitionName + "\" ERROR: Virtual alerts for correlation rules is ACTIVE but the property \"evt_mgmt.update_agg_group_record_with_main_alert\" is DISABLED. Grouping will not be done as expected - a workaround with GlideAggregate is needed.'); \n\
  		}\n\
  		acrScriptDebug += '\\nThe encoded query to retrieve child alerts is: \\n ' + childGr.getEncodedQuery() + '\\n\\n';\n\
  		// Init GR to use the encoded queries we have constructed, with ^NQ (NEW QUERY) relation.\n\
  		// This ^NQ relation is not achievable with a single GlideRecord without encoded queries as it doesn't have this method.\n\
  		gr = new GlideRecord('em_alert');\n\
  		if (childGr) {\n\
  			gr.addEncodedQuery(ungroupedAlertsGr.getEncodedQuery() + '^NQ' + childGr.getEncodedQuery());\n\
  		} else {\n\
  			gr.addEncodedQuery(ungroupedAlertsGr.getEncodedQuery());\n\
  		}\n\
  	}\n\n\
  	// FROM DEFINITION CONFIG - Maximum number alerts to compare against is: " + comparedAlertsLimit + " alerts. \n\
  	gr.setLimit(" + comparedAlertsLimit + "); \n\n\
  	gr.orderBy('initial_remote_time'); \n\
  	acrScriptDebug += '\\nThe encoded query to retrieve alerts to work on is: \\n ' + gr.getEncodedQuery() + '\\n\\n';\n\
  	gr.query(); \n\n\
  	t.queryEnd = Date.now(); // Measure milliseconds (ms) after querying compared alerts.\n\n\
  	acrScriptDebug += '\\nNumber of queried alerts: ' + gr.getRowCount() + ', the definition comparedAlertsLimit value is: " + comparedAlertsLimit + ".\\n';\n\
  	if (gr.getRowCount() >= " + comparedAlertsLimit + ") { \n\
  		acrScriptDebug += 'Queried alerts limit HAS BEEN REACHED.\\n';\n\
  		gs.warning('" + this.tbacUtils.logsPrefix + " - \"" + this.ACR_NAME_PREFIX + definitionName + "\" alert correlation rule has reached the limit of alerts to query: " + comparedAlertsLimit + ".'); \n\
  	} \n\n\
  	/* GROUPING LOGIC */\n\
  	// Initialize primary alert as currentAlert, may be overwritten ahead \n\
  	var primaryAlert = { \n\
  		sysId: currentAlert.getUniqueValue(), \n\
  		initialRemoteTime: currentAlert.getValue('initial_remote_time'), \n\
  		number: currentAlert.getValue('number'), \n\
  	}; \n\
  	var secondaries = []; \n\n";

  		if (tagsData.hasAdditionalInfoTags()) {
  			acrScript += "// Some tags include comparisons on additional_info, so extract it from currentAlert. \n";
  			acrScript += "var currentAddInfo = JSON.parse(currentAlert.getValue('additional_info')); \n";
  			acrScript += "var grAddInfo = {};\n";
  		}

  		if (tagsData.hasAlertTagsTags()) {
  			acrScript += "// Some tags include comparisons on sn_alert_tags, so extract it from currentAlert. \n";
  			acrScript += "var currentAlertTags = JSON.parse(currentAlert.getValue('" + alertTagsField + "')); \n";
  			acrScript += "var grAlertTags = {};\n";
  		}

  		acrScript += "\
  	" + enrich.afterQuery + "\n\n\
  	alertManager.addStep('Alert correlation rule advanced script - After alerts query, before loop'); // Add slow steps after query, before loop \n\n\
  	acrScriptDebug += 'List of queried alerts:\\n';\n\n\
  	// Create a GlideFilter instance to determine whether values need to be compared with case-sensitivity or not.\n\
  	var letterCaseGlideFilter = new GlideFilter('" + alertFilter + "', 'filterCondition'); \n\
  	if (isCorrelationCaseSensitive == 'false') {\n\
  		letterCaseGlideFilter.setCaseSensitive(false);\n\
  	}\n\n\
  	while (gr.next()) { \n\
  		if (!letterCaseGlideFilter.match(gr, true)) { \n\
  			acrScriptDebug += 'Skipping [code]<a href=\"em_alert.do?sys_id=' + gr.getUniqueValue() + '\" target=\"_blank\">' + gr.getValue('number') + '</a>[/code]';\n\
  			acrScriptDebug += ' ' + 'because of a case-sensitivity mismatch between the alert\\'s values and the definition filter\\n'; \n\
  			continue; \n\
  		} \n\n\
  		acrScriptDebug += '[code]<a href=\"em_alert.do?sys_id=' + gr.getUniqueValue() + '\" target=\"_blank\">' + gr.getValue('number') + '</a>[/code]';\n";
  		if (tagsData.hasAdditionalInfoTags()) {
  			acrScript += "// Some tags include comparisons on additional_info, so extract it from gr. \n";
  			acrScript += "grAddInfo = JSON.parse(gr.getValue('additional_info')) || {}; \n\n";
  		}
  		if (tagsData.hasCMDBKeyTags()) {
  			acrScript += "// Some tags include comparisons on cmdb_key_value, so extract it from gr.\n";
  			acrScript += "var otherCiKeyValues = tbacCmdbKeyUtils.getCiCmdbKeyValues(gr.getValue('cmdb_ci'));\n";
  		}
  		if (tagsData.hasAlertTagsTags()) {
  			acrScript += "// Some tags include comparisons on sn_alert_tags, so extract it from gr.\n";
  			acrScript += "grAlertTags = JSON.parse(gr.getValue('" + alertTagsField + "')) || {}; \n\n";
  		}
  		acrScript += "\
  	" + enrich.loopStart + "\n\n\
  		// Check if the currentAlert and the compared alert should be grouped \n\
  		// Compare currentAlert and gr alert values by tags \n\
  		// Currently, only AND operator between the tags is supported \n\
  		if (\n\
  			" + enrich.comparison + "\
  			\n){ \n\
  			// Reaching here means that the alerts should be grouped. \n\
  			// Replace current primary alert if: \n\
  			// 1. The compared alert initial_remote_time is smaller, \n\
  			// 2. Or, the initial_remote_time is equal, but the alert Number is smaller, as fallback. \n\
  			var isEarlier = gr.getValue('initial_remote_time') < primaryAlert.initialRemoteTime; \n\
  			var isSameTimeButSmallerNumber = (gr.getValue('initial_remote_time') == primaryAlert.initialRemoteTime) && (gr.getValue('number') < primaryAlert.number); \n\
  			var secondary = { \n\
  				sysId: gr.getUniqueValue(), \n\
  				initialRemoteTime: gr.getValue('initial_remote_time'), \n\
  				number: gr.getValue('number'), \n\
  			}; \n\
  			if (isEarlier || isSameTimeButSmallerNumber) { \n\
  				acrScriptDebug += ' - potentially primary';\n\
  				secondaries.push(primaryAlert); // Current primary alert should be replaced with compared alert, make it secondary. \n\
  				primaryAlert = secondary ; \n\
  			} else { \n\
  				acrScriptDebug += ' - secondary';\n\
  				secondaries.push(secondary); \n\
  			} \n\
  		} // end if \n\
  		acrScriptDebug += ','; // Separate between the alerts in the list. \n\
  	} // end while \n\n\
  	t.loopEnd = Date.now(); // Measure milliseconds (ms) after looping through compared alerts.\n\n\
  	alertManager.addStep('Alert correlation rule advanced script - After loop'); // Add slow steps after loop \n\n\
  	// Remove primary from secondaries, just in case it's there \n\
  	secondaries = secondaries.filter(function(secondaryAlertSysId) { \n\
  		return secondaryAlertSysId != primaryAlert.sysId; \n\
  	}); \n\n\
  	var group = {};\n\
  	var secondariesArray = secondaries.map(function(secondary) { return secondary.sysId; });\n\
  	if (acrUtils && acrUtils.isVirtualAlertFeatureActive()){\n\
  		group = {\n\
  			'ALERTS_SYS_IDS': [primaryAlert.sysId].concat(secondariesArray)\n\
  		};\n\
  	} else { \n\
  	// Set group result \n\
  		group = { \n\
  			'PRIMARY': [primaryAlert.sysId], \n\
  			'SECONDARY': secondariesArray \n\
  		};\n\
  	} \n\n\
  	t.end = Date.now(); // Measure milliseconds (ms) at the end of the script.\n\n\
  	// Log the alert correlation rule details if the debugging property is set to true.\n\
  	if(gs.getProperty('" + this.tbacUtils.LOG_ACR_DEBUG_PROPERTY + "', 'false') == 'true') {\n\
  		var readablePrimary = '[code]<a href=\"em_alert.do?sys_id=' + primaryAlert.sysId + '\" target=\"_blank\">' + primaryAlert.number + '</a>[/code]';\n\
  		var readableSecondaries = secondaries.map(function(secondary) { return '[code]<a href=\"em_alert.do?sys_id=' + secondary.sysId + '\" target=\"_blank\">' + secondary.number + '</a>[/code]'; });\n\
  		acrScriptDebug += '\\n\\nRESULTS:\\nPrimary alert: ' + readablePrimary;\n\
  		acrScriptDebug += '\\nSecondary alerts: ' + readableSecondaries.join(',');\n\
  		acrScriptDebug += '\\n\\nFinal alerts group structure (final returned structure):\\n' + JSON.stringify(group);\n\
  		acrScriptDebug += '\\n\\nPerformance (run time in ms):\\n';\n\
  		acrScriptDebug += 'Total runtime: ' + (t.end - t.start) + 'ms.\\n';\n\
  		acrScriptDebug += 'Compared alerts query time: ' + (t.queryEnd - t.start) + 'ms.\\n';\n\
  		acrScriptDebug += 'Loop compared alerts time: ' + (t.loopEnd - t.queryEnd) + 'ms.\\n';\n\
  		acrScriptDebug += '\\n\\In order to stop these debug messages - change the property \"" + this.tbacUtils.LOG_ACR_DEBUG_PROPERTY + "\" to \"false\".\\n';\n\
  		gs.info('" + this.tbacUtils.logsPrefix + "\\n' + acrScriptDebug);\n\
  	}\n\n\
  	//The alerts will be grouped under a virtual alert, and that the primary alert will be set dynamically to the alert of the highest severity.\n\
  	return JSON.stringify(group); \n\
  } catch (e) {\n\
  	var evtMgmtCommons = new EvtMgmtCommons();\n\
  	gs.error('" + this.tbacUtils.logsPrefix + " - \"" + this.ACR_NAME_PREFIX + definitionName + "\" An exception was thrown in alert correlation rule with sys_id: " + acrSysId + " for alert with sys_id: ' + currentAlert.getUniqueValue() + '. Exception: ' + evtMgmtCommons.getExceptionMessage(e, true)); \n\
  	// Returns an empty object to continue to next rules\n\
  	var group = { \n\
  		'PRIMARY': [], \n\
  		'SECONDARY': [], \n\
  		'ALERTS_SYS_IDS': [] \n\
  	};\n\
  	return JSON.stringify(group);\n\
  }\n\
})(currentAlert); \n\
";
      return acrScript;
  },

};

Sys ID

21c4a01db76830107c038229ce11a910

Offical Documentation

Official Docs: