Name

global.SLARepair

Description

Provides functions to repair Task SLAs based on a GlideRecord, filter or sys id of records from the contract_sla, task_sla or task tables. Each Task SLA identified for repair is removed and then recreated based on the audit history information on the parent Task and the current set of conditions in each SLA definition. As a result the repair process can potentially remove Task SLAs that no longer match the attach conditions or create Task SLAs that did not previously exist. System properties control how workflow is handled during the recreation of the Task SLAs.

Script

/**
* Repairs SLAs
*
* Repair - Erase all in-flight SLA data (for the given filter) and create SLA records from scratch based on task history and SLA
* definitions
*
*/
var SLARepair = Class.create();

SLARepair.RECREATE = "recreate";
SLARepair.SLA = "task_sla";
SLARepair.SLA_DEF = "contract_sla";
SLARepair.TASK = "task";
SLARepair.ENABLED_PROPERTY = "com.snc.sla.repair.enabled";
SLARepair.LOG_PROPERTY = "com.snc.sla.repair.log";
SLARepair.AUDIT_PROPERTY = "com.snc.sla.repair.audit";
SLARepair.PREVIEW_PROPERTY = "com.snc.sla.repair.preview";
SLARepair.EVENT_REPAIR_COMPLETE = "sla.repair_complete";
SLARepair.SLA_DATABASE_LOG = "com.snc.sla.log.destination";

SLARepair.COUNT = "count";
SLARepair.AUDIT_ON = "audit_on";
SLARepair.UNAUDITED_FIELDS = "unaudited_fields";
SLARepair.IGNORE_UNAUDITED = {
  "sys_tags": true
};

SLARepair.prototype = {
  initialize: function() {
      this.repairAction = null;
      this.sourceTable = null;
      this.baseTable = null;
      this.encodedQuery = "";
      this.functionName = "";
      this.auditingEnabled = (gs.getProperty(SLARepair.AUDIT_PROPERTY, 'true') != 'false');
      this.validateOnly = false;
      this.runWorkflow = true;
      this.runFlow = true;
  	this.slaUtil = new SLAUtil();
  	this.breakdownsPluginActive = GlidePluginManager.isActive("com.snc.sla.breakdowns");

      this.filterTerms = [];
      this.taskIds = [];
  	this.slaDefTables = this._getSlaDefTables();

      this.lu = new GSLog(SLARepair.LOG_PROPERTY, "SLARepair");
      if (gs.getProperty(SLARepair.SLA_DATABASE_LOG, "db") == "node")
          this.lu.disableDatabaseLogs();

      this.slaAudit = null; //Object to create Audit entries
      this.response = new SLARepair.Response();

      this.recordsFound = 0;
      this.processedRecords = 0;
  	this.currentUser = gs.getUserID();
  },

  /**
   * Enables/disables auditing for this SLARepair
   *
   * @param onOrOff - Boolean
   *
   * @returns 'this' to allow chaining
   */
  setAuditEnabled: function(onOrOff) {
      this.auditingEnabled = JSUtil.toBoolean(onOrOff);
      return this;
  },

  /**
   * Sets the SLARepair to only validate an action, not perform the action. Once this is set you can call any of the repair
   * methods to check if the request will be actioned. When set true only validity information will be returned from any of
   * these methods.
   *
   * @param onOrOff - Boolean
   *
   * @returns 'this' to allow chaining
   */
  setValidateOnly: function(onOrOff) {
      this.validateOnly = JSUtil.toBoolean(onOrOff);
      return this;
  },

  /**
    * Sets SLARepair to run/not run workflow when repairing SLAs.
    *
    * @param onOrOff - Boolean
    */
  setRunWorkflow: function(runWorkflow) {
  	this.runWorkflow = runWorkflow;
  },

  /**
    * Sets SLARepair to run/not run Flow when repairing SLAs.
    *
    * @param onOrOff - Boolean
    */
  setRunFlow: function(runFlow) {
  	this.runFlow = runFlow;
  },

  /**
   * Repairs SLA records from scratch based on sys id and tables name removing current data and creating new task_sla records.
   *
   * @param sysId - String sys id
   * @param sourceTable - String table name (task or extension of task, contract_sla, task_sla)
   * @param currentUser - String sys_id of the user to use for audit records (Optional)
   *
   * @returns an SLARepair.Response object (see EOF)
   */
  repairBySysId: function(sysId, sourceTable, currentUser) {
      if (currentUser)
  		this.currentUser = currentUser;

      this.repairAction = SLARepair.RECREATE;
      this._bySysId(sysId, sourceTable);
      return this.response;
  },

  /**
   * Repairs SLA records from scratch based on a filter and table name removing current data and creating new task_sla records.
   *
   * @param filter - String encoded query
   * @param sourceTable - String table name (task or extension of task, contract_sla, task_sla)
   * @param currentUser - String sys_id of the user to use for audit records (Optional)
   *
   * @returns an SLARepair.Response object (see EOF)
   */
  repairByFilter: function(filter, sourceTable, currentUser) {
      if (currentUser)
  		this.currentUser = currentUser;

      this.repairAction = SLARepair.RECREATE;
      this._byFilter(filter, sourceTable);
      return this.response;
  },

  /**
   * Repairs SLA records from scratch based on the provided GlideRecord removing current data and creating new task_sla records.
   *
   * @param params gr - GlideRecord (task or extension of task, contract_sla, task_sla)
   *
   * @returns an SLARepair.Response object (see EOF)
   */
  repairByGlideRecord: function(gr) {
      this.repairAction = SLARepair.RECREATE;
      this._byGlideRecord(gr);
      return this.response;
  },

   /**
   * Repairs SLA records from scratch based on the provided Offline Update records
   *
   * @param params sysIds - Array or comma-separated String of sys_id's of the records in the Offline Update [sla_offline_update] table
   *
   * @returns an SLARepair.Response object (see EOF)
   */
  repairByOfflineUpdate: function(sysIds) {
  	this.repairAction = SLARepair.RECREATE;
  	var documentObj = {};
  	var tableName;

  	var gr = new GlideRecord(SLAOfflineUpdateSNC.SLA_OFFLINE_UPDATE);

  	if (!JSUtil.nil(sysIds))
  		gr.addQuery('sys_id', 'IN', sysIds);

  	gr.addQuery('state', 'ready');

  	gr.query();
  	while (gr.next()) {
  		tableName = gr.getValue('document_table');
  		if (!documentObj.hasOwnProperty(tableName))
  			documentObj[tableName] = [];
  		documentObj[tableName].push(gr.getValue('document_id'));
  	}
  	this._byDocumentId(documentObj);
  	return this.response;
  },

  _byDocumentId: function(documentObj) {
  	this.functionName = this._getFunctionName("ByDocumentId");
  	if (JSUtil.typeOf(documentObj) !== 'object') {
  		this._addErrorMessage('{0}: No valid document object supplied', this.functionName);
  		this._logErrors();
  		return;
  	}

  	this._preRepairForDocumentId(documentObj);
  	this._trackAndRepair(this._repairFromDocumentId, documentObj);
  },

  _bySysId: function(sysId, sourceTable) {
      this.functionName = this._getFunctionName("BySysId");
      if (!sysId) {
          this._addErrorMessage("{0}: No sysId value supplied", this.functionName);
          this._logErrors();
          return;
      }

      var sysIdType = JSUtil.typeOf(sysId);
      if (sysIdType != "string" && sysIdType != "array") {
          this._addErrorMessage("{0}: Invalid sysId value supplied - '{1}'", [ this.functionName, JSUtil.typeOf(sysId) ]);
          this._logErrors();
          return;
      }

      this._byFilter("sys_idIN" + sysId, sourceTable);
  },

  _byGlideRecord: function(gr) {
      this.functionName = this._getFunctionName("ByGlideRecord");
      if (!gr || !(gr instanceof GlideRecord)) {
          this._addErrorMessage("{0}: Invalid GlideRecord value supplied - '{1}'", [ this.functionName, JSUtil.typeOf(gr) ]);
          return;
      }

      var sysIds = [];
      var gr2 = new GlideRecord(gr.getRecordClassName());
      gr2.addEncodedQuery(gr.getEncodedQuery());
      gr2.query();
      while (gr2.next())
          sysIds.push(gr2.getUniqueValue());

      this._byFilter("sys_idIN" + sysIds, gr.getRecordClassName());
  },

  _byFilter: function(filter, sourceTable) {
      this.sourceTable = sourceTable;
      this.encodedQuery = filter;
      this.filterTerms.length = 0;

      if (!this._isValidSource())
          return;

      this.functionName = this._getFunctionName("ByFilter");

      if (JSUtil.typeOf(filter) != "string") {
          this._addErrorMessage("{0}: Invalid filter supplied - '{1}'", [ this.functionName, JSUtil.typeOf(filter) ]);
          this._logErrors();
          return;
      }

  	var repairFrom;

  	// apply appropriate filter to task_sla record based on the filter we've been passed
      // and the source table
      switch (this.baseTable) {
          case SLARepair.TASK:
              this.taskIds.length = 0;
              var taskGr = new GlideRecord(this.sourceTable);
              taskGr.addEncodedQuery(filter);
  			taskGr.addQuery("sys_class_name", "IN", this.slaDefTables);
              taskGr.query();

              while (taskGr.next())
                  this.taskIds.push(taskGr.getValue("sys_id"));

              if (this.taskIds.length == 0) {
                  this._addErrorMessage("{0}: No matching Tasks found", this.functionName);
                  return;
              }

              this.filterTerms.push({
                  type: "field",
                  fieldName: "task",
                  fieldData: this.taskIds
              });

  			repairFrom = this._repairFromTask;
              break;

  		case SLARepair.SLA:
              this.filterTerms.push({
                  type: "encodedQuery",
                  queryString: filter
              });
  			repairFrom = this._repairFromTaskSLA;
              break;

  		case SLARepair.SLA_DEF:
              var slaDefIds = [];
              var slaDefGr = new GlideRecord(this.sourceTable);
              slaDefGr.addEncodedQuery(filter);
              slaDefGr.query();
              while (slaDefGr.next())
                  slaDefIds.push(slaDefGr.getValue("sys_id"));
              if (slaDefIds.length == 0) {
                  this._addErrorMessage("{0}: No matching SLA Definitions found", this.functionName);
                  return;
              }
              this.filterTerms.push({
                  type: "field",
                  fieldName: "sla",
                  fieldData: slaDefIds
              });
  			repairFrom = this._repairFromSLADef;
              break;
      }

      this._preRepair();
  	this._trackAndRepair(repairFrom, null);
  },

  _trackAndRepair: function(repairFromFunction, param) {
  	if (this.validateOnly)
          return;
  	this.tracker = SNC.GlideExecutionTracker.getLastRunning();
  	this.slaAudit = new SLARepairLog(this.repairAction, this.currentUser, gs.nowNoTZ());
  	this.slaAudit.setTrackerId(this.tracker.getSysID());
  	this.slaAudit.startLog();
  	this.response.audit_record_id = this.slaAudit.getLogSysId();

  	try {
  		// Set the execution trcker's source to our log header and kick it off
  		this.tracker.setSourceTable(SLARepairLog.LOG_TABLE);
  		this.tracker.setSource(this.slaAudit.getLogSysId());
  		this.tracker.run();
  		if (JSUtil.typeOf(repairFromFunction) === "function")
  			repairFromFunction.call(this, param);
  	} finally {
  		this.tracker.updateDetailMessage(gs.getMessage("The repair is complete"));
  		this.tracker.success(gs.getMessage("The repair is complete"));
  		this.slaAudit.finishLog();
  	}
  },

  _repairFromTask: function() {
      if (this.taskIds.length == 0) {
          this._addInfoMessage("{0}: 0 Task records found to repair Task SLAs for", this.functionName);
          this.lu.logNotice("0 Task records found to repair Task SLAs for");
          return;
      }

      this.tracker.setMaxProgressValue(this.taskIds.length);

      var taskGr = new GlideRecord(this.sourceTable);
      for (var i = 0; i < this.taskIds.length; i++) {
          if (taskGr.get(this.taskIds[i])) {
              this._repairTaskSLAs(taskGr);
              this.tracker.updateDetailMessage(gs.getMessage("Processing {0}", taskGr.number));
          }

          this.tracker.incrementProgressValue();
      }
  },

  _repairFromSLADef: function() {
      if (this.taskTables.length == 0) {
          this._addInfoMessage("{0}: 0 Task records found to repair Task SLAs for", this.functionName);
          this.lu.logNotice("0 Task records found to repair Task SLAs for");
          return;
      }

      var total = 0;
  	var taskGr;
      for (var i = 0; i < this.taskTables.length; i++) {
          taskGr = new GlideRecord(this.taskTables[i]);
          taskGr.query();
          total += taskGr.getRowCount();
      }

      this.tracker.setMaxProgressValue(total);

      for (var j = 0; j < this.taskTables.length; j++) {
          taskGr = new GlideRecord(this.taskTables[j]);
          taskGr.query();

          while (taskGr.next()) {
              this._repairTaskSLAs(taskGr, this.slaDefs);
              this.tracker.updateDetailMessage(gs.getMessage("Processing {0}", taskGr.number));
              this.tracker.incrementProgressValue();
          }
      }
  },

  _repairFromTaskSLA: function() {
      // if we've found 0 records to process and our base table is not "task"
      // report it and finish
      if (this.response.count == 0) {
          this._addInfoMessage("{0}: 0 records found to repair", this.functionName);
          this.lu.logNotice("0 Task SLA records found to process");
          return;
      }
      var taskSlaGr = new GlideAggregate(SLARepair.SLA);
      this._addFilterTerms(taskSlaGr);
      taskSlaGr.addQuery("task.sys_class_name", "!=", "");
      taskSlaGr.groupBy("task");
      taskSlaGr.groupBy("sla");
      taskSlaGr.query();
      if (!taskSlaGr.next()) {
          this._addInfoMessage("{0}: 0 records found to repair", this.functionName);
          this.lu.logNotice("0 Task SLA records found to process");
          return;
      }

      this.tracker.setMaxProgressValue(taskSlaGr.getRowCount());

      var slaDefIds = [];
      var currentTaskGr = taskSlaGr.task.getRefRecord();
      if (this.baseTable != SLARepair.TASK)
          slaDefIds.push(taskSlaGr.getValue("sla"));
      do {
          this.tracker.incrementProgressValue();

          if (taskSlaGr.getValue("task") != currentTaskGr.getUniqueValue() || !taskSlaGr.hasNext()) {
              if (!currentTaskGr.isValidRecord()) {
                  if (this.lu.atLevel(GSLog.ERR))
  					this.lu.logError("Task does not exist: " + currentTaskGr.getUniqueValue());
                  continue;
              }

  			// if we don't have any more records and the current task hasn't changed add the current sla definition in for processing
  			if (taskSlaGr.getValue("task") == currentTaskGr.getUniqueValue() && !taskSlaGr.hasNext())
  				slaDefIds.push(taskSlaGr.getValue("sla"));

              this.tracker.updateDetailMessage(gs.getMessage("Processing SLA for {0}", currentTaskGr.number));
              this._repairTaskSLAs(currentTaskGr, slaDefIds);

  			// if we don't have any more records and current task has changed we need to call repair for this last one
  			if (taskSlaGr.getValue("task") != currentTaskGr.getUniqueValue() && !taskSlaGr.hasNext()) {
  				slaDefIds.push(taskSlaGr.getValue("sla"));
  				currentTaskGr = taskSlaGr.task.getRefRecord();
  				this.tracker.updateDetailMessage(gs.getMessage("Processing SLA for {0}", currentTaskGr.number));
  				this._repairTaskSLAs(currentTaskGr, slaDefIds);
  			} else {
  				currentTaskGr = taskSlaGr.task.getRefRecord();
  				slaDefIds = [taskSlaGr.getValue("sla")];
  			}
          } else
  			slaDefIds.push(taskSlaGr.getValue("sla"));
      } while (taskSlaGr.next());

      if (this.lu.atLevel(GSLog.NOTICE))
  		this.lu.logNotice("Repair task_sla count = " + taskSlaGr.getRowCount());
  },

  _repairFromDocumentId: function(tasks) {
  	if (this.response.count === 0) {
  		this._addInfoMessage("_repairFromDocumentId: 0 Task records found to repair Task SLAs for");
  		this.lu.logNotice('0 Task records found to repair Task SLAs for');
  		return;
  	}

  	this.tracker.setMaxProgressValue(this.response.count);

  	var sysIds;
  	var tableNameArray = Object.keys(tasks);
  	var tableName;

  	for (var i = 0; i < tableNameArray.length; i++) {
  		tableName = tableNameArray[i];
  		var taskGr = new GlideRecord(tableName);
  		sysIds = tasks[tableName];
  		for (var j = 0; j < sysIds.length; j++) {
  			if (taskGr.get(sysIds[j])) {
  				this._repairTaskSLAs(taskGr);
  				this.tracker.updateDetailMessage(gs.getMessage("Processing {0}", taskGr.number));
  			}
  			this.tracker.incrementProgressValue();
  		}
  	}
  },

  _preRepair: function() {
      if (this.repairAction == SLARepair.RECREATE && this.baseTable == SLARepair.TASK)
          this._preRepairForTask();
      else if (this.repairAction == SLARepair.RECREATE && this.baseTable == SLARepair.SLA_DEF)
          this._preRepairForSLADef();
      else
          this._preRepairForTaskSLA();
  },

  _preRepairForTask: function() {
      var taskAg = new GlideAggregate(this.sourceTable);
      if (!JSUtil.nil(this.encodedQuery))
          taskAg.addEncodedQuery(this.encodedQuery);
  	taskAg.addQuery("sys_class_name", "IN", this.slaDefTables);
      taskAg.addAggregate("COUNT", "sys_id");
      taskAg.groupBy("sys_class_name");
      taskAg.query();

      this.response.count = 0;

      while (taskAg.next()) {
          var tableName = taskAg.sys_class_name + "";

          var tableInfo = {
              table: tableName,
              label: GlideMetaData.getTableLabel(tableName)
          };

          if (this._isTableAudited(tableName)) {
              tableInfo[SLARepair.AUDIT_ON] = true;
              var unauditedFields = this._getNoAuditFields(tableName);
              if (unauditedFields.length > 0)
                  tableInfo[SLARepair.UNAUDITED_FIELDS] = unauditedFields;

              this.response.count += taskAg.getAggregate("COUNT", "sys_id") - 0;
          }

          this.response.audit_info[tableName] = tableInfo;
      }
  },

  _preRepairForSLADef: function() {
      var slaDefGr = new GlideRecord(this.sourceTable);
      if (!JSUtil.nil(this.encodedQuery))
          slaDefGr.addEncodedQuery(this.encodedQuery);
      slaDefGr.addQuery("active", true);
      slaDefGr.query();

      var slaCnt = {};

      // Populate a unique list of tables with all the definitions for each table
      while (slaDefGr.next()) {
          if (JSUtil.nil(slaDefGr.collection))
              continue;

          var collection = slaDefGr.collection + "";
          if (!slaCnt[collection]) {
              slaCnt[collection] = {
                  count: 0,
                  definitionIds: []
              };
          }

          slaCnt[collection].count++;
          slaCnt[collection].definitionIds.push(slaDefGr.getUniqueValue());
      }

      this.taskTables = [];
      this.slaDefs = [];
      var slaTot = 0;
      for ( var tableName in slaCnt) {
          var tableInfo = {
              table: tableName,
              label: GlideMetaData.getTableLabel(tableName)
          };

          if (this._isTableAudited(tableName)) {
              tableInfo[SLARepair.AUDIT_ON] = true;
              var unauditedFields = this._getNoAuditFields(tableName);
              if (unauditedFields.length > 0)
                  tableInfo[SLARepair.UNAUDITED_FIELDS] = unauditedFields;

              slaTot = slaTot + slaCnt[tableName].count;

              this.taskTables.push(tableName);
              this.slaDefs = this.slaDefs.concat(slaCnt[tableName].definitionIds);
          }

          this.response.audit_info[tableName] = tableInfo;
      }

      var taskAg = new GlideAggregate(SLARepair.TASK);
      taskAg.addQuery("sys_class_name", this.taskTables);
      taskAg.addAggregate("COUNT", "sys_id");
      taskAg.query();

      this.response.count = taskAg.getTotal("COUNT", "sys_id") * slaTot;
  },

  _preRepairForTaskSLA: function() {
      var taskSlaGr = new GlideAggregate(SLARepair.SLA);
      this._addFilterTerms(taskSlaGr);
      taskSlaGr.addAggregate("COUNT", "task.sys_class_name");
      taskSlaGr.addQuery("task.sys_class_name", "!=", "");
      taskSlaGr.groupBy("task.sys_class_name");
      taskSlaGr.query();

      while (taskSlaGr.next()) {
          var tableName = taskSlaGr.getValue("task.sys_class_name");
          var tableInfo = {
              table: tableName,
              label: GlideMetaData.getTableLabel(tableName)
          };

          if (this._isTableAudited(tableName)) {
              tableInfo[SLARepair.AUDIT_ON] = true;
              var unauditedFields = this._getNoAuditFields(tableName);
              if (unauditedFields.length > 0)
                  tableInfo[SLARepair.UNAUDITED_FIELDS] = unauditedFields;

              this.response.count += taskSlaGr.getAggregate("COUNT", "task.sys_class_name") - 0;
          }

          this.response.audit_info[tableName] = tableInfo;
      }
  },

  _preRepairForDocumentId: function(documentObj) {
  	var taskAg;
  	var sysIds;
  	var count = 0;
  	var i;
  	var tableName;
  	var tableNameArray = Object.keys(documentObj);

  	for (i = 0; i < tableNameArray.length; i++) {
  		tableName = tableNameArray[i];
  		this.sourceTable = tableName;
  		if (!this._isValidSource())
  			return;

  		sysIds = documentObj[tableName];
  		if (!Array.isArray(sysIds)) {
  			this._addErrorMessage("SLARepair: Invalid document object supplied - Expecting an object of arrays but received a '{0}'", JSUtil.typeOf(sysIds));
  			this._logErrors();
  			return;
  		}

  		taskAg = new GlideAggregate(tableName);
  		taskAg.addAggregate('COUNT');
  		taskAg.addQuery('sys_id', sysIds);
  		taskAg.query();
  		while (taskAg.next()) {
  			count += taskAg.getAggregate('COUNT') - 0;
  		}
  	}

  	if (count === 0) {
  		this._addErrorMessage("SLARepair: No matching Tasks found to repair, make sure SLA Definition exists");
  		this._logErrors();
  		return;
  	}

  	this.response.count = count;

  	for (i =0; i < tableNameArray.length; i++) {
  		tableName = tableNameArray[i];
  		var tableInfo = {
  			table: tableName,
  			label: GlideMetaData.getTableLabel(tableName)
  		};
  		if (this._isTableAudited(tableName)) {
  			tableInfo[SLARepair.AUDIT_ON] = true;
  			var unauditedFields = this._getNoAuditFields(tableName);
  			if (unauditedFields.length > 0)
  				tableInfo[SLARepair.UNAUDITED_FIELDS] = unauditedFields;
  		}
  		this.response.audit_info[tableName] = tableInfo;
  	}
  },

  // -------------------------------- Validation functions --------------------------------
  _isValidSource: function() {
      this._clearMessages();
      if (!this.sourceTable)
          this._addErrorMessage("SLARepair: No source table provided");
      else {
          this.baseTable = "" + GlideDBObjectManager.getAbsoluteBase(this.sourceTable);
          if (this.baseTable != SLARepair.TASK && this.baseTable != SLARepair.SLA && this.baseTable != SLARepair.SLA_DEF) {
              this.baseTable = null;
              this._addErrorMessage("SLARepair: Invalid source table: {0}", this.sourceTable);
          }
      }
      if (!this.repairAction)
          this._addErrorMessage("SLARepair: No action provided - it must be \"repair\"");
      else if (this.repairAction != "recreate")
          this._addErrorMessage("SLARepair: Invalid action: {0} - it must be \"repair\"", this.repairAction);

      if (this.response.err_msg.length > 0) {
          this._logErrors();
          return false;
      }
      return true;
  },

  // -------------------------------- Logging functions --------------------------------
  _logErrors: function() {
  	if (this.lu.atLevel(GSLog.ERR))
  		for (var i = 0; i < this.response.err_msg.length; i++)
  			this.lu.logError(this.response.err_msg[i]);
  },

  _addInfoMessage: function(infoMsg, params) {
      if (!infoMsg)
          return;
      this.response.info_msg.push(gs.getMessage(infoMsg, params));
  	if (this.slaAudit)
  		this.slaAudit.logInfoMessage(infoMsg);
  },

  _addErrorMessage: function(errorMsg, params) {
      if (!errorMsg)
          return;
      this.response.err_msg.push(gs.getMessage(errorMsg, params));
  	if (this.slaAudit)
  		this.slaAudit.logErrorMessage(errorMsg);
  },

  _clearMessages: function() {
      this.response.info_msg.length = 0;
      this.response.err_msg.length = 0;
  },

  _getFunctionName: function(callingFunction) {
      var functionName = "SLARepair." + this.repairAction;
      if (callingFunction)
          functionName += callingFunction;
      return functionName;
  },

  // -------------------------------- Logging functions --------------------------------
  // -------------------------------- Repair functions --------------------------------
  _addFilterTerms: function(taskSlaGr) {
      if (!taskSlaGr || !((taskSlaGr instanceof GlideRecord) || (taskSlaGr instanceof GlideAggregate)))
          return;
      for (var i = 0; i < this.filterTerms.length; i++) {
          var filterTerm = this.filterTerms[i];
          if (filterTerm.type == "encodedQuery" && filterTerm.queryString)
              taskSlaGr.addEncodedQuery(filterTerm.queryString);
          else if (filterTerm.type == "field") {
              if (taskSlaGr.isValidField(filterTerm.fieldName) && filterTerm.fieldData)
                  taskSlaGr.addQuery(filterTerm.fieldName, filterTerm.fieldData);
          }
      }
  },

  _hasAdvancedJournalConditions: function (slaDefIds) {
  	var earlyReadGR = new GlideRecord("contract_sla"); // We need to do this for async and HW with Journal support
  	if (slaDefIds)
  		earlyReadGR.addQuery("sys_id", slaDefIds);
  	var cond = earlyReadGR.addQuery("adv_condition_type", "advanced_journal");
  	cond.addOrCondition("adv_condition_type", "=", "advanced_journal_and_system");
  	earlyReadGR.setLimit(1);
  	earlyReadGR.query();
  	return earlyReadGR.hasNext();
  },

  _hasAdvancedSystemConditions: function (slaDefIds) {
  	var earlyReadGR = new GlideRecord("contract_sla");
  	if (slaDefIds)
  		earlyReadGR.addQuery("sys_id", slaDefIds);
  	var cond = earlyReadGR.addQuery("adv_condition_type", "advanced_system");
  	cond.addOrCondition("adv_condition_type", "=", "advanced_journal_and_system");
  	earlyReadGR.setLimit(1);
  	earlyReadGR.query();
  	return earlyReadGR.hasNext();
  },

  _hasVariablesCondition: function (slaDefIds) {
  	if (!slaDefIds)
  		return false;

  	var contractGr = new GlideRecord("contract_sla");
  	contractGr.addQuery("sys_id", slaDefIds);
  	contractGr.query();
  	while (contractGr.next())
  		if (this.slaUtil.hasVariablesCondition(contractGr))
  			return true;

  	return false;
  },

  _repairTaskSLAs: function(taskGr, slaDefIds) {
      if (!taskGr || !taskGr.sys_id)
          return;

  	/* if the Task we're about to repair Task SLAs for has records in the "sla_async_queue" then it cannot be repaired so log a warning message */
  	if (new SLAAsyncQueue().isTaskQueued(taskGr.getUniqueValue())) {
  		 this.slaAudit.logWarningMessage("Task SLAs for " + taskGr.getClassDisplayValue() + " " + taskGr.getDisplayValue() +
  										 " cannot be repaired as there are unprocessed records in the SLA Async Queue for this task");
  		return;
  	}

  	var slaOfflineUpdate = new SLAOfflineUpdate(taskGr);
  	var isOfflineUpdate = slaOfflineUpdate.hasQueuedOfflineUpdate();
  	if (isOfflineUpdate) {
  		slaOfflineUpdate.setProcessing();
  		this.slaAudit.setOfflineUpdate(true);
  	} else
  		this.slaAudit.setOfflineUpdate(this._isOfflineFieldInHistory(taskGr));

  	// We need to do this for async and HW with Journal support

  	// We need to check if we have any contract_sla records that are using advanced journal conditions
  	var withJournal = this._hasAdvancedJournalConditions(slaDefIds);

  	// We need to check if we have any contract_sla records that are using advanced system conditions
  	var withSystem = this._hasAdvancedSystemConditions(slaDefIds);

  	// We need to check if we have any contract_sla records that are using catalog variables in conditions
  	var withVariables = this._hasVariablesCondition(slaDefIds);

  	// Create a HistoryWalker instance and walk the Task record to it's created values i.e. update 0
  	var hw = this.slaUtil.getHistoryWalker(
  		taskGr.getRecordClassName(),
  		taskGr.getValue('sys_id'),
  		/*recordLevelSecurity*/ false,
  		/*fieldLevelSecurity*/ false,
  		/*withVariables*/ withVariables,
  		/*walkToFirstUpdate*/ true,
  		/*withJournalFields*/ withJournal,
  		/*withSysFields*/ withSystem
  	);

  	if (!hw) {
  		this.lu.logError("_repairTaskSLAs: failed getting HistoryWalker to initial update");
  		return;
  	}

      var walkedTaskGr = hw.getWalkedRecord();

      var controller = new RepairTaskSLAController(walkedTaskGr);
      if (slaDefIds)
          controller.restrictSLADefinitions(slaDefIds);

      var beforeCounter = {};
      var auditTaskSlaGr = new GlideRecord(SLARepair.SLA);
      auditTaskSlaGr.addQuery("task", taskGr.sys_id);
      if (slaDefIds)
          auditTaskSlaGr.addQuery("sla", slaDefIds);
      auditTaskSlaGr.query();
  	var slaDefId = '';
      while (auditTaskSlaGr.next()) {
          if (this.auditingEnabled)
              this.slaAudit.createBeforeEntry(auditTaskSlaGr);

          // Keep a counter of the how many SLAs (per Definition) we had before repairing
          slaDefId = auditTaskSlaGr.sla + "";
          if (!beforeCounter[slaDefId])
              beforeCounter[slaDefId] = 0;

          beforeCounter[slaDefId]++;
      }

      this._deleteExistingSLAs(taskGr, slaDefIds);

  	do {
  		walkedTaskGr = hw.getWalkedRecord();

  		/* Check if the domain has changed on the Task
  		   If it has we need to mimic what business rule "Domain - Cascade Domain - Task" does
  		   and update the Task SLAs and associated workflow records to the new domain
  		   before performing SLA processing for this update to the Task */
  		if (walkedTaskGr.sys_domain.changes() && hw.getUpdateNumber() > 0)
  			this._updateDomainOnTaskSLAs(walkedTaskGr);

  		controller.setTaskGR(walkedTaskGr);
  		controller.runNow();
  	} while (hw.walkForward());

  	var taskSlaGr = new GlideRecord("task_sla");
      taskSlaGr.addQuery("task", taskGr.sys_id);
      if (slaDefIds)
          taskSlaGr.addQuery("sla", slaDefIds);
      taskSlaGr.query();

      while (taskSlaGr.next()) {
          /**
           * Work out how many SLAs have been created and updated.
           *
           * If beforeCounter doesn't have an property for the current Definition we create.
           *
           * If beforeCounter has a property for the current Definition we update. When we get to zero we remove so any
           * subsequent checks will create.
           *
           * Deletes are calculated by what we have left after removing all the updates.
           *
           */
          slaDefId = taskSlaGr.sla + "";
          if (!beforeCounter[slaDefId]) {
              this.slaAudit.incrementCreated();
          } else {
              if (beforeCounter[slaDefId] > 0) {
                  this.slaAudit.incrementUpdated();
                  beforeCounter[slaDefId]--;

                  if (beforeCounter[slaDefId] == 0)
                      delete beforeCounter[slaDefId];
              }
          }

  		var taskSLA = new RepairTaskSLA(taskSlaGr);
  		var slaDefGr = taskSLA.getContractSLA();

  		if (!taskSlaGr.active) {
  			if (this.runWorkflow && slaDefGr.flow.nil())
  				taskSLA.doWorkflow();
  			else if (this.runFlow)
  				taskSLA.doFlow();
              if (this.auditingEnabled)
                  this.slaAudit.createAfterEntry(taskSlaGr);
              continue;
          }

  		var slaRecalculated = false;
  		if (this.runWorkflow && slaDefGr.flow.nil()) {
  			taskSLA.calculate();
  			slaRecalculated = true;
  			taskSLA.doWorkflow();
  		} else if (this.runFlow) {
  			taskSLA.calculate();
  			slaRecalculated = true;
  			taskSLA.doFlow();
  		}

          if (taskSlaGr.getValue("stage") == "in_progress") {
              if (!slaRecalculated) //calculate if it's not been done already for processing the flow or workflow
  				taskSLA.calculate();
  			taskSLA._commit();

              if (this.auditingEnabled)
                  this.slaAudit.createAfterEntry(taskSLA._getTaskSLA());

              if (!taskSLA.breachedFlag)
                  taskSLA._insertBreachTrigger();
          } else {
              if (this.auditingEnabled)
                  this.slaAudit.createAfterEntry(taskSlaGr);
          }

      }

  	if (isOfflineUpdate)
  		slaOfflineUpdate.remove();
      // Increment the number of deleted Task SLAs
      for (var slaDefinitionId in beforeCounter)
          if (beforeCounter[slaDefinitionId] != 0)
              this.slaAudit.incrementDeleted(beforeCounter[slaDefinitionId]);
  },

  _isOfflineFieldInHistory: function(taskGr) {
  	var offlineFieldName = SLAOfflineUpdateSNC.getOfflineFieldName(taskGr.getRecordClassName());
  	if (JSUtil.nil(offlineFieldName))
  		return false;

  	var ghs = new GlideHistorySet(taskGr);
  	var hsSysId = ghs.generate();
  	var historyLineGr = new GlideRecord('sys_history_line');
  	historyLineGr.addQuery('set', hsSysId);
  	historyLineGr.addQuery('field', offlineFieldName);
  	historyLineGr.query();
  	return historyLineGr.hasNext();
  },

  _isTableAudited: function(tableName) {
      if (!tableName)
          return false;
      var gr = new GlideRecord(tableName);
      if (!gr.isValid())
          return false;

      return new GlideAuditor(tableName, null).auditTable();
  },

  _getNoAuditFields: function(tableName) {
      var noAuditFields = this.slaUtil.getNoAuditFields(tableName);

  	if (!noAuditFields)
  		return [];

  	return Object.keys(noAuditFields);
  },

  _getSlaDefTables: function() {
  	var slaDefGr = new GlideAggregate(SLARepair.SLA_DEF);
  	slaDefGr.addActiveQuery();
  	slaDefGr.groupBy("collection");
  	slaDefGr.query();

  	var slaDefTables = [];
  	while(slaDefGr.next())
  		slaDefTables.push(slaDefGr.getValue("collection"));

  	return slaDefTables;
  },

  _deleteExistingSLAs: function(taskGr, slaDefIds) {
      if (!taskGr || !taskGr.sys_id)
          return false;
      var taskSlaGr = new GlideRecord('task_sla');
      taskSlaGr.addQuery('task', taskGr.sys_id);
      if (slaDefIds)
          taskSlaGr.addQuery('sla', slaDefIds);
      taskSlaGr.query();

      var taskSlaIds = [];
      while (taskSlaGr.next()) {
          taskSlaIds.push(taskSlaGr.sys_id);
          this.workflow.cancel(taskSlaGr);
          taskSlaGr.deleteRecord();
      }
      if (taskSlaIds.length == 0)
          return null;

      var triggerGr = new GlideRecord('sys_trigger');
      triggerGr.addQuery('document', 'task_sla');
      triggerGr.addQuery('document_key', taskSlaIds);
      triggerGr.addQuery('name', 'STARTSWITH', 'SLA breach timer');
      triggerGr.deleteMultiple();
  },

  _updateDomainOnTaskSLAs: function(taskGr) {
  	if (!taskGr.getUniqueValue() || !taskGr.isValidField('sys_domain'))
  		return;

  	var taskSlaGr = new GlideRecord('task_sla');
  	taskSlaGr.setWorkflow(false);
  	taskSlaGr.addQuery("task", taskGr.getUniqueValue());
  	taskSlaGr.query();

  	var isSlaDomained = taskSlaGr.isValidField("sys_domain");

  	while (taskSlaGr.next()) {
  		if (isSlaDomained) {
  			taskSlaGr.sys_domain = taskGr.sys_domain;
  			taskSlaGr.update();
  		}

  		// Update any Workflow context associated with this Task SLA
  		var wfc = new GlideRecord('wf_context');
  		wfc.addQuery('id', taskSlaGr.sys_id);
  		wfc.query();
  		while (wfc.next()) {
  			wfc.sys_domain = taskGr.sys_domain;
  			wfc.update();

  			// ...and also the wf_executing and wf_history records on the wf_context
  			var wfe =  new GlideMultipleUpdate('wf_executing');
  			wfe.addQuery('context', wfc.sys_id);
  			wfe.setValue('sys_domain', taskGr.sys_domain);
  			wfe.execute();

  			var wfh =  new GlideMultipleUpdate('wf_history');
  			wfh.addQuery('context', wfc.sys_id);
  			wfh.setValue('sys_domain', taskGr.sys_domain);
  			wfh.execute();
  		}
  	}
  },

  type: "SLARepair",
};

/**
* Response object for an SLARepair request
*
* <pre>
* structure
* {
*      count: Integer - number of affected records
*      err_msg: String Array - Localised error messages
*      info_msg: String Array - Localised info messages
*      warn_msg: String Array - Localised warning messages
*      audit_info: Object - Table and field auditing information key is table name
*      {
*          tableName: Object - Details of auditing on each affected table
*          {
*              table: String - Name of the table
*              audit_on: Boolean - true if audit is turned on for this table
*              unaudited_fields: String Array - A list of the fields on the table which are not audited
*          }
*      }
*      audit_record_id: String sys id of the audit info record.  Not returned when validateOnly is set true.
* }
* </pre>
*/
SLARepair.Response = Class.create();

SLARepair.Response.prototype = {
  initialize: function() {
      this.count = 0;
      this.info_msg = [];
      this.err_msg = [];
      this.audit_info = {};
      this.audit_record_id = "";
  },
  type: "SLARepair.Response"
};

Sys ID

c31eac7deb62310070a9666cd206feee

Offical Documentation

Official Docs: