Name
global.CMDBDynamicIREProcessor
Description
No description available
Script
var CMDBDynamicIREProcessor = Class.create();
CMDBDynamicIREProcessor.prototype = {
// The size of batches to be processed before maximum_records is reached.
batchSize: 1000,
// Add this property to sys_properties type=string value=info/debug/warn/error.
// Then filter logs where source contains CMDBDynamicIREProcessor.
_LOG_PROPERTY: "glide.identification_engine.ire_dynamic.log.level",
// Tables
_CMDB_DYNAMIC_IRE_MATCH: 'cmdb_dynamic_ire_match',
_CMDB_DYNAMIC_IRE_FILTER_CONDITION: 'cmdb_dynamic_ire_filter_condition',
_CMDB_DYNAMIC_IRE_FEATURE: 'cmdb_dynamic_ire_feature',
_CMDB_DYNAMIC_IRE_FEATURE_SET: 'cmdb_dynamic_ire_feature_set',
_CMDB_DYNAMIC_IRE_MODEL: 'cmdb_dynamic_ire_model',
_CMDB_CI: "cmdb_ci",
// Tables filter for reference to cmdb tables.
filteredTables: ['ecc_event', 'discovery_log', 'cmdb_metric', 'u_eal_submission'],
// Columns filtered as not relevant for viewing.
filteredColumns: ['sys_domain_path', 'sys_class_path'],
initialize: function(logProperty) {
// The count of inserts into this._CMDB_DYNAMIC_IRE_MATCH
this.matchCount = 0;
// Feature set update records
this.featureSetUpdateCount = 0;
if (JSUtil.nil(logProperty))
logProperty = this._LOG_PROPERTY;
this.log = new GSLog(logProperty, this.type);
// this.log.disableDatabaseLogs();
},
/*
* Calculate the feature score. 1=equals, 0=not equal, -1 null or empty string. For the
* selected cmdb_dynamic_match records.
*
* @param modelId the model. cmdb_dymanic_model.sys_id
* @param matchFilterJSON a filter to pick the match records. {
"expected_match": [0, 1], // 0 = NoMatch, 1 = Match, 2 = Not Processed, 3 = Analyze
"reviewed": true,
"processed_time_from": "2020-04-19 20:35:41",
"processed_time_to": "2020-04-19 20:35:41"
}
*
* @return feature {message: "", processed_records: "", updated_records: "", processed_time: "", feature_count: ""}
*/
applyFeatures: function(modelId, matchFilterJSON) {
try {
var filterMatchObj = {
'expected_match': [0, 1],
'reviewed': true,
'processed_time_from': '',
'processed_time_to': ''
};
if (!gs.nil(matchFilterJSON))
filterMatchObj = JSON.parse(matchFilterJSON);
var expectedMatch = [];
if (filterMatchObj.hasOwnProperty('expected_match'))
expectedMatch = filterMatchObj.expected_match;
var swComplete = new GlideStopWatch();
var lastProccessId = '';
var complete = false;
var processedRecCount = 0;
this.featureSetUpdateCount = 0;
var featuresJSON = this.getFeatures(modelId);
var feature = {};
feature.message = '';
feature.processed_records = processedRecCount;
feature.updated_records = this.featureSetUpdateCount;
feature.processed_time = 0;
feature.feature_count = 0;
if (gs.nil(featuresJSON)) {
var message = this.formatMessage("Completed job CMDBDynamicIREProcessor::applyFeatures no active features defined for model Id: {0}", [modelId]);
this.log.logInfo(message);
feature.message = message;
return feature;
}
feature.feature_count = Object.keys(featuresJSON).length;
var matchGr = new GlideRecord(this._CMDB_DYNAMIC_IRE_MATCH);
// Only process matched record based on the filters.
if (expectedMatch.length > 0)
matchGr.addQuery('expected_match', 'IN', expectedMatch.toString());
if (filterMatchObj.hasOwnProperty('reviewed'))
matchGr.addQuery('reviewed', filterMatchObj.reviewed);
if (filterMatchObj.hasOwnProperty('processed_time_from') && filterMatchObj.hasOwnProperty('processed_time_to')) {
if (!gs.nil(filterMatchObj.processed_time_from))
matchGr.addQuery('processed_time', '>=', new GlideDateTime(filterMatchObj.processed_time_from));
if (!gs.nil(filterMatchObj.processed_time_to))
matchGr.addQuery('processed_time', '<=', new GlideDateTime(filterMatchObj.processed_time_to));
}
matchGr.query();
totalCount = matchGr.getRowCount();
while (!complete) {
var sw = new GlideStopWatch();
complete = true;
if (!gs.nil(lastProccessId))
matchGr.addQuery('sys_id', '>', lastProccessId);
// NOTE limit must be set BEFORE order by
matchGr.setLimit(this.batchSize);
matchGr.orderBy('sys_id');
matchGr.query();
while (matchGr.next()) {
var ci1Gr = new GlideRecord(matchGr.getValue('class_ci_1'));
if (!ci1Gr.get(matchGr.getValue('ci_1'))) {
this.log.logErr(this.formatMessage("CMDBDynamicIREProcessor::applyFeatures CI1 not found: {0}", [matchGr.getValue('ci_1')]));
continue;
}
var ci2Gr = new GlideRecord(matchGr.getValue('class_ci_2'));
if (!ci2Gr.get(matchGr.getValue('ci_2'))) {
this.log.logErr(this.formatMessage("CMDBDynamicIREProcessor::applyFeatures CI2 not found: {0}", [matchGr.getValue('ci_2')]));
continue;
}
var featuresScores = [];
var featureRef = null;
for (var key in featuresJSON) {
var featureObj = featuresJSON[key];
var columns = featureObj.attributes.split(',');
// Get the feature columns and values for each CI.
var columnValuesJSON = {};
if (gs.nil(featureObj.reference_field)) {
columnValuesJSON.ci1 = this._getFieldColumnsAndValues(columns, ci1Gr);
columnValuesJSON.ci2 = this._getFieldColumnsAndValues(columns, ci2Gr);
} else {
columnValuesJSON.ci1 = this._getReferenceFeatureColumnsAndValues(featureObj, columns, ci1Gr);
columnValuesJSON.ci2 = this._getReferenceFeatureColumnsAndValues(featureObj, columns, ci2Gr);
}
this.log.logDebug(this.formatMessage("Processing job CMDBDynamicIREProcessor::applyFeatures \nMatch Id: {0}\nFeature: {1}\nFeature Scores: {2}\nCI Data: {3}", [matchGr.getUniqueValue(), JSON.stringify(featureObj), JSON.stringify(featuresScores), JSON.stringify(columnValuesJSON)]));
// Just calculate plain equals for each value.
var score = this.calculateFeatureScore(featureObj, featuresScores, columnValuesJSON);
var featureScore = {};
featureScore.feature = key;
featureScore.score = score;
featuresScores.push(featureScore);
}
if (featuresScores.length > 0) {
featureRef = this.createFeatureSetRecord(matchGr.getValue('expected_match'), featuresScores);
}
//update the reference back on to the cmdb_dynamic_match table.
if (!gs.nil(featureRef)) {
matchGr.setValue('feature_record', featureRef);
updated = matchGr.update();
if (gs.nil(updated)) {
this.log.logErr(this.formatMessage("CMDBDynamicIREProcessor::applyFeatures updating the reference back on {0}", [this._CMDB_DYNAMIC_IRE_MATCH]));
}
}
lastProccessId = matchGr.getUniqueValue();
processedRecCount++;
if (processedRecCount === totalCount) {
complete = true;
break;
}
complete = false;
}
sw.stop();
this.log.logDebug(this.formatMessage("Processing job CMDBDynamicIREProcessor::applyFeatures Processed {0} match records out of {1}. Duration: {2} (sec).", [processedRecCount.toString(), totalCount, sw.getTime() / 1000]));
}
swComplete.stop();
var completeMessage = this.formatMessage("Completed job CMDBDynamicIREProcessor::applyFeatures Processed {0} CI's where expected match is {1}. Duration: {2} (sec).", [processedRecCount.toString(), expectedMatch.toString(), swComplete.getTime() / 1000]);
this.log.logInfo(completeMessage);
feature.message = completeMessage;
feature.processed_records = processedRecCount;
feature.updated_records = this.featureSetUpdateCount;
feature.processed_time = swComplete.getTime() / 1000;
return JSON.stringify(feature);
} catch (ex) {
this.log.logErr(this.formatMessage("CMDBDynamicIREProcessor::applyFeatures msg: {0}", [ex]));
}
},
/*
* Find CI's that have a common filter pattern i.e. name or serial_nuber or mac_address match.
*
* @param modelId the model. cmdb_dymanic_model.sys_id
* @return feature {message: "", maximum_records: "", processed_records: "", match_records: "", reference_filter: "", processed_time: ""}
*/
applyFilterConditions: function(modelId) {
try {
var result = [];
var filter = {};
var modelGr = new GlideRecord(this._CMDB_DYNAMIC_IRE_MODEL);
if (!modelGr.get(modelId)) {
filter.message = this.formatMessage("Completed job CMDBDynamicIREProcessor::applyFilterConditions Model not found Id {0}", [modelId]);
this.log.logInfo(filter.message);
result.push(filter);
return result;
}
var baseClass = modelGr.getValue('cmdb_class');
var baseClassTableTree = j2js(new TableUtils(baseClass).getTables());
baseClassTableTree = baseClassTableTree.concat(j2js(new TableUtils(baseClass).getTableExtensions()));
var maxRecords = 0;
var maxMatchRecords = 0;
if (!gs.nil(modelGr.getValue("maximum_records")))
maxRecords = parseInt(modelGr.getValue('maximum_records'));
if (!gs.nil(modelGr.getValue("maximum_match_records")))
maxMatchRecords = parseInt(modelGr.getValue("maximum_match_records"));
if (0 != maxRecords && this.batchSize > maxRecords)
this.batchSize = maxRecords;
// get the filters for this table.
var filterCondGr = new GlideRecord(this._CMDB_DYNAMIC_IRE_FILTER_CONDITION);
filterCondGr.addQuery('model_id', modelId);
filterCondGr.addActiveQuery();
filterCondGr.orderBy("order");
filterCondGr.query();
if (0 == filterCondGr.getRowCount()) {
filter.message = this.formatMessage("Completed job CMDBDynamicIREProcessor::applyFilterConditions no filters defined in table: {0} for model Id: {1}", [this._CMDB_DYNAMIC_IRE_FILTER_CONDITION, modelId]);
this.log.logInfo(filter.message);
result.push(filter);
return result;
}
while (filterCondGr.next()) {
this.log.logInfo(this.formatMessage("Started job CMDBDynamicIREProcessor::applyFilterConditions for filter: {0}.", [filterCondGr.getValue('name')]));
this.matchCount = 0;
var maxFilteredRecords = 0;
if (!gs.nil(filterCondGr.getValue("maximum_filtered_records")))
maxFilteredRecords = parseInt(filterCondGr.getValue('maximum_filtered_records'));
var filterName = filterCondGr.getValue('name');
var referenceField = filterCondGr.getValue('reference_field');
var filterClass = filterCondGr.getValue('cmdb_class');
var filterClasses = j2js(new TableUtils(filterClass).getTableExtensions());
filterClasses.push(filterClass);
var staticCondition = filterCondGr.getValue("static_condition");
var dynamicCondition = filterCondGr.getValue("dynamic_condition");
var condition = staticCondition;
if (!gs.nil(dynamicCondition))
condition = dynamicCondition.replace('EQ', staticCondition);
var dynamicConditionColumns = this._getColumnFromQuery(dynamicCondition);
if (gs.nil(referenceField)) {
filter = this._processFilterCMDBItems(filterName, maxFilteredRecords, maxRecords, maxMatchRecords, filterClasses, staticCondition, condition, dynamicConditionColumns, modelId);
} else {
filter = this._processFilterReferenceItems(filterName, maxFilteredRecords, maxRecords, maxMatchRecords, baseClassTableTree, staticCondition, condition, dynamicConditionColumns, filterClass, referenceField, modelId);
}
result.push(filter);
}
return JSON.stringify(result);
} catch (ex) {
this.log.logErr(this.formatMessage("CMDBDynamicIREProcessor::applyFilterConditions msg: {0}", [ex]));
}
},
getFeatures: function(modelId) {
var featuresJSON = {};
// Get all the active features/filters
var featureGr = new GlideRecord(this._CMDB_DYNAMIC_IRE_FEATURE);
var filterJoinCondition = featureGr.addJoinQuery(this._CMDB_DYNAMIC_IRE_FILTER_CONDITION, 'filter_condition_id', 'sys_id');
filterJoinCondition.addCondition('model_id', modelId);
featureGr.addNotNullQuery('attributes');
featureGr.addActiveQuery();
featureGr.orderBy("order");
featureGr.query();
if (0 == featureGr.getRowCount()) {
return null;
}
while (featureGr.next()) {
var feature = {};
feature.name = featureGr.getValue('name');
feature.attributes = featureGr.getValue('attributes');
feature.cmdb_class = featureGr.getValue('cmdb_class');
feature.matching_algorithm = parseInt(featureGr.getValue('matching_algorithm'));
feature.weight = featureGr.getValue('weight');
feature.featureColumnName = featureGr.getValue('feature');
var filterGr = featureGr.filter_condition_id.getRefRecord();
feature.model_id = filterGr.getValue('model_id');
feature.reference_field = gs.nil(filterGr.getValue('reference_field')) ? '' : filterGr.getValue('reference_field');
feature.static_condition = filterGr.getValue('static_condition');
feature.dynamic_condition = filterGr.getValue('dynamic_condition');
featuresJSON[featureGr.getValue('feature')] = feature;
}
return featuresJSON;
},
createMatchRecord: function(ci_1, class_ci_1, ci_2, class_ci_2, referenceFilter) {
if (gs.nil(ci_1) || gs.nil(ci_2) || ci_1 == ci_2)
return false;
var dynamicMatchGr = new GlideRecord(this._CMDB_DYNAMIC_IRE_MATCH);
var q = dynamicMatchGr.addQuery('cmdb_ci_1', ci_1);
q.addOrCondition('cmdb_ci_2', ci_1);
var q1 = dynamicMatchGr.addQuery('cmdb_ci_1', ci_2);
q1.addOrCondition('cmdb_ci_2', ci_2);
dynamicMatchGr.query();
if (dynamicMatchGr.next()) {
// getDisplayValue returns string true/false
var refFilter = dynamicMatchGr.getDisplayValue('reference_filter');
// Dont reset if this was already found with a reference filter
if ('true' !== refFilter) {
dynamicMatchGr.setValue('reference_filter', referenceFilter);
if (!dynamicMatchGr.update()) {
this.log.logErr(this.formatMessage("CMDBDynamicIREProcessor::createMatchRecord updating {0} ID: {1}", [this._CMDB_DYNAMIC_IRE_MATCH, dynamicMatchGr.getUniqueValue()]));
}
}
return false;
}
dynamicMatchGr = new GlideRecord(this._CMDB_DYNAMIC_IRE_MATCH);
dynamicMatchGr.initialize();
dynamicMatchGr.setValue('cmdb_ci_1', ci_1);
dynamicMatchGr.setValue('ci_1', ci_1);
dynamicMatchGr.setValue('class_ci_1', class_ci_1);
dynamicMatchGr.setValue('cmdb_ci_2', ci_2);
dynamicMatchGr.setValue('ci_2', ci_2);
dynamicMatchGr.setValue('class_ci_2', class_ci_2);
dynamicMatchGr.setValue('reference_filter', referenceFilter);
dynamicMatchGr.setValue('model_id', modelId);
dynamicMatchGr.setValue('expected_match', 2);
var dynamicMatchId = dynamicMatchGr.insert();
if (gs.nil(dynamicMatchId)) {
this.log.logErr(this.formatMessage("CMDBDynamicIREProcessor::createMatchRecord inserting {0}", [this._CMDB_DYNAMIC_IRE_MATCH]));
}
this.matchCount++;
return true;
},
createFeatureSetRecord: function(expectedMatch, features) {
if (features.length == 0)
return null;
var newDynamicFeatureSetGr = new GlideRecord(this._CMDB_DYNAMIC_IRE_FEATURE_SET);
newDynamicFeatureSetGr.initialize();
newDynamicFeatureSetGr.setValue('label', expectedMatch);
for (var j = 0; j < features.length; j++) {
newDynamicFeatureSetGr.setValue(features[j].feature, features[j].score);
}
var dynamicFeatureSetId = newDynamicFeatureSetGr.insert();
if (gs.nil(dynamicFeatureSetId)) {
this.log.logErr(this.formatMessage("CMDBDynamicIREProcessor::createFeatureSetRecord inserting {0}", [this._CMDB_DYNAMIC_IRE_FEATURE_SET]));
}
return dynamicFeatureSetId;
},
/*
* Calculate the feature score. 1=equals, 0=not equal, -1 null or empty string.
* @param featureObj the feature.
* @param featuresScores {feature_1: <score>, feature_2: <score>, ...}
* @param columnValuesJSON {ci1: {column1: [value1, value2],column2: [value3, value4], ...},ci2: ...
* @return int score.
*/
calculateFeatureScore: function(featureObj, featuresScores, columnValuesJSON) {
// Alorgrithm to calculate match Equals (0), Non Existing (1), Distance (2).
var matchAlgorithm = parseInt(featureObj.matching_algorithm);
var ci1ColValues = columnValuesJSON.ci1;
var ci2ColValues = columnValuesJSON.ci2;
// Score to be returned
var score = 0;
// The number of set pairs
var keyCount = 0;
var nonExisting = true;
for (var key in ci1ColValues) {
var ci1Values = ci1ColValues[key];
var ci2Values = ci2ColValues[key];
// If no values.
if (ci1Values.length == 0 && ci2Values.length == 0) {
continue;
}
nonExisting = false;
// Calculate the Interset of 2 sets
// i.e set1= [1,2,3,4] set2=[3,4,5,6,7] intersect = [1,2[3,4,3,4]5,6,7]
// score = 4/set1.length + set2.length = 0.4444
var matchedCount = 0;
for (var i = 0; i < ci1Values.length; i++) {
if (ci2Values.indexOf(ci1Values[i]) !== -1) {
// If it exists in both
matchedCount++;
}
}
for (var j = 0; j < ci2Values.length; j++) {
if (ci1Values.indexOf(ci2Values[j]) !== -1) {
// If it exists in both
matchedCount++;
}
}
// matchAlgorithm === 0 i.e. Equals i.e. all values must be equal
if (matchAlgorithm === 0 && (matchedCount !== (ci1Values.length + ci2Values.length))) {
return 0;
}
keyCount++;
score = score + (matchedCount / (ci1Values.length + ci2Values.length));
}
if (0 !== keyCount) {
score = score / keyCount;
}
// Non Existing
if (matchAlgorithm === 1) {
if (nonExisting) {
score = 1;
} else {
score = 0;
}
} else {
if (nonExisting)
score = -1;
}
// Score should always be less than equal to 1
if (score > 1) {
this.log.logErr(this.formatMessage("Processing job CMDBDynamicIREProcessor::calculateFeatureScore score greater than 1 \nScore: {0}\nFeature: {1}\n Previous Feature Scores: {2}\nCI Data: {3}", [score, JSON.stringify(featureObj), JSON.stringify(featuresScores), JSON.stringify(columnValuesJSON)]));
}
return score;
},
/*
* The Levenshtein distance between two strings stringA, stringB (of length |stringA| and |stringB| respectively)
* is given by levenshteinDistance{stringA,stringB}(|stringA|,|stringB|)
* Use to get a percent value
* var levenshteinDistance = this.levenshteinDistance(stringA, stringB);
* var bigger = Math.max(stringA.length, stringB.length);
* var percent = (bigger - levenshteinDistance) / bigger;
*
*/
levenshteinDistance: function(stringA, stringB) {
if (gs.nil(stringA))
stringA = '';
if (gs.nil(stringB))
stringB = '';
if (stringA.length == 0) return stringB.length;
if (stringB.length == 0) return stringA.length;
var matrix = [];
// increment along the first column of each row
var i;
for (i = 0; i <= stringB.length; i++) {
matrix[i] = [i];
}
// increment each column in the first row
var j;
for (j = 0; j <= stringA.length; j++) {
matrix[0][j] = j;
}
// Fill in the rest of the matrix
for (i = 1; i <= stringB.length; i++) {
for (j = 1; j <= stringA.length; j++) {
if (stringB.charAt(i - 1) == stringA.charAt(j - 1)) {
matrix[i][j] = matrix[i - 1][j - 1];
} else {
matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, // substitution
Math.min(matrix[i][j - 1] + 1, // insertion
matrix[i - 1][j] + 1)); // deletion
}
}
}
return matrix[stringB.length][stringA.length];
},
/*
*
* @param matchJSON {"cmdb_dynamic_ire_match": [{"sys_id": "<value>","expected_match": "Match"},
* {"sys_id": "<value>","expected_match": "value"}]}
* @return matchJSON {"cmdb_dynamic_ire_match": [{"sys_id": "<value>","expected_match": "value", "updated": "true"},
* {"sys_id": "<value>","expected_match": "value", "updated": "true"}]}.
*/
updateMatchRecords: function(matchJSON) {
try {
matchJSON = JSON.parse(matchJSON);
var matchRecs = matchJSON.cmdb_dynamic_ire_match;
for (var i = 0; i < matchRecs.length; i++) {
var matchGr = new GlideRecord(this._CMDB_DYNAMIC_IRE_MATCH);
if (matchGr.get(matchRecs[i].sys_id)) {
matchGr.setValue('expected_match', matchRecs[i].expected_match);
if (matchGr.update()) {
matchRecs[i].updated = true;
} else {
matchRecs[i].updated = false;
this.log.logErr(this.formatMessage("CMDBDynamicIREProcessor::updateMatchRecords updating {0} ID: {1}", [this._CMDB_DYNAMIC_IRE_MATCH, matchGr.getUniqueValue()]));
}
} else {
this.log.logErr(this.formatMessage("CMDBDynamicIREProcessor::updateMatchRecords updating {0} ID: {1}", [this._CMDB_DYNAMIC_IRE_MATCH, matchRecs[i].sys_id]));
}
}
return JSON.stringify(matchJSON);
} catch (ex) {
this.log.logErr(this.formatMessage("CMDBDynamicIREProcessor::updateMatchRecords Parsing json msg: {0}", [ex]));
}
},
/*
*
* @param matchJSON {"cmdb_dynamic_ire_match": [{"sys_id": "<value>","expected_match": "value"},
* {"sys_id": "<value>","expected_match": "value"}]}
*
* @return matchJSON {"columns": ["DynamicIREIT_89_9191","DynamicIREIT_89_9191"],"cmdb_ci_win_server": {"rows": [{"asset": ["YAH-0F4C-9191 - Unknown",""],"asset_tag": ["YAH-0F4C-9191","YAH-0F4C-9292"]} ]},"cmdb_serial_number": {"rows": [{"serial_number": ["4f-fc-cd-db-b1-919853","4f-fc-cd-db-b1-919855"],"serial_number_type": ["chassis","chassis"]},{"serial_number": ["4f-fc-cd-db-b1-919854","4f-fc-cd-db-b1-919854"],"serial_number_type": ["chassis","chassis"]},{"serial_number": ["","4f-fc-cd-db-b1-919853"],"serial_number_type": ["","chassis"]}]}}
*
*/
getMatchRecords: function(matchJSON, excludeNull) {
try {
matchJSON = JSON.parse(matchJSON);
var matchRecs = matchJSON.cmdb_dynamic_ire_match;
var items = {};
var index = 0;
for (var i = 0; i < matchRecs.length; i++) {
var matchGr = new GlideRecord(this._CMDB_DYNAMIC_IRE_MATCH);
if (matchGr.get(matchRecs[i].sys_id)) {
items = this._getCIItemColumnsAndValues((matchRecs.length * 2), index, items, matchGr.getValue('ci_1'), matchGr.getValue('class_ci_1'), excludeNull);
index++;
items = this._getCIItemColumnsAndValues((matchRecs.length * 2), index, items, matchGr.getValue('ci_2'), matchGr.getValue('class_ci_2'), excludeNull);
index++;
} else {
this.log.logErr(this.formatMessage("CMDBDynamicIREProcessor::getMatchRecords Match record not found: {0}", [matchRecs[i].sys_id]));
}
}
return JSON.stringify(items);
} catch (ex) {
this.log.logErr(this.formatMessage("CMDBDynamicIREProcessor::getMatchRecords Parsing json msg: {0}", [ex]));
}
},
_getCIItemColumnsAndValues: function(ciCount, index, items, ciId, ciClass, excludeNull) {
var ciGr = new GlideRecord(ciClass);
if (!ciGr.get(ciId)) {
this.log.logErr(this.formatMessage("CMDBDynamicIREProcessor::_getCIItemColumnsAndValues CI not found: {0}", [ciId]));
return items;
}
var tableName = ciGr.getTableName();
if (!items.hasOwnProperty('columns')) {
items['columns'] = [ciGr.getValue('name')];
} else {
items['columns'].push(ciGr.getValue('name'));
}
var rownumber = 0;
this._populateFieldsAndValues(excludeNull, ciCount, index, rownumber, tableName, items, ciGr);
// Get hte reference tables for the features.
var featureGr = new GlideRecord(this._CMDB_DYNAMIC_IRE_FEATURE);
var filterJoinCondition = featureGr.addJoinQuery(this._CMDB_DYNAMIC_IRE_FILTER_CONDITION, 'filter_condition_id', 'sys_id');
featureGr.addActiveQuery();
featureGr.orderBy("order");
featureGr.query();
var referenceObjs = {};
while (featureGr.next()) {
// Get the reference tables and fields.
// {"cmdb_ci_network_adapter": {"referenceField": "cmdb_ci","referenceFields": ["mac_address"]},
// "cmdb_serial_number": {"referenceField": "cmdb_ci","referenceFields": ["serial_number_type","serial_number"]}}
if (!gs.nil(featureGr.filter_condition_id.reference_field)) {
var className = featureGr.getValue('cmdb_class');
var referenceObj = null;
var referenceFields = [];
for (var key in referenceObjs) {
if (className === key) {
referenceObj = referenceObjs[key];
referenceFields = referenceObj.referenceFields;
break;
}
}
if (gs.nil(referenceObj)) {
referenceObj = {};
referenceObj.referenceField = featureGr.filter_condition_id.reference_field;
referenceObj.referenceFields = referenceFields;
referenceObjs[featureGr.getValue('cmdb_class')] = referenceObj;
}
var attributes = featureGr.getValue('attributes');
if (!gs.nil(attributes)) {
attributes = attributes.split(',');
for (var i = 0; i < attributes.length; i++) {
if (referenceFields.indexOf(attributes[i]) === -1) {
referenceFields.push(attributes[i]);
}
}
}
}
}
for (var table in referenceObjs) {
var referenceItemObj = referenceObjs[table];
var refGr = new GlideRecord(table);
refGr.addQuery(referenceItemObj.referenceField, ciGr.getUniqueValue());
refGr.query();
rownumber = 0;
while (refGr.next()) {
this._populateFieldsAndValues(excludeNull, ciCount, index, rownumber, table, items, refGr);
rownumber++;
}
}
return items;
},
_populateFieldsAndValues: function(excludeNull, ciCount, index, rowNumber, tableName, items, gr) {
var columns = new GlideRecordUtil().getFields(gr);
columns = this._filterColumns(columns);
columns.sort();
var tableLabel = this._getClassLabel(tableName);
if (!items.hasOwnProperty(tableLabel)) {
items[tableLabel] = {
rows: [{}]
};
}
var row;
var rows = items[tableLabel].rows;
// Add a new row
if (rowNumber >= rows.length) {
row = {};
rows.push(row);
} else {
row = rows[rowNumber];
}
// Add the column values for this row object.
for (var i = 0; i < columns.length; i++) {
var columnLabel = this._getColumnForTable(tableName, columns[i], true);
if (!row.hasOwnProperty(columnLabel))
row[columnLabel] = this._getEmptyRow(ciCount);
row[columnLabel][index] = gr.getDisplayValue(columns[i]);
// If the row is empty and its the last value to be added remove the element.
if ((ciCount - 1) == index && this._isEmptyRow(row[columnLabel]) && excludeNull)
delete row[columnLabel];
}
},
_isEmptyRow: function(rowValues) {
for (var i = 0; i < rowValues.length; i++) {
if (!gs.nil(rowValues[i]))
return false;
}
return true;
},
_getEmptyRow: function(ciCount) {
var row = [];
for (var i = 0; i < ciCount; i++) {
row.push('');
}
return row;
},
_getFieldColumnsAndValues: function(columns, ciGr) {
var columnObj = {};
if (gs.nil(columns))
return columnObj;
columns.sort();
for (var i = 0; i < columns.length; i++) {
var value = this._getValueForColumn(columns[i], ciGr);
if (gs.nil(value)) {
columnObj[columns[i]] = [];
} else {
columnObj[columns[i]] = value;
}
}
return columnObj;
},
_getValueForColumn: function(column, gr) {
var fields = column.split('.');
var ciObj;
var value;
for (var j = 0; j < fields.length; j++) {
var field = '' + fields[j];
if (j == 0)
ciObj = gr.getElement(field);
else
ciObj = ciObj.getElement(field);
if (gs.nil(ciObj)) {
value = '';
} else {
if (j < fields.length - 1) {
ciObj = ciObj.getRefRecord();
} else {
value = [ciObj.getValue(field)];
}
}
}
return value;
},
/*
*
* @parm featureObj
* @parm columns
* @parm ciGr
* @return {"serial_number_type":["chassis","chassis"],"serial_number":["4f-fc-cd-db-b1-919843","4f-fc-cd-db-b1-919844"]}
*/
_getReferenceFeatureColumnsAndValues: function(featureObj, columns, ciGr) {
var cmdbFeatureGr = new GlideRecord(featureObj.cmdb_class);
cmdbFeatureGr.addQuery(featureObj.reference_field, ciGr.getUniqueValue());
if (!gs.nil(featureObj.static_condition))
cmdbFeatureGr.addEncodedQuery(featureObj.static_condition);
cmdbFeatureGr.query();
// Nothing found return empty column/values.
var referencedObj;
if (cmdbFeatureGr.getRowCount() > 0) {
while (cmdbFeatureGr.next()) {
var ciColValues = this._getFieldColumnsAndValues(columns, cmdbFeatureGr);
if (gs.nil(referencedObj)) {
referencedObj = ciColValues;
} else {
for (var key in referencedObj) {
var values = referencedObj[key];
values = values.concat(ciColValues[key]);
referencedObj[key] = values;
}
}
}
} else {
referencedObj = {};
for (var i = 0; i < columns.length; i++) {
referencedObj[columns[i]] = [];
}
}
return referencedObj;
},
_getEncodedQueryCondition: function(columns, conditionQuery, itemGr) {
var encodedQuery = '';
if (gs.nil(conditionQuery))
return encodedQuery;
var conditionColumn = conditionQuery.split('^');
var encodedQueryWildCard = '';
for (var i = 0; i < conditionColumn.length; i++) {
var conditionColumnWildCard = conditionColumn[i].split('?');
encodedQueryWildCard = '';
if (conditionColumnWildCard.length > 1) {
for (var j = 0; j < conditionColumnWildCard.length; j++) {
var condition = conditionColumnWildCard[j];
if (gs.nil(condition))
continue;
for (var k = 0; k < columns.length; k++) {
if (condition.indexOf(columns[k]) !== -1 && (condition.split('.').length === columns[k].split('.').length)) {
var value = this._getValueForColumn(columns[k], itemGr);
if (!gs.nil(value)) {
encodedQueryWildCard = condition + value;
}
}
}
}
}
if (gs.nil(encodedQueryWildCard)) {
if (gs.nil(encodedQuery))
encodedQuery = encodedQuery + conditionColumn[i];
else
encodedQuery = encodedQuery + '^' + conditionColumn[i];
} else {
if (gs.nil(encodedQuery))
encodedQuery = encodedQuery + encodedQueryWildCard;
else
encodedQuery = encodedQuery + '^' + encodedQueryWildCard;
}
}
return encodedQuery;
},
/**
* Only gets tables which have data for that column.
* Gets clazz and all tables that extend clazz +
* reference tables of clazz and its extended from classes.
*/
_getColumnsForLookupTable: function(clazz, column, referenceOnly) {
var results = [];
if (gs.nil(clazz) || gs.nil(column))
return results;
var tables = [];
// Get all extended tables from clazz
if (!referenceOnly) {
tables = j2js(new TableUtils(clazz).getTableExtensions());
tables.push(clazz);
}
// Get parent tables and ref tables for clazz
var parentTables = j2js(new TableUtils(clazz).getTables());
for (var k = 0; k < parentTables.length; k++) {
// Add reference tables.
var refTables = this._getReferencingTables(parentTables[k]);
for (var l = 0; l < refTables.length; l++)
tables.push(refTables[l]);
}
tables = this._filterTables(tables);
for (var i = 0; i < tables.length; i++) {
var columns = this._getTableColumns(tables[i]);
for (var j = 0; j < columns.length; j++) {
if (column == columns[j]) {
var entry = {};
entry.table = tables[i];
entry.column = columns[j];
results.push(entry);
}
}
}
return results;
},
train: function(tableName, featuresColumns, outputColumn, modelName, modelId) {
var result = {};
try {
var modelGr = new GlideRecord(this._CMDB_DYNAMIC_IRE_MODEL);
if (!modelGr.get(modelId)) {
var message = this.formatMessage("Completed job CMDBDynamicIREProcessor::train Model not found Id {0}", [modelId]);
this.log.logErr(message);
result.status = "FAILURE";
return result;
}
//deep copy
var fieldNames = JSON.parse(JSON.stringify(featuresColumns));
fieldNames.push(outputColumn);
var fieldDetails = [];
for (var j = 0; j < featuresColumns.length; j++) {
fieldDetails.push({
'name': featuresColumns[j],
type: 'numeric'
});
}
var myData = new sn_ml.DatasetDefinition({
'tableName': tableName,
'fieldNames': fieldNames,
'fieldDetails': fieldDetails
});
var mySolution = new sn_ml.ClassificationSolution({
'label': modelName,
'dataset': myData,
'predictedFieldName': outputColumn,
'inputFieldNames': featuresColumns
});
var solutionName = sn_ml.ClassificationSolutionStore.add(mySolution);
// submit training job
var solutionVersion = mySolution.submitTrainingJob();
result.solutionName = mySolution.getName();
var trainingStatus = JSON.parse(solutionVersion.getStatus());
result.solutionVersion = trainingStatus;
result.status = "SUCCESS";
modelGr.setValue('ml_model_name', solutionName);
modelGr.setValue('version', trainingStatus);
if (gs.nil(modelGr.update())) {
result.status = "FAILURE";
this.log.logErr(this.formatMessage("CMDBDynamicIREProcessor::train msg: Unable to update {0} with the solutionName and solutionVersion", [this._CMDB_DYNAMIC_IRE_MODEL]));
}
} catch (ex) {
this.log.logErr(this.formatMessage("CMDBDynamicIREProcessor::train msg: {0}", [ex]));
result.status = "FAILURE";
}
return JSON.stringify(result);
},
monitorStatus: function(solutionName) {
var result = {};
try {
var mlSolution = sn_ml.ClassificationSolutionStore.get(solutionName);
var trainingStatus = JSON.parse(mlSolution.getLatestVersion().getStatus());
result.state = trainingStatus['state'];
result.precentComplete = trainingStatus['percentComplete'];
result.hasJobEnded = trainingStatus['hasJobEnded'];
result.status = "SUCCESS";
} catch (ex) {
result.status = "FAILURE";
this.log.logErr(this.formatMessage("CMDBDynamicIREProcessor::monitorStatus msg: {0}", [ex]));
}
return JSON.stringify(result);
},
predict: function(tableName, solutionName, modelId, isComputeLocalPrediction) {
try {
var result = {};
var options = {};
var message = '';
options.apply_threshold = false;
var mlSolution = sn_ml.ClassificationSolutionStore.get(solutionName);
var activeMlSolutionVersion = mlSolution.getActiveVersion();
var complete = false;
var swComplete = new GlideStopWatch();
var inputGr = new GlideRecord(tableName);
inputGr.query();
var rowCount = inputGr.getRowCount();
var lo = 0;
var hi = 50;
var batchSize = 50;
if (isComputeLocalPrediction) {
var featuresJSON = this.getFeatures(modelId);
if (gs.nil(featuresJSON)) {
message = this.formatMessage("Completed job CMDBDynamicIREProcessor::predict no active features defined for model Id: {0}", [modelId]);
this.log.logErr(message);
result.status = 'FAILURE';
result.statusCode = 'no_active_features_present_make_sure_to_run_configure_model';
return JSON.stringify(result);
}
var modelConfigInfo = this.getModelConfigInfo(modelId);
modelConfigInfoObject = JSON.parse(modelConfigInfo);
if (modelConfigInfoObject.status == 'SUCCESS') {
if (gs.nil(modelConfigInfoObject.biasValue) || modelConfigInfoObject.biasValue == '') {
message = this.formatMessage("Completed job CMDBDynamicIREProcessor::predict model configuration not done yet, run configureModel for model Id: {0}", [modelId]);
result.status = 'FAILURE';
result.statusCode = 'no_bias_value_present_make_sure_to_run_configure_model';
return JSON.stringify(result);
}
} else {
message = this.formatMessage("Completed job CMDBDynamicIREProcessor::predict error while fetching bias weight and reverse flag for model Id: {0}", [modelId]);
result.status = 'FAILURE';
result.statusCode = 'error_while_fetching_bias_weight_and_reverse_flag_make_sure_to_run_configure_model';
return JSON.stringify(result);
}
}
while (!complete) {
var sw = new GlideStopWatch();
inputGr.orderByDesc('sys_id');
inputGr.chooseWindow(lo, hi);
inputGr.query();
var tempSysIds = [];
while (inputGr.next()) {
tempSysIds.push('' + inputGr.sys_id);
}
var gr = new GlideRecord(tableName);
gr.addQuery('sys_id', 'IN', tempSysIds);
gr.query();
var results = {};
if (isComputeLocalPrediction)
results = this._computeLocalPrediction(gr, featuresJSON, modelConfigInfoObject);
else
results = activeMlSolutionVersion.predict(gr, options);
this._updatePredictionAndProbability(results);
lo = hi;
hi = lo + batchSize;
if (lo >= rowCount) {
complete = true;
}
sw.stop();
this.log.logInfo(this.formatMessage("Completed job CMDBDynamicIREProcessor::predict. Processed {0} records of {1}. Duration: {2} (sec).", [lo, rowCount, sw.getTime() / 1000]));
}
swComplete.stop();
this.log.logInfo(this.formatMessage("Completed job CMDBDynamicIREProcessor::predict. Processed {0} records of {1}. Duration: {2} (sec).", [hi, 50, rowCount, swComplete.getTime() / 1000]));
result.status = "SUCCESS";
} catch (ex) {
this.log.logErr(this.formatMessage("CMDBDynamicIREProcessor::predict Msg: {0}", [ex]));
result.status = "FAILURE";
}
return JSON.stringify(result);
},
/*
* Fetch the weights of the trained model from ML platform tables and
* update the cmdb_dynamic_ire_feature table with it for each of the features.
* @param modelId The modelId for the specific class of the the CMDB class
* @param mlSolutionName the soutionName for the final Solution to be used by IRE
* @param options {'mlSolutionVersion':1} JSON string which contains the solutionVersion for the final Solution to be used by IRE
* @return {status: "SUCCESS"}"}
*/
configureModel: function(modelId, mlSolutionName, options) {
try {
var result = {};
result.status = "SUCCESS";
var optionsObject = {
'mlSolutionVersion': 1
};
if (!gs.nil(options))
optionsObject = JSON.parse(options);
var mlSolutionVersion = 1;
if (optionsObject.hasOwnProperty('mlSolutionVersion'))
mlSolutionVersion = optionsObject.mlSolutionVersion;
//verify that there is a model configured in the cmdb_dynamic_ire_model table
var modelGr = new GlideRecord(this._CMDB_DYNAMIC_IRE_MODEL);
if (!modelGr.get(modelId)) {
this.log.logErr(this.formatMessage("Completed job CMDBDynamicIREProcessor::configureModel Model not found Id {0}", [modelId]));
result.status = "FAILURE";
result.statusCode = "model_not_found";
return JSON.stringify(result);
}
//update the ml_model_name and ml_solution_version in the cmdb_dynamic_ire_model
modelGr.setValue('ml_model_name', mlSolutionName);
modelGr.setValue('version', mlSolutionVersion);
if (gs.nil(modelGr.update())) {
this.log.logErr(this.formatMessage("Completed job CMDBDynamicIREProcessor::configureModel Unable to update the model solutionName {0} modelSolutionVersion {1}", [mlSolutionName, mlSolutionVersion]));
result.status = "FAILURE";
result.statusCode = "unable_to_update_model_solution_name";
return JSON.stringify(result);
}
var mlInterface = new global.CMDBDynamicIREMLInterface();
var modelMetaData = mlInterface.getModelMetaData(mlSolutionName, mlSolutionVersion);
if (gs.nil(modelMetaData)) {
this.log.logErr(this.formatMessage("Comleted Job CMDBDynamicIREProcessor::configureModel Unable to configure the model, there was an exception while fetching the model metadata from the ml platfrom tables {0}", [modelMetaData]));
result.status = "FAILURE";
result.statusCode = "ml_meta_data_undefined_or_failure";
return JSON.stringify(result);
}
var modelMetaDataObject = JSON.parse(modelMetaData);
if (modelMetaDataObject.status === 'FAILURE') {
this.log.logErr(this.formatMessage("Comleted Job CMDBDynamicIREProcessor::configureModel Unable to configure the model, there was an exception while fetching the model metadata from the ml platfrom tables {0}", [modelMetaData]));
result.status = "FAILURE";
result.statusCode = "ml_meta_data_undefined_or_failure";
return JSON.stringify(result);
}
var modelObject = modelMetaDataObject.weights;
if (gs.nil(modelObject)) {
this.log.logErr(this.formatMessage("Completed Job CMDBDynamicIREProcessor::configureModel Unable to parse the response from the ML API for the model MetaData"));
result.status = "FAILURE";
result.statusCode = "unable_to_parse_ml_meta_data";
return JSON.stringify(result);
}
var feature_count = modelObject.feature_count;
if (feature_count === 0) {
this.log.logErr(this.formatMessage("Completed Job CMDBDynamicIREProcessor::configureModel There are no features which made it to the model. Ensure that the training was successful and the ML model for solution name {0} and solution version {1} is valid", [mlSolutionName, mlSolutionVersion]));
result.status = "FAILURE";
result.statusCode = "no_features_found_in_model";
return JSON.stringify(result);
}
var featureGr = new GlideRecord(this._CMDB_DYNAMIC_IRE_FEATURE);
var filterJoinCondition = featureGr.addJoinQuery(this._CMDB_DYNAMIC_IRE_FILTER_CONDITION, 'filter_condition_id', 'sys_id');
filterJoinCondition.addCondition('model_id', modelId);
featureGr.addNotNullQuery('attributes');
featureGr.orderBy("order");
featureGr.query();
while (featureGr.next()) {
if (gs.nil(modelObject[featureGr.getValue('feature')])) {
//feature did not make it into the model can set this feature in table to inactive
featureGr.setValue('active', 'false');
featureGr.setValue('weight', '');
} else {
featureGr.setValue('active', 'true');
featureGr.setValue('weight', modelObject[featureGr.getValue('feature')]);
}
if (gs.nil(featureGr.update())) {
this.log.logErrr(this.formatMessage('CMDBDynamicIREProcessor::configureModel Issue with updating the weight values for feature Id {0}', [featureGr.getUniqueValue()]));
result.statusCode = "unable_to_update_feature_weight";
result.status = "FAILURE";
}
}
var updateModelGr = new GlideRecord(this._CMDB_DYNAMIC_IRE_MODEL);
updateModelGr.addQuery('sys_id', modelId);
updateModelGr.query();
if (updateModelGr.next()) {
updateModelGr.setValue('ml_model_bias', modelObject['bias']);
updateModelGr.setValue('ml_model_class_label', modelObject['rev']);
if (gs.nil(updateModelGr.update())) {
this.log.logErrr(this.formatMessage('CMDBDynamicIREProcessor::configureModel Issue with updating the bias weight for model Id {0}', [updateModelGr.getUniqueValue()]));
result.statusCode = "unable_to_update_bias_weight";
result.status = "FAILURE";
}
}
} catch (ex) {
this.log.logErr(this.formatMessage("CMDBDynamicIREProcessor::configureModel exception while configuring model {0}", [ex]));
result.statusCode = "unhandled_exception";
result.status = "FAILURE";
}
return JSON.stringify(result);
},
getModelConfigInfo: function(modelId) {
var modelConfigInfo = {};
modelConfigInfo.status = 'SUCCESS';
var modelGr = new GlideRecord(this._CMDB_DYNAMIC_IRE_MODEL);
modelGr.addQuery('sys_id', modelId);
modelGr.query();
if (modelGr.next()) {
modelConfigInfo.isReverse = modelGr.getValue('ml_model_class_label');
modelConfigInfo.biasValue = modelGr.getValue('ml_model_bias');
} else {
var message = this.formatMessage("Completed job CMDBDynamicIREProcessor::getModelConfigInfo Model not found Id {0}", [modelId]);
this.log.logErr(message);
modelConfigInfo.status = 'FAILURE';
}
return JSON.stringify(modelConfigInfo);
},
_computeLocalPrediction: function(gr, featuresJSON, modelConfigInfoObject) {
var results = {};
var message = '';
if (gs.nil(featuresJSON)) {
message = this.formatMessage("Completed job CMDBDynamicIREProcessor::_computeLocalPrediction no active features provided");
this.log.logErr(message);
return JSON.stringify(result);
}
if (gs.nil(modelConfigInfoObject)) {
message = this.formatMessage("Completed job CMDBDynamicIREProcessor::_computeLocalPrediction no modelConfigInfoObject provided", [modelConfigInfoObject]);
this.log.logErr(message);
return JSON.stringify(result);
}
if (gs.nil(modelConfigInfoObject.biasValue)) {
message = this.formatMessage("Completed job CMDBDynamicIREProcessor::_computeLocalPrediction no_bias_value_present_make_sure_to_run_configure_model", [modelConfigInfoObject.biasValue]);
this.log.logErr(message);
return JSON.stringify(result);
}
if (gs.nil(modelConfigInfoObject.isReverse)) {
message = this.formatMessage("Completed job CMDBDynamicIREProcessor::_computeLocalPrediction no_isReverse_present_make_sure_to_run_configure_model", [modelConfigInfoObject.isReverse]);
this.log.logErr(message);
return JSON.stringify(result);
}
while (gr.next()) {
//for each featureset row get each featureValue as defined in the model from the table
var product = parseFloat(modelConfigInfoObject.biasValue);
for (var key in featuresJSON) {
var featureObj = featuresJSON[key];
product += (featureObj.weight * gr.getValue(featureObj.featureColumnName));
}
var probability = this._applyLogistic(product);
var confidence = probability > 0.5 ? probability * 100 : (1 - probability) * 100;
var predictedValue = (modelConfigInfoObject.isReverse == "1") ? (probability > 0.5) ? "0" : "1" : (probability > 0.5) ? "1" : "0";
results[gr.getUniqueValue()] = [{
"confidence": confidence,
"predictedValue": predictedValue
}];
}
return JSON.stringify(results);
},
/**
* compute the logistic function
*/
_applyLogistic: function(num) {
return 1 / (1 + Math.exp(-num));
},
/**
* Get table column where the table has data.
*/
_getTableColumns: function(clazz) {
var columns = [];
try {
// If the table has no data it will not be added.
var grCheck = new GlideRecord(clazz);
grCheck.setLimit(1);
grCheck.query();
if (grCheck.next()) {
var fields = new GlideRecordUtil().getFields(grCheck);
var gr = new GlideRecord('sys_db_object');
// CMDB tables
var column = '';
for (var j = 0; j < fields.length; j++) {
if (!gs.nil(grCheck.sys_class_name) && gr.get('name', grCheck.sys_class_name)) {
// Extended tables
column = this._getColumnForTable(gr.super_class.name, fields[j], false);
if (!gs.nil(column))
columns.push(column);
else {
column = this._getColumnForTable(grCheck.sys_class_name, fields[j], false);
if (!gs.nil(column))
columns.push(column);
}
} else {
//None CMDB tables
if (gr.get('name', clazz)) {
column = this._getColumnForTable(gr.name, fields[j], false);
if (!gs.nil(column))
columns.push(column);
}
}
}
}
} catch (ex) {
this.log.logInfo(this.formatMessage("Info: _getTableColumns for class: {0}. Msg: {1}", [clazz, ex]));
}
return columns;
},
_updatePredictionAndProbability: function(results) {
var resultsJson = JSON.parse(results);
for (var key in resultsJson) {
var arrayObject = resultsJson[key];
var matchGr = new GlideRecord(this._CMDB_DYNAMIC_IRE_MATCH);
matchGr.addQuery('feature_record', key);
matchGr.query();
if (0 == matchGr.getRowCount()) {
this.log.logErr(this.formatMessage("_updatePredictionAndProbability - unable to find match record for sys_id: {0}.", [key]));
}
if (matchGr.next()) {
matchGr.setValue('prediction', parseInt(arrayObject[0].predictedValue));
matchGr.setValue('probability', arrayObject[0].confidence);
if (!matchGr.update())
this.log.logErr(this.formatMessage("Error: _updatePredictionAndProbability - unable to update the match record for sys_id: {0}.", [key]));
}
}
},
_getColumnForTable: function(className, field, columnLabel) {
var dict = new GlideRecord('sys_dictionary');
dict.addQuery('name', className);
dict.addQuery('element', field);
dict.addActiveQuery();
dict.query();
if (dict.next()) {
if (columnLabel)
return dict.column_label;
else
return dict.element;
}
return null;
},
_getReferenceTableForColumn: function(referenceTable, field) {
var gr = new GlideAggregate('sys_dictionary');
gr.addQuery('name', referenceTable);
// exclude entries for duplicate_of field
gr.addQuery('element', field);
gr.addAggregate('count');
gr.orderByAggregate('count');
gr.groupBy('reference');
gr.query();
if (gr.next())
return '' + gr.reference;
return null;
},
_getLookupTables: function(className) {
var map = {};
var tables = j2js(new TableUtils(className).getTables());
for (var i = 0; i < tables.length; i++) {
var refTables = this._getReferencingTables(tables[i]);
for (var j = 0; j < refTables.length; j++)
map[refTables[j]] = '';
}
var result = [];
result.push(className);
for (var tab in map)
if (tab != className)
result.push(tab);
result = this._filterTables(result);
return result;
},
_getLookupTablesForClassManager: function(className) {
var result = [];
var lookupTables = this._getLookupTables(className);
for (var i = 0; i < lookupTables.length; i++) {
if (className == lookupTables[i])
continue;
var entry = {};
entry.value = lookupTables[i];
entry.name = this._getClassLabel(lookupTables[i]);
result.push(entry);
}
return result;
},
_filterColumns: function(columns) {
var filtered = [];
for (var i = 0; i < columns.length; i++) {
var tbl = columns[i];
var j = 0;
while (j < this.filteredColumns.length) {
if (tbl.indexOf(this.filteredColumns[j]) !== -1)
break;
j++;
}
// filter out tables meant for workflow
if (tbl.startsWith('var__'))
continue;
if (j == this.filteredColumns.length)
filtered.push(tbl);
}
return filtered;
},
_filterTables: function(tables) {
var filtered = [];
for (var i = 0; i < tables.length; i++) {
var tbl = tables[i];
var j = 0;
while (j < this.filteredTables.length) {
if (tbl.indexOf(this.filteredTables[j]) !== -1)
break;
j++;
}
// filter out tables meant for workflow
if (tbl.startsWith('var__'))
continue;
if (j == this.filteredTables.length)
filtered.push(tbl);
}
return filtered;
},
_getReferencingTables: function(table) {
var referencingTables = [];
var gr = new GlideAggregate('sys_dictionary');
gr.addQuery('reference', table);
// exclude entries for duplicate_of field
gr.addQuery('element', '!=', 'duplicate_of');
gr.addAggregate('count');
gr.orderByAggregate('count');
gr.groupBy('name');
gr.query();
while (gr.next())
referencingTables.push('' + gr.name);
return referencingTables;
},
_getClassLabel: function(className) {
var descriptor = GlideTableDescriptor.get(className);
return descriptor.getLabel();
},
_processFilterCMDBItems: function(filterName, maxFilteredRecords, maxRecords, maxMatchRecords, filterClasses, staticCondition, condition, dynamicConditionColumns, modelId) {
var swComplete = new GlideStopWatch();
var complete = false;
var processedRecCount = 0;
var lastProccessId = '';
var filter = {
"name": filterName
};
while (!complete) {
var sw = new GlideStopWatch();
complete = true;
var tableGr = new GlideRecord(this._CMDB_CI);
tableGr.addQuery('sys_class_name', 'IN', filterClasses.toString());
if (!gs.nil(lastProccessId))
tableGr.addQuery('sys_id', '>', lastProccessId);
if (!gs.nil(staticCondition)) {
tableGr.addEncodedQuery(staticCondition);
}
// NOTE limit must be set BEFORE order by
tableGr.setLimit(this.batchSize);
tableGr.orderBy('sys_id');
tableGr.query();
while (tableGr.next()) {
// Apply the filter condition
if (!gs.nil(condition)) {
var filterCIsGr = new GlideRecord(tableGr.getValue('sys_class_name'));
// Get the encodedQuery with the wildcards substituted for actaul values.
var encodedQuery = this._getEncodedQueryCondition(dynamicConditionColumns, condition, tableGr);
if (!gs.nil(encodedQuery))
filterCIsGr.addEncodedQuery(encodedQuery);
filterCIsGr.addQuery('sys_id', '!=', tableGr.getUniqueValue());
if (0 != maxFilteredRecords)
filterCIsGr.setLimit(maxFilteredRecords);
filterCIsGr.query();
var cmdbCI = [];
while (filterCIsGr.next()) {
if (cmdbCI.indexOf(filterCIsGr.getUniqueValue()) === -1) {
cmdbCI.push(filterCIsGr.getUniqueValue());
// createMatchRecord(ci_1, class_ci_1, ci_2, class_ci_2)
this.createMatchRecord(tableGr.getUniqueValue(), tableGr.getValue('sys_class_name'), filterCIsGr.getUniqueValue(), filterCIsGr.getTableName(), false, modelId);
}
}
lastProccessId = tableGr.getUniqueValue();
processedRecCount++;
if (maxRecords != 0 && processedRecCount >= maxRecords) {
complete = true;
break;
}
// Check if we have reached the limit of maxMatchRecords
if (0 != maxMatchRecords && this.matchCount >= maxMatchRecords) {
complete = true;
break;
}
complete = false;
}
}
if (0 != tableGr.getRowCount()) {
sw.stop();
this.log.logDebug(this.formatMessage("Processing job CMDBDynamicIREProcessor::applyFilterConditions for filter: {0}. Processed {1} CI's of {2}. Found {3} duplicates. Duration: {4} (sec).", [filterName, processedRecCount.toString(), maxRecords.toString(), this.matchCount.toString(), sw.getTime() / 1000]));
}
}
swComplete.stop();
var message = this.formatMessage("Completed job CMDBDynamicIREProcessor::applyFilterConditions for filter: {0}. Processed {1} CI's of {2}. Found {3} duplicates. Duration: {4} (sec).", [filterName, processedRecCount.toString(), maxRecords.toString(), this.matchCount.toString(), swComplete.getTime() / 1000]);
filter.message = message;
filter.maximum_records = maxRecords;
filter.processed_records = processedRecCount;
filter.match_records = this.matchCount;
filter.reference_filter = false;
filter.processed_time = swComplete.getTime() / 1000;
this.log.logInfo(message);
return filter;
},
_processFilterReferenceItems: function(filterName, maxFilteredRecords, maxRecords, maxMatchRecords, baseClassCITree, staticCondition, condition, dynamicConditionColumns, filterClass, referenceField, modelId) {
var swComplete = new GlideStopWatch();
var complete = false;
var processedRecCount = 0;
var lastProccessId = '';
var filter = {
"name": filterName
};
while (!complete) {
var sw = new GlideStopWatch();
complete = true;
var tableGr = new GlideRecord(filterClass);
var cmdbClass = this._getReferenceTableForColumn(filterClass, referenceField);
var joinCondition = tableGr.addJoinQuery(cmdbClass, referenceField, 'sys_id');
joinCondition.addCondition('sys_class_name', 'IN', baseClassCITree.toString());
if (!gs.nil(lastProccessId))
tableGr.addQuery('sys_id', '>', lastProccessId);
if (!gs.nil(staticCondition)) {
tableGr.addEncodedQuery(staticCondition);
}
// NOTE limit must be set BEFORE order by
tableGr.setLimit(this.batchSize);
tableGr.orderBy('sys_id');
tableGr.query();
while (tableGr.next()) {
// Apply the filter condition
if (!gs.nil(condition)) {
var filterCIsGr = new GlideRecord(filterClass);
var filterJoinCondition = filterCIsGr.addJoinQuery(cmdbClass, referenceField, 'sys_id');
filterJoinCondition.addCondition('sys_class_name', 'IN', baseClassCITree.toString());
// Get the encodedQuery with the wildcards substituted for actaul values.
var encodedQuery = this._getEncodedQueryCondition(dynamicConditionColumns, condition, tableGr);
if (!gs.nil(encodedQuery))
filterCIsGr.addEncodedQuery(encodedQuery);
filterCIsGr.addQuery('sys_id', '!=', tableGr.getUniqueValue());
if (0 != maxFilteredRecords)
filterCIsGr.setLimit(maxFilteredRecords);
filterCIsGr.query();
var cmdbCI = [];
while (filterCIsGr.next()) {
if (cmdbCI.indexOf(filterCIsGr[referenceField]) === -1) {
cmdbCI.push(filterCIsGr[referenceField]);
// createMatchRecord(ci_1, class_ci_1, ci_2, class_ci_2)
this.createMatchRecord(tableGr[referenceField], tableGr[referenceField].sys_class_name, filterCIsGr[referenceField], filterCIsGr[referenceField].sys_class_name, true, modelId);
}
}
lastProccessId = tableGr.getUniqueValue();
processedRecCount++;
if (maxRecords != 0 && processedRecCount >= maxRecords) {
complete = true;
break;
}
// Check if we have reached the limit of maxMatchRecords
if (0 != maxMatchRecords && this.matchCount >= maxMatchRecords) {
complete = true;
break;
}
complete = false;
}
}
if (0 != tableGr.getRowCount()) {
sw.stop();
this.log.logDebug(this.formatMessage("Processing job CMDBDynamicIREProcessor::applyFilterConditions for filter: {0}. Processed {1} CI's of {2}. Found {3} matches. Duration: {4} (sec).", [filterName, processedRecCount.toString(), maxRecords.toString(), this.matchCount.toString(), sw.getTime() / 1000]));
}
}
swComplete.stop();
var message = this.formatMessage("Completed job CMDBDynamicIREProcessor::applyFilterConditions for filter: {0}. Processed {1} CI's of {2}. Found {3} matches. Duration: {4} (sec).", [filterName, processedRecCount.toString(), maxRecords.toString(), this.matchCount.toString(), swComplete.getTime() / 1000]);
filter.message = message;
filter.maximum_records = maxRecords;
filter.processed_records = processedRecCount;
filter.match_records = this.matchCount;
filter.reference_filter = true;
filter.processed_time = swComplete.getTime() / 1000;
this.log.logInfo(message);
return filter;
},
_getColumnFromQuery: function(conditionQuery) {
var columns = [];
var conditionColumn = conditionQuery.split('^');
for (var i = 0; i < conditionColumn.length; i++) {
if (gs.nil(conditionColumn[i]))
continue;
var column = '';
conditionColumn[i].replace(/[a-z_.]/g, function(m) {
column = column + m;
});
if (!gs.nil(column))
columns.push(column);
}
return columns;
},
formatMessage: function(str, args) {
this._replaceExpr = this._replaceExpr || [];
for (var i = 0; i < args.length; i++) {
if(i >= this._replaceExpr.length)
this._replaceExpr[i] = new RegExp('\\{' + i + '\\}', 'gi');
str = str.replace(this._replaceExpr[i], args[i]);
}
return str;
},
type: 'CMDBDynamicIREProcessor'
};
Sys ID
0a90d4700b50201099b8ea7885673aa4