Name

global.DiscoveryResultManager

Description

Manager for returning data displayed on discovery results page.

Script

var DiscoveryResultManager = Class.create();
DiscoveryResultManager.prototype = {
  initialize: function() {
  	this.MAX_RESULTS = 10; // max # of results to pull for each schedule (sorted desc)
  	this.SCHEDULE_WINDOW = 10; // how many schedules to query at a time
  	this.DISCOVERY_APP = 'd5069fe9e70332001a310a6103f6a94b';
  	this.cloudResourceChartInfo = [
  		{
  			'id': 'awsCloudResourcesTab',
  			'tabContentId': 'awsCloudResourcesTabContent',
  			'name': gs.getMessage("AWS"),
  			'disabled': false,
  			'reportSysId': '96603bad3b6500108774cedf34efc480'
  		},
  		{
  			'id': 'azureCloudResourcesTab',
  			'tabContentId': 'azureCloudResourcesTabContent',
  			'name': gs.getMessage("Azure"),
  			'disabled': false,
  			'reportSysId': '47d37f213ba500108774cedf34efc4fe'
  		},
  		{
  			'id': 'vmwareCloudResourcesTab',
  			'tabContentId': 'vmwareCloudResourcesTabContent',
  			'name': gs.getMessage("VMware"),
  			'disabled': false,
  			'reportSysId': '6d6977253ba500108774cedf34efc48b'
  		},
  		{
  			'id': 'googleCloudResourcesTab',
  			'tabContentId': 'googleCloudResourcesTabContent',
  			'name': gs.getMessage("Google"),
  			'disabled': false,
  			'reportSysId': '092321483b5200108774cedf34efc417'
  		},
  		{
  			'id': 'ibmCloudResourcesTab',
  			'tabContentId': 'ibmCloudResourcesTabContent',
  			'name': gs.getMessage("IBM"),
  			'disabled': false,
  			'reportSysId': '39e5e5883b5200108774cedf34efc467'
  		}
  	];
  },

  getInProgressDiscoveries: function() {
  	var inProgressDiscoveries = [];
  	var statusGr = new GlideRecord("discovery_status");
  	statusGr.addQuery('state', 'IN', 'starting,active');
  	statusGr.query();

  	var scheduleGr = new GlideRecord('discovery_schedule');
  	while (statusGr.next()) {
  		// make sure the schedule that spawned the status still exists ...
  		if (statusGr.dscheduler && scheduleGr.get(statusGr.dscheduler)) {
  			// we only care about Configuration Item discoveries
  			if (scheduleGr.discover+'' == 'CIs') {
  				inProgressDiscoveries.push({
  					statusSysId: statusGr.sys_id +'',
  					scheduleSysId: scheduleGr.sys_id +'',
  					scheduleName: scheduleGr.name +'',
  					statusNumber: statusGr.number +'',
  					createdOn: statusGr.sys_created_on +'',
  					started: statusGr.started.getDisplayValue() +'',
  					startedDate: statusGr.started.getValue() +'',
  					completed: statusGr.completed.getDisplayValue() +'',
  					progress: statusGr.progress +''
  				});
  			}
  		}
  	}

  	return inProgressDiscoveries;
  },

  // Returns the number of devices that extend cmdb_ci_hardware
  // discovered in the last 30 days
  _getNumDiscoveredDevices: function() {
  	var hardwareGr = new GlideRecord('cmdb_ci_hardware');
  	hardwareGr.addQuery('last_discovered', '>=', gs.daysAgo(30));
  	var srcQry = hardwareGr.addQuery('discovery_source', 'ServiceNow');
  	srcQry.addOrCondition('discovery_source', 'CredentiallessDiscovery');
  	hardwareGr.query();

  	return hardwareGr.getRowCount();
  },

  runSchedule: function(scheduleId) {
  	var result = {};
  	var scheduleGr = new GlideRecord("discovery_schedule");
  	if (!scheduleGr.get(scheduleId)) {
  		result.error = "Could not find schedule with sys_id: " + scheduleId;
  		return result;
  	}

  	var disco = new Discovery();
  	if (!disco.isValidDiscoverySchedule(scheduleGr)) {
  		result.error = "Discovery schedule is not valid - no active range or necessary parameters are specified";
  		return result;
  	}

  	var jobSysId = SncTriggerSynchronizer.executeNow(scheduleGr);

  	// make a status record for our discovery...
  	var job = new DiscoveryJob(jobSysId);
  	var status = new DiscoveryStatus(job, 'Discover Now', 'Discover_now_schedule');
  	var statusID = status.sysID;
  	result.status = statusID;
  	result.number = status.number;
  	
  	return result;
  },

  //Returns service account list from the cmp_discovery_ldc_config
  //Based on schedule checking from the discovery_schedule
  getServiceAccount: function() {
  	var discoverLdcConfigGr = new GlideRecord("cmp_discovery_ldc_config");
  	var discoveryScheduleGr = discoverLdcConfigGr.addJoinQuery("discovery_schedule", "discovery_schedule", "sys_id");
  	discoverLdcConfigGr.addNotNullQuery('service_account');
  	discoverLdcConfigGr.query();
  	var serviceAccountList = [];
  	while (discoverLdcConfigGr.next()) {
  		var serviceAccObj = { sys_id: discoverLdcConfigGr.getValue("service_account") , name: discoverLdcConfigGr.getDisplayValue("service_account")};
  		var serviceAccountIndex = serviceAccountList.map(function (item) { return item.sys_id; }).indexOf(discoverLdcConfigGr.getValue("service_account"));
  		if (serviceAccountIndex == -1)
  			serviceAccountList.push(serviceAccObj);
  	}
  	return serviceAccountList;
  },

  //Returns number of cloud resource schedule from the discovery_schedule and filtering based on discover type is Cloud Resources
  //And validating whether the schedule is active and contains service account other than "ansible and chef" from the cmp_discovery_ldc_config
  getCloudResourceScheduleCount: function() {
  	var scheduleGr = new GlideRecord("discovery_schedule");
  	scheduleGr.addQuery('discover', 'Cloud Resources');
  	scheduleGr.addCondition("active", true);
  	var ldcConfigGr = scheduleGr.addJoinQuery("cmp_discovery_ldc_config", "sys_id", "discovery_schedule");
  	ldcConfigGr.addCondition("service_account", "!=", null);
  	scheduleGr.addActiveQuery();
  	scheduleGr.query();
  	return scheduleGr.getRowCount();
  },

  //Returns number of datacenter list from the cmdb_ci_logical_datacenter table. Once discovery is start for service account
  //All the discovered datacenter are populate in cmdb_ci_logical_datacenter table.
  getDataCentersCount: function() {
  	var logicalDatacenterCount =new GlideAggregate('cmdb_ci_logical_datacenter');
  	logicalDatacenterCount.addAggregate('COUNT');
  	logicalDatacenterCount.query();
  	var dataCenterCount = 0;
  	if(logicalDatacenterCount.next())
  	   dataCenterCount = logicalDatacenterCount.getAggregate('COUNT');
  	return dataCenterCount;
  },

  /*
  * Returns number of disocvered cloud resources based on the CIs that extend "cmdb_ci" and not among the ignored list
  * Using fetched CIs, discovered resources count will be calculated.
  * 
  * NOTE: List of CIs added in ignoredList are based on the Report Source.
  *       Report Source with 'Visualise Cloud Discovery Results' name and '2f2ffc406725230022646c706785efe2' sys_id
  *       Conditions added below should be in sync with the conditions used in respective Report Source.
  */
  getCloudResources: function() {
  	var count = 0;
  	var cloudResourceDiscoHandler = new global.CloudResourceDiscoveryCountHandler();
  	
  	var ignoredList = ['cmdb_ci_spkg', 'cmdb_ci_computer', 'cmdb_ci_service', 'cmdb_ci_printer', 'cmdb_ci_win_cluster_node', 'cmdb_ci_database', 'cmdb_ci_web_server', 'cmdb_ci_server', 'cmdb_ci_win_server', 'cmdb_ci_unix_server', 'cmdb_ci_ups', 'cmdb_ci_email_server', 'cmdb_ci_netgear', 'cmdb_ci_rack', 'cmdb_ci_zone', 'cmdb_ci_linux_server', 'cmdb_ci_win_cluster', 'cmdb_ci_cluster_node', 'cmdb_ci_ip_router', 'cmdb_ci_msd', 'cmdb_ci_network_adapter', 'cmdb_ci_aix_server', 'cmdb_ci_computer_room', 'cmdb_ci_disk', 'cmdb_ci_peripheral', 'cmdb_ci_appl', 'cmdb_ci_app_server_java', 'cmdb_ci_cluster', 'cmdb_ci_datacenter', 'cmdb_ci_db_mysql_catalog', 'cmdb_ci_db_ora_catalog', 'cmdb_ci_ip_switch', 'cmdb_ci_service_group', 'cmdb_ci_storage_switch', 'cmdb_ci_translation_rule', 'cmdb_ci_aws_datacenter', 'cmdb_ci_azure_datacenter', 'cmdb_ci_vcenter_datacenter', 'cmdb_ci_cloud_service_account', 'cmdb_ci_infra_service', 'cmdb_ci_hardware', 'cmdb_ci_storage_device', 'cmdb_ci_app_server', 'cmdb_ci_db_catalog', 'cmdb_ci_endpoint_acl', 'cmdb_ci_endpoint_block', 'cmdb_ci_endpoint_comp_security', 'cmdb_ci_endpoint_cust_gateway', 'cmdb_ci_endpoint_intgateway', 'cmdb_ci_endpoint_nat', 'cmdb_ci_endpoint_route_table', 'cmdb_ci_endpoint_subnet', 'cmdb_ci_endpoint_vnic', 'cmdb_ci_endpoint_vpg', 'cmdb_ci_customer_gateway', 'cmdb_ci_iscsi_disk', 'cmdb_ci_vpn_connection', 'cmdb_ci_vm_object', 'cmdb_ci_vcenter_object', 'cmdb_ci_db_instance'];
  	
  	//Fetching all the childs and grand-childs that extend implicitly or explicitly from 'cmdb_ci' table
  	var tables = cloudResourceDiscoHandler.fetchAllChildCIsForParentCI("cmdb_ci");
  	
  	//Removing the ignored CIs from the fetched list
  	for (var j in ignoredList) {
  		var index = tables.indexOf(ignoredList[j]);
  		if (index > -1)
  			tables.splice(index, 1);
  	}
  	
  	for (var i in tables) {
  		var resourceTableGr = new GlideAggregate(tables[i]+'');
  		resourceTableGr.addAggregate('COUNT');
  		resourceTableGr.query();
  		if (resourceTableGr.next())
  			count = count + parseInt(resourceTableGr.getAggregate('COUNT'));
  	}
  	return count;
  },

  getVMScheduleCount: function() {
  	var scheduleGR = new GlideRecord('discovery_schedule');
  	scheduleGR.addNotNullQuery('vm_run');
  	scheduleGR.addActiveQuery();
  	scheduleGR.query();
  	return scheduleGR.getRowCount();
  },
  
  getSuggestedApplicationsCount: function(){
  	var processGroupGR = new GlideRecord("cmdb_process_groups");
  	processGroupGR.addNullQuery('classifier');
  	processGroupGR.query();
  	return processGroupGR.getRowCount();

  },
  isPredictivePluginInstalled : function(){
  	if (GlidePluginManager.isActive("com.glide.platform_ml")){
  		return 1;
  	}else{
  		return 0;
  	}
  },
  isDomainMaint : function(){
  	if ((gs.getUser().getDomainID() == null || gs.getUser().getDomainID() == "global") && gs.hasRole('discovery_admin')){
  		return 1;
  	} else {
  		return 0;
  	}
  },
  getHomepageStats: function() {
  	var result = {
  		'devices': this._getNumDiscoveredDevices(),
  		'errors': 0,
  		'totalIps': 0,
  		'unexploredIps': 0,
  		'unexploredIpSchedules': 0,
  		'errorsSchedules': 0,
  		'totalSchedules': 0,
  		'locations': 0,
  		'suggestedApplications':this.getSuggestedApplicationsCount(),
  		'predictivePluginInstalled':this.isPredictivePluginInstalled(),
  		'isDomainMaint':this.isDomainMaint(),
  		'serviceAccounts' : this.getServiceAccount().length,
  		'datacenters': this.getDataCentersCount()
  	};
  	
  	// get last completed result for each schedule for total count calc
  	var scheduleGr = new GlideRecord("discovery_schedule");
  	scheduleGr.addQuery('discover', 'CIs');
  	scheduleGr.addActiveQuery();
  	scheduleGr.query();
  	result.totalSchedules = scheduleGr.getRowCount() + this.getCloudResourceScheduleCount() - this.getVMScheduleCount();
  	var scheduleSysIds = [];
  	var unexplored;
  	// flag to check if we ever came across a schedule without an assigned location
  	var foundUnassignedSchedule = false;

  	// TODO: return error if no results ...

  	this.getScheduleLocations(result);
  	this.getIpAddressInfo(result);

  	return result;
  },

  getIpAddressInfo: function(result) {
  	// unexplored and total_ips for inactive results
  	var resultGr = new GlideAggregate("discovery_result");
  	resultGr.addEncodedQuery("sys_created_on>=javascript:gs.hoursAgo(24)");
  	resultGr.addNotNullQuery("schedule");
  	resultGr.addQuery("state","!=","active");
  	resultGr.addAggregate('SUM', 'n_active_nc_ips');
  	resultGr.addAggregate('SUM', 'n_total_ips');
  	resultGr.setGroup(false);
  	resultGr.query();

  	if (resultGr.next()) {
  	    // using a short-circuit || here to prevent returning NaN
  		result.unexploredIps += parseInt(resultGr.getAggregate('SUM', 'n_active_nc_ips')) || 0;
  		result.totalIps += parseInt(resultGr.getAggregate('SUM', 'n_total_ips')) || 0;
  	}

  	// unexplore and total_ips for active results
  	resultGr = new GlideRecord("discovery_result");
  	resultGr.addEncodedQuery("sys_created_on>=javascript:gs.hoursAgo(24)");
  	resultGr.addNotNullQuery("schedule");
  	resultGr.addQuery("state","active");
  	resultGr.addEncodedQuery("sys_created_on>=javascript:gs.hoursAgo(24)");
  	resultGr.query();

  	while (resultGr.next()) {
  		result.unexploredIps += parseInt(this.getInProgressStats(resultGr.getValue('sys_id')).unidentifiedIps) || 0;
  		result.totalIps += parseInt(resultGr.getValue('n_total_ips')) || 0;
  	}
  },

  getScheduleLocations: function(result) {
  	var gr = new GlideAggregate('discovery_schedule');
  	gr.addQuery('active', 'true');
  	gr.addNotNullQuery('location');
  	gr.addAggregate('COUNT(DISTINCT', 'location');
  	gr.setGroup(false);
  	gr.query();

  	if (gr.next()) {
  		result.locations = gr.getAggregate('COUNT(DISTINCT', 'location');
  	}
  },

  discoveryInProgress: function(status) {
  	if (!status)
  		return false;

  	var statusGr = new GlideRecord("discovery_status");
  	// GlideRecord.get 1 arg form falls back to 'name' if not found by sys_id, 2 arg form throws exception if arg2 is null.
  	statusGr.get('sys_id', status);
  	if (!statusGr) // TODO: should return error back to caller
  		return false;

  	if (statusGr.state != 'Completed' || statusGr.state != 'Canceled')
  		return true;

  	return false;
  },

  aggregateCount: function(categories) {
  	var count = 0;
  	categories.forEach(function(category) {count += category.errorNumber;});
  	return count;
  },

  getAllErrorsForStatus: function(status) {
  	var json = new JSON();
  	var errors = {};
  	var errManager = new sn_svcerr.ErrorMgrScript(this.DISCOVERY_APP);
  	var errCategories = errManager.getInstanceCategories(status);
  	var result = {list:[]};
  	errCategories.forEach(function(errCat) {
  		result.list.push({
  			id:                  errCat.getId(),
  			name:                errCat.getName(),
  			errorNumber:         errCat.getTotalErrors(),
  			icon:                errCat.getIcon()
  		});
  	});
  	errors.categories = result.list;
  	errors.count = this.aggregateCount(errors.categories);
  	return errors;
  },

  getScheduleErrorCount: function(schedule) {
  	var json = new JSON();
  	// TODO: this is used for sorting the schedules by error count.  This needs
  	// to be replaced by a query on one of the instance tables.
  	var count = SNC.DiscoveryErrorMessage.queryScheduleErrorCount(schedule) + '';
  	return parseInt(count, 10);
  },

  getInProgressStats: function(status) {
  	var dh = new GlideRecord("discovery_device_history");
  	dh.addQuery('status', status);
  	dh.query();

  	var result = {
  		'createdDevices': 0,
  		'updatedDevices': 0,
  		'unidentifiedIps': 0,
  		'duplicateIps': 0
  	};

  	while (dh.next()) {
  		switch ('' + dh.last_state) {
  			case "Updated CI":
  				result.updatedDevices += 1;
  				break;
  			case "Created CI":
  				result.createdDevices += 1;
  				break;
  			case "Identified, ignored extra IP":
  				result.duplicateIps += 1;
  				break;
  			default:
  				result.unidentifiedIps += 1;
  				break;
  		}
  	}

  	return result;
  },

  getCountOfSchedulesPerLocation: function() {
  	var scheduleLocationList = [];
  	var result;

  	var gr = new GlideAggregate('discovery_schedule');
  	gr.addQuery('discover', 'CIs');
  	gr.addAggregate('COUNT');
  	gr.addActiveQuery();
  	//gr.addNotNullQuery('location');
  	gr.groupBy('location');
  	gr.query();

  	while(gr.next()) {
  		scheduleLocationList.push({
  			locationId: gr.location+'',
  			location: gr.location.name+'',
  			count: gr.getAggregate('COUNT')
  		});
  	}

  	return scheduleLocationList;
  },

  getScheduleResults: function(scheduleType, searchTerm, sortBy, filteredSchedules) {
  	var schedulesObj = this.getSchedules(scheduleType, searchTerm, sortBy, filteredSchedules);

  	if (JSUtil.nil(schedulesObj) || JSUtil.nil(schedulesObj.schedules))
  		return {};

  	// grab up to last (this.MAX_RESULTS) results for each schedule
  	var schedule;
  	for (var i = 0; i < schedulesObj.schedules.length; i++) {
  		schedule = schedulesObj.schedules[i];
  		schedule.results = this.getResultsForSchedule(schedule.sysID, this.MAX_RESULTS);
  	}

  	// add the count of schedules per type info
  	schedulesObj.scheduleLocations = this.getCountOfSchedulesPerLocation();
  	schedulesObj.totalSchedulesCount = this.getTotalSchedulesCount();

  	return schedulesObj;
  },
  
  getTotalSchedulesCount: function() {
  	var gr = new GlideRecord('discovery_schedule');
  	gr.query();
  	
  	return gr.getRowCount();
  },

  getSchedules: function(locationId, searchTerm, sortBy, filteredSchedules) {
  	var scheduleGr = new GlideRecord('discovery_schedule');
  	var lastResult;
  	var sortedSchedules;
  	
  	// set up schedule query with initial filtering
  	if (locationId) {
  		if (locationId == 'unassigned')
  			scheduleGr.addNullQuery('location');
  		else
  			scheduleGr.addQuery('location', locationId);
  	}
  	if (searchTerm)
  		scheduleGr.addQuery('name', 'CONTAINS', searchTerm);
  	if (filteredSchedules)
  		scheduleGr.addQuery('sys_id', 'NOT IN', filteredSchedules);

  	// we only care about 'Configuration Item' type discoveries
  	scheduleGr.addQuery('discover', 'CIs');
  	scheduleGr.addActiveQuery();
  	// add sorting for the filtered schedules
  	if (!sortBy || sortBy == 'alphabetical') {
  		scheduleGr.orderBy('name');	
  		scheduleGr.setLimit(this.SCHEDULE_WINDOW);
  		scheduleGr.query();
  	} else if (sortBy == 'errors') {
  		scheduleGr.query();
  		var schedulesToErrorMap = {};
  		while (scheduleGr.next()) {
  			var errCount = this.getScheduleErrorCount(scheduleGr.sys_id + '');
  			schedulesToErrorMap[scheduleGr.sys_id +''] = errCount;
  		}

  		// sort in descending order
  		sortedSchedules = Object.keys(schedulesToErrorMap).sort(function(a, b) {
  			return schedulesToErrorMap[b] - schedulesToErrorMap[a];
  		});

  		scheduleGr = new GlideRecord('discovery_schedule');
  		scheduleGr.addQuery('sys_id', 'IN', sortedSchedules.slice(0, this.SCHEDULE_WINDOW));
  		scheduleGr.query();
  	} else if (sortBy == 'unidentified') {
  		scheduleGr.query();
  		var schedulesToUnidentifiedMap = {};
  		while (scheduleGr.next()) {
  			lastResult = this.getLastResultForSchedule(scheduleGr);
  			schedulesToUnidentifiedMap[scheduleGr.sys_id +''] = ((lastResult && parseInt(lastResult.n_active_nc_ips+'')) || 0);
  		}

  		// sort in descending order
  		sortedSchedules = Object.keys(schedulesToUnidentifiedMap).sort(function(a, b) {
  			return schedulesToUnidentifiedMap[b] - schedulesToUnidentifiedMap[a];
  		});

  		scheduleGr = new GlideRecord('discovery_schedule');
  		scheduleGr.addQuery('sys_id', 'IN', sortedSchedules.slice(0, this.SCHEDULE_WINDOW));
  		scheduleGr.query();
  	} else if (sortBy == 'running') {
  		var runningSchedulesList = this.getRunningSchedulesList();
  		scheduleGr.addQuery('sys_id', 'IN', runningSchedulesList);
  		scheduleGr.setLimit(this.SCHEDULE_WINDOW);
  		scheduleGr.query();
  	}

  	var result = {
  		total: scheduleGr.getRowCount(),
  		schedules: this._getSchedulesAsList(scheduleGr)
  	};

  	return result;
  },
  
  getRunningSchedulesList: function() {
  	var runningSchedulesMap = {};
  	var runningSchedulesList = [];
  	var scheduleSysId;

  	var statusGr = new GlideRecord('discovery_status');
  	statusGr.addQuery('state', 'IN', 'starting,active');
  	statusGr.query();
  	while (statusGr.next()) {
  		scheduleSysId = statusGr.dscheduler+'';
  		if (!runningSchedulesMap[scheduleSysId]) {
  			runningSchedulesMap[scheduleSysId] = true;
  			runningSchedulesList.push(scheduleSysId);
  		}
  	}

  	return runningSchedulesList;
  },

  getLastResultForSchedule: function(schedule) {
  	var resultGr = new GlideRecord('discovery_result');
  	resultGr.addQuery('schedule', schedule.sys_id +'');
  	resultGr.addQuery('state', 'Completed');
  	resultGr.orderByDesc('sys_created_on');
  	resultGr.setLimit(1);
  	resultGr.query();

  	if (!resultGr.hasNext())
  		return null;
  	
  	resultGr.next();
  	return resultGr;
  },

  getAllSchedules: function() {
  	var scheduleGr = new GlideRecord("discovery_schedule");
  	scheduleGr.orderByDesc("sys_created_on");
  	scheduleGr.query();

  	return this._getSchedulesAsList(scheduleGr);
  },
  
  refreshResults: function(schedules) {
  	if (!schedules)
  		return {};
  	
  	var result = {};
  	for (var i = 0; i < schedules.length; i++) {
  		result[schedules[i]] = this.getResultsForSchedule(schedules[i]);
  	}

  	return result;
  },

  getResultsForSchedule: function(scheduleId, limit) {
  	var isInProgress = false;
  	var resultGr = new GlideRecord("discovery_result");
  	resultGr.addQuery("schedule", scheduleId);
  	resultGr.orderByDesc("sys_created_on");
  	limit = limit || this.MAX_RESULTS;
  	resultGr.setLimit(limit);
  	resultGr.query();

  	var resultList = [];
  	var result, createdDevices, updatedDevices,
  		unidentifiedIps, duplicateIps, inProgResults;
  	while (resultGr.next()) {
  		// check if discovery in progress then grab data from device history
  		if (resultGr.state+'' === 'Active') {
  			isInProgress = true;
  			inProgResults = this.getInProgressStats(resultGr.status +'');
  			createdDevices = inProgResults.createdDevices;
  			updatedDevices = inProgResults.updatedDevices;
  			unidentifiedIps = inProgResults.unidentifiedIps;
  			duplicateIps = inProgResults.duplicateIps;
  		} else {
  			createdDevices = resultGr.n_created_devices +'';
  			updatedDevices = resultGr.n_updated_devices +'';
  			unidentifiedIps = resultGr.n_active_nc_ips +'';
  			duplicateIps = resultGr.n_duplicate_ips +'';
  		}

  		resultList.push({
  			sysId: resultGr.sys_id +'',
  			schedule: scheduleId,
  			status:  resultGr.status +'',
  			totalIps: resultGr.n_total_ips +'',
  			aliveIps: resultGr.n_alive_ips +'',
  			deadIps: resultGr.n_dead_ips +'',
  			activeIps: resultGr.n_active_ips +'',
  			unidentifiedIps: unidentifiedIps,
  			duplicateIps: duplicateIps,
  			createdDevices: createdDevices,
  			updatedDevices:  updatedDevices,
  			errors: this.getAllErrorsForStatus(resultGr.status +''),
  			started: resultGr.getDisplayValue('sys_created_on'),
  			startedDate: resultGr.started.getDisplayValue() +'',
  			ended: this.getUpdatedTime(isInProgress, scheduleId, resultGr),
  			state: resultGr.state +''
  		});
  	}

  	return resultList;
  },
  
  getUpdatedTime: function(isInProgress, scheduleId, resultGr){
  	if (isInProgress) {
  		var statusGr = new GlideRecord('discovery_status');
  		statusGr.addQuery('dscheduler', scheduleId);
  		statusGr.addQuery('sys_id', resultGr.status +'');
  		statusGr.query();
  		if (statusGr.next())
  			return statusGr.getDisplayValue('sys_updated_on');

  	}
  	return resultGr.getDisplayValue('sys_updated_on');
  },

  _getSchedulesAsList: function(scheduleGr) {
  	var scheduleList = [];
  	var schedule;
  	while (scheduleGr.next()) {
  		schedule = {};
  		schedule.sysID = scheduleGr.sys_id +'';
  		schedule.name = scheduleGr.name +'';
  		schedule.type = scheduleGr.discover.getDisplayValue() +'';
  		schedule.discoRunType = scheduleGr.disco_run_type +'';
  		if (schedule.discoRunType == 'after_discovery' && JSUtil.notNil(scheduleGr.run_after))
  			schedule.runAfter = scheduleGr.run_after.name;
  		schedule.runDayOfMonth = scheduleGr.run_dayofmonth +'';
  		schedule.runDayOfWeek = scheduleGr.run_dayofweek +'';
  		schedule.runPeriod = scheduleGr.run_period +'';
  		schedule.runTime = scheduleGr.run_time.getDisplayValue() +'';
  		schedule.runStart = scheduleGr.run_start.getDisplayValue() +'';
  		schedule.locationID = scheduleGr.location +'';
  		schedule.location = scheduleGr.location.name +'';
  		schedule.active = !!scheduleGr.active;

  		scheduleList.push(schedule);
  	}

  	return scheduleList;
  },

  getCloudResourcesChartInfo: function() {
  	return this.cloudResourceChartInfo;
  },

  type: 'DiscoveryResultManager'
};

Sys ID

3fd4f6d2c3473200e412bea192d3ae6a

Offical Documentation

Official Docs: