Collaboration Store, Part XXVIII

“The best way out is always through.”
Robert Frost

Now that we have completed the functions that send the application record and version record over to the Host instance, the last thing that we need to do is to send over the Update Set XML file attached to the version record. For sending over an attachment, we will need to use the attachment REST API instead of the standard table REST API. Since the contents of the file that we are sending over is plain text, we can use a little hackery to bypass the need to send over an actual file and just place the text content in the body of the request. But first, as usual, we need to grab the GlideRecord that we want to send before we do anything else.

var gsa = new GlideSysAttachment();
var sysAttGR = new GlideRecord('sys_attachment');
if (sysAttGR.get(answer.attachmentId)) {
	...
} else {
	answer = this.processError(answer, 'Invalid attachment record sys_id: ' + answer.attachmentId);
}

Once we have obtained the record, we will need to grab the usual suspects from the System Properties and then construct the appropriate URL for the request.

var host = gs.getProperty('x_11556_col_store.host_instance');
var token = gs.getProperty('x_11556_col_store.active_token');
var url = 'https://';
url += host;
url += '.service-now.com/api/now/attachment/file?table_name=x_11556_col_store_member_application_version&table_sys_id=';
url += answer.hostVerId;
url += '&file_name=';
url += sysAttGR.getDisplayValue('file_name');

Next, we will need to construct and configure our sn_ws.RESTMessageV2 object.

var request  = new sn_ws.RESTMessageV2();
request.setBasicAuth(this.WORKER_ROOT + host, token);
request.setRequestHeader('Content-Type', sysAttGR.getDisplayValue('content_type'));
request.setRequestHeader('Accept', 'application/json');
request.setHttpMethod('post');
request.setEndpoint(url);
request.setRequestBody(gsa.getContent(sysAttGR));

Two things to note on this one: 1) the Content-Type header sets the value of the content_type property of the target instance sys_attachment record, and 2) we use our old friend, the GlideSysAttachment utility to obtain the actual XML of the attachment in lieu of a real file (the file contents are not actually a part of the GlideRecord for the sys_attachment table, hence the need to utilize GlideSysAttachment).

Now all we need to do is to execute the request and check the response.

var response = request.execute();
if (response.haveError()) {
	answer = this.processError(answer, 'Error returned from Host instance: ' + response.getErrorCode() + ' - ' + response.getErrorMessage());
} else if (response.getStatusCode() != 201) {
	answer = this.processError(answer, 'Invalid HTTP Response Code returned from Host instance: ' + response.getStatusCode());
}

Unlike the application and version records, we have no need for any information from the returned JSON string, so there is no need to attempt to parse it and pull out any data. As long as there are no errors, we are good to go.

Putting it all together, the entire function looks like this:

processPhase7: function(answer) {
	var gsa = new GlideSysAttachment();
	var sysAttGR = new GlideRecord('sys_attachment');
	if (sysAttGR.get(answer.attachmentId)) {
		var host = gs.getProperty('x_11556_col_store.host_instance');
		var token = gs.getProperty('x_11556_col_store.active_token');
		var url = 'https://';
		url += host;
		url += '.service-now.com/api/now/attachment/file?table_name=x_11556_col_store_member_application_version&table_sys_id=';
		url += answer.hostVerId;
		url += '&file_name=';
		url += sysAttGR.getDisplayValue('file_name');
		var request  = new sn_ws.RESTMessageV2();
		request.setBasicAuth(this.WORKER_ROOT + host, token);
		request.setRequestHeader('Content-Type', sysAttGR.getDisplayValue('content_type'));
		request.setRequestHeader('Accept', 'application/json');
		request.setHttpMethod('post');
		request.setEndpoint(url);
		request.setRequestBody(gsa.getContent(sysAttGR));
		var response = request.execute();
		if (response.haveError()) {
			answer = this.processError(answer, 'Error returned from Host instance: ' + response.getErrorCode() + ' - ' + response.getErrorMessage());
		} else if (response.getStatusCode() != 201) {
			answer = this.processError(answer, 'Invalid HTTP Response Code returned from Host instance: ' + response.getStatusCode());
		}
	} else {
		answer = this.processError(answer, 'Invalid attachment record sys_id: ' + answer.attachmentId);
	}

	return answer;
},

Unfortunately, when I took it out for a spin, it didn’t work. When I was nosing around to see what the source of the problem was, I figured out that the sys_id value that I was using for the attachment GlideRecord was not a string, but an array of comma-separated sys_id pairs, one for the original attachment and one for the copied attachment. This value came out of the fourth step, where we copied the attachment from the original scoped application record to the version record. Once I realized the actual format of the data returned from the GlideSysAttachment copy function, I did a little rewriting of the processPhase4 function to accommodate the actual structure of the returned data.

processPhase4: function(answer) {
	var gsa = new GlideSysAttachment();
	var values = gsa.copy('sys_app', answer.appSysId, 'x_11556_col_store_member_application_version', answer.versionId);
	gsa.deleteAttachment(answer.attachmentId);
	if (values[0]) {
		var ids = values[0].split(',');
		if (ids[1]) {
			answer.attachmentId = ids[1];
		} else {
			answer = this.processError(answer, 'Unrecognizable response from attachment copy: ' + values);
		}
	} else {
		answer = this.processError(answer, 'Unrecognizable response from attachment copy: ' + values);
	}
		
	return answer;
},

While I was at it, I went ahead and did a little work on the processError function to add a few diagnostic breadcrumbs to the system log whenever there is an error. That function now looks like this:

processError: function(answer, message) {
	gs.info('ApplicationPublisher.processError: ' + message);
	gs.info('ApplicationPublisher.processError: ' + JSON.stringify(answer));
	gs.addErrorMessage(message);
	answer.error = message;
	return answer;
},

Once I straightened all of that out, everything finally worked as intended. This is the last step in the process, so this essentially completes the code for publishing an application to the Collaboration Store. At this point, I should probably cut another Update Set so that the folks who would like to participate in the testing can take things out for a little test drive. I still need to address the issues with the set-up process uncovered by the last round of testing, so I think I will take that on next time out and then release a new Update Set for those of you who are willing to put things through their paces and report your results. As always, all feedback is very much appreciated.