Name

sn_appclient.ScheduledInstallService

Description

This Script manages the scheduled Installation of Applications and Plugins

Script

var ScheduledInstallService = Class.create();
ScheduledInstallService.prototype = {
  initialize: function(scheduleGr) {
  	this.scheduleGr = scheduleGr;
  },
  processError : function(error){
  	return {
  		responseData: {"error" : error},
  		statusCode: 400
  	};
  },
  sendResponse : function(data){
  	return {
  		responseData: data,
  		statusCode: 200
  	};
  },
  processRequest : function(requestParams){
  	var requestType =  requestParams.action;
  	if (gs.nil(requestType)) {
  		return this.processError("invalid action for schedule");
  	}
  	try {
  		var response = "";
  		var error = "";
  		switch(requestType.toLowerCase()) {
  			case "get_all_schedules" :
  				response = this.fetchAllActiveScheduledItems();
  				break;
  			case "get_schedule_stats" :
  				response = this.getCurrentScheduledState();
  				break;
  			case "create_schedule":
  				var scheduleObj = requestParams.schedule;
  				if (!scheduleObj){
  					error = "invalid schedule found";
  					break;
  				}
  				scheduleObj = JSON.parse(scheduleObj);
  				var scheduleInfo = this.createSchedule(scheduleObj);
  				if (!gs.nil(scheduleInfo.error)){
  					error = scheduleInfo.error;
  					break;
  				}
  				this.captureScheduleForAnalytics(scheduleObj);
  				response = scheduleInfo;
  				break;
  			case "update_schedule":
  				var newscheduleObj = requestParams.schedule;
  				var current_schedule_id = requestParams.schedule_id;
  				if (!current_schedule_id){
  					error = "schedule id cannot be empty";
  					break;
  				}
  				if (!newscheduleObj){
  					error = "invalid schedule found";
  					break;
  				}
  				newscheduleObj = JSON.parse(newscheduleObj);
  				var errors = this.checkForErrors(newscheduleObj);
  				if (errors.length != 0) {
  					error = errors.toString();
  					break;
  				}
  				var newScheduledInfo = this.updateSchedule(current_schedule_id, newscheduleObj);
  				if (!gs.nil(newScheduledInfo.error)){
  					error = newScheduledInfo.error;
  					break;
  				}
  				response = newScheduledInfo;
  				break;
  			case "delete_schedule":
  				var schedule_id = requestParams.schedule_id;
  				var source_app_id = requestParams.source_app_id;
  				if (!schedule_id){
  					response = "schedule id cannot be empty";
  					break;
  				}
  				if (!gs.nil(source_app_id))
  					response = {
  						"success" : this.deleteScheduleItem(schedule_id, source_app_id)
  					};
  				else
  					response = {
  						"success" : this.deleteSchedule(schedule_id)
  					};
  				break;
  			case "validate_slot":
  				var scheduled_time = requestParams.scheduled_time;
  				var appCount = requestParams.appCount;
  				var existing_schedule_id = requestParams.schedule_id;
  				if (gs.nil(scheduled_time) || gs.nil(appCount)){
  					error = "scheduled_time/appCount cannot be empty";
  					break;
  				}
  				if (gs.nil(appCount))
  					appCount = 1;
  				else
  					appCount = parseInt(appCount);
  				response = this.isConflictingSchedule(scheduled_time, appCount, existing_schedule_id);
  				break;
  			case "get_next_available_slot":
  				appCount = parseInt(requestParams.appCount) || 1;
  				response = this.getNextAvailableScheduleSlot(appCount);
  				break;
  			default:
  				error = "Unknown action. Bad Request";
  		}
  	}catch(e) {
  		return this.processError(e);
  	}
  	if (gs.nil(error))
  		return this.sendResponse(response);
  	else
  		return this.processError(error);
  },
  checkForErrors : function(schedule){
  	var error = "";
  	if (!schedule.name || !schedule.start_time) {
  		gs.error("Missing one of(name,start_time,end_time) for the schedule name:" +  schedule.name  + "start_time:"+ schedule.start_time);
  		error = gs.getMessage("Missing information about plugin name, start time");
  	}
  	else if (!this.validateEntriesInSchedule(schedule.list_of_apps, error)) {
  		error = gs.getMessage("Unable to create schedule due to invalid app or plugin selected for schedule");
  	}
  	return error;	
  },
  validateEntriesInSchedule : function(listOfApps,error){
  	var scheduleInput = {
  		isValid : true,
  		error : error
  	};
  	if (!listOfApps || listOfApps.length ==0 ) {
  		scheduleInput.isValid = false;
  		gs.error("No items found to schedule the installation");
  		scheduleInput.error = gs.getMessage("No items found in the schedule");
  	}
  	listOfApps.forEach(function(app){
  		if (!app.is_plugin && !app.is_store_app) {
  			this.captureError(scheduleInput,app,"Installation item can only be either app/plugin");
  			return;
  		}
  		if (!app.source_app_id)
  			this.captureError(scheduleInput,app,"missing app/plugin id");
  		if (app.is_store_app && !app.version)
  			this.captureError(scheduleInput,app,"Missing version");
  		else if (app.is_customization_version && !app.customization_version)
  			this.captureError(scheduleInput, app , "Missing customization version info");
  	},this);
  	return scheduleInput.isValid;
  },
  captureError : function(scheduleInput,app,message){
  	scheduleInput.isValid = false;
  	scheduleInput.error = message;
  	gs.error("Invalid item found due to ("+ message +") in "+ JSON.stringify(app));
  },
  isValidTime : function(start_time,error){
  	var userSelectedTime = new GlideDateTime();
  	userSelectedTime.setDisplayValue(start_time);
  	if (new GlideDateTime().after(userSelectedTime)) {
  		return false;
  	}
  	return true;
  },
  createSchedule : function(schedule){
  	var error = this.checkForErrors(schedule);
  	if (error.length != 0)
  		return {
  			error : error,
  			status : 400
  		};
  	try{
  		var start_time = new GlideDateTime();
  		start_time.setDisplayValue(schedule.start_time);
  		var estimatedEndTime = 3600 *  schedule.list_of_apps.length;
  		var end_time = new GlideDateTime(start_time.getValue());
  		end_time.addSeconds(estimatedEndTime);
  		var scheduleGr = new GlideRecord("sys_installation_schedule");
  			scheduleGr.initialize();
  			scheduleGr.setValue("name", schedule.name);
  			scheduleGr.setValue("active", true);
  			scheduleGr.setValue("start_time", start_time);
  			scheduleGr.setValue("end_time", end_time);
  		    scheduleGr.setValue("failure_strategy", schedule.failure_strategy);
  			scheduleGr.setValue("load_demo_data",schedule.load_demo_data);
  			var schedule_sys_id = scheduleGr.insert();
  			if (gs.nil(schedule_sys_id))
  				return {
  					error: gs.getMessage("failed to create schedule"),
  					status : 500
  				};
  			schedule.list_of_apps.forEach(function(plugin){
  				this.createScheduledItem(plugin,schedule_sys_id, start_time, end_time);
  			},this);		
  	}catch(e){
  		return {
  			error : gs.getMessage("Failed to create schedule due to {0}", e),
  			status : 400
  		};
  	}
  	this.createScheduledEventForInstall(scheduleGr);
  	return this.fetchSchedule(schedule_sys_id);
  },
  createScheduledItem : function(scheduledItem,schedule_sys_id, start_time, end_time) {
  	var scheduleItemGr = new GlideRecord("sys_installation_schedule_item");
  	scheduleItemGr.initialize();
  	scheduleItemGr.setValue("name" , scheduledItem.name);
  	scheduleItemGr.setValue("scope" , scheduledItem.scope);
  	scheduleItemGr.setValue("source_app_id" , scheduledItem.source_app_id);
  	if (!scheduledItem.is_plugin){
  		scheduleItemGr.setValue("delivery_source" , scheduledItem.delivery_source || null);
  		scheduleItemGr.setValue("dependencies" , scheduledItem.dependencies || null);
  	}
  	scheduleItemGr.setValue("version" , scheduledItem.version || null);
  	scheduleItemGr.setValue("customization_version" , scheduledItem.customization_version || null);
  	scheduleItemGr.setValue("is_plugin" , scheduledItem.is_plugin || false);
  	scheduleItemGr.setValue("is_store_app" , scheduledItem.is_store_app || false);
  	scheduleItemGr.setValue("is_customization" , scheduledItem.is_customization_version || false);
  	scheduleItemGr.setValue("schedule" , schedule_sys_id);
  	scheduleItemGr.setValue("product" , scheduledItem.product || null);
  	scheduleItemGr.setValue("start_time" , start_time);
  	scheduleItemGr.setValue("end_time" , end_time);
  	scheduleItemGr.insert();
  },
  deleteSchedule : function(existing_schedule_id){
  	var gr = new GlideRecord("sys_installation_schedule_item");
  	gr.addQuery("schedule.sys_id", existing_schedule_id);
  	gr.deleteMultiple();
  	var gr = new GlideRecord("sys_installation_schedule");
  	gr.get(existing_schedule_id);
  	return gr.deleteRecord();
  },
  deleteScheduleItem : function(existing_schedule_id, source_app_id){
  	var isDeleted = false;
  	var gr = new GlideRecord("sys_installation_schedule_item");
  	gr.addQuery("schedule.sys_id", existing_schedule_id);
  	gr.addQuery("source_app_id", source_app_id);
  	gr.query();
  	if(gr.next())
  		isDeleted = gr.deleteRecord();

  	return isDeleted;
  },
  updateSchedule : function(existing_schedule_id,schedule){
  	this.deleteSchedule(existing_schedule_id);
  	return this.createSchedule(schedule);
  },
  fetchSchedule : function(schedule_sys_id) {
  	var schedule = {};
  	var scheduleGr = new GlideRecord("sys_installation_schedule");
  	scheduleGr.addQuery("active" , true);
  	scheduleGr.addQuery("sys_id" , schedule_sys_id);
  	scheduleGr.query();
  	if(scheduleGr.next()) {
  		schedule["name"]  = scheduleGr.getValue("name");
  		schedule["start_time"] = scheduleGr.getValue("start_time");
  		//schedule["end_time"] = scheduleGr.getValue("end_time");
  		schedule["state"] = scheduleGr.getValue("state");
  		schedule["failure_strategy"] = scheduleGr.getValue("failure_strategy");
  		schedule["load_demo_date"] = scheduleGr.load_demo_data;
  		schedule["execution_tracker_id"] = scheduleGr.getValue("execution_tracker_id");
  		return this.fetchScheduledItems(schedule,schedule_sys_id);
  	}
  	return {
  			"error" : "no schedule exists",
  			"status" :  400
  	};
  },
  fetchScheduledItems : function(schedule, schedule_sys_id)	{
  	var scheduledItems = [];
  	var scheduleItemGr = new GlideRecord("sys_installation_schedule_item");
  	scheduleItemGr.addQuery("schedule" , schedule_sys_id);
  	scheduleItemGr.addQuery("state" ,"IN" ,"ready"); // failed ones are for retry
  	scheduleItemGr.query();
  	while(scheduleItemGr.next()) {
  		scheduledItems.push(this.getScheduledItemObj(scheduleItemGr));
  	}
  	schedule['list_of_apps'] = scheduledItems;
  	return schedule;
  },
  fetchAllActiveScheduledItems : function(){
  	var scheduledItems = {};
  	var blockedSlots = [];
  	var schedulesBlocked = {};
  	var startTime = "";
  	var endTime = "";
  	var slot = "";
  	var blockedSlots = {};
  	var slotTime = new GlideDateTime();
  	var scheduleItemGr = new GlideRecord("sys_installation_schedule_item");
  	scheduleItemGr.addQuery("state" , "ready");
  	scheduleItemGr.query();
  	while(scheduleItemGr.next()){
  		startTime = scheduleItemGr.getDisplayValue("start_time");
  		endTime = scheduleItemGr.getDisplayValue("end_time");
  		if (scheduleItemGr.getValue("product"))
  			scheduledItems[scheduleItemGr.getValue("product")] = {
  				"scheduled_on" : startTime + "|" + endTime,
  				"schedule_id" : scheduleItemGr.getValue("schedule")
  			};
  		scheduledItems[scheduleItemGr.getValue("source_app_id")] = this.getScheduledItemObj(scheduleItemGr);
  		this.captureBlockedSlot(scheduleItemGr, blockedSlots);
  	}
  	var slots = this.toArray(blockedSlots);
  	gs.getSession().putClientData("blocked_slots", JSON.stringify(slots));
  	return scheduledItems;
  },
  fetchActiveSchedulesForEntity: function(args) {
  	var scheduledItems = {};
  	var blockedSlots = [];
  	var startTime = "";
  	var endTime = "";
  	var scheduleItemGr = new GlideRecord("sys_installation_schedule_item");
  	scheduleItemGr.addQuery("state" , "ready");
  	scheduleItemGr.addQuery("source_app_id" , args.source_app_id);
  	scheduleItemGr.query();
  	while(scheduleItemGr.next()){
  		startTime = scheduleItemGr.getDisplayValue("start_time");
  		endTime = scheduleItemGr.getDisplayValue("end_time");
  		if (scheduleItemGr.getValue("product"))
  			scheduledItems[scheduleItemGr.getValue("product")] = {
  				"scheduled_on" : startTime + "|" + endTime,
  				"schedule_id" : scheduleItemGr.getValue("schedule")
  			};
  		scheduledItems[scheduleItemGr.getValue("source_app_id")] = this.getScheduledItemObj(scheduleItemGr);
  		this.captureBlockedSlot(scheduleItemGr, blockedSlots);
  	}
  	var slots = this.toArray(blockedSlots);
  	return scheduledItems;
  },
  captureBlockedSlot : function(scheduleItemGr, blockedSlots){
  	var scheduledSlot = scheduleItemGr.getValue("schedule");
  	var blockedSlot = {};
  	var slotTime = new GlideDateTime();
  	var slot = "";
  	if (blockedSlots[scheduledSlot])
  		return;

  	var startTime = scheduleItemGr.getDisplayValue("start_time");
  	var	endTime = scheduleItemGr.getDisplayValue("end_time");
  	slotTime.setDisplayValue(startTime);
  	blockedSlot.start_time = slotTime.getNumericValue();
  	slotTime.setDisplayValue(endTime);
  	blockedSlot.end_time = slotTime.getNumericValue();
  	blockedSlot.schedule_id = scheduleItemGr.getValue("schedule");
  	blockedSlot.scheduled_on = startTime + "|" + endTime;
  	blockedSlots[scheduledSlot] = blockedSlot;
  	return blockedSlots;
  },
  getBlockedSlots : function() {
  	var blockedSlotsJSON = {};
  	var blockedSlots = [];
  	var scheduleItemGr = new GlideRecord("sys_installation_schedule_item");
  	scheduleItemGr.addQuery("state" , "ready");
  	scheduleItemGr.query();
  	while(scheduleItemGr.next()){
  		this.captureBlockedSlot(scheduleItemGr, blockedSlotsJSON);
  	}
  	return this.toArray(blockedSlotsJSON);
  },
  toArray : function(blockedSlotsJSON) {
  	var blockedSlots = [];
  	for (var slot in blockedSlotsJSON) {
  		if (blockedSlotsJSON.hasOwnProperty(slot)) {
  			blockedSlots.push(blockedSlotsJSON[slot]);
  		}
  	}
  	return blockedSlots;
  },
  getScheduledItemObj: function(scheduleItemGr) {
  	return {
  			"name" : scheduleItemGr.getValue("name"),
  			"scheduled_on" : scheduleItemGr.getDisplayValue("start_time") + "|" + scheduleItemGr.getDisplayValue("end_time"),
  			"schedule_id" : scheduleItemGr.getValue("schedule"),
  			"version" : scheduleItemGr.getValue("version"),
  			"customization_version" : scheduleItemGr.getValue("customization_version"),
  			"source_app_id" : scheduleItemGr.getValue("source_app_id"),
  			"is_plugin" : scheduleItemGr.getValue("is_plugin") == '1',
  			"is_store_app" : scheduleItemGr.getValue("is_store_app") == '1',
  			"is_customization" : scheduleItemGr.getValue("is_customization") == '1',
  			"scope" : scheduleItemGr.getValue("scope"),
  			"product" : scheduleItemGr.getValue("product"),
  			"time" :  scheduleItemGr.getDisplayValue("start_time") + "|" + scheduleItemGr.getDisplayValue("end_time"),
  			"load_demo_data" : !!scheduleItemGr.load_demo_data,
  			"state" : scheduleItemGr.getValue("state"),
  			"status" : scheduleItemGr.getDisplayValue("state"),
  			"delivery_source" : scheduleItemGr.getValue("delivery_source"),
  			"id" :  scheduleItemGr.getValue("source_app_id"),
  			"start_time" : scheduleItemGr.getDisplayValue("start_time"),
  			"end_time" : scheduleItemGr.getDisplayValue("end_time")
  		};
  },
  createScheduledEventForInstall : function(scheduleGr){
  	if (gs.nil(scheduleGr.start_time))
  		return;
  	
  	gs.eventQueueScheduled("sn_appclient.install.scheduled.plugins",scheduleGr,"","", scheduleGr.start_time);
  },
  processSchedule : function(schedule,retries){
  	try{
  		if (!schedule.active){
  			gs.debug("Skipped schedule as its inactive");
  			this.updateScheduleStatus(schedule,"schedule not active", true);
  			return;
  		}

  		var batchPayload = this.getBatchPayload(schedule);
  		if (Object.keys(batchPayload).length == 0){
  			this.updateScheduleStatus(schedule,"invalid batch payload", true);
  			return;
  		}

  		if (this.isSystemBusy()){
  			if (gs.nil(retries))
  				retries = 0;
  			retries = parseInt(retries);
  			if (retries > 3)
  				this.updateScheduleStatus(schedule,"Failed as system is busy continously for 3 retries", true);
  			else
  				this.retrySchedule(schedule, retries);
  			return; // either retry the schedule or cancel it if already retried thrice
  		}
  		this.syncWithStore(batchPayload);
  		var filteredAppsInfo = this.filterUnableToInstallApps(batchPayload.packages);
  		if (filteredAppsInfo.packages.length == 0) {
  			this.updateScheduleStatus(schedule,"No items to install", filteredAppsInfo.retire);
  			return;
  		}

  		batchPayload.packages = filteredAppsInfo.packages;
  		var batchInfo = new AppUpgrader().installBatch(JSON.stringify(batchPayload), true);
  		batchInfo = JSON.parse(batchInfo);
  		this.updateBatchDetails(batchInfo);
  		this._addProgressTracker(batchPayload, batchInfo);
  	}catch(e){
  		this.updateScheduleStatus(schedule,e, false);
  	}
  	
  },
  markPendingScheduledItems : function(schedule_sys_id, state, message){
  	var itemGr = new GlideRecord("sys_installation_schedule_item");
  	itemGr.addQuery("schedule.sys_id", schedule_sys_id);
  	itemGr.addQuery("state", "IN", "ready,in_progress");
  	itemGr.query();
  	while(itemGr.next()){
  		itemGr.setValue("state", state);
  		if (!gs.nil(message))
  			itemGr.setValue("failure_reason", message);
  		itemGr.setValue("processed_on", new GlideDateTime());
  		itemGr.update();
  	}
  },
  getBatchPayload : function(schedule){
  	
  	var batchPayload = {};
  	batchPayload.name = schedule.getValue("name");
  	var scheduleObj = this.fetchScheduledItems({},schedule.getUniqueValue());
  	var packages = [];
  	var installationObj = {};
  	if (!scheduleObj.list_of_apps || scheduleObj.list_of_apps.length ==0)
  		return {};
  	scheduleObj.list_of_apps.forEach(function(item){
  		installationObj = {
  			"id" : item.id,
  			"type" : this.getType(item),
  			"load_demo_data" : schedule.getValue("load_demo_data") == "1",
  			"requested_version" : gs.nil(item.version)? "" : item.version
  		};
  		if (item.is_customization){
  			installationObj.requested_customization_version = item.customization_version;
  		}
  		packages.push(installationObj);
  	},this);
  	batchPayload.packages = packages;
  	return batchPayload;
  },
  getType: function(scheduledItem){
  	if (scheduledItem.is_plugin)
  		return "plugin";
  	if (scheduledItem.is_store_app)
  		return "application";
  },
  isSystemBusy : function(){
  	var isMutexAvailable = sn_app_api.AppStoreAPI.isUpdateMutexAvailable();
  	return (gs.isPaused() || !isMutexAvailable) ;
  },
  retireSchedule : function(scheduleGr, comments){
  	scheduleGr.setValue("active", false);
  	if (!gs.nil(comments))
  		scheduleGr.setValue("comments" , comments);
  	scheduleGr.update();
  	this.markPendingScheduledItems(scheduleGr.getUniqueValue(), "failed", comments);
  },
  updateScheduleStatus : function(scheduleGr, comments, retireSchedule){
  	if (retireSchedule || !(parseInt(scheduleGr.getValue("retry_count")) == 0 && "retry_next_day" == scheduleGr.getValue("failure_strategy"))) {
  		this.retireSchedule(scheduleGr, comments);
  	}
  	if (parseInt(scheduleGr.getValue("retry_count")) == 0 && "retry_next_day" == scheduleGr.getValue("failure_strategy")) //this schedule was said not to shut off and user selected to retry and have 1 more chance
  		this.rescheduleNextDay(scheduleGr.getUniqueValue());
  },
  retrySchedule : function(scheduleGr,retries){
  	var startTime = new GlideDateTime();
  	startTime.addSeconds(900);
  	scheduleGr.setValue("comments", "Rescheduled to process in next 15mins as system is busy");
  	scheduleGr.update();
gs.eventQueueScheduled("sn_appclient.install.scheduled.plugins",scheduleGr,retries + 1,"", startTime);
  	
  },
  rescheduleNextDay : function(scheduleSysId){
  	var scheduleGr = new GlideRecord("sys_installation_schedule");
  	scheduleGr.get(scheduleSysId);
  	if (!scheduleGr.isValidRecord()){
  		gs.error("No schedule available for retry");
  		return;
  	}

  	gs.debug("Rescheduling due to failed event on " + scheduleGr.getValue("name"));
  	var noOfAppsToRetry = this.getRetryAppsCount(scheduleGr.getUniqueValue());
  	if (noOfAppsToRetry == 0)
  		return;

  	var startTime = new GlideDateTime(scheduleGr.getValue("start_time"));
  	startTime.addDaysUTC(1);
  	var conflictingSchedule = this.isConflictingSchedule(startTime.getDisplayValue(), noOfAppsToRetry, scheduleGr.getUniqueValue());
  	if (conflictingSchedule.conflict){
  		startTime.setDisplayValue(conflictingSchedule.nextSlot);
  	}
  	var estimatedEndTime = 3600 *  noOfAppsToRetry;
  	var end_time = new GlideDateTime(startTime.getValue());
  		end_time.addSeconds(estimatedEndTime);
  	this.copySchedule(scheduleGr, startTime, end_time);
  	this.retireSchedule(scheduleGr);
  },
  copySchedule : function(scheduleGr, startTime, endTime){
  	gs.debug("********Copying schedule" + scheduleGr.getUniqueValue() +" "+scheduleGr.getValue("name"));
  	var newSchedulegr = new GlideRecord("sys_installation_schedule");
  	newSchedulegr.initialize();
  	newSchedulegr.setValue("name", "Rescheduled : " + scheduleGr.getValue("name"));
  	newSchedulegr.setValue("active", true);
  	newSchedulegr.setValue("start_time", startTime);
  	newSchedulegr.setValue("end_time", endTime);
  	newSchedulegr.setValue("failure_strategy", scheduleGr.getValue("failure_strategy"));
  	newSchedulegr.setValue("load_demo_data",scheduleGr.load_demo_data);
  	newSchedulegr.setValue("retry_count",1);
  	var newScheduleSysId = newSchedulegr.insert();
  	gs.debug("New schedule created for " + newSchedulegr.getValue("name"));
  	// copy schedule items
  	var scheduleItemObj = {};
  	var itemGr = new GlideRecord("sys_installation_schedule_item");
  	itemGr.addQuery("schedule.sys_id", scheduleGr.getUniqueValue());
  	itemGr.addQuery("state", "IN", "ready,failed,invalid");
  	itemGr.query();
  	while(itemGr.next()){
  		scheduleItemObj = this.getScheduledItemObj(itemGr);
  		this.createScheduledItem(scheduleItemObj, newScheduleSysId, startTime, endTime);
  		gs.debug("New schedule item created" + scheduleItemObj.name);
  	}
  	this.createScheduledEventForInstall(newSchedulegr);
  },
  getRetryAppsCount : function(schedule_sys_id){
  	var gr = new GlideRecord("sys_installation_schedule_item");
  	gr.addQuery("state", "IN" ,"failed,invalid,ready");
  	gr.addQuery("schedule.sys_id", schedule_sys_id);
  	gr.query();
  	return gr.getRowCount();
  },
  syncWithStore : function(batchPayload) {
  	try{
  		new UpdateChecker(batchPayload,true).checkAvailableUpdates(false,true);
  	}catch(e){
  		gs.error("Failed to sync to store due to", e);
  	}
  },
  filterUnableToInstallApps : function(batchedPackages){
  	var response = {
  		retire : true,
  		packages : []
  	};
  	batchedPackages.forEach(function(app){
  		if(app.requested_customization_version) {
  			response.packages.push(app); // if customization install do not check for whether app is installed or not or plugin is installed or not
  			return;
  		}
  		if (app.type == "application"){
  			if (this.isAppAlreadyInstalled(app)) {
  				this.updateScheduledInstallItem(app , "failed","Application already installed");
  				return;
  			}
  			if (!this.isAvailableForInstall(app)){
  				response.retire = false;
  				this.updateScheduledInstallItem(app, "failed","Application version not available for installation");
  				return;
  			}
  		}
  		if (app.type == "plugin"){
  			if (this.isPluginActive(app)) {
  				this.updateScheduledInstallItem(app , "failed","Plugin already active");
  				return;
  			}
  		}
  		response.packages.push(app);
  	},this);
  	return response;
  },
  isAvailableForInstall : function(app){
  	var gr = new GlideRecord("sys_app_version");
  	gr.addQuery("source_app_id",app.id);
  	gr.addQuery("version",app.requested_version);
  	gr.addQuery("block_install",false);
  	gr.query();
  	return gr.getRowCount() > 0;
  },
  isAppAlreadyInstalled : function(app){
  	var versionComparator = new VersionComparator();
  	var gr = new GlideRecord("sys_store_app");
  	gr.get(app.id);
  	if (!gr.isValidRecord())
  		return false;
  	return gr.active && (versionComparator.isDowngrade(gr.getValue("version"),app.requested_version) || versionComparator.isEqual(gr.getValue("version"),app.requested_version));
  },
  isPluginActive : function(app){
  	return GlidePluginManager.isActive(app.id);
  },
  updateScheduledInstallItem : function(app,state,message){
  	var gr = new GlideRecord("sys_installation_schedule_item");
  	gr.addQuery("schedule.sys_id", this.scheduleGr.getUniqueValue());
  	gr.addQuery("source_app_id", app.id);
  	gr.addQuery("version", app.requested_version);
  	gr.addQuery("state", "IN" , "ready,failed");
  	gr.query();
  	if (gr.next()) {
  		gr.setValue("state",state);
  		gr.setValue("failure_reason",message);
  		gr.setValue("processed_on" , new GlideDateTime());
  		gr.update();
  	}
  },
  captureBatchOnScheduledInstallItem : function(batchId){ // used by java layer to update the status based on installation item
  	var gr = new GlideRecord("sys_installation_schedule_item");
  	gr.addQuery("schedule.sys_id", this.scheduleGr.getUniqueValue());
  	gr.query();
  	while (gr.next()) {
  		gr.setValue("batch_install_plan",batchId);
  		gr.update();
  	}
  },
  updateBatchDetails : function(batchInfo){
  	if (!batchInfo || !batchInfo.batch_installation_id)
  		return;
  	var scheduleGr = this.scheduleGr;
  	scheduleGr.setValue("batch_install_plan",batchInfo.batch_installation_id);
  	scheduleGr.setValue("execution_tracker_id",batchInfo.execution_tracker_id);
  	scheduleGr.update();
  	this.captureBatchOnScheduledInstallItem(batchInfo.batch_installation_id);
  },
  getCurrentScheduledState : function(){
  	var progressTracker = new ProgressTracker();
  	var status = {"failed" : 0,"success":0, "currentRunningBatch" : progressTracker.getPendingBatchInstalls()};
  	var gd = new GlideDateTime();
  	gd.addWeeksLocalTime(-1);
  	
  	var count = new GlideAggregate('sys_installation_schedule_item');
  	count.addQuery('sys_updated_on', '>=' ,gd);
  	count.addQuery('state', 'IN' ,'failed,invalid,installed');
  	count.addAggregate('COUNT','state');
  	count.query();
  	while(count.next()) {
  		if (["failed","invalid"].indexOf(count.getValue("state")) != -1)
  			status["failed"] += parseInt(count.getAggregate('COUNT',"state"));
  		if ("installed" == count.getValue("state"))
  			status["success"] += parseInt(count.getAggregate('COUNT',"state"));
  	}
  	return status;
  },
  getNextAvailableScheduleSlot: function(appCount) {
  	var d = new GlideDateTime();
  	d.add(2 * Constants.HOUR_MILLIS);
  	var nextSlotTime = d.getDisplayValue().toString().slice(0,-5)+'00:00';
  	var isScheduleConflict = this.isConflictingSchedule(nextSlotTime, appCount);

  	if(isScheduleConflict.conflict) {
  		nextSlotTime = isScheduleConflict.nextSlot;
  	}
  	return {
  		nextSlot: nextSlotTime
  	};
  },

  isConflictingSchedule : function(userSelectedTime, countOfApps, schedule_id){
  	/*                                             {userselected time}
  	 1st Condition : blockedslotStart-------------------------------------------blockedslotEnd ==> conflict
  	 (user selected time can fall under existing blocked slot)

  	                 {userselected time}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{end time}
  	 2nd condition :                       |blockedslot start-------------------------blockedslot end|   ==> conflict
  	 (end time can fall under existing schedule)

  	 3rd condition : {userselected time}~~~~~~~~~~~~~~{blocked start time} --------------{blocked end time}~~~~~~~~~~~~{end time}
  	 (user selected time is outer range of existing blocked time)  =====> conflict
  	*/

  	var error;
  	if (!this.isValidTime(userSelectedTime)) {
  		var d = new GlideDateTime();
  		d.add(2 * Constants.HOUR_MILLIS);
  		var displayTime = d.getDisplayValue().toString().slice(0,-5)+'00:00';
  		gs.error("user selected time is in the past" + userSelectedTime);
  		error = gs.getMessage("The start time you chose for the installation is in the past. Installation has been set for the next possible time,");
  		error = error + " " + displayTime;
  		return {
  			conflict: true,
  			error: error,
  			nextSlot: displayTime
  		};
  	}

  	var blockedSlots = "";
  	if (gs.isInteractive()) {
  		blockedSlots = gs.getSession().getClientData("blocked_slots");
  		blockedSlots = JSON.parse(blockedSlots || "[]");
  	}
  	else
  		blockedSlots = this.getBlockedSlots();
  	var response = {
  		conflict : false
  	};
  	if (blockedSlots.length == 0)
  		return response;

  	var timeToComplete = 3600 *  countOfApps;
  	var dateTime = new GlideDateTime();
  	dateTime.setDisplayValue(userSelectedTime);
  	userSelectedTime = dateTime.getNumericValue();
  	dateTime.addSeconds(timeToComplete);
  	var estimatedEndTime = dateTime.getNumericValue();
  	blockedSlots.sort(function(slot1, slot2){
  		return slot1.start_time - slot2.start_time;
  	});
  	dateTime.setNumericValue(blockedSlots[blockedSlots.length - 1].end_time);
  	var estimatedTimeToComplete = estimatedEndTime - userSelectedTime;
  		for(var i = 0; i < blockedSlots.length; i++) {
  			if(blockedSlots[i].schedule_id !== schedule_id &&
  				((userSelectedTime >= blockedSlots[i].start_time && userSelectedTime < blockedSlots[i].end_time) ||
  				(estimatedEndTime > blockedSlots[i].start_time && estimatedEndTime <= blockedSlots[i].end_time) || 
  				(userSelectedTime <= blockedSlots[i].start_time && blockedSlots[i].end_time <= estimatedEndTime))) {
  				response.conflict = true;
  				if((i == blockedSlots.length - 1) ||
  					(blockedSlots[i+1].start_time - blockedSlots[i].end_time >= estimatedTimeToComplete)) {
  					dateTime.setNumericValue(blockedSlots[i].end_time);
  					break;
  				}
  				userSelectedTime = blockedSlots[i].end_time;
  				estimatedEndTime = userSelectedTime + estimatedTimeToComplete;
  			}
  		}
  	var errorMsg = gs.getMessage("cannot be installed at the time you chose because it's too close to another scheduled installation. It will be installed at the next available time,");
  	if(response.conflict) {
  		response.error = errorMsg + " " + dateTime.getDisplayValue().slice(0, 16) + " "+gs.getSession().getTimeZoneName();
  		response.nextSlot = dateTime.getDisplayValue();
  	}
  	return response;
  },

  captureScheduleForAnalytics: function(scheduleObj) {
  	var collectionData = {};
  	collectionData.installation_type = "Schedule Install";
  	collectionData.is_search_applied = scheduleObj.isSearchApplied;
  	collectionData.is_filter_applied = scheduleObj.isFilterApplied;
  	new AppClientGCFUtil().captureBatchInstallAnalytics(collectionData, scheduleObj.list_of_apps);
  },

  _addProgressTracker: function(batchPayload, batchInfo) {
      var responsePayload = batchInfo;
      responsePayload.trackerId = batchInfo.execution_tracker_id;
      var id;
  var type; 
  if(batchPayload && batchPayload.packages && batchPayload.packages.length==1){
  	id = batchPayload.packages[0].id;
  	type= batchPayload.packages[0].type;
  }
  if(type =='application' || type == 'plugin'){
  	CommonUtils.addProgressTracker(batchPayload, responsePayload, id, type, Constants.SCHEDULE_INSTALL);
  }else{
  	CommonUtils.addProgressTracker(batchPayload, responsePayload, batchPayload.product_id, Constants.BATCH_INSTALL, Constants.SCHEDULE_INSTALL);
  }
  },

  type: 'ScheduledInstallService'
};

Sys ID

000b82eb93312010ebd4f157b67ffb86

Offical Documentation

Official Docs: