Name

global.DiscoveryStatusTimeline

Description

Schedule page script include for generating a view that shows the discovery status on the Timeline. See the TimelineAbstractSchedulePage script include description for correct implementation and API usage.

Script

// Discovery

/**
* This script include parses discovery_status and displays all the discovery runs in a timeline fashion.
*
*/

// Class Imports
var TimelineItem = GlideTimelineItem;
gs.include("j2js");

var DiscoveryStatusTimeline = Class.create();
DiscoveryStatusTimeline.prototype = Object.extendsObject(AbstractTimelineSchedulePage, {
  // Constants
  DISCOVERY_SCHEDULE : 'discovery_schedule',
  DISCOVERY_STATUS : 'discovery_status',
  SYS_TRIGGER : 'sys_trigger',
  WEEK : 7, // 1 week = 7 days
  DAY : 86400000, // 1 Day = 86400000 ms

  spanCount: 0,
  
  //////////////////////////////////////////////////////////////////////////////////////////////////
  // GET_ITEMS       																			 //
  //////////////////////////////////////////////////////////////////////////////////////////////////
  
  getItems: function() {
  	this.max = new GlideDateTime();
  	this.max.addMonthsUTC(1); // Limit on the number of months to display
  	this.currentDT = new GlideDateTime();
  	
  	// Get the active discovery schedules to span on the timeline
  	var gr = new GlideRecord(this.DISCOVERY_SCHEDULE);
  	gr.addActiveQuery();
  	gr.addQuery('disco_run_type', '!=', 'after_discovery');
  	gr.query();
  	if (gr.hasNext())
  		this.insertItems(gr, 0, null);
  },
  
  // Add item and spans to the timeline
  insertItems: function(gr, maxRunTime, precedingItem) {
  	while(gr.next()) {
  		var item = new TimelineItem(this.DISCOVERY_SCHEDULE, gr.sys_id);
  		var spanProperties = this.setSpanProperties(gr, item);
  		this.insertSpans(gr, spanProperties, maxRunTime, precedingItem);
  	}
  },
  
  // Setting the span properties
  setSpanProperties: function(gr, item) {
  	var span = {};
  	span.item = item;
  	span.sys_id = gr.sys_id;
  	span.text = gr.name;
  	span.tooltip = this.getTooltip(gr.sys_id);
  	span.duration = this.getPreviousDuration(gr.sys_id);
  	span.maxRunTime = gr.max_run.getGlideObject().getNumericValue();
  	
  	return span;
  },
  	
  // Calculate the spans for each type of the discovery schedule
  insertSpans: function(gr, span, precedingMaxRunTime, precedingItem) {
  	if (JSUtil.notNil(this.getStartDateTime(gr)))
  		span.dt = new GlideDateTime(this.getStartDateTime(gr));
  	else {
  			gs.warn('Upcoming Discovery Schedules - The corresponding trigger job for schedule ' + gr.name + ' was not found in sys_trigger');
  			return;
  	}
  	
  	// Take the appropriate action based on the type of the discovery schedule
  	switch (gr.disco_run_type + '') {
  		case 'daily':
  			this.spanDailySchedule(span);
  			break;
  		
  		case 'weekly':
  			this.spanWeeklySchedule(span);
  			break;
  		
  		case 'monthly':
  			this.spanMonthlySchedule(span);
  			break;
  		
  		case 'periodically':
  			this.spanPeriodicSchedule(span, gr.run_period.getGlideObject().getNumericValue());
  			break;
  		
  		case 'once':
  			this.spanOnceSchedule(span);
  			break;
  		
  		case 'weekdays':
  			this.spanWeekdaysSchedule(span);
  			break;
  		
  		case 'weekends':
  			this.spanWeekendsSchedule(span);
  			break;
  		
  		case 'month_last_day':
  			this.spanMonthLastDaySchedule(span);
  			break;
  		
  		case 'calendar_quarter_end':
  			this.spanCalenderQuarterEndSchedule(span);
  			break;
  			
  		case 'after_discovery':
  			this.spanAfterSchedule(gr, span, precedingMaxRunTime, precedingItem);
  			break;
  			
  		default:
  			break;
  	}
  	
  	this.add(span.item);
  	this.spanFollowerSchedules(gr, span.item);
  },
  
  // Gets the next start time from sys_trigger table
  getStartDateTime: function(g) {
  	var gr = new GlideRecord(this.SYS_TRIGGER);
  	gr.addQuery('document', this.DISCOVERY_SCHEDULE);
  	gr.addQuery('document_key', g.sys_id);
  	gr.query();
  	
  	if (gr.next())
  		return gr.next_action;
  },
  
  // Check for the followers schedule
  spanFollowerSchedules: function(preceding, previousItem) {
  	var followers = this.getFollowers(preceding.sys_id);
  	if (followers.hasNext())
  		this.insertItems(followers, preceding.max_run.getGlideObject().getNumericValue(), previousItem);
  },
  
  // Create an item and span the schedule
  spanAfterSchedule: function(follower, spanProperties, precedingMaxRunTime, previousItem) {
  	var jsArray = j2js(previousItem.getTimelineSpans());
  	for (var i = 0; i < jsArray.length; i++)
  		if (precedingMaxRunTime)
  			this.addSpan(spanProperties, jsArray[i].getInnerSegmentEndTimeMs());
  		else
  			this.addSpan(spanProperties, jsArray[i].getEndTimeMs());
  },

  // Get all the followers of a schedule if there are any
  getFollowers: function(schedule) {
  	var gr = new GlideRecord(this.DISCOVERY_SCHEDULE);
  	gr.addActiveQuery();
  	gr.addQuery('run_after', schedule);
  	gr.query();
  	
  	return gr;
  },	
  
  // Get the duration of the previous successful discovery.
  getPreviousDuration: function(schedule) {
  	var status = new GlideRecord(this.DISCOVERY_STATUS);
  	status.addQuery('dscheduler', schedule);
  	status.addQuery('state', 'completed');
  	status.addQuery('description', '!=', 'Discover CI');
  	status.orderByDesc('sys_created_on');
  	status.setLimit(1);
  	status.query();
  	if (status.next())
  		return status.duration.getGlideObject();
  	else
  		return -1;
  },
  
  getTooltip: function(sys_id) {
  	var tooltip = "";
  	var gr = new GlideRecord(this.DISCOVERY_SCHEDULE);
  	if (sys_id)
  		gr.get('sys_id', sys_id);
  	var behName = gr.behavior.getDisplayValue();
  	var midName = gr.mid_server.getDisplayValue();
  	var duration = this.getPreviousDuration(sys_id);
  	var projectedDuration =  duration != -1 ? duration.getDisplayValue() : 'No Estimation';
  		
  	// if Behavior and MID name are both nil, just omit
  	if (JSUtil.notNil(behName))
  		tooltip += "<strong>" + "Behavior: " + "</strong>" + behName + "<br/>";
  	else if (JSUtil.notNil(midName))
  		tooltip += "<strong>" + "MID Server: " + "</strong>" + midName + "<br/>";
  	tooltip += "<strong>" + "Projected Duration: " + "</strong>" + projectedDuration + "<br/>";
  	
  	return tooltip;
  },
  
  addSpan: function(parm, startTime) {
  	this.spanCount++;
  	// Add unique ID to each span. The double click event on the span for the Discovery Schedule
  	// it references will still work since 'boolIsWorkManagementPage' is 'true'. This causes
  	// the click handler to split on '-' and obtain the valid sys_id for lookup. See TimelineUtils.js
  	var spanId = parm.sys_id + "-" + this.spanCount;
  	var span = parm.item.createTimelineSpan(this.DISCOVERY_SCHEDULE, spanId, false);
  	// If the discovery schedule has the max run time, display/span in inner segment fashion
  	var duration = parm.duration != -1 ? parm.duration.getNumericValue() : 0;
  	if (parm.maxRunTime) {
  		span.setTimeSpan(startTime, startTime + parm.maxRunTime);
  		span.setInnerSegmentTimeSpan(startTime, startTime + duration);
  		span.setInnerSegmentClass('darkcyan');
  		span.setSpanColor('black');
  	}
  	else {
  		span.setTimeSpan(startTime, startTime + duration);
  		span.setSpanColor('darkcyan');
  	}
  	span.setSpanText(parm.text);
  	span.setTooltip(parm.tooltip);
  	
  	return span;
  },
  
  spanDailySchedule: function(span) {
  	var gdt = span.dt;
  	while (gdt.getNumericValue() < this.max.getNumericValue()) {
  		this.addSpan(span, gdt.getNumericValue());
  		gdt.addDaysUTC(1);
  	}
  },
  
  spanWeeklySchedule: function(span) {
  	var gdt = span.dt;
  	while (gdt.getNumericValue() < this.max.getNumericValue()) {
  		this.addSpan(span, gdt.getNumericValue());
  		gdt.addWeeksUTC(1);
  	}
  },
  
  spanMonthlySchedule: function(span) {
  	var gdt = span.dt;
  	while (gdt.getNumericValue() < this.max.getNumericValue()) {
  		this.addSpan(span, gdt.getNumericValue());
  		gdt.addMonthsUTC(1);
  	}
  },
  
  spanPeriodicSchedule: function(span, period) {
  	var gdt = span.dt;
  	while (gdt.getNumericValue() < this.max.getNumericValue()) {
  		this.addSpan(span, gdt.getNumericValue());
  		gdt.add(period);
  	}
  },
  
  spanOnceSchedule: function(span) {
  	var gdt = span.dt;
  	if (gdt.getNumericValue() < this.max.getNumericValue())
  		this.addSpan(span, gdt.getNumericValue());
  },
  
  spanWeekdaysSchedule: function(span) {
  	var gdt = span.dt;
  	while (gdt.getNumericValue() < this.max.getNumericValue()) {
  		if (gdt.getDayOfWeekUTC() != 6 && gdt.getDayOfWeekUTC() != 7)
  			this.addSpan(span, gdt.getNumericValue());
  		gdt.addDaysUTC(1);
  	}
  },
  
  spanWeekendsSchedule: function(span) {
  	var gdt = span.dt;
  	while (gdt.getNumericValue() < this.max.getNumericValue()) {
  		if (gdt.getDayOfWeekUTC() == 6 || gdt.getDayOfWeekUTC() == 7)
  			this.addSpan(span, gdt.getNumericValue());
  		gdt.addDaysUTC(1);
  	}
  },
  
  spanMonthLastDaySchedule: function(span) {
  	var gdt = span.dt;
  	while (gdt.getNumericValue() < this.max.getNumericValue()) {
  		if (gs.isLastDayOfMonth(gdt))
  			this.addSpan(span, gdt.getNumericValue());
  		gdt.addDaysUTC(1);
  	}
  },
  
  // Quarter end schedules are spanned just once because the timeline shows data worth 1 month only
  // Hence there is no need to go further beyond the first occurrence
  spanCalenderQuarterEndSchedule: function(span) {
  	var gdt = span.dt;
  	var quarterEnd = new GlideDateTime(gs.endOfThisQuarter());
  	quarterEnd.subtract(this.DAY);
  	var diffSeconds = gs.dateDiff(gdt.getDate(), quarterEnd.getDate(), true);
  	gdt.addSeconds(diffSeconds);
  	if (gdt.getNumericValue() < this.max.getNumericValue())
  		this.addSpan(span, gdt.getNumericValue());
  },
  
  type: 'DiscoveryStatusTimeline'
});

Sys ID

35d3da71eb712100d636a20fa206fec4

Offical Documentation

Official Docs: