Name

global.OnCallEscalationSNC

Description

On-Call escalation information for a rotation

Script

var OnCallEscalationSNC = Class.create();
OnCallEscalationSNC.prototype = {
  TABLES: {
  	CMN_ROTA: 'cmn_rota',
  	CMN_ROTA_ROSTER: 'cmn_rota_roster'
  },

  initialize: function () {
  	this.log = new GSLog("com.snc.on_call_rotation.log.level", this.type);
  	this.clear();
  },

  clear: function() {
  	this.rotaList = [];
  	this.entries = {};
  	this.entryKeys = [];
  	this.entryKeyIndex = 0;
  	this.groupSysId = "";
  	this.rotaSysIds = [];
  	this.timeZone = "";
  	this.notifiedList = [];
  	this.firstNotifiedGdt = null;
  	this.primaryUserSysId = "";
  	this.primaryUserName = "";
  	this.primaryUsers = {};
  },

  addRotaToList: function(rotaSysId) {
  	if (rotaSysId)
  		this.rotaList.push(rotaSysId);
  },

  getRotaList: function() {
  	return this.rotaList;
  },

  isInRotaList: function(rotaSysId) {
  	var inList = this.rotaList.indexOf(rotaSysId) !== -1;

  	if (this.log.atLevel(GSLog.DEBUG))
  		this.log.debug("[isInRotaList] inList: " + inList);

  	return inList;
  },

  getEntries: function() {
  	return this.entries;
  },

  setGroupID: function(groupSysId) {
  	this.groupSysId = groupSysId;
  },

  getGroupID: function() {
  	return this.groupSysId;
  },

  setRotaID: function(rotaSysId) {
  	this.rotaSysIds = [rotaSysId];
  },

  setRotaIDs: function(rotaSysIds) {
  	this.rotaSysIds = rotaSysIds;
  },

  addRotaID: function(rotaSysId) {
  	this.rotaSysIds.push(rotaSysId);
  },

  getRotaID: function() {
  	return this.rotaSysIds[0];
  },

  getRotaIDs: function() {
  	return this.rotaSysIds;
  },

  setTimeZone: function(timeZone) {
  	this.timeZone = timeZone;
  },

  getTimeZone: function() {
  	return this.timeZone;
  },

  setPrimaryUserID: function (userSysId, rotaId, rosterId) {
  	if (userSysId) {
  		this.primaryUserSysId = userSysId;
  		var user = GlideUser.getUserByID(userSysId);
  		if (user)
  			this.primaryUserName = user.getDisplayName();
  		else
  			this.primaryUserName = "";
  		if(rotaId) {
  			this.primaryUsers[rotaId] = {
  				userSysId: userSysId,
  				userName: this.primaryUserName,
  				rosterId: rosterId
  			};
  		}
  	}
  },

  addPrimaryUserID: function(userSysId, rotaId, rosterId) {
  	if (userSysId) {
  		var user = GlideUser.getUserByID(userSysId);
  		var primaryUserName = "";
  		if (user)
  			primaryUserName = user.getDisplayName();
  		if(rotaId) {
  			this.primaryUsers[rotaId] = {
  				userSysId: userSysId,
  				userName: primaryUserName,
  				rosterId: rosterId
  			};
  		}
  	}
  },

  getPrimaryUserID: function() {
  	return this.primaryUserSysId;
  },

  getPrimaryUsers: function() {
  	return this.primaryUsers;
  },

  getPrimaryUserIdByRota: function(rotaId) {
  	return this.primaryUsers[rotaId] ? this.primaryUsers[rotaId].userSysId : "";
  },

  getPrimaryUserName: function() {
  	return this.primaryUserName;
  },

  getPrimaryUserNameByRota: function(rotaId) {
  	return this.primaryUsers[rotaId] ? this.primaryUsers[rotaId].userName : "";
  },

  getFirstNotified: function() {
  	return this.firstNotifiedGdt;
  },

  /**
   * Add an escalation entry - delay indicates the time from the first escalation until this escalation
   */
  add: function(onCallEscalationEntry) {
  	// Make sure that the entries stay in delay order, and ensure no duplicates
  	var delay = parseInt(this.createKey(onCallEscalationEntry.getDelay()));

  	if (this.log.atLevel(GSLog.DEBUG))
  		this.log.debug("[add] delay: " + delay);

  	if (this.entries[delay]) {
  		var index = this.entryKeys.indexOf(delay);
  		this.entryKeys.splice(index, 1);
  	}

  	this.entryKeys.splice(this._getSortedIndex(this.entryKeys, delay), 0, delay);

  	if (this.log.atLevel(GSLog.DEBUG))
  		this.log.debug("[add] this.entryKeys: " + this.entryKeys.join(","));

  	this.entries[delay] = onCallEscalationEntry;
  },

  /**
   * Position to the start of the escalation list
   */
  gotoTop: function() {
  	this.entryKeyIndex = -1;
  	return this.hasNext();
  },

  hasNext: function() {
  	return this.entryKeyIndex + 1 < this.entryKeys.length;
  },

  size: function() {
  	return this.entryKeys.length;
  },

  next: function() {
  	if (this.hasNext())
  		return this.entries[this.entryKeys[++this.entryKeyIndex]];
  	return null;
  },

  /**
   * Determine the time when we should run the first escalation that is in the future
   */
  runAt: function() {
  	var nowMS = new GlideDateTime().getNumericValue();
  	var runAtValue = -1;
  	this.entryKeys.some(function(key) {
  		var _runAtValue = this.entries[key].getRunAt();
  		var found = _runAtValue > nowMS;

  		if (this.log.atLevel(GSLog.DEBUG))
  			this.log.debug("[runAt] _runAtValue: " + _runAtValue + " nowMS: " + nowMS + " found: " + found);

  		if (found)
  			runAtValue = _runAtValue;
  		return found;
  	}, this);
  	return runAtValue;
  },

  /**
   * add current escalation to the notified list by setting the notified time
   */
  addToNotified: function(onCallEscalationEntry) {
  	// set the notified time in the pending entries list as this will keep us from writing it twice
  	var nowGdt = new GlideDateTime();
  	this.entryKeys.some(function(key) {
  		var entry = this.entries[key];
  		var found = onCallEscalationEntry.sameNotify(entry);
  		if (found)
  			entry.setNotifiedTime(nowGdt);
  		return found;
  	}, this);
  	onCallEscalationEntry.setNotifiedTime(nowGdt);
  	this.notifiedList.push(onCallEscalationEntry);
  },

  /**
   * Save the escalation entries as xml
   */
  toXml: function(allEntries) {
  	var doc = GlideXMLUtil.newDocument("escalations");

  	var root = doc.getDocumentElement();
  	root.setAttribute("group", this.groupSysId);
  	root.setAttribute("current_rota", this.rotaSysIds.join(","));
  	root.setAttribute("processed_rota_list", this.rotaList.join(","));
  	root.setAttribute("primary_user_id", this.primaryUserSysId);
  	root.setAttribute("primary_user_name", this.primaryUserName);

  	var notifications = doc.createElement("notifications");
  	root.appendChild(notifications);

  	// Record those already notified
  	for (var i = 0, length = this.notifiedList.length; i < length; i++)
  		this.notifiedList[i].toXml(doc, notifications);

  	var nowMS = new GlideDateTime().getNumericValue();
  	this.entryKeys.forEach(function(key) {
  		var entry = this.entries[key];
  		if (allEntries || (entry.getRunAt() > nowMS)) {
  			if (entry.getNotifiedTime() == null)
  				entry.toXml(doc, notifications);
  		}
  	}, this);
  	return doc;
  },

  /**
   * Load up the escalation information from the xml string
   */
  fromXml: function(escalationXmlStr) {
  	this.clear();
  	if (!escalationXmlStr)
  		return;
  	var doc = GlideXMLUtil.parse(escalationXmlStr);
  	if (!doc)
  		return;

  	var root = doc.getDocumentElement();
  	this.groupSysId = root.getAttribute("group");
  	this.rotaSysIds = [];
  	var rotaSysIds = root.getAttribute("current_rota").split(",");
  	var k;
  	for (k = 0; k < rotaSysIds.length; k++) {
  		this.rotaSysIds.push(rotaSysIds[k] + "");
  	}
  	this.primaryUserSysId = root.getAttribute("primary_user_id");
  	this.primaryUserName = root.getAttribute("primary_user_name");
  	this.rotaList = [];
  	var rotaList = root.getAttribute("processed_rota_list").split(",");
  	for (k = 0; k < rotaList.length; k++) {
  		this.rotaList.push(rotaList[k]);
  	}

  	var pending = GlideXMLUtil.selectNodes(root, "/escalations/notifications/" + OnCallEscalationEntrySNC.PENDING);
  	for (var i = 0, length = pending.getLength(); i < length; i++) {
  		var pendingEntry = new OnCallEscalationEntry();
  		pendingEntry.fromXml(pending.item(i));
  		var key = this.createKey(pendingEntry.getDelay());
  		this.entries[key] = pendingEntry;
  		this.entryKeys.push(key);
  	}

  	var sent = GlideXMLUtil.selectNodes(root, "/escalations/notifications/" + OnCallEscalationEntrySNC.SENT);
  	var sentLength = sent.getLength();
  	for (var j = 0; j < sentLength; j++) {
  		var sentEntry = new OnCallEscalationEntry();
  		sentEntry.fromXml(sent.item(j));
  		this.notifiedList.push(sentEntry);
  		if (j === 0)
  			this.firstNotifiedGdt = new GlideDateTime(sentEntry.getNotifiedTime());
  	}

  	// Start the iterator at the beginning of the list
  	this.gotoTop();
  },

  /**
   * Generate string representation of all On Call Escalation entries
   *
   * return: [string] OnCallEscalationSNCNotification instance
   */
  toString: function() {
  	var rv = "pending={";
  	this.entryKeys.forEach(function(key) {
  		var entry = this.entries[key];
  		rv += "\n" + entry.toString();
  	}, this);

  	rv += "}\nsent={";

  	this.notifiedList.forEach(function(notifiedEntry) {
  		rv += "\n" + notifiedEntry.toString();
  	}, this);

  	rv += "}";
  	return rv;
  },

  /**
   * Create a unique sequenced key for storing the escalation entries in our list
   */
  createKey: function(value) {
  	if (this.log.atLevel(GSLog.DEBUG))
  		this.log.debug("[createKey] param value: " + value);

  	value *= 100000;
  	value += this.entryKeys.length;

  	if (this.log.atLevel(GSLog.DEBUG))
  		this.log.debug("[createKey] return value: " + value);

  	return value;
  },

  /**
   * Return the escalation type of a rota based on:
   * - If there's more than one active roster, return rotate through rosters
   * - If there's only one active roster, return rotate through members
   * - Otherwise return message indicating missing rosters
   *
   * rotaSysId: [string] sys_id of rota
   * return: [string] escalation type
   */
  getEscalationType: function(rotaSysId) {
  	var type = "The specified rota does not contain any active rosters";
  	if (!rotaSysId)
  		return type;

  	var rotaGr = new GlideRecord(this.TABLES.CMN_ROTA);
  	if (rotaGr.get(rotaSysId)) {
  		var ocRotation = new OnCallRotation();
  		if (ocRotation.isGroupEscalationApplied(rotaSysId)) {
  			return 'custom';
  		}
  		if (rotaGr.getValue('use_custom_escalation') == 1)
  			return 'custom';
  	}

  	var rosterGa = new GlideAggregate(this.TABLES.CMN_ROTA_ROSTER);
  	rosterGa.addAggregate("COUNT");
  	rosterGa.addActiveQuery();
  	rosterGa.addQuery("rota", rotaSysId);
  	rosterGa.query();
  	var count = 0;
  	if (rosterGa.next())
  		count = parseInt(rosterGa.getAggregate("COUNT"));

  	if (count === 1)
  		type = "member";
  	else if (count > 1)
  		type = "roster";
  	return type;
  },

  _getSortedIndex: function(array, value) {
  	var low = 0,
  	high = array.length;

  	while (low < high) {
  		var mid = (low + high) >>> 1;
  		if (array[mid] < value)
  			low = mid + 1;
  		else
  			high = mid;
  	}
  	return low;
  },

  type: "OnCallEscalationSNC"
};

Sys ID

7ebc5ec5d7230300532c24837e610305

Offical Documentation

Official Docs: