Name

global.SLATimeline

Description

Task SLA timeline based on task audit history

Script

var SLATimeline = Class.create();
SLATimeline.prototype = Object.extendsObject(AbstractTimelineSchedulePage, {
  STATE_IN_PROGRESS : 'in_progress',
  STATE_PAUSED      : 'paused',
  STATE_CANCELLED   : 'cancelled',
  STATE_COMPLETED   : 'completed',
  SLA_RETROACTIVE_PAUSE: 'com.snc.sla.retroactive_pause',
  SLA_CONDITION_FIELD_ORDER: ['start_condition', 'stop_condition', 'reset_condition', 'cancel_condition', 'pause_condition', 'resume_condition'],
  SLA_CONDITION_FIELDS: {'start_condition' : gs.getMessage("Start"), 'cancel_condition' : gs.getMessage("Cancel"),
                         'pause_condition' : gs.getMessage("Pause"), 'resume_condition' : gs.getMessage("Resume"),
                         'stop_condition' : gs.getMessage("Stop"), 'reset_condition' : gs.getMessage("Reset"),
                         'service_commitment' : gs.getMessage("Service Commitment")},
  SLA_CONDITION_FUNCTIONS: {'attach' : gs.getMessage("Attach"), 'pause' : gs.getMessage("Pause"),
                            'resume' : gs.getMessage("Resume"), 'complete' : gs.getMessage("Complete"),
                            'cancel' : gs.getMessage("Cancel"), 'reattach' : gs.getMessage("Reattach")},
  SLA_CONDITION_FUNCTIONS_ORDER: ['attach', 'complete', 'reattach', 'cancel', 'pause', 'resume'],
  CONDITION_MATCH_IMAGE: '<span><img style="padding: 2px 3px 4px 8px" src="/images/icon_availability_up.gifx"></span>',
  CONDITION_NO_MATCH_IMAGE: '<span><img style="padding: 2px 3px 4px 8px" src="/images/icon_availability_down.gifx"></span>',
  CONDITION_IGNORE_IMAGE: '<span><img style="padding: 2px 3px 4px 8px" src="/images/icon_availability_unknown.gifx"></span>',



  getItems : function() {
      this._getParameters();
      this._getRecords();
      var i18nMsg1 = gs.getMessage("(Service Offering SLA) ");
      var soc = (this.slaGR.isValidField('sys_class_name') && this.slaGR.getValue('sys_class_name') === 'service_offering_sla') ?  i18nMsg1 : "";
      if (this.slaGR && this.taskGR){
          this.setPageTitle(this.slaGR.getDisplayValue() + " " + soc + this.taskGR.getDisplayValue());
          this._renderAll();
      }
  },

  _getParameters : function() {
      this.sla = this.getParameter("sysparm_timeline_sla");
      this.task = this.getParameter("sysparm_timeline_task");
      this.items = {};
      this.count = 0;
      this.sla_cur = null;
      this.task_slas = {};
      this.stateChanges = {};
  },

  _getRecords : function() {
      var task = new GlideRecord("task");
      task.get(this.task);
      if (task.isValidRecord())
          this.taskGR = task;

      var sla = new GlideRecord("contract_sla");
      sla.get(this.sla);
      if (sla.isValidRecord()) {
          this.slaGR = sla;
          this.emptySLAConditions = {};
          for (var conditionFieldName in this.SLA_CONDITION_FIELDS)
              if (sla[conditionFieldName].nil())
                  this.emptySLAConditions[conditionFieldName] = true;

          this.ignoreSLAConditions = {};
          if (sla.when_to_resume + '' !== "on_condition")
              this.ignoreSLAConditions["resume_condition"] = true;
          if (sla.when_to_cancel + '' !== "on_condition")
              this.ignoreSLAConditions["cancel_condition"] = true;

          var serviceOfferingField = gs.getProperty(this.SLA_SERVICE_OFFERING, 'cmdb_ci');
          var soc = new GlideRecord("service_offering_commitment");
          if (!soc.isValid() || !task.getValue(serviceOfferingField) )
              this.ignoreSLAConditions["service_commitment"] = true;

          // Reread the task record with the correct table (like incident or change_request)
          task = new GlideRecord(sla.collection);
          task.get(this.task);
          if (task.isValidRecord())
              this.taskGR = task;
      }

      this.scheduleGR = new SLATimelineAPI().getScheduleGR(this.sla, this.task);
      this.schedule = this.scheduleGR.getID();
  },

  _renderAll : function() {
      this.auditItem = new GlideTimelineItem('task', this.task);
      this.auditItem.setLeftLabelText(gs.getMessage("Task History"));
      var gr = this._renderAudits();

      //render schedule
      if (this.schedule)
          this._getScheduleTimeline(gr);

      //render task sla timelines
      for (var i = 1; i <= this.count;i++) {
          var slaItem = new GlideTimelineItem('task_sla', this.items[i]);
          slaItem.setParent(this.task);

          var list = this.task_slas[i];
          var prev = [new GlideDuration(0),new GlideDuration(0)];
          this.rel_end_date = null;
          if (this.slaGR.duration_type) {
              var timelineAPI = new SLATimelineAPI();
              this.rel_end_date = timelineAPI.getRelDurationEndDate(list[1].time, this.taskSLAGR, this.taskGR);
          }

          for (var j = 1; j < list.length - 1; j++) {
              if (list[j].time === list[j+1].time || list[1].time > list[j+1].time)
                  continue;
              list[j].time = (list[j].time > list[1].time) ? list[j].time: list[1].time;
              //get timeline span by schedule
              prev = this._splitBySchedule(list[j], list[j+1],slaItem, prev,new GlideDateTime(list[0]));
          }
          if(list[1].time > list[list.length-1].time){
              var auditSpan = slaItem.createTimelineSpan("task_sla" + i, "");
              auditSpan.setSpanText(gs.getMessage("Expected start"));
              var exp_time = new GlideDateTime(list[1].time).getNumericValue();
              auditSpan.setTimeSpan(exp_time,exp_time);
          }
          slaItem.setLeftLabelText(gs.getMessage("Task SLA {0} ({1})", [i, prev[0].getDisplayValue()]));
          this.add(slaItem);
      }
      this.add(this.auditItem);
  },

  _splitBySchedule : function(list1, list2, slaItem, prev, sla_created) {
      //split by schedule
      if (this.schedule) {
          var timeSpans = this.scheduleGR.getSpans(new GlideDateTime(list1['time']),new GlideDateTime(list2['time']));
  		var start_time;
  		var end_time;
          if (timeSpans.size() > 0) {
              start_time = timeSpans.get(0).getStart().getGlideDateTime();
              if (start_time > new GlideDateTime(list1['time'])) {
                  if (list1.state === this.STATE_PAUSED)
                      this._createPauseSpan(slaItem, new GlideDateTime(list1.time), start_time, prev,sla_created, false);
                  else if (list1.state === this.STATE_IN_PROGRESS)
                      prev = this._createTimeSpan(slaItem, new GlideDateTime(list1['time']), start_time, prev, sla_created, false);
              }
          }
          for (var i = 0; i < timeSpans.size(); i++) {
              start_time = timeSpans.get(i).getStart().getGlideDateTime();
              end_time = timeSpans.get(i).getEnd().getGlideDateTime();
              if (list1.state === this.STATE_PAUSED)
                  this._createPauseSpan(slaItem, start_time, end_time, prev,sla_created, true);
              else if (list1.state === this.STATE_IN_PROGRESS)
                  prev = this._seperateByBreach(slaItem, start_time, end_time, prev, sla_created);

              if (i < timeSpans.size() -1) {
                  if (list1.state === this.STATE_PAUSED)
                      this._createPauseSpan(slaItem, end_time, timeSpans.get(i+1).getStart().getGlideDateTime(), prev,sla_created, false);
                  else if (list1.state === this.STATE_IN_PROGRESS)
                      prev = this._createTimeSpan(slaItem, end_time, timeSpans.get(i+1).getStart().getGlideDateTime(), prev,sla_created, false);
              }
          }
          if (timeSpans.size() > 0) {
              end_time = timeSpans.get(timeSpans.size()-1).getEnd().getGlideDateTime();
              if (end_time < new GlideDateTime(list2['time'])){
                  if (list1.state === this.STATE_PAUSED)
                      this._createPauseSpan(slaItem, end_time, new GlideDateTime(list2.time), prev,sla_created, false);
                  else if (list1.state === this.STATE_IN_PROGRESS)
                      prev = this._createTimeSpan(slaItem, end_time, new GlideDateTime(list2['time']), prev, sla_created, false);
              }
          }
          else if (timeSpans.size() === 0){
              if (list1.state === this.STATE_PAUSED)
                  this._createPauseSpan(slaItem, new GlideDateTime(list1.time), new GlideDateTime(list2.time), prev,sla_created, false);
              else if (list1.state === this.STATE_IN_PROGRESS)
                  prev = this._createTimeSpan(slaItem, new GlideDateTime(list1['time']), new GlideDateTime(list2['time']), prev, sla_created, false);
          }

      } else {//no need to split by schedule
          if (list1.state === this.STATE_PAUSED)
              this._createPauseSpan(slaItem, new GlideDateTime(list1.time), new GlideDateTime(list2.time), prev,sla_created, true);
          else if (list1.state === this.STATE_IN_PROGRESS)
              prev = this._seperateByBreach(slaItem, new GlideDateTime(list1['time']), new GlideDateTime(list2['time']), prev, sla_created);
      }
      return prev;
  },

  _seperateByBreach : function(slaItem, start_time, end_time, prev, sla_created) {
      var cur_bs = prev[0].add(GlideDateTime.subtract(start_time, end_time));
      var mid_breach = true;
      if (!this.slaGR.duration_type && (cur_bs.getValue() <= new GlideDateTime(this.slaGR.duration) ||prev[0].getValue() >= new GlideDateTime(this.slaGR.duration)))
          mid_breach = false;
      else if (this.slaGR.duration_type)
          mid_breach = !(start_time > this.rel_end_date || end_time < this.rel_end_date);

      if (!mid_breach)
          prev = this._createTimeSpan(slaItem, start_time, end_time, prev ,sla_created, true);
      //if breach in middle
      else {
          var mid_time;
          if (!this.slaGR.duration_type) {
              var duration = this.slaGR.duration.getGlideObject();
              mid_time =  new GlideDateTime(start_time);
              mid_time.add(duration.subtract(prev[0]));
          }else
              mid_time = this.rel_end_date;
          prev = this._createTimeSpan(slaItem, start_time, mid_time ,prev, sla_created, true);
          prev = this._createTimeSpan(slaItem, mid_time, end_time ,prev, sla_created, true);
      }
      return prev;
  },

  //for paused state
  _createPauseSpan : function(slaItem, start_time, end_time, prev,sla_created, in_business) {
      var auditSpan = slaItem.createTimelineSpan("task_sla", "");
      auditSpan.setTimeSpan(start_time.getNumericValue(), end_time.getNumericValue());
      if (in_business) {
          auditSpan.setSpanText(gs.getMessage("Stage: Paused"));
          auditSpan.setSpanColor("DimGray");
      }
      else
          auditSpan.setSpanColor("DarkGray");
  	var i18nStagePaused = gs.getMessage("Stage: Paused");
  	var i18nPauseFrom = gs.getMessage("Pause From: {0}", start_time.getDisplayValue());
  	var i18nPauseDuration = gs.getMessage("Pause Duration: {0}", GlideDateTime.subtract(start_time, end_time).getDisplayValue());
  	var i18nAET = gs.getMessage("Actual elapsed time:  + 0 ({0})", prev[1].getDisplayValue());
  	var i18nEBT = gs.getMessage("Elapsed business time:  + 0 ({0})", prev[0].getDisplayValue());

      var toolTip = i18nStagePaused + "</br>" + i18nPauseFrom + "</br>" + i18nPauseDuration
          + "</br>" + i18nAET + "</br>"
          + i18nEBT;

      if (end_time <= sla_created) {
          var i18nRBT = gs.getMessage("Retroactive business time:  + 0 ({0})", prev[0].getDisplayValue());
          toolTip += "</br>" + i18nRBT;
          }
      auditSpan.setTooltip(toolTip);
  },

  _createTimeSpan : function(slaItem, start_time, end_time, prev, sla_created, in_business) {
      var auditSpan = slaItem.createTimelineSpan("task_sla", "");
      auditSpan.setTimeSpan(start_time.getNumericValue(), end_time.getNumericValue());
      var total_spantime = GlideDateTime.subtract(start_time, end_time);
      prev[1] = prev[1].add(total_spantime);
      var spantime = new GlideDuration(0);
      var cur_bs = prev[0];
      var toolTip = gs.getMessage('Stage: In Progress');

      var has_breached = false;
      if (in_business) {
          spantime = total_spantime;
          cur_bs = prev[0].add(spantime);
      }
      if (!this.slaGR.duration_type)
          has_breached = (cur_bs.getValue() > new GlideDateTime(this.slaGR.duration));
      else
          has_breached = (end_time > this.rel_end_date);

      if (in_business) {
          //check breach
          if (has_breached) {
              auditSpan.setSpanColor("Red");
              auditSpan.setSpanText(gs.getMessage('Stage: In Progress (Breached)'));
          }
          else {
              auditSpan.setSpanColor("Green");
              auditSpan.setSpanText(gs.getMessage('Stage: In Progress'));
          }
      } else {
          var i18nOutOfSchedule = gs.getMessage(" (Out of schedule)");
          toolTip += i18nOutOfSchedule;
          if (has_breached)
              auditSpan.setSpanColor("IndianRed");
          else
              auditSpan.setSpanColor("LightGreen");
      }
  	var i18nAET = gs.getMessage("Actual elapsed time:  + {0} ({1})", [total_spantime.getDisplayValue(), prev[1].getDisplayValue()]);
  	var i18nEBT = gs.getMessage("Elapsed business time:  + {0} ({1})", [spantime.getDisplayValue(), cur_bs.getDisplayValue()]);
  	toolTip += "</br>" + i18nAET + "</br>" + i18nEBT;
      //check whether in retroactive timespan
  	if (start_time < sla_created) {
  		if (in_business && end_time > sla_created)
  			spantime = GlideDateTime.subtract(start_time, sla_created);
  		var retr_sum = prev[0].add(spantime);
  		var i18nRBT = gs.getMessage("Retroactive business time:  + {0} ({1})", [spantime.getDisplayValue(), retr_sum.getDisplayValue()]);
  		auditSpan.setInnerSegmentTimeSpan(start_time.getNumericValue(), sla_created.getNumericValue());
  		toolTip += "</br>" + i18nRBT;
      }
      auditSpan.setTooltip(toolTip);
      prev[0] = cur_bs;
      return prev;
  },

  _getScheduleTimeline : function(gr) {
      var auditItem = new GlideTimelineItem('cmn_schedule', '');
      auditItem.setLeftLabelText(this.scheduleGR.getName());

      var startTime = new GlideScheduleDateTime(gr.sys_created_on);
      var endTime = new GlideScheduleDateTime(gr.sys_updated_on);
      var timeSpans = this.scheduleGR.getSpans(startTime.getGlideDateTime(),endTime.getGlideDateTime());
      for (var i = 0; i < timeSpans.size();i++) {
          var auditSpan = auditItem.createTimelineSpan("schedule","");
          auditSpan.setTimeSpan(timeSpans.get(i).getStart().getMS(),timeSpans.get(i).getEnd().getMS());
          auditSpan.setIsBaseline(true);
      }
      this.add(auditItem);
  },

  _renderAudits : function() {
      var historyGR = this._buildHistory();
      var stage = 0;
      var sysUpdatedOn;
      var gr = new GlideRecord(this.taskGR.sys_class_name);
      var task_changes = "";
      var taskSLA;
      this.sla_conditions = {"start_condition" : false, "stop_condition" : false, "cancel_condition" : false, "reset_condition" : false, "pause_condition" : false, "resume_condition" : false, "service_commitment" : false};
      this.alreadyAttached = {};
      while (historyGR.next()) {
          if (parseInt(historyGR.getValue("update")) !== stage) {
              if (sysUpdatedOn && stage === 0)
                  gr.setValue('sys_created_on', sysUpdatedOn);
              gr.setValue('sys_updated_on', sysUpdatedOn);

              taskSLA = this._updateState(taskSLA, gr, false);
              this._getSpan(gr, task_changes, taskSLA);
              task_changes = "";
              stage = historyGR.getValue("update") - 0;
          }
          task_changes += this._generateTaskChange(historyGR);
          if (historyGR.getValue("new_value"))
              gr.setValue(historyGR.getValue("field"), historyGR.getValue("new_value"));
          else
              gr.setValue(historyGR.getValue("field"), historyGR.getValue("new"));
          sysUpdatedOn = historyGR.getValue('update_time');
      }
      if (stage === 0)
          gr.setValue('sys_created_on', sysUpdatedOn);
      gr.setValue('sys_updated_on', sysUpdatedOn);

      taskSLA = this._updateState(taskSLA, gr, false);
      this._getSpan(gr, task_changes, taskSLA);

      if (taskSLA && (taskSLA.getValue('stage') === this.STATE_PAUSED || taskSLA.getValue('stage') === this.STATE_IN_PROGRESS)) {
          gr.setValue('sys_updated_on',new GlideDateTime());
          this.task_slas[this.count].push({"state" : taskSLA.getValue('stage'), "time" : gr.getValue("sys_updated_on")});
      }
      return gr;
  },

  _updateState: function(taskSLA, gr, retroactive) {
      var slaTimelineAPI;
  	var start_time;
  	var created_stage;
      if (taskSLA) {
          var previousState = taskSLA.getValue('stage');
          slaTimelineAPI = new SLATimelineAPI(taskSLA,gr);
          slaTimelineAPI.checkExistingSLA(retroactive);
          taskSLA = slaTimelineAPI.getTaskSLA();
          if (taskSLA.getValue('stage') !== this.stage)
              this.task_slas[this.count].push({"state" : taskSLA.getValue('stage'), "time" : gr.getValue('sys_updated_on')});
          if (!retroactive && !slaTimelineAPI.getNewTaskSLA())
              this.stateChanges[this.count].push({"previousState" : previousState, "state" : taskSLA.getValue('stage'), stateChanges : slaTimelineAPI.getStateChanges()});
      } else {
          slaTimelineAPI = new SLATimelineAPI(this.slaGR, gr);
          taskSLA = slaTimelineAPI.getTaskSLA();
          if (taskSLA) {
              slaTimelineAPI.checkExistingSLA(true);
              taskSLA = slaTimelineAPI.getTaskSLA();
              this.taskSLAGR = taskSLA;
              this.count += 1;
              this.sla_cur = this.count;
              this.task_slas[this.count] = [];
              this.stateChanges[this.count] = [];
              this.task_slas[this.count].push(gr.getValue("sys_updated_on"));
              start_time = (this.slaGR.retroactive && gr.getValue(this.slaGR.set_start_to)) ? gr.getValue(this.slaGR.set_start_to) : gr.getValue("sys_updated_on");
              created_stage = this.slaGR.retroactive ? this.STATE_IN_PROGRESS : taskSLA.getValue('stage');
              this.task_slas[this.count].push({"state" : created_stage, "time" : start_time});
              this.stateChanges[this.count].push({"previousState" : "", "state" : taskSLA.getValue('stage'), stateChanges : slaTimelineAPI.getStateChanges()});
              if (!retroactive && this.slaGR.retroactive && this.slaGR.retroactive_pause)
                  this._updateHistory(gr,taskSLA);
  			if (taskSLA.getValue('stage') !== created_stage)
  				this.task_slas[this.count].push({"state" : taskSLA.getValue('stage') , "time" : gr.getValue("sys_updated_on")});
          }
      }

      if(slaTimelineAPI.getNewTaskSLA()){
          taskSLA = slaTimelineAPI.getNewTaskSLA();
          if (taskSLA) {
  			var currentStateChanges = slaTimelineAPI.getStateChanges();
  			currentStateChanges["attach"] = true;
              slaTimelineAPI = new SLATimelineAPI(taskSLA, gr);
              slaTimelineAPI.setStateChanges(currentStateChanges);
  			slaTimelineAPI.setProcessReset();
              slaTimelineAPI.checkExistingSLA(true);
              taskSLA = slaTimelineAPI.getTaskSLA();
              this.taskSLAGR = taskSLA;
              this.count += 1;
              this.sla_cur = this.count;
              this.task_slas[this.count] = [];
              this.stateChanges[this.count] = [];
              this.task_slas[this.count].push(gr.getValue("sys_updated_on"));
              start_time = (this.slaGR.retroactive && gr.getValue(this.slaGR.set_start_to)) ? gr.getValue(this.slaGR.set_start_to) : gr.getValue("sys_updated_on");
              created_stage = this.slaGR.retroactive ? this.STATE_IN_PROGRESS : taskSLA.getValue('stage');
              this.task_slas[this.count].push({"state" : created_stage, "time" : start_time});
              this.stateChanges[this.count].push({"previousState" : "", "state" : taskSLA.getValue('stage'), stateChanges : slaTimelineAPI.getStateChanges()});

              if (this.slaGR.retroactive && this.slaGR.retroactive_pause)
                  this._updateHistory(gr, taskSLA);

  			if (taskSLA.getValue('stage') !== created_stage)
  				this.task_slas[this.count].push({"state" : taskSLA.getValue('stage') , "time" : gr.getValue("sys_updated_on")});
          }
      }

      if (taskSLA) {
          if (taskSLA.getValue('stage') === this.STATE_CANCELLED || taskSLA.getValue('stage') === this.STATE_COMPLETED) {
              this.stage = '';
              taskSLA = null;
          } else
              this.stage = taskSLA.getValue('stage');
      }
      return taskSLA;
  },

  //revert history in case of retroactive sla
  _updateHistory : function(task, taskSLA){
  	// When calculating for retroactive pause we need the initial SLA stage to be "In progress" to make sure
  	// we get the correct initial stage on the SLA
  	this.stage = this.STATE_IN_PROGRESS;

  	var historyGR = this._buildHistory();
      var stage = 0;
      var sysUpdatedOn;
      var gr = new GlideRecord(this.taskGR.sys_class_name);
      var final_state = true;
      while (historyGR.next()) {
          if (parseInt(historyGR.getValue("update")) !== stage) {
              if (sysUpdatedOn && stage === 0)
                  gr.setValue('sys_created_on', sysUpdatedOn);
              gr.setValue('sys_updated_on', sysUpdatedOn);
              taskSLA = this._updateState(taskSLA,gr, true);
              stage = historyGR.getValue("update") - 0;
          }
          if (historyGR.getValue('update_time') > task.getValue('sys_updated_on')){
              final_state = false;
              break;
          }
          if (historyGR.getValue("new_value"))
              gr.setValue(historyGR.getValue("field"), historyGR.getValue("new_value"));
          else
              gr.setValue(historyGR.getValue("field"), historyGR.getValue("new"));
          sysUpdatedOn = historyGR.getValue('update_time');
      }
      if (final_state){
          if (sysUpdatedOn && stage === 0)
              gr.setValue('sys_created_on', sysUpdatedOn);
          gr.setValue('sys_updated_on', sysUpdatedOn);
          taskSLA = this._updateState(taskSLA,gr, true);
      }
  },

  //generate audit history changes
  _generateTaskChange : function(historyGR) {
      if (!this.taskGR[historyGR.getValue("field")].canRead())
          return "";

      var tc = "<strong>" + historyGR.getValue("label") + "</strong>: " + historyGR.getDisplayValue("new") + '<br />';
      return tc;
  },

  //get timeline span content
  _getSpan : function(gr, task_changes) {
      var slaTimelineAPI = new SLATimelineAPI();
      var aConditionChanged = false;
      var sla_conditions = this.sla_conditions;
      var toolTip_conditions = {};

      for (var conditionField in this.SLA_CONDITION_FIELDS) {
          if (this.ignoreSLAConditions[conditionField])
              continue;

          // Service commitment is a special case
          if (conditionField === 'service_commitment') {
              var soc = slaTimelineAPI.getServOfStatus(this.slaGR, gr);
              if (soc)
                  toolTip_conditions[conditionField] = this.CONDITION_MATCH_IMAGE + this.SLA_CONDITION_FIELDS[conditionField];
              else
                  toolTip_conditions[conditionField] = this.CONDITION_NO_MATCH_IMAGE + this.SLA_CONDITION_FIELDS[conditionField];

              if (soc !== this.sla_conditions[conditionField]) {
                  aConditionChanged = true;
                  this.sla_conditions[conditionField] = sla_conditions[conditionField];
              }

              continue;
          }

          if (this.emptySLAConditions[conditionField]) {
              sla_conditions[conditionField] = false;
              toolTip_conditions[conditionField] = this.CONDITION_IGNORE_IMAGE + this.SLA_CONDITION_FIELDS[conditionField];
              continue;
          }

  		var condition = this.slaGR.getValue(conditionField);
          if (!condition)
              sla_conditions[conditionField] = false;
          else
              sla_conditions[conditionField] = GlideFilter.checkRecord(gr, condition);

          if (sla_conditions[conditionField] !== this.sla_conditions[conditionField]) {
              aConditionChanged = true;
              this.sla_conditions[conditionField] = sla_conditions[conditionField];
          }

          if (sla_conditions[conditionField])
              toolTip_conditions[conditionField] = this.CONDITION_MATCH_IMAGE + this.SLA_CONDITION_FIELDS[conditionField];
          else
              toolTip_conditions[conditionField] = this.CONDITION_NO_MATCH_IMAGE + this.SLA_CONDITION_FIELDS[conditionField];             
      }

      var auditSpan = this.auditItem.createTimelineSpan("task", "");
      var timespan = gr.sys_updated_on.getGlideObject().getNumericValue();
      auditSpan.setTimeSpan(timespan, timespan);

      var toolTip;

      // Get the current task_sla, it's state and whether it is active
      var stateChanges;
      var latestStateChanges;

      if (this.stateChanges && this.count) {
          stateChanges = this.stateChanges[this.count];
          latestStateChanges = stateChanges[stateChanges.length - 1].stateChanges;
      }

  	if ((latestStateChanges && !latestStateChanges.processed) || aConditionChanged) {
  		var i18nMsg = gs.getMessage('SLA Definition conditions:');
  		toolTip = i18nMsg + "</br>";

  		for (var i = 0; i < this.SLA_CONDITION_FIELD_ORDER.length; i++) {
  			var conditionFieldName = this.SLA_CONDITION_FIELD_ORDER[i];
  			if (toolTip_conditions[conditionFieldName])
  				toolTip += toolTip_conditions[conditionFieldName] + " ";
  		}
  		toolTip += "</br></br>";

  		var slac = slaTimelineAPI.newSLACondition(this.slaGR, gr);

  		// Walk through the same condition checks that TaskSLAController would do which
  		// may mean we don't test one or more functions e.g. if we match complete we'll never test cancel or pause/resume
  		//
  		// Initialize all condition functions as ignored
  		var i18nSLACondition = gs.getMessage('SLA condition rule ({0})', slac.type);
  		toolTip += i18nSLACondition + ':</br>';

  		// Task SLA isn't completed, reset or cancelled so we can test resume/pause
  		for (var j = 0; j < this.SLA_CONDITION_FUNCTIONS_ORDER.length; j++) {
  			var conditionFunction = this.SLA_CONDITION_FUNCTIONS_ORDER[j];
  			if (latestStateChanges && typeof latestStateChanges[conditionFunction] !== "undefined") {
  				if (latestStateChanges[conditionFunction])
  					toolTip += this.CONDITION_MATCH_IMAGE + this.SLA_CONDITION_FUNCTIONS[conditionFunction] + '  ';
  				else
  					toolTip += this.CONDITION_NO_MATCH_IMAGE + this.SLA_CONDITION_FUNCTIONS[conditionFunction] + '  ';
  			} else
  				toolTip += this.CONDITION_IGNORE_IMAGE + this.SLA_CONDITION_FUNCTIONS[conditionFunction] + '  ';
  	    }

  		toolTip += "</br></br>";

  		latestStateChanges.processed = true;
  	} else
  		toolTip = gs.getMessage("No SLA stage change</br>");

      //check whether there is task_sla record
      if (this.sla_cur) {
          var tsl = new GlideRecord("task_sla");
          tsl.addQuery("task", this.taskGR.sys_id);
          tsl.addQuery("sla", this.slaGR.sys_id);
          tsl.addQuery("sys_created_on", ">=", gr.sys_updated_on);
          tsl.addQuery("end_time", ">=", gr.sys_updated_on).addOrCondition("end_time","") ;
          tsl.query();
          if (!tsl.next()) {
              var i18nTaskSLASim = gs.getMessage("Task SLA for simulation.");
              toolTip += i18nTaskSLASim + "</br>";
              this.items[this.count] = 'simulation';
          } else
              this.items[this.count] = tsl.getUniqueValue();
          var i18nTaskSLA = gs.getMessage("Task SLA {0}", this.sla_cur);
          var task_sla = gs.getMessage("{0} activated.", i18nTaskSLA);
          if(this.slaGR.retroactive) {
              var i18nMsg1 = gs.getMessage(" However, it is setup to be retroactive from {0}.", this.taskGR[this.slaGR.getValue("set_start_to")].getLabel());
              task_sla += i18nMsg1;
          }
          auditSpan.setTooltip(toolTip + task_changes);
          auditSpan.setSpanText(task_sla);
          this.sla_cur = null;
      } else
          auditSpan.setTooltip(toolTip + task_changes);
  },

  _buildHistory : function() {
      var hs = new GlideHistorySet(this.taskGR);
      var hs_sys_id = hs.generate();
      var historyGR = new GlideRecord('sys_history_line');
      historyGR.addQuery('set', hs_sys_id);
      historyGR.addQuery('type','audit').addOrCondition('type','');
      historyGR.orderBy('update');
      historyGR.query();
      return historyGR;
  },

  /**
  * Prevent public access to this processor
  */
  isPublic: function() {
  	return false;
  },

  type: 'SLATimeline'
});

Sys ID

5e5b03ee930002002c68530b547ffb4c

Offical Documentation

Official Docs: