Name

sn_slm_timer.SLATimerSNC

Description

Base ServiceNow API

Script

var SLATimerSNC = Class.create();

SLATimerSNC.LOG_PROP = 'com.snc.sla.timer.log';
SLATimerSNC.FIRST_TO_BREACH = 1;
SLATimerSNC.MAPPING = 2;
SLATimerSNC.INVALID_IDS = 'invalid_ids';
SLATimerSNC.INVALID_TASK_IDS = 'invalid_task_ids';
SLATimerSNC.INVALID_CONFIG_IDS = 'invalid_config_ids';
SLATimerSNC.DEFAULT_CONFIG_ID = 'DEFAULT_CONFIG_ID';

SLATimerSNC.prototype = {
  initialize: function(_gr, _gs) {
  	this._log = new global.GSLog(SLATimerSNC.LOG_PROP, this.type);

  	if (this._log.atLevel(global.GSLog.DEBUG))
  		this._log.debug('[initialize] type: ' + this.type);

  	this._gr = _gr;
  	this._gs = _gs || gs;
  	this._slaUtil = new global.SLAUtil();

  	this._isTwentyElevenEngine = this._gs.getProperty('com.snc.sla.engine.version', '2010') === '2011';
  	this._isBreachCompatibility = this._gs.getProperty('com.snc.sla.compatibility.breach', false) + '' === 'true';

  	if (this._log.atLevel(global.GSLog.DEBUG))
  		this._log.debug('[initialize] isTwentyElevenEngine: ' + this._isTwentyElevenEngine + ' isBreachCompatibility: ' + this._isBreachCompatibility);
  },

  get: function(params) {
  	var timerData = {
  		sla_timers: {},
  		config: {}
  	};

  	if (!params)
  		return timerData;

  	var taskSysIds = params.task_ids || '';
  	var configSysIds = params.config_ids || '';

  	if (!taskSysIds || (Array.isArray(taskSysIds) && taskSysIds.length < 1))
  		return timerData;

  	if (this._log.atLevel(global.GSLog.DEBUG))
  		this._log.debug('[get] taskSysIds: ' + taskSysIds + ' configSysIds: ' + configSysIds);

  	var uniqueTaskSysIds = this._uniqueSysIds(taskSysIds);
  	var uniqueConfigSysIds = this._uniqueSysIds(configSysIds);

  	if (this._log.atLevel(global.GSLog.DEBUG))
  		this._log.debug('[get] uniqueTaskSysIds: ' + uniqueTaskSysIds + ' uniqueConfigSysIds: ' + uniqueConfigSysIds);

  	var hasValidTaskIds = uniqueTaskSysIds && Array.isArray(uniqueTaskSysIds) && uniqueTaskSysIds.length > 0;
  	var hasValidConfigIds = uniqueConfigSysIds && Array.isArray(uniqueConfigSysIds) && uniqueConfigSysIds.length > 0;

  	if (this._log.atLevel(global.GSLog.DEBUG))
  		this._log.debug('[get] hasValidTaskIds: ' + hasValidTaskIds + ' hasValidConfigIds: ' + hasValidConfigIds);

  	if (!hasValidTaskIds)
  		return timerData;

  	// Refresh all Task SLA records
  	this._slaUtil.refreshTaskSlasByTask(uniqueTaskSysIds);

  	// Get all task SLA data records
  	timerData.sla_timers = this._getTaskSlaData(uniqueTaskSysIds);

  	// Get all the config data records
  	if (!uniqueConfigSysIds || !Array.isArray(uniqueConfigSysIds) || uniqueConfigSysIds.length < 1)
  		timerData.config = this._getDefaultConfig(uniqueTaskSysIds);
  	else
  		timerData.config = this._getConfig(uniqueTaskSysIds, uniqueConfigSysIds);

  	if (this._log.atLevel(global.GSLog.DEBUG))
  		this._log.debug('[get] timerData: ' + JSON.stringify(timerData));

  	return timerData;
  },

  _uniqueSysIds: function(sysIds) {
  	sysIds = typeof sysIds === 'string' ? sysIds.split(',') : sysIds;
  	sysIds = sysIds.filter(function(element) { return element || false; });
  	return Array.isArray(sysIds) ? sysIds.filter(this.filters.unique) : sysIds;
  },

  // returns first to breach with show complete and cancel both true
  _getDefaultConfig: function(taskSysIds) {

  	if (this._log.atLevel(global.GSLog.DEBUG))
  		this._log.debug('[_getDefaultConfig] taskSysIds: ' + taskSysIds);

  	var configData = {};

  	if (!taskSysIds || !Array.isArray(taskSysIds) || taskSysIds.length < 1)
  		return configData;

  	configData[SLATimerSNC.DEFAULT_CONFIG_ID] = {
  		name: {
  			display_value: this._gs.getMessage('DEFAULT Configuration'),
  			value: SLATimerSNC.DEFAULT_CONFIG_ID
  		}
  	};

  	taskSysIds.forEach(function(taskSysId) {
  		var firstToBreachSysId = this._getFirstToBreach(taskSysId, true, true);

  		if (firstToBreachSysId)
  			configData[SLATimerSNC.DEFAULT_CONFIG_ID][taskSysId] = firstToBreachSysId;
  	}, this);

  	return configData;
  },

  _getConfig: function(taskSysIds, configSysIds) {

  	if (this._log.atLevel(global.GSLog.DEBUG))
  		this._log.debug('[_getConfig] taskSysIds: ' + taskSysIds + ' configSysIds: ' + configSysIds);

  	var configData = {};

  	if (!taskSysIds || !Array.isArray(taskSysIds) || taskSysIds.length < 1 || !configSysIds || !Array.isArray(configSysIds) || configSysIds.length < 1)
  		return configData;

  	var validSysIds = [];
  	var configGr = new GlideRecord('sla_timer_config');
  	configGr.addQuery('sys_id', 'IN', configSysIds);
  	configGr.addActiveQuery();
  	configGr.query();

  	var validConfigs = [];

  	while (configGr.next()) {
  		configSysId = configGr.getUniqueValue();
  		validConfigs.push(configSysId);

  		if (this._log.atLevel(global.GSLog.DEBUG))
  			this._log.debug('[_getConfig] configSysId: ' + configSysId);

  		configData[configSysId] = {};
  		var showCompleted = !!(parseInt(configGr.getValue('show_complete')));
  		var showCancelled = !!(parseInt(configGr.getValue('show_cancelled')));
  		var showActualTime = !!(parseInt(configGr.getValue('show_actual_time')));

  		configData[configSysId].name = {
  			display_value: configGr.name.getDisplayValue(),
  			value: configGr.getValue('name')
  		};

  		configData[configSysId].showActualTime = showActualTime;

  		var slaTimeSource = configGr.getValue('sla_timer_source');

  		if (!isNaN(slaTimeSource))
  			slaTimeSource = parseInt(slaTimeSource);

  		if (this._log.atLevel(global.GSLog.DEBUG))
  			this._log.debug('[_getConfig] slaTimeSource: ' + slaTimeSource);

  		if (slaTimeSource === SLATimerSNC.FIRST_TO_BREACH)
  			taskSysIds.forEach(function(taskSysId) {
  				var firstToBreachSysId = this._getFirstToBreach(taskSysId, showCompleted, showCancelled);

  				if (firstToBreachSysId)
  					configData[configSysId][taskSysId] = firstToBreachSysId;
  			}, this);
  		else if (slaTimeSource === SLATimerSNC.MAPPING)
  			taskSysIds.forEach(function(taskSysId) {
  				var mappedTaskSlaSysId = this._getMapping(configSysId, taskSysId, showCompleted, showCancelled);

  				if (mappedTaskSlaSysId)
  					configData[configSysId][taskSysId] = mappedTaskSlaSysId;
  			}, this);
  	}

  	var invalidConfigs = configSysIds.filter(function(configSysId) {
  		return validConfigs.indexOf(configSysId) === -1;
  	});

  	if (invalidConfigs.length > 0 && this._log.atLevel(global.GSLog.WARNING))
  		this._log.warn('[_getConfig] The following SLA Timer configurations do not exist: ' + invalidConfigs.join(', '));

  	if (this._log.atLevel(global.GSLog.DEBUG))
  		this._log.debug('[_getConfig] configData: ' + JSON.stringify(configData));

  	return configData;
  },

  _getTaskSlaData: function(taskSysIds) {
  	var slaTimerData = {};
  	var taskSlaGr = new GlideRecord('task_sla');
  	taskSlaGr.addQuery('task', 'IN', taskSysIds);
  	taskSlaGr.query();

  	var validTasks = [];

  	while (taskSlaGr.next()) {
  		if (taskSlaGr.canRead()) {
  			var taskSlaSysId = taskSlaGr.getUniqueValue();
  			validTasks.push(taskSlaGr.getValue('task'));

  			slaTimerData[taskSlaSysId] = {};

  			slaTimerData[taskSlaSysId].sys_id = {
  				value: taskSlaSysId,
  				display_value: taskSlaGr.getDisplayValue()
  			};

  			slaTimerData[taskSlaSysId].task = {
  				value: taskSlaGr.getValue('task'),
  				display_value: taskSlaGr.task.getDisplayValue()
  			};

  			var scheduleData = this._slaUtil.getScheduleData(taskSlaGr);

  			var durationType = taskSlaGr.sla.duration_type + '';
  			var fullActualDurationInMillis;
  			var fullBusinessDurationInMillis;

  			// Duration type returns sys_id of relative duration if one is chosen or null for user specified duration
  			if (durationType) {
  				// support property com.snc.sla.always_populate_business_fields=false
  				var elapsedActualTimeInMillis = this._durationInMillis(taskSlaGr.getValue('duration'));
  				var remainingActualTimeInMillis = this._durationInMillis(taskSlaGr.getValue('time_left'));
  				fullActualDurationInMillis = elapsedActualTimeInMillis + remainingActualTimeInMillis;

  				if (scheduleData) {
  					var elapsedBusinessTimeInMillis = this._durationInMillis(taskSlaGr.getValue('business_duration'));
  					var remainingBusinessTimeInMillis = this._durationInMillis(taskSlaGr.getValue('business_time_left'));
  					fullBusinessDurationInMillis = elapsedBusinessTimeInMillis + remainingBusinessTimeInMillis;

  					slaTimerData[taskSlaSysId].businessDuration = {
  						value: fullBusinessDurationInMillis,
  						display_value: fullBusinessDurationInMillis
  					};
  				}

  			} else {
  				fullActualDurationInMillis = this._durationInMillis(taskSlaGr.sla.duration + '');
  				fullBusinessDurationInMillis = fullActualDurationInMillis;
  			}

  			slaTimerData[taskSlaSysId].duration = {
  				value: fullActualDurationInMillis,
  				display_value: fullActualDurationInMillis
  			};

  			// support property com.snc.sla.always_populate_business_fields=false
  			var actualTimeLeftValue = this._durationInMillis(taskSlaGr.getValue('time_left'));
  			var actualTimeLeftDisplayValue = taskSlaGr.time_left.getDisplayValue();

  			slaTimerData[taskSlaSysId].time_left = {
  				value: actualTimeLeftValue,
  				display_value: actualTimeLeftDisplayValue
  			};

  			if (scheduleData) {
  				// Adjust for difference between business_percentage and business_time_left calculations
  				var calculatedTimeLeft = fullBusinessDurationInMillis - this._durationInMillis(taskSlaGr.getValue('business_duration'));
  				var businessTimeLeftValue = Math.max(calculatedTimeLeft, this._durationInMillis(taskSlaGr.getValue('business_time_left')));

  				var businessTimeLeftDuration = new GlideDuration(businessTimeLeftValue);
  				var businessTimeLeftDisplayValue = businessTimeLeftDuration.getDisplayValue();

  				slaTimerData[taskSlaSysId].businessTimeLeft = {
  					value: businessTimeLeftValue,
  					display_value: businessTimeLeftDisplayValue
  				};

  				slaTimerData[taskSlaSysId].businessDuration = {
  					value: fullBusinessDurationInMillis,
  					display_value: fullBusinessDurationInMillis
  				};

  				slaTimerData[taskSlaSysId].schedule = scheduleData;
  			}

  			slaTimerData[taskSlaSysId].sla = {
  				value: taskSlaGr.getValue('sla'),
  				display_value: taskSlaGr.sla.getDisplayValue()
  			};

  			slaTimerData[taskSlaSysId].stage = {
  				value: taskSlaGr.getValue('stage'),
  				display_value: taskSlaGr.stage.getDisplayValue()
  			};

  			slaTimerData[taskSlaSysId].has_breached = {
  				value: !!(parseInt(taskSlaGr.getValue('has_breached'))),
  				display_value: taskSlaGr.has_breached.getDisplayValue()
  			};

  			slaTimerData[taskSlaSysId].target = {
  				value: taskSlaGr.sla.target + '',
  				display_value: taskSlaGr.sla.target.getDisplayValue()
  			};

  			if (slaTimerData[taskSlaSysId].has_breached.value)
  				slaTimerData[taskSlaSysId].breach_time = {
  					value: taskSlaGr.getValue('planned_end_time'),
  					display_value: taskSlaGr.planned_end_time.getDisplayValue()
  				};

  			slaTimerData[taskSlaSysId].end_time = {
  				value: taskSlaGr.getValue('end_time'),
  				display_value: taskSlaGr.end_time.getDisplayValue()
  			};
  		}
  	}

  	var invalidTasks = taskSysIds.filter(function(taskSysId) {
  		return validTasks.indexOf(taskSysId) === -1;
  	});

  	if (invalidTasks.length > 0 && this._log.atLevel(global.GSLog.WARNING))
  		this._log.warn('[_getTaskSlaData] The following tasks are either invalid or have no associated SLAs: ' + invalidTasks.join(', '));

  	if (this._log.atLevel(global.GSLog.DEBUG))
  		this._log.debug('[_getTaskSlaData] slaTimerData: ' + JSON.stringify(slaTimerData));

  	return slaTimerData;
  },

  // For a given task return the task_sla to breach first
  _getFirstToBreach: function(taskSysId, showCompleted, showCancelled) {
  	if (this._log.atLevel(global.GSLog.DEBUG))
  		this._log.debug('[_getFirstToBreach] taskSysId: ' + taskSysId);

  	if (!taskSysId)
  		return '';

  	var taskSlaGr = new GlideRecordSecure('task_sla');
  	taskSlaGr.addQuery('task', taskSysId);
  	taskSlaGr.orderBy('planned_end_time');
  	// This ordering is added to remove arbitrary order returned from db
  	taskSlaGr.orderBy('sys_id');

  	var firstToBreachSysId = this._getTaskSla(taskSlaGr, showCompleted, showCancelled);

  	if (this._log.atLevel(global.GSLog.DEBUG))
  		this._log.debug('[_getFirstToBreach] firstToBreachSysId: ' + firstToBreachSysId);

  	return firstToBreachSysId;
  },

  _getMapping: function(configSysId, taskSysId, showCompleted, showCancelled) {
  	if (this._log.atLevel(global.GSLog.DEBUG))
  		this._log.debug('[_getMapping] configSysId: ' + configSysId + ' taskSysId: ' + taskSysId);

  	if (!configSysId || !taskSysId)
  		return '';

  	var taskSlaSysId = '';

  	var configMapGr = new GlideRecord('sla_timer_config_mapping');
  	configMapGr.addQuery('config', configSysId);
  	configMapGr.addQuery('table', this._getTableName(taskSysId));
  	configMapGr.addActiveQuery();
  	configMapGr.orderBy('order');
  	// This ordering is added to remove arbitrary order returned from db
  	configMapGr.orderBy('sys_id');
  	configMapGr.query();

  	while (configMapGr.next()) {
  		if (this._log.atLevel(global.GSLog.DEBUG))
  			this._log.debug('[_getMapping] sla: ' + configMapGr.sla.getDisplayValue());

  		var taskSlaGr = new GlideRecordSecure('task_sla');
  		taskSlaGr.addQuery('task', taskSysId);
  		taskSlaGr.addQuery('sla', configMapGr.sla);
  		// This ordering is added to remove arbitrary order returned from db
  		taskSlaGr.orderBy('sys_id');

  		taskSlaSysId = this._getTaskSla(taskSlaGr, showCompleted, showCancelled);
  		if (taskSlaSysId)
  			break;
  	}

  	if (this._log.atLevel(global.GSLog.DEBUG))
  		this._log.debug('[_getMapping] taskSlaSysId: ' + taskSlaSysId);

  	return taskSlaSysId;
  },

  _getTaskSla: function(taskSlaGr, showCompleted, showCancelled) {

  	if (this._log.atLevel(global.GSLog.DEBUG))
  		this._log.debug('[_getTaskSla] showCompleted: ' + showCompleted + ' showCancelled: ' + showCancelled);

  	var stageComplete = this._getStageComplete();

  	// Modify query based on should Completed and Cancelled Task SLAs be shown and run it
  	this._modifyQueryForCompletedCancelled(taskSlaGr, showCompleted, showCancelled);

  	var taskSlasByStage = {
  		in_progress: null,
  		paused: null,
  		breached_in_progress: null,
  		breached: null,
  		breached_paused: null
  	};

  	if (showCompleted)
  		taskSlasByStage.completed = null;

  	if (showCancelled)
  		taskSlasByStage.cancelled = null;

  	taskSlaGr.query();

  	if (this._log.atLevel(global.GSLog.DEBUG))
  		this._log.debug('[_getTaskSla] encodedQuery: ' + taskSlaGr.getEncodedQuery());

  	var taskSlaSysId = '';

  	while (taskSlaGr.next()) {
  		var stageValue = taskSlaGr.getValue('stage');
  		taskSlaSysId = taskSlaGr.getUniqueValue();
  		var isBreached = this._isBreached(taskSlaGr);

  		if (this._log.atLevel(global.GSLog.DEBUG))
  			this._log.debug('[_getTaskSla] stageValue: ' + stageValue + ' isBreached: ' + isBreached);

  		// in_progress and paused stages should return non-breached Task SLAs as they are more important
  		if (!isBreached) {
  			if (stageValue === 'in_progress' && !taskSlasByStage.in_progress) {
  				taskSlasByStage.in_progress = taskSlaSysId;
  				break;
  			}

  			if (stageValue === 'paused' && !taskSlasByStage.paused)
  				taskSlasByStage.paused = taskSlaSysId;
  		} else {
  			// Breached Task SLAs should follow the same hierarchy of stages as non-breached
  			if (stageValue === 'in_progress' && !taskSlasByStage.breached_in_progress)
  				taskSlasByStage.breached_in_progress = taskSlaSysId;

  			if (stageValue === 'breached' && !taskSlasByStage.breached)
  				taskSlasByStage.breached = taskSlaSysId;

  			if (stageValue === 'paused' && !taskSlasByStage.breached_paused)
  				taskSlasByStage.breached_paused = taskSlaSysId;
  		}

  		// For Completed and Cancelled stages we do not care is Task SLA breached or not
  		if (showCompleted && stageValue === stageComplete && !taskSlasByStage.completed)
  			taskSlasByStage.completed = taskSlaSysId;

  		if (showCancelled && stageValue === 'cancelled' && !taskSlasByStage.cancelled)
  			taskSlasByStage.cancelled = taskSlaSysId;

  		// Stop iterating through Task SLA records if each stage has been filled
  		var unfilledStages = Object.keys(taskSlasByStage).filter(function(key) {
  			return !taskSlasByStage[key];
  		});

  		if (unfilledStages.length === 0)
  			break;
  	}

  	if (this._log.atLevel(global.GSLog.DEBUG))
  		this._log.debug('[_getTaskSla] taskSlasByStage: ' + JSON.stringify(taskSlasByStage));

  	taskSlaSysId = taskSlasByStage.in_progress || taskSlasByStage.paused || taskSlasByStage.breached_in_progress || taskSlasByStage.breached || taskSlasByStage.breached_paused || taskSlasByStage.completed || taskSlasByStage.cancelled || null;

  	if (this._log.atLevel(global.GSLog.DEBUG))
  		this._log.debug('[_getTaskSla] taskSlaSysId: ' + taskSlaSysId);

  	return taskSlaSysId;
  },

  _modifyQueryForCompletedCancelled: function(taskSlaGr, showCompleted, showCancelled) {
  	var excludedStages = [];
  	var stageComplete = this._getStageComplete();

  	if (!showCompleted)
  		excludedStages.push(stageComplete);

  	if (!showCancelled)
  		excludedStages.push('cancelled');

  	if (!stageComplete.length)
  		taskSlaGr.addQuery('stage', 'NOT IN', excludedStages.join(','));
  },

  _isBreached: function(taskSlaGr) {
  	return taskSlaGr.getValue('has_breached') === '1' || taskSlaGr.getValue('stage') === 'breached';
  },

  _getTableName: function(taskSysId) {
  	if (this._log.atLevel(global.GSLog.DEBUG))
  		this._log.debug('[_getTableName] taskSysId: ' + taskSysId);

  	if (!taskSysId)
  		return '';

  	var gr = new GlideRecord('task');
  	if (!gr.get(taskSysId))
  		return '';

  	var tablename = gr.getValue('sys_class_name');

  	if (this._log.atLevel(global.GSLog.DEBUG))
  		this._log.debug('[_getTableName] tablename: ' + tablename);

  	return tablename;
  },

  _getStageComplete: function() {
  	var stageComplete = !this._isTwentyElevenEngine || this._isBreachCompatibility ? 'achieved' : 'completed';

  	if (this._log.atLevel(global.GSLog.DEBUG))
  		this._log.debug('[_getStageComplete] stageComplete: ' + stageComplete);

  	return stageComplete;
  },

  _durationInMillis: function(durationValue) {
  	var duration = new GlideDuration();
  	duration.setValue(durationValue);

  	var durationInMillis = duration.getNumericValue();

  	if (this._log.atLevel(global.GSLog.DEBUG))
  		this._log.debug('[_durationInMillis] durationInMillis: ' + durationInMillis);

  	return durationInMillis;
  },

  filters: {
  	sysId: function(sysId) {
  		return GlideStringUtil.isEligibleSysID(sysId);
  	},

  	unique: function(value, index, arr) {
  		return arr.indexOf(value) === index;
  	}
  },

  assign: function(target, varArgs) {
  	if (target == null)
  		throw new TypeError('Cannot convert undefined or null to object');

  	var to = Object(target);

  	for (var index = 1; index < arguments.length; index++) {
  		var nextSource = arguments[index];

  		if (nextSource != null) {
  			for (var nextKey in nextSource) {
  				if (Object.prototype.hasOwnProperty.call(nextSource, nextKey))
  					to[nextKey] = nextSource[nextKey] + '';
  			}
  		}
  	}
  	return to;
  },

  type: 'SLATimerSNC'
};

Sys ID

4731d4745712001009993da73d94f9df

Offical Documentation

Official Docs: