Name

global.TimelineGeneratorSchedulePage

Description

Schedule page script include for the Timeline Generator schedule page. See the AbstractTimelineSchedulePage script include description for correct implementation and API usage.

Script

// Class Imports
var TimelineItem = GlideTimelineItem;

ERROR_TITLE = gs.getMessage('ERROR');
ERROR_NO_PAGE_ID = gs.getMessage('One of the required fields was not specified. Make sure "sysparm_timeline_page_id" is specified.');
ERROR_INTERACTIVE = gs.getMessage('One of the required parameters was not properly specified');
ERROR_SECURITY = gs.getMessage('Modification of this record is not allowed');
ERROR_INVALID_PAGE_ID = gs.getMessage('Unable to find a timeline page with the specified ID');
ERROR_INVALID_SUB_ID = gs.getMessage('Unable to find a timeline page sub item with the specified ID');

var SuccessorRange = Class.create();
SuccessorRange.prototype = {
  initialize: function() {
      this.min = -1;
      this.max = -1;
      this.leftIDs = "";
      this.rightIDs = "";
  },

  setMin: function(timeMs, id) {
      if (this.min == -1 || timeMs <= this.min) {
          if (timeMs != this.min)
              this.leftIDs = "";
          this.min = timeMs;
          this.leftIDs = this.leftIDs + "," + id;
      }
  },

  setMax: function(timeMs, id) {
      if (this.max == -1 || timeMs >= this.max) {
          if (timeMs != this.max)
              this.rightIDs = "";
          this.max = timeMs;
          this.rightIDs = this.rightIDs + "," + id;
      }
  },

  merge: function(other) {
      if (other.min < this.min) {
          this.min = other.min;
          this.leftIDs = other.leftIDs;
      } else if (other.min == this.min) {
          this.leftIDs = this.leftIDs + "," + other.leftIDs;
      }
      if (other.max > this.max) {
          this.max = other.max;
          this.rightIDs = other.rightIDs;
      } else if (other.max == this.max) {
          this.rightIDs = this.rightIDs + "," + other.rightIDs;
      }
  }
};

var TimelineGeneratorSchedulePage = Class.create();
TimelineGeneratorSchedulePage.prototype = Object.extendsObject(AbstractTimelineSchedulePage, {

  // ////////////////////////////////////////////////////////////////////////////////////////////////
  // GET_ITEMS
  // ////////////////////////////////////////////////////////////////////////////////////////////////
  ERROR_CODE_NO_ID: 1,
  ERROR_CODE_INVALID_ID: 2,
  ERROR_SUB_INVALID_ID: 3,

  _getPageRecord: function() {
      var timelinePageId = this.getParameter("sysparm_timeline_page_id");
      var timelineOldPageName = this.getParameter("sysparm_timeline");
      if (timelinePageId == null && timelineOldPageName == null)
          return this.ERROR_CODE_NO_ID;
      var pageRecord = new GlideRecord('cmn_timeline_page');
      if (timelinePageId != null)
          pageRecord.addQuery('sys_id', timelinePageId);
      else
          pageRecord.addQuery('name', timelineOldPageName);
      pageRecord.query();
      if (!pageRecord.next())
          return this.ERROR_CODE_INVALID_ID;
      else
          return pageRecord;
  },

  _setStatusError: function(code) {
      switch (code) {
          case this.ERROR_CODE_NO_ID:
              return this.setStatusError(ERROR_TITLE, ERROR_NO_PAGE_ID);
          case this.ERROR_CODE_INVALID_ID:
              return this.setStatusError(ERROR_TITLE, ERROR_INVALID_PAGE_ID);
          case this.ERROR_SUB_INVALID_ID:
              return this.setStatusError(ERROR_TITLE, ERROR_INVALID_SUB_ID);
      }
  },

  getItems: function() {
      var result = this._getPageRecord();
      if (typeof result == "number")
          return this._setStatusError(result);

      // Set page title
      if (result.name != '')
          this.setPageTitle(result.getDisplayValue("name"));

      //Create range calculator if specified
      var calculator;
      if (result.range_calculator && result.range_calculator.name)
          eval("calculator = new " + result.range_calculator.name + "()");

      this.populateItems(result, null, -1, -1, null, calculator);
  },

  populateItems: function(result, parentID, parentMinMS, parentMaxMS, successorRange, calculator) {
      if (calculator)
          calculator.logMessage(parentID);

      var restricted = false;

      // Obtain the list of span styles associated with this timeline page.
      var styles = new GlideRecord("cmn_timeline_page_style");
      styles.addQuery("timeline_page", result.sys_id);
      styles.orderBy("order");
      styles.query();

      // Ok now lets create the spans
      var gr = new GlideRecordSecure(result.table);
      if (parentID)
          gr.addQuery(result.parent_col, parentID);

      // Do we have ordering query information?
      if (result.sort_by != null && result.sort_by != '' && result.sort_by_order == 'DESC')
          gr.orderByDesc(result.sort_by);
      else if (result.sort_by != null && result.sort_by != '')
          gr.orderBy(result.sort_by);
      else
          gr.orderBy(result.start_date_field);

      // Is there a query condition?
      if (result.condition != null)
          gr.addEncodedQuery(result.condition);

      gr.setQueryReferences(true);
      gr.query();

      while (gr.next()) {
          // check user has access to record date fields to avoid NPE later on
          if (JSUtil.nil(gr.getValue(result.start_date_field)) || JSUtil.nil(gr.getValue(result.end_date_field)))
              continue;

          var recordTable = result.table;
          var recordID = gr.sys_id;

          var item = new TimelineItem(recordTable, recordID);
          if (parentID)
              item.setParent(parentID);

          var span = item.createTimelineSpan(recordTable, recordID);
          if (parentID)
              span.addPredecessor(parentID);

          // ----- SPAN START/END TIME -----
          var sdo = gr[result.start_date_field].getGlideObject();
          var edo = gr[result.end_date_field].getGlideObject();

          // Ensure we have a start date; otherwise, do not display.
          if (!sdo.hasDate())
              continue;

          // If an item has no end date, we will set it to the start date for
          // all timeline pages
          // as it is common for tasks, incidents, changes, etc. to have only
          // a start date. These
          // will show up as points on the timeline.
          if (!edo.hasDate())
              edo = sdo;

          span.setTimeSpan(sdo, edo);

          if (calculator) {
              var maxResult = calculator.getMaxRangeDetails(recordID, result.table);
              span.setTimeSpanMaxRange(maxResult[0], maxResult[1]);
              span.setMaxRestrictorIDs(maxResult[2]);
          } else {
              if (successorRange && (result.restriction == "restrict_parent" || result.restriction == "update_parent")) {
                  successorRange.setMin(span.getStartTimeMs(), recordID);
                  successorRange.setMax(span.getEndTimeMs(), recordID);
              }
              if (result.restriction == "restrict_parent") {
                  span.setTimeSpanMaxRange(parentMinMS, parentMaxMS);
                  span.setMaxRestrictorIDs(parentID);
              }
          }

          // ----- SPAN COLOR -----
          if (result.css_span_color != '')
              span.setSpanColor(result.css_span_color);

          // ----- SPAN TEXT -----
          var strText = '';
          var cols = result.labels.split(',');
          var displayValue = '';
          var val = '';
          for (var i = 0, l = cols.length; i < l; i++) {
              val = cols[i];
              if (JSUtil.nil(val))
                  continue;

              displayValue = gr.getDisplayValue(cols[i]);
              if (JSUtil.nil(displayValue))
                  continue;

              strText += (i != 0 ? ', ' : '') + displayValue;
          }

          item.setLeftLabelText(strText);
          span.setSpanText(strText);

          // ----- SPAN TOOLTIP -----
          if (result.show_tooltips == true) {
              strText = '';
              cols = result.tooltip_label.split(',');
              for (var j = 0, k = cols.length; j < k; j++) {
                  val = cols[j];
                  if (JSUtil.nil(val))
                      continue;

                  displayValue = gr.getDisplayValue(cols[j]);
                  if (JSUtil.nil(displayValue))
                      continue;

                  var label = gr.getElement(cols[j]).getLabel();
                  strText += '<strong>' + SNC.GlideHTMLSanitizer.sanitize(label) + '</strong>: ' + SNC.GlideHTMLSanitizer.sanitize(displayValue) + '<br />';
              }
              span.setTooltip(strText);
          }

          // ----- INTERACTIVE OPTIONS -----
          if (calculator) {
              span.setRestriction("calculator");
          } else if (result.restriction) {
              if (result.restriction != "none")
                  restricted = true;
              span.setRestriction(result.restriction);
          }
          span.setChildRestricted(false);

          if (result.allow_dragging == true)
              span.setAllowXMove(true);
          if (result.allow_drag_left == true)
              span.setAllowXDragLeft(true);
          if (result.allow_drag_right == true)
              span.setAllowXDragRight(true);

          // ----- OVERRIDE WITH SPAN STYLES -----
          var match = false;
          styles.restoreLocation();
          while (styles.next() && !match) {
              var conditions = styles.getValue("condition");
              if (!conditions || GlideFilter.checkRecord(gr, conditions))
                  match = true;

              if (match) {
                  var spanColorElement = styles.getElement("span_color");
                  if (!spanColorElement.nil())
                      span.setSpanColor(spanColorElement.getDisplayValue());

                  span.setLabelDecoration(styles.label_decoration);
                  var labelColorRecord = styles.label_color.getRefRecord();
                  if (!labelColorRecord.nil())
                      span.setLabelColor(labelColorRecord.color);
              }
          }

          var localSuccessorRange;
          if (!calculator)
              localSuccessorRange = new SuccessorRange();
          var sub = new GlideRecord("cmn_timeline_sub_item");
          sub.addQuery("parent", result.sys_id);
          sub.query();
          while (sub.next()) {
              if (this.populateItems(sub, recordID, span.getStartTimeMs(),
                      span.getEndTimeMs(), localSuccessorRange, calculator)) {
                  if (!calculator) {
                      span.setChildRestricted(true);
                      if (successorRange)
                          successorRange.merge(localSuccessorRange);
                  }
              }
          }
          if (calculator) {
              var minResult = calculator.getMinRangeDetails(recordID, result.table);
              span.setTimeSpanMinRange(minResult[0], minResult[1]);
              span.setMinRestrictorLeftIDs(minResult[2]);
              span.setMinRestrictorRightIDs(minResult[3]);
          } else if (span.getChildRestricted()) {
              span.setTimeSpanMinRange(localSuccessorRange.min, localSuccessorRange.max);
              span.setMinRestrictorLeftIDs(localSuccessorRange.leftIDs);
              span.setMinRestrictorRightIDs(localSuccessorRange.rightIDs);
          }
          this.add(item);

      }

      return restricted;
  },

  _searchForPageRecord: function(parentID) {
      var result = '';
      if (!parentID) {
          result = this._getPageRecord();
          if (typeof result == "number")
              return this._setStatusError(result);
          if (result.table == this.getParameter("sysparm_table_name"))
              return result;
          result = this._searchForPageRecord(result.sys_id);
          if (!result)
              return this._setStatusError(this.ERROR_SUB_INVALID_ID);
          else
              return result;
      } else {
          var subRecord = new GlideRecord('cmn_timeline_sub_item');
          subRecord.addQuery('parent', parentID);
          subRecord.query();
          while (subRecord.next()) {
              if (subRecord.table == this.getParameter("sysparm_table_name"))
                  return subRecord;
              result = this._searchForPageRecord(subRecord.sys_id);
              if (result)
                  return result;
          }
          return null;
      }
  },

  findTopTimelineRecord: function(timelineRecord) {
      if (!timelineRecord.parent)
          return timelineRecord;

      var result = new GlideRecord('cmn_timeline_sub_item');
      result.addQuery('sys_id', timelineRecord.parent);
      result.query();
      if (!result.next()) {
          result = new GlideRecord('cmn_timeline_page');
          result.addQuery('sys_id', timelineRecord.parent);
          result.query();
          result.next();
          return result;
      }
      return this.findTopTimelineRecord(result);
  },

  updateParentRecord: function(startDate, endDate, sysID, timelineRecord, recordID) {
      var topTimelineRecord = this.findTopTimelineRecord(timelineRecord);
      if (topTimelineRecord.range_calculator && topTimelineRecord.range_calculator.name) {
          var calculator;
          eval("calculator = new " + topTimelineRecord.range_calculator.name + "()");
          calculator.updateParents(recordID, timelineRecord.table, startDate, endDate);
      } else if (timelineRecord.parent && timelineRecord.restriction && timelineRecord.restriction == "update_parent") {
          var result = new GlideRecord('cmn_timeline_sub_item');
          result.addQuery('sys_id', timelineRecord.parent);
          result.query();
          var found = result.next();
          if (!found) {
              result = new GlideRecord('cmn_timeline_page');
              result.addQuery('sys_id', timelineRecord.parent);
              result.query();
              found = result.next();
          }
          if (found) {
              var gr = new GlideRecord(result.table);
              gr.addQuery('sys_id', sysID);
              gr.query();
              if (gr.next()) {
                  var sd = parseInt(gr[result.start_date_field].getGlideObject().getNumericValue(), 10);
                  var ed = parseInt(gr[result.end_date_field].getGlideObject().getNumericValue(), 10);
                  var changed = false;
                  if (startDate && startDate < sd) {
                      changed = true;
                      var gdtStart = new GlideDateTime();
                      gdtStart.setNumericValue(startDate);
                      gr.setValue(result.start_date_field, gdtStart);
                  }
                  if (endDate && endDate > ed) {
                      changed = true;
                      var gdtEnd = new GlideDateTime();
                      gdtEnd.setNumericValue(endDate);
                      gr.setValue(result.end_date_field, gdtEnd);
                  }
                  if (changed) {
                      gr.update();
                      if (result.parent_col && result.restriction != "none")
                          this.updateParentRecord(startDate, endDate, gr[result.parent_col], result);
                  }
              }
          }
      }
  },

  // ////////////////////////////////////////////////////////////////////////////////////////////////
  // ELEMENT_MOVE_X
  // ////////////////////////////////////////////////////////////////////////////////////////////////

  elementMoveX: function(spanId, newStartDateTimeMs) {
      if (!spanId || spanId == null || spanId == '')
          return this.setStatusError(ERROR_TITLE, ERROR_INTERACTIVE);
      var result = this._searchForPageRecord();
      if (typeof result == "number")
          return this._setStatusError(result);

      var gr = new GlideRecordSecure(result.table);
      gr.addQuery('sys_id', spanId);
      gr.query();
      if (gr.next()) {
          // Calculate duration
          var sd = parseInt(gr[result.start_date_field].getGlideObject().getNumericValue(), 10);
          var ed = parseInt(gr[result.end_date_field].getGlideObject().getNumericValue(), 10);
          var newEndDateTimeMs = parseInt(newStartDateTimeMs) + (ed - sd);
          var gdtStart = new GlideDateTime();
          gdtStart.setNumericValue(newStartDateTimeMs);
          var gdtEnd = new GlideDateTime();
          gdtEnd.setNumericValue(newEndDateTimeMs);
          if (this._auditDisabled())
              gr.setWorkflow(false);
          gr.setValue(result.start_date_field, gdtStart);
          gr.setValue(result.end_date_field, gdtEnd);
          if (!gr.update())
              return this.setStatusError(ERROR_TITLE, ERROR_SECURITY);
          this.updateParentRecord(newStartDateTimeMs, newEndDateTimeMs, gr[result.parent_col], result, spanId);
          return this.setDoReRenderTimeline(true);
      }
      return this.setStatusError(ERROR_TITLE, ERROR_INTERACTIVE);
  },

  findRecord: function(spanId) {

  },

  // ////////////////////////////////////////////////////////////////////////////////////////////////
  // ELEMENT_TIME_ADJUST START
  // ////////////////////////////////////////////////////////////////////////////////////////////////

  elementTimeAdjustStart: function(spanId, newStartDateTimeMs) {
      if (!spanId || spanId == null || spanId == '')
          return this.setStatusError(ERROR_TITLE, ERROR_INTERACTIVE);

      var result = this._searchForPageRecord();
      if (typeof result == "number")
          return this._setStatusError(result);

      var gr = new GlideRecordSecure(result.table);
      gr.addQuery('sys_id', spanId);
      gr.query();
      if (gr.next()) {
          var gdtStart = new GlideDateTime();
          gdtStart.setNumericValue(newStartDateTimeMs);
          if (this._auditDisabled())
              gr.setWorkflow(false);
          gr.setValue(result.start_date_field, gdtStart);
          if (!gr.update())
              return this.setStatusError(ERROR_TITLE, ERROR_SECURITY);
          this.updateParentRecord(newStartDateTimeMs, null, gr[result.parent_col], result, spanId);
          return this.setDoReRenderTimeline(true);
      }

      return this.setStatusError(ERROR_TITLE, ERROR_INTERACTIVE);
  },

  // ////////////////////////////////////////////////////////////////////////////////////////////////
  // ELEMENT_TIME_ADJUST END
  // ////////////////////////////////////////////////////////////////////////////////////////////////

  elementTimeAdjustEnd: function(spanId, newEndDateTimeMs) {
      if (!spanId || spanId == null || spanId == '')
          return this.setStatusError(ERROR_TITLE, ERROR_INTERACTIVE);

      var result = this._searchForPageRecord(null);
      if (typeof result == "number")
          return this._setStatusError(result);

      var gr = new GlideRecordSecure(result.table);
      gr.addQuery('sys_id', spanId);
      gr.query();
      if (gr.next()) {
          var gdtEnd = new GlideDateTime();
          gdtEnd.setNumericValue(newEndDateTimeMs);
          if (this._auditDisabled())
              gr.setWorkflow(false);
          gr.setValue(result.end_date_field, gdtEnd);
          if (!gr.update())
              return this.setStatusError(ERROR_TITLE, ERROR_SECURITY);
          this.updateParentRecord(null, newEndDateTimeMs, gr[result.parent_col], result, spanId);
          return this.setDoReRenderTimeline(true);
      }
      return this.setStatusError(ERROR_TITLE, ERROR_INTERACTIVE);
  },

  _auditDisabled: function() {
      // If property exists and its value is true, do not setWorkflow to false when updating a record.
      return !gs.getProperty('timeline.enable.audit', false);
  }
});

Sys ID

10a70a220a0a0b7707666ebc6055267c

Offical Documentation

Official Docs: