Name
sn_em_tbac.EvtMgmtTagBasedTagEntity
Description
Object to represent an Alert Clustering Tag, with specific methods to enrich alert correlation rules.
Script
var EvtMgmtTagBasedTagEntity = Class.create();
EvtMgmtTagBasedTagEntity.prototype = {
type: 'EvtMgmtTagBasedTagEntity',
initialize: function(tagGr) {
if (!tagGr) return;
this.tbacUtils = new sn_em_tbac.EvtMgmtTagBasedAlertClusteringUtils();
// Constants:
this.ALERT_SOURCE = 'em_alert';
this.ADD_INFO_SOURCE = 'additional_info';
this.ALERT_CI_SOURCE = 'cmdb_ci';
this.ALERT_CI_KEY_SOURCE = 'cmdb_key_value';
this.ALERT_TAGS_SOURCE = 'alert_tags';
this.EXACT_MATCH = 'exact';
this.FUZZY_MATCH = 'fuzzy';
this.PATTERN_MATCH = 'pattern';
/*
* Use dot field references and NOT tagGr.getValue(FIELD).
* When tagGr is a referenced object from M2M table then tagGr.getValue() throws error
*/
// Escape the name of the tag entity before other scripts use it as it may break the script, see the attached image in DEF0229745 for example.
this.name = this.tbacUtils.stripAndEscapeChars(tagGr.getValue('name'));
// USE CAREFULLY! Not safe to be used in ACR script as it may contain characters that break it, see the attached image in DEF0229745 for example.
this.unsafe_original_name = tagGr.getValue('name');
this.description = tagGr.getValue('description');
this.source = tagGr.getValue('source');
this.selected_field = tagGr.getValue('selected_field');
this.additional_info_key = tagGr.getValue('additional_info_key');
this.match_method = tagGr.getValue('match_method');
this.fuzzy_algorithm = tagGr.getValue('fuzzy_algorithm');
this.fuzzy_similarity = this.getNumericalValue(tagGr.getValue('fuzzy_similarity'));
this.pattern = this.getCleanPatternValue(tagGr.getValue('pattern'));
this.sys_id = tagGr.getUniqueValue();
this.sys_domain = tagGr.getValue('sys_domain');
this.sys_domain_display_value = tagGr.sys_domain.getDisplayValue();
this.sys_overrides = tagGr.getValue('sys_overrides');
this.sys_overrides_display_value = tagGr.sys_overrides.getDisplayValue();
this.cmdb_key = tagGr.getValue('cmdb_key');
// Utility members
// Dot walk of the selected field relatively to 'em_alert', e.g. "metric_name" or "cmdb_ci.ip_address".
this.relativeSelectedField = this.computeRelativeSelectedField();
// Value to extract from "currentAlert", through dot walk because getValue() doesn't work with dot walk and reference fields.
// For example, getValue('metric_name') works, but the reference getValue('cmdb_ci.ip_address') doesn't work.
this.currentAlertValue = 'String(currentAlert.' + this.relativeSelectedField + ')';
// Value to extract from the compared alert (in turns) "gr", through dot walk because getValue() doesn't work with dot walk and reference fields.
// For example, getValue('metric_name') works, but the reference getValue('cmdb_ci.ip_address') doesn't work.
this.comparedAlertValue = 'String(gr.' + this.relativeSelectedField + ')';
},
// This JavaScript object is not a GlideRecord!!!
// BUT, this function was added as a safety measure so values will return and errors won't be thrown if someone
// confuses an instance of this entity with GlideRecord and accidently calls it with .getValue()
getValue: function(fieldName) {
if (!fieldName || !this[fieldName]) {
return '';
}
return this[fieldName];
},
getNumericalValue: function(val, defaultVal, min, max) {
var numVal = Number(val);
if (isNaN(numVal)) {
return defaultVal || 0; // 0 as fallback to defaultVal if it's undefined or empty
}
if (min && numVal < min) return min;
if (max && numVal > max) return max;
return numVal;
},
/**
* This function replaces unsafe characters with pattern's match character symbol (?).
*/
getCleanPatternValue: function(patternValue) {
if (!patternValue) return '';
patternValue = String(patternValue);
// Replace the character / because it can break the regex expression in the script.
// Replace the character " and \ because they can break the script.
return patternValue.replaceAll('/', '?').replaceAll('"', '?').replaceAll('\\', '?');
},
/**
* Computes the selected field name relatively to 'em_alert' table.
* Compared field values are being retrieved relatively to em_alert records,
* so if the selected field is from 'cmdb_ci', we must dot walk there.
* @returns: String - the selected field, relatively to 'em_alert', with dot walk if needed.
*/
computeRelativeSelectedField: function() {
// For cmdb ci - create 'cmdb_ci.' relative dot walk from the alert for the query.
var dotWalkFromAlert = (this.source == 'em_alert') ? '' : (this.source + '.');
var relativeSelectedField = dotWalkFromAlert + this.selected_field;
return relativeSelectedField;
},
/**
* Add logic before querying compared alerts.
* currentAlert is available here.
* @returns: String.
*/
getBeforeQuery: function() {},
/**
* Add logic for querying compared alerts.
* currentAlert is available here.
* 'gr' GlideRecord variable of 'em_alert' is available here BEFORE its ".query()" method was called.
* @returns: String - Query of alerts, e.g. "gr.addQuery(FIELD, OPERATOR, VALUE)"
*/
getQuery: function() {
var query = '';
if (this.source) {
switch (this.source) {
case this.ADD_INFO_SOURCE:
query += 'gr.addQuery("additional_info", "CONTAINS", "' + this.additional_info_key + '");';
break;
case this.ALERT_CI_KEY_SOURCE:
query += 'gr.addEncodedQuery("cmdb_ciISNOTEMPTY");\n';
break;
case this.ALERT_TAGS_SOURCE:
query += 'gr.addQuery("' + this.tbacUtils.getAlertTagsField() + '", "CONTAINS", "' + this.additional_info_key + '");';
break;
default:
if (this.match_method == this.EXACT_MATCH) {
// For exact match - filter by "equals" (=) only if a value exists.
// If a value doesn't exist ("else" condition) - we'd still like to filter anyway,
// as we'd want to narrow the amount of queried alerts, to reduce performance impact.
query += 'if (' + this.currentAlertValue + ') { \n';
query += 'gr.addEncodedQuery("' + this.relativeSelectedField + 'ISNOTEMPTY^' + this.relativeSelectedField + '=" + ' + this.currentAlertValue + ');\n';
query += '} else { \n';
query += 'gr.addEncodedQuery("' + this.relativeSelectedField + 'ISNOTEMPTY");\n';
query += '}';
} else {
query += 'gr.addEncodedQuery("' + this.relativeSelectedField + 'ISNOTEMPTY");';
}
break;
}
}
return query;
},
/**
* Add logic after the query result of compared alerts and before the while(gr.next) loop.
* currentAlert is available here.
* 'gr' is available here AFTER its ".query()" method was called and BEFORE its ".next()" method was called.
* You can use 'gr' methods that DON'T affect the gr itself, e.g. gr.getRowCount() but NOT gr.next()
* If the match method of the tag is fuzzy then tagBasedComparators variable for the script include is available here.
* If the source field of the tag is additional_info then:
currentAddInfo - available here, contains the additional_info of the currentAlert as JavaScript object after parse.
* @returns: String
*/
getAfterQuery: function() {},
/**
* Add logic within and in the beginning of the the while(gr.next) { } loop.
* currentAlert is available here.
* 'gr' GlideRecord variable of an actual record in 'em_alert' is available here, after gr.next() was called.
* You can use gr.getValue(FIELDNAME), and most other gr operations here.
* If the match method of the tag is fuzzy then tagBasedComparators variable for the script include is available here.
* If the source field of the tag is additional_info then:
currentAddInfo - available here, contains the additional_info of the currentAlert as JavaScript object after parse.
grAddInfo - available here, contains the additional_info of the gr as JavaScript object after parse.
* This is basically for preparations of tag specific data, e.g. to retrieve data from a different table based on gr field value, before comparing the values.
* @returns: String
*/
getLoopStart: function() {},
/**
* Add logic that returns the condition by which tags are compared.
* currentAlert is available here.
* 'gr' GlideRecord variable of an actual record in 'em_alert' is available here, after gr.next() was called.
* You can use gr.getValue(FIELDNAME), and most other gr operations here.
* If the match method of the tag is fuzzy then tagBasedComparators variable for the script include is available here.
* If the source field of the tag is additional_info then:
currentAddInfo - available here, contains the additional_info of the currentAlert as JavaScript object after parse.
grAddInfo - available here, contains the additional_info of the gr as JavaScript object after parse.
* @returns: String - A condition within brackets, i.e: ( CONDITION ), that is evaluated within an if() clause.
*/
getComparison: function() {
var comparison = '';
// Default values to 'em_alert', 'cmdb_ci' source, override in the switch-case ahead.
var currentVal = this.currentAlertValue;
var grVal = this.comparedAlertValue;
var fieldKey = this.selected_field;
// Extract the current alert and compared alert values by tag source.
// Override defaults as needed.
switch (this.source) {
case this.ADD_INFO_SOURCE:
currentVal = "currentAddInfo['" + this.additional_info_key + "']";
grVal = "grAddInfo['" + this.additional_info_key + "']";
fieldKey = this.additional_info_key;
break;
case this.ALERT_CI_KEY_SOURCE:
currentVal = "currentCiKeyValues['" + this.cmdb_key + "']";
grVal = "otherCiKeyValues['" + this.cmdb_key + "']";
fieldKey = this.cmdb_key;
break;
case this.ALERT_TAGS_SOURCE:
currentVal = "currentAlertTags['" + this.additional_info_key + "']";
grVal = "grAlertTags['" + this.additional_info_key + "']";
fieldKey = this.tbacUtils.getAlertTagsField();
break;
default:
break;
}
// Use the extracted values and compare them.
// the compared values MUST BE wrapped in brackets, e.g. ( x == y ), or ( tagBasedComparators.fuzzy(sim, s1, s2, alg) )
//
// Note:
// The comparisons below are translated to: ((val1 && val2) && (val1 == val2)),
// Which means (val1 && val2) evaluates to false and doesn't get to the comparison condition, when val1 or val2 are
// equal to the boolean false, e.g. empty string (''), undefined, null, the number 0, NaN, boolean false.
// Since the compared values are string values, "0" is a legitimate value to compare, in contrast to the number 0 or to the empty string "".
// For booleans, we might compare "true" and "false" strings (or "0" and "1", and vice versa), which are always equal to the boolean true,
// but in this case the comparison will act on (val1 == val2), and evaluate them to false if the values are not identical, as expected.
switch (this.match_method) {
case this.FUZZY_MATCH:
comparison += '// Compare ' + this.source + ' field/key: "' + fieldKey + '" by Fuzzy method with similarity of ' + this.fuzzy_similarity + '%, from tag "' + this.name + '" with sys_id: ' + this.sys_id + ' from domain: ' + this.sys_domain_display_value + ' (sys_domain: ' + this.sys_domain + ') \n';
comparison += '((' + currentVal + ' && ' + grVal + ') && tagBasedComparators.fuzzy(' + this.fuzzy_similarity + ', ' + currentVal + ', ' + grVal + ', "' + this.fuzzy_algorithm + '"))';
break;
case this.EXACT_MATCH:
comparison += '// Compare ' + this.source + ' field/key: "' + fieldKey + '" by Exact method, from tag "' + this.name + '" with sys_id: ' + this.sys_id + ' from domain: ' + this.sys_domain_display_value + ' (sys_domain: ' + this.sys_domain + ') \n';
comparison += '((' + currentVal + ' && ' + grVal + ') && tagBasedComparators.exact(' + currentVal + ', ' + grVal + '))'; // Check values exist (non-empty strings) and then compare them.
break;
case this.PATTERN_MATCH:
comparison += '// Compare ' + this.source + ' field/key: "' + fieldKey + '" by Pattern method with pattern of ' + this.tbacUtils.stripAndEscapeChars(this.pattern) + ', from tag "' + this.name + '" with sys_id: ' + this.sys_id + ' from domain: ' + this.sys_domain_display_value + ' (sys_domain: ' + this.sys_domain + ') \n';
// GlideFilter.checkRecord() can't check JSON fields (the additional_info or the sn_alert_tags of the alerts) so check it individually.
// the additional_info check has supporting code in
// Also cmdb_key_value records need to be loaded into memory and regexed
switch (this.source) {
case this.ADD_INFO_SOURCE:
case this.ALERT_TAGS_SOURCE:
case this.ALERT_CI_KEY_SOURCE:
var patternToRegex = this.pattern;
var lastIndex = (patternToRegex.length - 1);
// If the pattern doesn't begin with a wildcard (*) then prepend the regex start-of-line symbol (^)
if (patternToRegex[0] != '*') {
patternToRegex = '^' + patternToRegex;
}
// If the pattern doesn't end with a wildcard (*) then append the regex end-of-line symbol ($)
if (patternToRegex[lastIndex] != '*') {
patternToRegex = patternToRegex + '$';
}
// Convert SN's pattern identifiers to valid regex identifiers.
// (.*) - regex wildcard to replace the pattern's wildcard *
// . - regex matches any character symbol to replace the pattern's match any symbol ?
patternToRegex = patternToRegex.replaceAll('*', '(.*)').replaceAll('?', '.');
comparison += "( tagBasedComparators.pattern(" + currentVal + ", " + grVal + ", '" + patternToRegex + "') )";
break;
default:
var patternFieldComparison = this.relativeSelectedField + "MATCH_PAT" + this.pattern;
comparison += "( tagBasedComparators.checkRecord(currentAlert, \"" + patternFieldComparison + "\") && tagBasedComparators.checkRecord(gr, \"" + patternFieldComparison + "\") )";
break;
}
break;
default:
break;
}
return comparison;
},
};
Sys ID
d6a74ffdb78530107c038229ce11a9ff