Collaboration Store, Part LXIV

“Optimism is an occupational hazard of programming: feedback is the treatment.”
Kent Beck

Last time, we wrapped up the last of the refactoring for all of the features that push artifacts from one instance to another. Although that covers the majority of the REST API calls, there are still a few remaining functions that make REST API calls of their own, and we want to have those calls logged just like all of the others in the shared functions. The first of those is the getStoreInfo function in the CollaborationStoreUtils Script Include.

getStoreInfo: function(host) {
	var result = {};

	var request  = new sn_ws.RESTMessageV2();
	request.setHttpMethod('get');
	request.setEndpoint('https://' + host + '.service-now.com/api/x_11556_col_store/v1/info');
	var response = request.execute();
	result.responseCode = response.getStatusCode();
	if (response.haveError()) {
		result.error = response.getErrorMessage();
		result.errorCode = response.getErrorCode();
		result.body = response.getBody();
	} else if (result.responseCode == '200') {
		result.storeInfo = JSON.parse(response.getBody());
		if (result.storeInfo.result.status == 'success') {
			result.name = result.storeInfo.result.info.name;
			var csgu = new global.CollaborationStoreGlobalUtils();
			csgu.setProperty('x_11556_col_store.active_token', result.storeInfo.result.info.sys_id);
		} else {
			result.error = 'This instance is not a Host instance';
		}
	} else {
		result.error = 'Invalid HTTP Response Code: ' + result.responseCode;
		result.body = response.getBody();
	}

	return result;
}

It shouldn’t be too difficult to rework the code a little bit to adopt the standard result object that the logging function is expecting. The main problem with this particular function is timing: we need to pass the GlideRecord of the target instance to the logging function, but we are calling the getStoreInfo function so that we can get the data needed to create the GlideRecord for the Host instance. At the moment that we are making the call, the GlideRecord for the Host instance does not yet exist. Since we do not yet have a Host instance GlideRecord to pass, we will have to pass null, but we will also have to modify the logging function to handle that possibility. Here is the refactored getStoreInfo function:

getStoreInfo: function(host) {
	var result = {};

	result.url = 'https://' + host + '.service-now.com/api/x_11556_col_store/v1/info';
	result.method = 'GET';
	var request = new sn_ws.RESTMessageV2();
	request.setEndpoint(result.url);
	request.setHttpMethod(result.method);
	request.setRequestHeader('Content-Type', 'application/json');
	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();
		}
	}
	result.error = response.haveError();
	if (result.error) {
		result.error_code = response.getErrorCode();
		result.error_message = response.getErrorMessage();
	} else if (result.obj) {
		if (result.obj.result.status == 'success') {
			result.name = result.obj.result.info.name;
			var csgu = new global.CollaborationStoreGlobalUtils();
			csgu.setProperty('x_11556_col_store.active_token', result.obj.result.info.sys_id);
		} else {
			result.error = true;
			result.error_code = '99';
			result.error_message = 'This instance is not a Host instance';
		}
	}
	this.logRESTCall(null, result);

	return result;
}

To avoid a null pointer exception in the logging function, we need to add a check for the target instance GlideRecord before we attempt to snag its sys_id.

logRESTCall: function (targetGR, result, payload) {
	var logGR = new GlideRecord('x_11556_col_store_rest_api_log');
	if (targetGR) {
		logGR.instance = targetGR.getUniqueValue();
	}
	...
}

Finally, to correct the log records once the Host instance record has been created in the set-up process, we can call this simple function:

function fixLogRecords(targetGR) {
	var logGR = new GlideRecord('x_11556_col_store_rest_api_log');
	logGR.addQuery('instance', null);
	logGR.query();
	while (logGR.next()) {
		logGR.instance = targetGR.getUniqueValue();
		logGR.update();
	}
}

Basically, it just looks for any log records that do not have a target instance value and updates them with the new Host instance record’s sys_id. That should take care of that.

There is yet another REST API call made before the Host record is created and that one is in the registerWithHost function. Here is the current version:

registerWithHost: function(mbrGR) {
	var result = {};

	this.createUpdateWorker(mbrGR.getUniqueValue());
	var host = gs.getProperty('x_11556_col_store.host_instance');
	var token = gs.getProperty('x_11556_col_store.active_token');
	var payload = {};
	payload.sys_id = mbrGR.getUniqueValue();
	payload.name = mbrGR.getDisplayValue('name');
	payload.instance = mbrGR.getDisplayValue('instance');
	payload.email = mbrGR.getDisplayValue('email');
	payload.description = mbrGR.getDisplayValue('description');
	var request = new sn_ws.RESTMessageV2();
	request.setHttpMethod('post');
	request.setBasicAuth(this.WORKER_ROOT + host, token);
	request.setRequestHeader("Accept", "application/json");
	request.setEndpoint('https://' + host + '.service-now.com/api/x_11556_col_store/v1/register');
	request.setRequestBody(JSON.stringify(payload));
	var response = request.execute();
	result.responseCode = response.getStatusCode();
	result.bodyText = response.getBody();
	try {
		result.body = JSON.parse(response.getBody());
	} catch(e) {
		//
	}
	if (response.getErrorCode()) {
		result.error = response.getErrorMessage();
		result.errorCode = response.getErrorCode();
	} else if (result.responseCode != '202') {
		result.error = 'Invalid HTTP Response Code: ' + result.status;
	} else {
		mbrGR.accepted = new GlideDateTime();
		mbrGR.update();
	}

	return result;
}

Once again, we will need to rework this a little bit to adopt the standard result object that the logging function is expecting, and will have to pass null for the target instance GlideRecord, as that record has still not been created at this point in the process.

registerWithHost: function(mbrGR) {
	var result = {};

	this.createUpdateWorker(mbrGR.getUniqueValue());
	var payload = {};
	payload.sys_id = mbrGR.getUniqueValue();
	payload.name = mbrGR.getDisplayValue('name');
	payload.instance = mbrGR.getDisplayValue('instance');
	payload.email = mbrGR.getDisplayValue('email');
	payload.description = mbrGR.getDisplayValue('description');
	var host = gs.getProperty('x_11556_col_store.host_instance');
	result.url = 'https://' + host + '.service-now.com/api/x_11556_col_store/v1/register';
	result.method = 'POST';
	var request = new sn_ws.RESTMessageV2();
	request.setEndpoint(result.url);
	request.setHttpMethod(result.method);
	request.setBasicAuth(this.WORKER_ROOT + host, gs.getProperty('x_11556_col_store.active_token'));
	request.setRequestHeader("Accept", "application/json");
	request.setRequestBody(JSON.stringify(payload));
	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();
		}
	}
	result.error = response.haveError();
	if (result.error) {
		result.error_code = response.getErrorCode();
		result.error_message = response.getErrorMessage();
	} else if (result.status != '202') {
		result.error = true;
		result.error_code = result.status;
		result.error_message = 'Invalid HTTP Response Code: ' + result.status;
	} else {
		mbrGR.accepted = new GlideDateTime();
		mbrGR.update();
	}
	this.logRESTCall(null, result);

	return result;
}

That should take care of all of the REST API calls in the CollaborationStoreUtils Script Include. There were never any REST API calls in the ApplicationInstaller Script Include, and we just removed all of the REST API calls in the ApplicationPublishers Script Include, but there are still some remaining in the InstanceSyncUtils, so we will need to take a look at those. That looks like a little bit of an effort, though, so let’s save that for our next installment.