Collaboration Store, Part LVIII

“Progress is not in enhancing what is, but in advancing toward what will be.”
Khalil Gibran

Last time, we laid out all of the work that will need to be done to incorporate the new logo fields into the various processes of our application. Now we need to get busy doing that work. To begin, we can create a common function to move a logo image from one instance to another. We already have a common function to move an XML Update Set attachment from one instance to another, so let’s take a quick look at that guy and see if there is anything there that we can salvage for our new purpose.

pushAttachment: function(attachmentGR, targetGR, remoteVerId) {
	var result = {};

	var gsa = new GlideSysAttachment();
	result.url = 'https://';
	result.url += targetGR.getDisplayValue('instance');
	result.url += '.service-now.com/api/now/attachment/file?table_name=x_11556_col_store_member_application_version&table_sys_id=';
	result.url += remoteVerId;
	result.url += '&file_name=';
	result.url += attachmentGR.getDisplayValue('file_name');
	result.method = 'POST';
	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('Content-Type', attachmentGR.getDisplayValue('content_type'));
	request.setRequestHeader('Accept', 'application/json');
	request.setRequestBody(gsa.getContent(attachmentGR));
	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();
	}

	return result;
}

Since we want our new function to work for both instance logo images and application logo images, we will want to pass in both the table name and the table sys_id to our new function. Since the table name is part of the end point URL, we will want to change this:

result.url = 'https://';
result.url += targetGR.getDisplayValue('instance');
result.url += '.service-now.com/api/now/attachment/file?table_name=x_11556_col_store_member_application_version&table_sys_id=';
result.url += remoteVerId;
result.url += '&file_name=';
result.url += attachmentGR.getDisplayValue('file_name');

… to this:

result.url = 'https://';
result.url += targetGR.getDisplayValue('instance');
result.url += '.service-now.com/api/now/attachment/file?table_name=ZZ_YY';
result.url += tableName;
result.url += '&table_sys_id=';
result.url += tableSysId;
result.url += '&file_name=';
result.url += attachmentGR.getDisplayValue('file_name');

In addition to using the passed table name in the URL, we also prepend the string ZZ_YY to the value. This is a convention of the Now Platform to hide the attachment icon from the record for that image. When you manually add a logo image to a record and then go take a look at that image record in the sys_attachment table, you can see that the system has automatically prepended the ZZ_YY string to the table name. We want to our process to behave in the same manner, so we do that here as well.

The other difference between our logo image attachment and the XML Update Set attachment is that the XML content is in plain text and our image is stored in binary. Fortunately, the GlideSysAttachment object that we are using has a built-in way of handling that, so we just need to change this:

request.setRequestBody(gsa.getContent(attachmentGR));

… to this:

request.setRequestBody(gsa.getContentBase64(attachmentGR));

Other than these two changes, we should be able to use the rest of the original function intact. That makes our new function now look like this:

pushImageAttachment: function(attachmentGR, targetGR, tableName, tableSysId) {
	var result = {};

	var gsa = new GlideSysAttachment();
	result.url = 'https://';
	result.url += targetGR.getDisplayValue('instance');
	result.url += '.service-now.com/api/now/attachment/file?table_name=ZZ_YY';
	result.url += tableName;
	result.url += '&table_sys_id=';
	result.url += tableSysId;
	result.url += '&file_name=';
	result.url += attachmentGR.getDisplayValue('file_name');
	result.method = 'POST';
	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('Content-Type', attachmentGR.getDisplayValue('content_type'));
	request.setRequestHeader('Accept', 'application/json');
	request.setRequestBody(gsa.getContentBase64(attachmentGR));
	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();
	}

	return result;
}

That still does not complete the process, however. In addition to creating the attachment record linked to the base record, the logo field on the base record contains the sys_attachment record sys_id as the value. Once we create the sys_attachment record on the target system using the above function, we need to grab the resulting sys_id and update the logo field value on the base record. For that, we will need yet another function to make an additional REST API call to the target system to make that update. Since the new image field on both tables is named logo, we should again be able to create a single function that will work for both use cases. The payload that we will be sending just needs to contain the one value that we intend to update:

updateLogoField: function(attachmentId, targetGR, tableName, tableSysId) {
	var result = {};
 
	var payload = {};
	payload.logo = attachmentId;
	...
 
	return result;
}

For the URL, we will need to use both the passed table name and the passed table sys_id.

result.url = 'https://';
result.url += targetGR.getDisplayValue('instance');
result.url += '.service-now.com/api/now/table/';
result.url += tableName;
result.url += '/';
result.url += tableSysId;

… and the rest of the function is just our standard REST API call and result check:

updateLogoField: function(attachmentId, targetGR, tableName, tableSysId) {
	var result = {};
 
	var payload = {};
	payload.logo = attachmentId;
	result.url = 'https://';
	result.url += targetGR.getDisplayValue('instance');
	result.url += '.service-now.com/api/now/table/';
	result.url += tableName;
	result.url += '/';
	result.url += tableSysId;
	result.method = 'PUT';
	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('Content-Type', 'application/json');
	request.setRequestHeader('Accept', 'application/json');
	request.setRequestBody(JSON.stringify(payload, null, '\t'));
	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();
	}
 
	return result;
}

Now all we have to do is call this function from our pushImageAttachment function, but only if all went well and the attachment was successfully sent over. We already have the targetGR, tableName, and tableSysId arguments available, but we will need to extract the remote system’s attachmentId from the response body of our call to send over the attachment. We are already checking for an error condition here:

result.error = response.haveError();
if (result.error) {
	result.error_code = response.getErrorCode();
	result.error_message = response.getErrorMessage();
}

… so we should be able to just add an else condition to that if statement to make the call.

result.error = response.haveError();
if (result.error) {
	result.error_code = response.getErrorCode();
	result.error_message = response.getErrorMessage();
} else {
	if (result.status == '201' && result.obj) {
		this.updateLogoField(result.obj.result.sys_id, targetGR, tableName, tableSysId);
	}
}

So now we have a common function that will send over the logo image for both instance records and application records, and then update the base record with attachment’s local sys_id. That’s a good start, but there is lots more to do, so we will keep plowing ahead next time out.