Collaboration Store, Part LXV

“There is no such thing as completion. These are only stages in an endless progression. There are no final outcomes or decisions, since nothing ever stays the same.”
Frederick Lenz

Last time, we finished adding the logging process to the remaining REST API calls in the CollaborationStoreUtils Script Include. Now we need to do the same thing for all of the remaining REST API calls in the InstanceSyncUtils Script Include. Here is the first one as it stands right now.

syncInstances: function(targetGR, instanceList) {
	var request  = new sn_ws.RESTMessageV2();
	request.setHttpMethod('get');
	request.setBasicAuth(this.CSU.WORKER_ROOT + targetGR.getDisplayValue('instance'), targetGR.getDisplayValue('token'));
	request.setRequestHeader("Accept", "application/json");
	request.setEndpoint('https://' + targetGR.getDisplayValue('instance') + '.service-now.com/api/now/table/x_11556_col_store_member_organization?sysparm_fields=instance%2Csys_id');
	var response = request.execute();
	if (response.haveError()) {
		gs.error('InstanceSyncUtils.syncInstance - Error returned from attempt to fetch instance list from instance ' + targetGR.getDisplayValue('instance') + ': ' + response.getErrorCode() + ' - ' + response.getErrorMessage());
	} else if (response.getStatusCode() == '200') {
		var jsonString = response.getBody();
		var jsonObject = {};
		try {
			jsonObject = JSON.parse(jsonString);
		} catch (e) {
			gs.error('InstanceSyncUtils.syncInstance - Unparsable JSON string returned from attempt to fetch instance list: ' + jsonString);
		}
		if (Array.isArray(jsonObject.result)) {
			for (var i=0; i<instanceList.length; i++) {
				var thisInstance = instanceList[i];
				var remoteSysId = '';
				for (var j=0; j<jsonObject.result.length && remoteSysId == ''; j++) {
					if (jsonObject.result[j].instance == thisInstance) {
						remoteSysId = jsonObject.result[j].sys_id;
					}
				}
				if (remoteSysId == '') {
					remoteSysId = this.sendInstance(targetGR, thisInstance);
				}
				this.syncApplications(targetGR, thisInstance, remoteSysId);
			}
		} else {
			gs.error('InstanceSyncUtils.syncInstance - Invalid response body returned from attempt to fetch instance list: ' + response.getBody());
		}
	} else {
		gs.error('InstanceSyncUtils.syncInstance - Invalid HTTP response code returned from attempt to fetch instance list: ' + response.getStatusCode());
	}
}

Up to this point, we have always called the logging routine just before we returned the result object. In the above function, however, we call other functions that also make their own REST API calls, so it would be preferable to log this call before calling any other function that might make a call of its own. Because of this, not only will we need to restructure the code to build the result object that the logging function is expecting, we will also need to make the call to the logging function prior to making the call to the other functions in the instance sync process. To begin, we will build the result object in the normal manner by populating the url and method properties, and then using those values to populate the sn_ws.RESTMessageV2 object.

var result = {};
result.url = 'https://' + targetGR.getDisplayValue('instance') + '.service-now.com/api/now/table/x_11556_col_store_member_organization?sysparm_fields=instance%2Csys_id';
result.method = 'GET';
var request = new sn_ws.RESTMessageV2();
request.setEndpoint(result.url);
request.setHttpMethod(result.method);
request.setBasicAuth(this.WORKER_ROOT + targetGR.getDisplayValue('instance'), targetGR.getDisplayValue('token'));
request.setRequestHeader('Accept', 'application/json');

Once the sn_ws.RESTMessageV2 is fully populated, we can then obtain the response object by executing the call and then continue populating the result object with the values returned in the response.

var response = request.execute();
result.status = response.getStatusCode();
result.body = response.getBody();
if (result.body) {
	try {
		result.obj = JSON.parse(result.body);
	} catch (e) {
		result.parse_error = e.toString();
		gs.error('InstanceSyncUtils.syncInstance - Unparsable JSON string returned from attempt to fetch instance list: ' + result.body);
	}
}
result.error = response.haveError();
if (result.error) {
	result.error_code = response.getErrorCode();
	result.error_message = response.getErrorMessage();
	gs.error('InstanceSyncUtils.syncInstance - Error returned from attempt to fetch instance list from instance ' + targetGR.getDisplayValue('instance') + ': ' + result.error_code + ' - ' + result.error_message);
} else if (result.status != '200') {
	gs.error('InstanceSyncUtils.syncInstance - Invalid HTTP response code returned from attempt to fetch instance list: ' + result.status);
}

Now that the result object is fully populated, we can go ahead and make the call to the logging function before calling the other functions involved in the instance sync process.

this.logRESTCall(targetGR, result);
if (!result.error && result.status == '200' && result.obj) {
	if (Array.isArray(result.obj.result)) {
		for (var i=0; i<instanceList.length; i++) {
			var thisInstance = instanceList[i];
			var remoteSysId = '';
			for (var j=0; j<result.obj.result.length && remoteSysId == ''; j++) {
				if (result.obj.result[j].instance == thisInstance) {
					remoteSysId = result.obj.result[j].sys_id;
				}
			}
			if (remoteSysId == '') {
				remoteSysId = this.sendInstance(targetGR, thisInstance);
			}
			this.syncApplications(targetGR, thisInstance, remoteSysId);
		}
	} else {
		gs.error('InstanceSyncUtils.syncInstance - Invalid response body returned from attempt to fetch instance list: ' + result.body);
	}
}

Putting it all together, the entire new function now looks like this.

syncInstances: function(targetGR, instanceList) {
	var result = {};
	result.url = 'https://' + targetGR.getDisplayValue('instance') + '.service-now.com/api/now/table/x_11556_col_store_member_organization?sysparm_fields=instance%2Csys_id';
	result.method = 'GET';
	var request = new sn_ws.RESTMessageV2();
	request.setEndpoint(result.url);
	request.setHttpMethod(result.method);
	request.setBasicAuth(this.WORKER_ROOT + targetGR.getDisplayValue('instance'), targetGR.getDisplayValue('token'));
	request.setRequestHeader('Accept', 'application/json');
	var response = request.execute();
	result.status = response.getStatusCode();
	result.body = response.getBody();
	if (result.body) {
		try {
			result.obj = JSON.parse(result.body);
		} catch (e) {
			result.parse_error = e.toString();
			gs.error('InstanceSyncUtils.syncInstance - Unparsable JSON string returned from attempt to fetch instance list: ' + result.body);
		}
	}
	result.error = response.haveError();
	if (result.error) {
		result.error_code = response.getErrorCode();
		result.error_message = response.getErrorMessage();
		gs.error('InstanceSyncUtils.syncInstance - Error returned from attempt to fetch instance list from instance ' + targetGR.getDisplayValue('instance') + ': ' + result.error_code + ' - ' + result.error_message);
	} else if (result.status != '200') {
		gs.error('InstanceSyncUtils.syncInstance - Invalid HTTP response code returned from attempt to fetch instance list: ' + result.status);
	}
	this.logRESTCall(targetGR, result);
	if (!result.error && result.status == '200' && result.obj) {
		if (Array.isArray(result.obj.result)) {
			for (var i=0; i<instanceList.length; i++) {
				var thisInstance = instanceList[i];
				var remoteSysId = '';
				for (var j=0; j<result.obj.result.length && remoteSysId == ''; j++) {
					if (result.obj.result[j].instance == thisInstance) {
						remoteSysId = result.obj.result[j].sys_id;
					}
				}
				if (remoteSysId == '') {
					remoteSysId = this.sendInstance(targetGR, thisInstance);
				}
				this.syncApplications(targetGR, thisInstance, remoteSysId);
			}
		} else {
			gs.error('InstanceSyncUtils.syncInstance - Invalid response body returned from attempt to fetch instance list: ' + result.body);
		}
	}
}

That takes care of the syncInstances function. Now we need to do the same with the syncApplications function, which currently look like this.

syncApplications: function(targetGR, thisInstance, remoteSysId) {
	var applicationList = [];
	var applicationGR = new GlideRecord('x_11556_col_store_member_application');
	applicationGR.addQuery('provider.instance', thisInstance);
	applicationGR.query();
	while (applicationGR.next()) {
		applicationList.push(applicationGR.getDisplayValue('name'));
	}
	if (applicationList.length > 0) {
		var request  = new sn_ws.RESTMessageV2();
		request.setHttpMethod('get');
		request.setBasicAuth(this.CSU.WORKER_ROOT + targetGR.getDisplayValue('instance'), targetGR.getDisplayValue('token'));
		request.setRequestHeader("Accept", "application/json");
		request.setEndpoint('https://' + targetGR.getDisplayValue('instance') + '.service-now.com/api/now/table/x_11556_col_store_member_application?sysparm_fields=name%2Csys_id&sysparm_query=provider%3D' + remoteSysId);
		var response = request.execute();
		if (response.haveError()) {
			gs.error('InstanceSyncUtils.syncApplications - Error returned from attempt to fetch application list: ' + response.getErrorCode() + ' - ' + response.getErrorMessage());
		} else if (response.getStatusCode() == '200') {
			var jsonString = response.getBody();
			var jsonObject = {};
			try {
				jsonObject = JSON.parse(jsonString);
			} catch (e) {
				gs.error('InstanceSyncUtils.syncApplications - Unparsable JSON string returned from attempt to fetch application list: ' + jsonString);
			}
			if (Array.isArray(jsonObject.result)) {
				for (var i=0; i<applicationList.length; i++) {
					var thisApplication = applicationList[i];
					var remoteAppId = '';
					for (var j=0; j<jsonObject.result.length && remoteAppId == ''; j++) {
						if (jsonObject.result[j].name == thisApplication) {
							remoteAppId = jsonObject.result[j].sys_id;
						}
					}
					if (remoteAppId == '') {
						remoteAppId = this.sendApplication(targetGR, thisApplication, thisInstance, remoteSysId);
					}
					this.syncVersions(targetGR, thisApplication, thisInstance, remoteAppId);
				}
			} else {
				gs.error('InstanceSyncUtils.syncApplications - Invalid response body returned from attempt to fetch application list: ' + response.getBody());
			}
		} else {
			gs.error('InstanceSyncUtils.syncApplications - Invalid HTTP response code returned from attempt to fetch application list: ' + response.getStatusCode());
		}
	} else {
		gs.info('InstanceSyncUtils.syncApplications - No applications to sync for instance ' + thisInstance);
	}
}

Using the same restructuring approach, we can convert the function to this.

syncApplications: function(targetGR, thisInstance, remoteSysId) {
	var applicationList = [];
	var applicationGR = new GlideRecord('x_11556_col_store_member_application');
	applicationGR.addQuery('provider.instance', thisInstance);
	applicationGR.query();
	while (applicationGR.next()) {
		applicationList.push(applicationGR.getDisplayValue('name'));
	}
	if (applicationList.length > 0) {
		var result = {};
		result.url = 'https://' + targetGR.getDisplayValue('instance') + '.service-now.com/api/now/table/x_11556_col_store_member_application?sysparm_fields=name%2Csys_id&sysparm_query=provider%3D' + remoteSysId;
		result.method = 'GET';
		var request = new sn_ws.RESTMessageV2();
		request.setEndpoint(result.url);
		request.setHttpMethod(result.method);
		request.setBasicAuth(this.WORKER_ROOT + targetGR.getDisplayValue('instance'), targetGR.getDisplayValue('token'));
		request.setRequestHeader('Accept', 'application/json');
		var response = request.execute();
		result.status = response.getStatusCode();
		result.body = response.getBody();
		if (result.body) {
			try {
				result.obj = JSON.parse(result.body);
			} catch (e) {
				result.parse_error = e.toString();
				gs.error('InstanceSyncUtils.syncApplications - Unparsable JSON string returned from attempt to fetch application list from instance ' + targetGR.getDisplayValue('instance') + ': ' + result.body);
			}
		}
		result.error = response.haveError();
		if (result.error) {
			result.error_code = response.getErrorCode();
			result.error_message = response.getErrorMessage();
			gs.error('InstanceSyncUtils.syncApplications - Error returned from attempt to fetch application list from instance ' + targetGR.getDisplayValue('instance') + ': ' + result.error_code + ' - ' + result.error_message);
		} else if (result.status != '200') {
			gs.error('InstanceSyncUtils.syncApplications - Invalid HTTP response code returned from attempt to fetch application list from instance ' + targetGR.getDisplayValue('instance') + ': ' + result.status);
		}
		this.logRESTCall(targetGR, result);
		if (!result.error && result.status == '200' && result.obj) {
			if (Array.isArray(result.obj.result)) {
				for (var i=0; i<applicationList.length; i++) {
					var thisApplication = applicationList[i];
					var remoteAppId = '';
					for (var j=0; j<result.obj.result.length && remoteAppId == ''; j++) {
						if (result.obj.result[j].name == thisApplication) {
							remoteAppId = result.obj.result[j].sys_id;
						}
					}
					if (remoteAppId == '') {
						remoteAppId = this.sendApplication(targetGR, thisApplication, thisInstance, remoteSysId);
					}
					this.syncVersions(targetGR, thisApplication, thisInstance, remoteAppId);
				}
			} else {
				gs.error('InstanceSyncUtils.syncApplications - Invalid response body returned from attempt to fetch application list from instance ' + targetGR.getDisplayValue('instance') + ': ' + result.body);
			}
		}
	} else {
		gs.info('InstanceSyncUtils.syncApplications - No applications to sync for instance ' + thisInstance);
	}
}

We can repeat this same refactoring exercise for the two other similar functions, syncVersions and syncAttachments, which now look like this.

syncVersions: function(targetGR, thisApplication, thisInstance, remoteAppId) {
	var versionList = [];
	var versionIdList = [];
	var versionGR = new GlideRecord('x_11556_col_store_member_application_version');
	versionGR.addQuery('member_application.name', thisApplication);
	versionGR.addQuery('member_application.provider.instance', thisInstance);
	versionGR.query();
	while (versionGR.next()) {
		versionList.push(versionGR.getDisplayValue('version'));
		versionIdList.push(versionGR.getUniqueValue());
	}
	if (versionList.length > 0) {
		result.url = 'https://' + targetGR.getDisplayValue('instance') + '.service-now.com/api/now/table/x_11556_col_store_member_application_version?sysparm_fields=version%2Csys_id&sysparm_query=member_application%3D' + remoteAppId;
		result.method = 'GET';
		var request = new sn_ws.RESTMessageV2();
		request.setEndpoint(result.url);
		request.setHttpMethod(result.method);
		request.setBasicAuth(this.WORKER_ROOT + targetGR.getDisplayValue('instance'), targetGR.getDisplayValue('token'));
		request.setRequestHeader('Accept', 'application/json');
		var response = request.execute();
		result.status = response.getStatusCode();
		result.body = response.getBody();
		if (result.body) {
			try {
				result.obj = JSON.parse(result.body);
			} catch (e) {
				result.parse_error = e.toString();
				gs.error('InstanceSyncUtils.syncVersions - Unparsable JSON string returned from attempt to fetch version list from instance ' + targetGR.getDisplayValue('instance') + ': ' + result.body);
			}
		}
		result.error = response.haveError();
		if (result.error) {
			result.error_code = response.getErrorCode();
			result.error_message = response.getErrorMessage();
			gs.error('InstanceSyncUtils.syncVersions - Error returned from attempt to fetch version list from instance ' + targetGR.getDisplayValue('instance') + ': ' + result.error_code + ' - ' + result.error_message);
		} else if (result.status != '200') {
			gs.error('InstanceSyncUtils.syncVersions - Invalid HTTP response code returned from attempt to fetch version list from instance ' + targetGR.getDisplayValue('instance') + ': ' + result.status);
		}
		this.logRESTCall(targetGR, result);
		if (!result.error && result.status == '200' && result.obj) {
			if (Array.isArray(result.obj.result)) {
				for (var i=0; i<versionList.length; i++) {
					var thisVersion = versionList[i];
					var thisVersionId = versionIdList[i];
					var remoteVerId = '';
					for (var j=0; j<result.obj.result.length && remoteVerId == ''; j++) {
						if (result.obj.result[j].version == thisVersion) {
							remoteVerId = result.obj.result[j].sys_id;
						}
					}
					if (remoteVerId == '') {
						remoteVerId = this.sendVersion(targetGR, thisVersion, thisApplication, thisInstance, remoteAppId);
					}
					this.syncAttachments(targetGR, thisVersionId, thisVersion, thisApplication, thisInstance, remoteVerId);
				}
			} else {
				gs.error('InstanceSyncUtils.syncVersions - Invalid response body returned from attempt to fetch version list from instance ' + targetGR.getDisplayValue('instance') + ': ' + result.body);
			}
		}
	} else {
		gs.info('InstanceSyncUtils.syncVersions - No versions to sync for application ' + thisApplication + ' on instance ' + thisInstance);
	}
}
syncAttachments: function(targetGR, thisVersionId, thisVersion, thisApplication, thisInstance, remoteVerId) {
	var attachmentList = [];
	var attachmentGR = new GlideRecord('sys_attachment');
	attachmentGR.addQuery('table_name', 'x_11556_col_store_member_application_version');
	attachmentGR.addQuery('table_sys_id', thisVersionId);
	attachmentGR.addQuery('content_type', 'CONTAINS', 'xml');
	attachmentGR.query();
	while (attachmentGR.next()) {
		attachmentList.push(attachmentGR.getUniqueValue());
	}
	if (attachmentList.length > 0) {
		var result = {};
		result.url = 'https://' + targetGR.getDisplayValue('instance') + '.service-now.com/api/now/table/sys_attachment?sysparm_fields=sys_id&sysparm_query=table_name%3Dx_11556_col_store_member_application_version%5Etable_sys_id%3D' + remoteVerId + '%5Econtent_typeCONTAINSxml';
		result.method = 'GET';
		var request = new sn_ws.RESTMessageV2();
		request.setEndpoint(result.url);
		request.setHttpMethod(result.method);
		request.setBasicAuth(this.WORKER_ROOT + targetGR.getDisplayValue('instance'), targetGR.getDisplayValue('token'));
		request.setRequestHeader('Accept', 'application/json');
		var response = request.execute();
		result.status = response.getStatusCode();
		result.body = response.getBody();
		if (result.body) {
			try {
				result.obj = JSON.parse(result.body);
			} catch (e) {
				result.parse_error = e.toString();
				gs.error('InstanceSyncUtils.syncAttachments - Unparsable JSON string returned from attempt to fetch attachment list from instance ' + targetGR.getDisplayValue('instance') + ': ' + result.body);
			}
		}
		result.error = response.haveError();
		if (result.error) {
			result.error_code = response.getErrorCode();
			result.error_message = response.getErrorMessage();
			gs.error('InstanceSyncUtils.syncAttachments - Error returned from attempt to fetch attachment list from instance ' + targetGR.getDisplayValue('instance') + ': ' + result.error_code + ' - ' + result.error_message);
		} else if (result.status != '200') {
			gs.error('InstanceSyncUtils.syncAttachments - Invalid HTTP response code returned from attempt to fetch attachment list from instance ' + targetGR.getDisplayValue('instance') + ': ' + result.status);
		}
		this.logRESTCall(targetGR, result);
		if (!result.error && result.status == '200' && result.obj) {
			if (Array.isArray(result.obj.result)) {
				if (result.obj.result.length == 0) {
					this.sendAttachment(targetGR, attachmentList[0], remoteVerId, thisVersion, thisApplication);
				}
			} else {
				gs.error('InstanceSyncUtils.syncAttachments - Invalid response body returned from attempt to fetch attachment list: ' + result.body);
			}
		}
	} else {
		gs.info('InstanceSyncUtils.syncAttachments - No attachments to sync for version ' + thisVersionId + ' of application ' + thisApplication + ' on instance ' + thisInstance);
	}
}

That should take care of all of the REST API calls in all of the Script Includes in the application. Now every call will be recorded in the new table and linked to the instance to which the call was made. With the completion of the work on the images and the logging, it is about time to create yet another Update Set and turn it over to the testers for some serious regression testing. Before we do that, though, it would probably be a good idea to try all of this out ourselves and make sure that it all works. Let’s jump right into that next time out.