Workflow Driven Catalog UI Policies

Recently I was working with a client on a large Service Catalog project. One of the requirements was that they needed to have specific variables to be visible/required on catalog tasks based on where the item was within the workflow and based on answers that were given to other variables. There are obviously many ways to tackle this; at a very basic level you can choose to show/not show these variables via the slushbucket on the task activity. While this does work in most cases, in more complex scenarios like this, it does not cover the capability to conditionally show/hide certain variables based on the values of other variables. In the past, I have tackled these types of issues by usingĀ UI Policies that looked for specific text/data in order to “figure out” where the item was at in the workflow. While this approach can work in certain circumstances, I wanted a more robust method of controlling what fields were required/visible/mandatory. The result was “Workflow generated UI Policies” that can be called by any item (using a Catalog Client Script), with a back-end AJAX Script Include that queries the item’s Workflow context, then grabs the UI Policies (exist as a scratchpad array) off of the scratchpad, figures out which one is applicable, then sends the payload back to the client script.

Here is what the Workflow Run script contains:

//set UI Policy scratchpad
workflow.scratchpad.ui_policies = [];
workflow.scratchpad.ui_policies.push(
		{
			"applies_to": workflow.scratchpad.task_sys_id,
			"readOnly": ['group'],
			"writeable":['first_name','last_name','laptop'],
			"mandatory":['laptop'],
			"visible":[''],
			"hidden":['assigned_to']
		});

Here’s the Script Include:

var CatalogItemAJAXUtil = Class.create();
CatalogItemAJAXUtil.prototype = Object.extendsObject(AbstractAjaxProcessor, {

	getWorkflowScratchpad : function (){
		var req_item =  this.getParameter('sysparm_req_item');
		var record_id = this.getParameter('sysparm_record_id');
		var req_item_policies;
		var matched_policies = '0';

		//get the workflow script include helper
		var workflow = new Workflow();

		//get the requested items workflow context
		var item = new GlideRecord('sc_req_item');
		item.get(req_item);
		var context = workflow.getContexts(item);

		//make sure we have a valid context
		if (context.next()) {
			gs.log('++CatalogItemAJAXUtil: Got Policies:' + new JSON().encode(context.scratchpad.ui_policies));
			//this has already been instantiated as an array/collection, we can use it directly
			req_item_policies = context.scratchpad.ui_policies;

			//loop through all policies, find out which one's apply to this record
			matched_policies = this.getMatchingPolicies (req_item_policies,record_id);
			//gs.log('++Policy Applies to: ' + req_item_policies.applies_to +' Record we were on: ' + record_id);

		} else {
			//no workflows found for this item; ignoring...
		}
		if (matched_policies) {
			gs.log('++Returning UI Policies: ' + matched_policies + ' for Record: ' + record_id);
		} else {
			gs.log('++No Matching UI Policies found for Record: ' + record_id);
		}

		//convert to JSON String before sending
		return new JSON().encode(matched_policies);
	},

	getMatchingPolicies : function (policies,record_id){
		var item_policies = [];
		for (var i = 0; i < policies.length; i++) {
			var policy = policies[i];
			if (policy.applies_to == record_id) {
				//gs.log('++ Found matching UI Policy for this record');
				item_policies.push(policy);
			}
		}
		return item_policies;
	},

	type: 'CatalogItemAJAXUtil'
});

Finally, here is what the Catalog Client Script looks like:

function onLoad() {
	var ga = new GlideAjax('CatalogItemAJAXUtil');
	ga.addParam('sysparm_name','getWorkflowScratchpad');
	ga.addParam('sysparm_req_item',g_form.getValue('request_item'));
	ga.addParam('sysparm_record_id',g_form.getUniqueValue());
	ga.getXML(ScratchpadParse);

}

function ScratchpadParse(response) {
	var answer = response.responseXML.documentElement.getAttribute("answer");
	jslog('++ AJAX Answer was: ' + JSON.stringify(answer));
	var workflow_ui_policies = JSON.parse(answer);
	jslog('++ Scratchpad UI Policy Obj was: ' + JSON.stringify(workflow_ui_policies));

	//loop through and apply each UI Policy

	for (var i = 0; i < workflow_ui_policies.length; i++) {
		var policy = workflow_ui_policies[i];
		if (policy == '0' || policy == null){
			jslog('++ Returned UI Policy is not Valid for this record');
			continue;
		}

		jslog('++ Running Scratchpad Policies ' + policy['readOnly'] + ' String: ' + JSON.stringify(policy));
		setPolicyValues(policy['readOnly'],'readOnly');

		//jslog('++ReadOnly:' + policy['readOnly']);
		setPolicyValues(policy['writeable'],'writeable');
		//jslog('++writeable:' + policy['writeable']);

		setPolicyValues(policy['mandatory'],'mandatory');
		//jslog('++mandatory:' + policy['mandatory']);

		setPolicyValues(policy['visible'],'visible');
		//jslog('++visible:' + policy['visible']);

		setPolicyValues(policy['hidden'],'hidden');
		//jslog('++hidden:' + policy['hidden'])
	}
}

function setPolicyValues(policy_fields,action){
	for (var i = 0; i < policy_fields.length; i++) {
		var field = policy_fields[i];

		if (action == 'readOnly') {
			g_form.setReadOnly(field, true);
		} else if (action == 'writeable') {
			g_form.setReadOnly(field, false);
		} else if (action == 'mandatory') {
			g_form.setMandatory(field, true);
		} else if (action == 'visible') {
			g_form.setVisible(field, true);
		} else if (action == 'hidden') {
			g_form.setVisible(field, false);
		}
	}
}

It is these 3 pieces of code that make up the Custom Workflow UI Policy process. Once this is setup, you can have multiple custom/scratchpad UI Policies in your workflow and then based on the Record’s ID (sys_id), it will dynamically apply the corresponding UI Policy for that record.

Ok, that’s it for now, please let me know if you have any questions.