Name

global.MetricBaseTriggerUtils

Description

Utility class for MetricBase Triggers

Script

var MetricBaseTriggerUtils = Class.create();
MetricBaseTriggerUtils.prototype = {
  initialize: function(legacy) {
  	if (legacy === true) {
  		this.bandTriggerTableName = 'sys_metric_trigger';
  		this.bandLevelTableName = 'sys_metric_trigger_level';
  		this.windowFieldName = 'window';
  		this.aggregatorFieldName = 'aggregator';
  		this.valueFieldName = 'value';
  		this.bandValueFieldName = 'band_value';
  		this.toleranceFieldName = 'tolerance';
  	} else {
  		this.bandTriggerTableName = 'sys_metric_trigger_band';
  		this.bandLevelTableName = 'sys_metric_trigger_level_band';
  		this.windowFieldName = 'window_band';
  		this.aggregatorFieldName = 'aggregator_band';
  		this.valueFieldName = 'value_band';
  		this.bandValueFieldName = 'band_value_band';
  		this.toleranceFieldName = 'tolerance_band';
  	}
  },

  validateBandTrigger: function(triggerGr) {
  	var window = new GlideDateTime(triggerGr.getElement(this.windowFieldName)).getNumericValue();
  	if (window == 0) {
  		gs.addErrorMessage(gs.getMessage("Invalid window. Please enter a positive window value"));
  		triggerGr.setAbortAction(true);
  		return;
  	}

  	var metric = new GlideRecord('sys_metric');
  	metric.get('sys_id', triggerGr.metric);

  	var has_schedule = this._getRetentionPolicySchedule(triggerGr, metric);
  	if (gs.nil(has_schedule))
  		return;

  	var schedule = new GlideRecord('sys_metric_schedule');
  	schedule.get('sys_id', has_schedule.schedule);

  	var period = new GlideDateTime(schedule.getElement('period')).getNumericValue();
  	if (window % period != 0) {
  		gs.addErrorMessage(gs.getMessage("Window value should be multiples of metric sampling period '{0}'", new GlideDuration(period).getDurationValue()));
  		triggerGr.setAbortAction(true);
  	}

  	var max = gs.getProperty('glide.clotho.trigger.max_window');
  	if (window / period > max) {
  		gs.addErrorMessage(gs.getMessage("Window value '{0}' cannot be more than '{1}' times the sampling period '{2}'", [new GlideDuration(window).getDurationValue(), max, new GlideDuration(period).getDurationValue()]));
  		triggerGr.setAbortAction(true);
  	}
  },

  _getRetentionPolicySchedule: function(record, metric) {
  	var has_schedule = new GlideRecord('sys_metric_policy_has_schedule');
  	has_schedule.addQuery('retention_policy', metric.retention_policy);
  	has_schedule.orderBy('schedule.period');
  	has_schedule.setLimit(1);
  	has_schedule.query();
  	if (!has_schedule.next()) {
  		gs.addErrorMessage(gs.getMessage("Unable to retrieve metric sampling period for metric '{0}'", metric.getDisplayValue()));
  		record.setAbortAction(true);
  		return null;
  	}

  	return has_schedule;
  },

  validateBandTriggerLevel: function(levelGr) {
  	// function cannot be empty
  	this._checkFunctionNotNone(levelGr);

  	// clear the band value if no band function
  	if (JSUtil.nil(levelGr.getValue('band_function')))
  		levelGr.setValue(this.bandValueFieldName, '');

  	// trigger values must be valid
  	if (isNaN(parseFloat(levelGr.getValue(this.valueFieldName)))) {
  		gs.addErrorMessage(gs.getMessage('Invalid limit value'));
  		levelGr.setAbortAction(true);
  	}

  	if (JSUtil.notNil(levelGr.getValue('band_function')) && isNaN(parseFloat(levelGr.getValue(this.bandValueFieldName)))) {
  		gs.addErrorMessage(gs.getMessage('Invalid limit value'));
  		levelGr.setAbortAction(true);
  	}

  	// trigger level value cannot be 0
  	this._checkLevelValueNotZero(levelGr);

  	// trigger level value shall be unique
  	this._checkLevelValueUnique(levelGr, this.bandLevelTableName);

  	// trigger band shall not overlap an existing band
  	var gr = new GlideRecord(this.bandLevelTableName);
  	gr.addQuery('trigger', levelGr.trigger);
  	gr.addQuery('sys_id', '!=', levelGr.sys_id);
  	gr.query();
  	while (gr.next()) {
  		if (this._levelCollides(levelGr, gr)) {
  			gs.addErrorMessage(gs.getMessage('The band defined by this level is colliding with level {0}', gr.level));
  			levelGr.setAbortAction(true);
  		}
  	}
  },

  _checkFunctionNotNone: function(levelRecord) {
  	if (JSUtil.nil(levelRecord.getValue('function'))) {
  		gs.addErrorMessage(gs.getMessage('Function column cannot be empty'));
  		levelRecord.setAbortAction(true);
  	}
  },

  _checkLevelValueNotZero: function(levelRecord) {
  	if (levelRecord.level == 0) {
  		gs.addErrorMessage(gs.getMessage("Level zero is reserved, User can not use this"));
  		levelRecord.setAbortAction(true);
  	}
  },

  _checkLevelValueUnique: function(levelRecord, levelTableName) {
  	var gr = new GlideRecord(levelTableName);
  	gr.addQuery('trigger', levelRecord.trigger);
  	gr.addQuery('level', levelRecord.level);
  	gr.addQuery('sys_id', '!=', levelRecord.sys_id);
  	gr.query();
  	if (gr.hasNext()) {
  		gs.addErrorMessage(gs.getMessage("Duplicate levels are not allowed. Level '{0}' already exists", levelRecord.level));
  		levelRecord.setAbortAction(true);
  	}
  },

  _levelCollides: function(level1, level2) {
  	if (this._limitCollides(level1.getValue('function'), parseFloat(level1.getValue(this.valueFieldName)), level2))
  		return true;

  	if (JSUtil.notNil(level1.getValue('band_function')) && this._limitCollides(level1.getValue('band_function'), parseFloat(level1.getValue(this.bandValueFieldName)), level2))
  		return true;

  	if (this._limitCollides(level2.getValue('function'), parseFloat(level2.getValue(this.valueFieldName)), level1))
  		return true;

  	if (JSUtil.notNil(level2.getValue('band_function')) && this._limitCollides(level2.getValue('band_function'), parseFloat(level2.getValue(this.bandValueFieldName)), level1))
  		return true;

  	return false;
  },

  _limitCollides: function(operator, value, level) {
  	var firstFunctionCollides = this._collides(operator, value, level.getValue('function'), parseFloat(level.getValue(this.valueFieldName)));
  	var secondFunctionCollides = true;
  	if (JSUtil.notNil(level.getValue('band_function')))
  		secondFunctionCollides = this._collides(operator, value, level.getValue('band_function'), parseFloat(level.getValue(this.bandValueFieldName)));

  	return firstFunctionCollides && secondFunctionCollides;
  },

  _collides: function(operator, value, limit_operator, limit) {
  	if (value == limit) {
  		if (operator == 'less')
  			return limit_operator != 'moreorequals' && limit_operator != 'more';
  		if (operator == 'more')
  			return limit_operator != 'lessorequals' && limit_operator != 'less';
  		if (operator == 'lessorequals')
  			return limit_operator != 'more';
  		if (operator == 'moreorequals')
  			return limit_operator != 'less';
  	}

  	if (limit_operator == 'lessorequals' || limit_operator == 'less')
  		return (value < limit);

  	if (limit_operator == 'more' || limit_operator == 'moreorequals')
  		return (value > limit);

  	return false;
  },

  validateGapTriggerLevel: function(levelGr) {
  	// function cannot be empty
  	this._checkFunctionNotNone(levelGr);

  	// function should be lessorequals
  	if (levelGr.getValue('function') != 'moreorequals') {
  		gs.addErrorMessage(gs.getMessage('Function can only be More than or is'));
  		levelGr.setAbortAction(true);
  	}

  	// trigger window must be valid
  	var window = new GlideDateTime(levelGr.getValue('value_gap')).getNumericValue();
  	if (window <= 0) {
  		gs.addErrorMessage(gs.getMessage("Invalid window. Please enter a positive window value"));
  		levelGr.setAbortAction(true);
  	}

  	// trigger level value cannot be 0
  	this._checkLevelValueNotZero(levelGr);

  	// Trigger gap value should be greater than or equal to minimum trigger gap 'glide.clotho.trigger.gap.min_window'
  	var metric = new GlideRecord('sys_metric');
  	metric.get('sys_id', levelGr.trigger.metric);

  	var has_schedule = this._getRetentionPolicySchedule(levelGr.trigger, metric);
  	if (gs.nil(has_schedule))
  		return;

  	var schedule = new GlideRecord('sys_metric_schedule');
  	schedule.get('sys_id', has_schedule.schedule);

  	var period = new GlideDateTime(schedule.getElement('period')).getNumericValue();
  	var gapTriggerMinWindow = gs.getProperty('glide.clotho.trigger.gap.min_window') * 60 * 1000;
  	var minWindowGreaterThanPeriod = gapTriggerMinWindow >= period ? true : false;

  	if (minWindowGreaterThanPeriod) {
  		if (window < gapTriggerMinWindow) {
  			gs.addErrorMessage(gs.getMessage("Invalid window value. Please enter a window value greater than or equal to trigger minimum window value of '{0}'", new GlideDuration(gapTriggerMinWindow).getDurationValue()));
  			levelGr.setAbortAction(true);
  		}
  	} else if (window < period) {
  		gs.addErrorMessage(gs.getMessage("Invalid window value. Please enter a window value greater than or equal to metric sampling period of '{0}'", new GlideDuration(period).getDurationValue()));
  		levelGr.setAbortAction(true);
  	}

  	// Level Should be Uniue & GAP between two trigger level windows should be greater than or equal to the minimum trigger gap 'glide.clotho.trigger.gap.min_window'
  	var currentLevel = levelGr.getValue('level');
  	var gr = new GlideRecord('sys_metric_trigger_level_gap');
  	gr.addQuery('trigger', levelGr.trigger);
  	gr.addQuery('sys_id', '!=', levelGr.sys_id);
  	gr.orderBy('value_gap');
  	gr.query();
  	while (gr.next()) {
  		var triggerLevel = gr.getValue('level');
  		if (triggerLevel == currentLevel) {
  			gs.addErrorMessage(gs.getMessage("Duplicate levels are not allowed. Level '{0}' already exists", levelGr.level));
  			levelGr.setAbortAction(true);
  		}

  		var triggerLevelGap = new GlideDateTime(gr.getValue('value_gap')).getNumericValue();
  		var gapBetweenLevels = triggerLevelGap - window;
  		gapBetweenLevels = -gapBetweenLevels > 0 ? -gapBetweenLevels : gapBetweenLevels; //Modulus function

  		if (minWindowGreaterThanPeriod) {
  			if (gapBetweenLevels < gapTriggerMinWindow) {
  				gs.addErrorMessage(gs.getMessage("Invalid window. The window period difference between the two levels ({0} and {1}) should be greater than or equal to trigger minimum window value of '{2}'", [currentLevel, triggerLevel, new GlideDuration(gapTriggerMinWindow).getDurationValue()]));
  				levelGr.setAbortAction(true);
  			}
  		} else if (gapBetweenLevels < period) {
  			gs.addErrorMessage(gs.getMessage("Invalid window. The window period difference between the two levels ({0} and {1}) should be greater than or equal to metric sampling period of '{2}'",  [currentLevel, triggerLevel, new GlideDuration(period).getDurationValue()]));
  			levelGr.setAbortAction(true);
  		}
  	}
  },

  validateLinearTrigger: function(triggerGr) {
  	var confidence_interval = current.confidence_interval;
  	if (confidence_interval <= 0 || confidence_interval > 100) {
  		gs.addErrorMessage(gs.getMessage('Confidence Level value should be greater than 0 and less or equals to 100'));
  		triggerGr.setAbortAction(true);
  		return;
  	}

  	var metric = new GlideRecord('sys_metric');
  	metric.get('sys_id', current.metric);

  	var has_schedule = this._getRetentionPolicySchedule(triggerGr, metric);
  	if (gs.nil(has_schedule))
  		return;

  	var schedule = new GlideRecord('sys_metric_schedule');
  	schedule.get('sys_id', has_schedule.schedule);

  	var period = new GlideDateTime(schedule.getElement('period')).getNumericValue();
  	var window = new GlideDateTime(triggerGr.window_linear_predictor).getNumericValue();
  	if (window % period != 0) {
  		gs.addErrorMessage(gs.getMessage("Window value should be multiples of metric sampling period '{0}'", [new GlideDuration(period).getDurationValue()]));
  		triggerGr.setAbortAction(true);
  	}

  	var min = gs.getProperty('glide.clotho.trigger.linear.predictor.min_window');
  	if (window / period < min) {
  		gs.addErrorMessage(gs.getMessage("Window value '{0}' cannot be less than '{1}' times the sampling period '{2}'", [new GlideDuration(window).getDurationValue(), min, new GlideDuration(period).getDurationValue()]));
  		triggerGr.setAbortAction(true);
  	}

  	var max = gs.getProperty('glide.clotho.trigger.max_window');
  	if (window / period > max) {
  		gs.addErrorMessage(gs.getMessage("Window value '{0}' cannot be more than '{1}' times the sampling period '{2}'", [new GlideDuration(window).getDurationValue(), max, new GlideDuration(period).getDurationValue()]));
  		triggerGr.setAbortAction(true);
  	}
  },

  validateLinearTriggerLevel: function(levelGr) {
  	// function cannot be empty
  	this._checkFunctionNotNone(levelGr);

  	// function should be lessorequals
  	if (levelGr.getValue('function') != 'lessorequals') {
  		gs.addErrorMessage(gs.getMessage('Function can only be Less than or is'));
  		levelGr.setAbortAction(true);
  	}

  	// trigger window must be valid
  	var window = new GlideDateTime(levelGr.getValue('value_linear_predictor')).getNumericValue();
  	if (window <= 0) {
  		gs.addErrorMessage(gs.getMessage("Invalid window. Please enter a positive window value"));
  		levelGr.setAbortAction(true);
  	}

  	// trigger level value cannot be 0
  	this._checkLevelValueNotZero(levelGr);

  	// trigger level value shall be unique
  	this._checkLevelValueUnique(levelGr, 'sys_metric_trigger_level_linear_predictor');

  	// trigger window shall be unique
  	var gr = new GlideRecord('sys_metric_trigger_level_linear_predictor');
  	gr.addQuery('trigger', levelGr.trigger);
  	gr.addQuery('value_linear_predictor', levelGr.value_linear_predictor);
  	gr.addQuery('sys_id', '!=', levelGr.sys_id);
  	gr.query();
  	if (gr.next()) {
  		gs.addErrorMessage(gs.getMessage("Window already exists"));
  		levelGr.setAbortAction(true);
  	}
  },
  
  validateModelTriggerLevel: function(levelGr) {
  	// function cannot be empty
  	this._checkFunctionNotNone(levelGr);

  	// function should be moreorequals
  	if (levelGr.getValue('function') != 'moreorequals') {
  		gs.addErrorMessage(gs.getMessage('Function can only be More than or is'));
  		levelGr.setAbortAction(true);
  	}

  	// trigger level value cannot be 0
  	this._checkLevelValueNotZero(levelGr);

      // trigger level value shall be unique
      this._checkLevelValueUnique(levelGr, 'sys_metric_trigger_level_model');
      
      // trigger value shall be unique
      var gr = new GlideRecord('sys_metric_trigger_level_model');
  	gr.addQuery('trigger', levelGr.trigger.sys_id);
  	gr.addQuery('value_model', levelGr.value_model);
  	gr.addQuery('sys_id', '!=', levelGr.sys_id);
  	gr.query();
  	if (gr.next()) {
  		gs.addErrorMessage(gs.getMessage("Duplicate values are not allowed. Value '{0}' already exists", levelGr.value_model));
  		levelGr.setAbortAction(true);
  	}
  },
  
  validateModelTrigger: function(triggerGr) {
  	var direction = current.direction;
  	if (direction != 'either' && direction != 'upward' && direction != 'downward') {
  		gs.addErrorMessage(gs.getMessage('Spike Direction value must be upward, downward, or either.'));
  		triggerGr.setAbortAction(true);
  		return;
  	}
  	
  	var model = current.model;
  	var modelInstanceRecord = new GlideRecord('mb_model_instance');
  	modelInstanceRecord.addQuery('mb_model', model);
  	modelInstanceRecord.query();
  	if (!modelInstanceRecord.next()){
  		gs.addErrorMessage(gs.getMessage('The selected model does not have a model instance associated. Verify the model was successfully trained.'));
  		triggerGr.setAbortAction(true);
  	}
  },

  type: 'MetricBaseTriggerUtils'
};

Sys ID

9eda28b95b101300789947cc11f91a55

Offical Documentation

Official Docs: