johnstettin
Tera Expert

Disclaimer: This worked for our use case, and as with anything on the platform, there are probably many other ways to do this. (Except OOB)

 

One major draw back to assessments/surveys is the inability to filter the results displayed in reference metric fields. ServiceNow simply states that "Note: Reference qualifiers are not supported." and expects the end user to do the filtering based on the the entire table returned as a result. Our requirement was to pre-filter the results in a dynamic way for each specific reference field on an assessment. We created a re-usable and dynamic process to do so using a table to hold all the metric questions references, filters, and business rules that create QUERY business rules on the reference remote table to pre-filter the results before they are returned to the assessment and displayed to the user. 

To accomplish this we did the following-

Create a script include that creates/removes business rules and builds the filter query.

Name: AsmtRefUtils

Client Callable: True

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

	buildBusRule: function(table){
		// Check input variables to identify client or server side
		if(gs.nil(table)){
			table = this.getParameter('sysparm_table');

			
			// Validate variables are not empty if so return error
			if(gs.nil(table))
				return "ERROR: Invalid Inputs";
		}
		
		
		// Make sure the rule doesnt already exist and activate it if nessessary
		if(this._checkBusRuleExists(table)){
			this._enableBusRule(table);
			return "SUCCESS: Business Rule Exists.";
		}
		
		// Create the advanced script
		var advancedScript = "(function executeRule(current, previous /*null when async*/) {\n";
		advancedScript += "   try{\n";
		advancedScript += "      var url = gs.action.getGlideURI();\n";
		advancedScript += "      if(url.includes(\'ASMTQUESTION\')){\n";
		advancedScript += "         var encodedQuery = new AsmtRefUtils().buildEncodedQuery(url);\n";
		advancedScript += "         if(encodedQuery)\n";
		advancedScript += "            current.addEncodedQuery(new AsmtRefUtils().buildEncodedQuery(url));\n";
		advancedScript += "            \n";
		advancedScript += "      }\n";
		advancedScript += "    }catch(e){}";
		advancedScript += "})(current, previous);";
		
		var bsRule = new GlideRecord('sys_script');
		if (!bsRule.canCreate())
			return "ERROR: You do not have permission to create business rules on table " + table + ".";
			
		bsRule.initialize();
		bsRule.name = 'Auto generate Assessment Ref Filter';
		bsRule.description = 'Auto generated business rule to filter Assessment reference field results.';
		bsRule.collection = table;
		bsRule.active = true;
		bsRule.advanced = true;
		bsRule.when = 'before';
		bsRule.action_query = true;
		bsRule.script = advancedScript;
		bsRule.insert();		
		
		return "SUCCESS: Business Rule Created.";
	},
	
	removeBusRule: function(table){
		
		// Check if other reference rules exist for the table, if none remove business rule
		var asmt_ref_rule = new GlideRecord('u_asmt_ref_filter');
		asmt_ref_rule.addQuery('u_ref_table', table);
		asmt_ref_rule.query();
		
		if(asmt_ref_rule.next())
			return;
		
		// Remove table business rule
		var bsRule = new GlideRecord('sys_script');
		bsRule.addQuery('collection', table);
		bsRule.addQuery('name', 'Auto generate Assessment Ref Filter');
		bsRule.query();
		if(bsRule.next()){
			// bsRule.deleteRecord(); Inactivate vs Delete
			bsRule.active = false;
			bsRule.update();
		}
	},
	
	buildEncodedQuery: function(url){
		// Parse the question sys_id from url
		var r = /ASMTQUESTION%3a([0-9a-f]{32})/;
		var asmt_question_id = r.exec(url)[1];

		// Get the question record
		var asmt_question = new GlideRecord('asmt_assessment_instance_question');
		asmt_question.addQuery('sys_id', asmt_question_id);
		asmt_question.query();
		if(asmt_question.next()){		
			// Query the Assessment Reference Filters
			var asmt_ref = new GlideRecord('u_asmt_ref_filter');
			asmt_ref.addQuery('u_asmt_metric', asmt_question.metric);
			asmt_ref.query();

			if(asmt_ref.next()){
				return asmt_ref.u_asmt_ref_filter;
			}
		}
		return;
	},
	
	_enableBusRule: function(table) {
		var gr = new GlideRecord('sys_script');
		gr.addQuery('collection', table);
		gr.addQuery('name', 'Auto generate Assessment Reference Filter');
		gr.addQuery('active', false);
		gr.query();
		if(gr.next()){
			gr.active = true;
			gr.update();
		}		
	},
	
	_checkBusRuleExists: function(table){
		var gr = new GlideRecord('sys_script');
		gr.addQuery('collection', table);
		gr.addQuery('name', 'Auto generate Assessment Reference Filter');
		gr.query();
		if(gr.next()){
			return true;
		}
		return false;
	},
	
    type: 'AsmtRefUtils'
});

 

We created a table that gave admins a dynamic way to add new filters to any assessment metric with a reference question.

Name: u_asmt_ref_filter

find_real_file.png

Adding a reference qualifier to the Assessment Metric reference field "datatype=reference^categoryISNOTEMPTY^EQ"

 

Then create two business rules on the "u_asmt_ref_filter" table. One that creates a business rule on the referenced table and another to remove/cleanup the business rule.

 

Name: "Create Table Query Business Rules"

Before/Insert

Script:

(function executeRule(current, previous /*null when async*/) {

	var asmtRefUtils = new global.AsmtRefUtils();
	var result = asmtRefUtils.buildBusRule(current.u_ref_table);
	if(result.includes('ERROR')){
		gs.addErrorMessage(result);
		current.setAbortAction(true);
	}
})(current, previous);

 

Name: "Remove Table Query Business Rules"

Async/Delete

Script:

(function executeRule(current, previous /*null when async*/) {

	var asmtRefUtils = new global.AsmtRefUtils();
	var result = asmtRefUtils.removeBusRule(current.u_ref_table);
	if(result.includes('ERROR'))
		gs.addErrorMessage("Error removing query business rule on " + current.u_ref_table);
})(current, previous);

 

Then we added a new module entry in Assessments > Administration > "Reference Filters" that shows the list of records in the "u_asmt_ref_filter" table.

Now any time the table is queried, the URL is examined and if its being referenced from an assessment, the assessment question/metric is identified and the filter table queried. If there are any matches to the assessment question, the results are filtered before returned to the survey/assessment. Making for a much simpler user experience taking/completing the assessments. 

Admin UI-

find_real_file.png

Comments
Mwatkins
ServiceNow Employee
ServiceNow Employee

I strongly recommend against this solution because creating/deleting business rules will cause churn in the Application Files (sys_metadata) table [Edit: note that the author confirmed they have specifically designed this implementation not to cause churn, so this seems not to apply in this case]. The sys_metadata table is the central table for all "configurations" in ServiceNow. Basically, it stores anything that can be put in an Update Set to control behavior. The sys_metadata table should never contain ephemeral data. Even when a sys_metadata record is deleted it does not really go away! It stays in a child table of sys_metadata called sys_metadata_delete. This will cause heavy overhead on the platform.

I would say, why not just Update the business rule active field, toggling it on and off, but that will create churn in the sys_update_version table.

I'm sure there is a way to do this without needing to build automated changes to a sys_metadata table. Can you create a before/query business rule that only fires under certain conditions and access the script include for special filters?

Please Correct if this solves your issue and/or 👍 if Helpful

"Simplicity does not precede complexity, but follows it"

johnstettin
Tera Expert

Thank you, however the business rule is created once and used by any filter that applies to that table. This minimizes "churn" down to an acceptable level - atleast for our use cases. Also, thank you for helping me realize a bug in the logic. The business rule should not delete unless all references against that table are removed from the filter table.  And per your suggestion, I'll modify it to inactivate vs delete.

 

If ServiceNow provides a way to do this OOB, I would be happy to adopt it. However with this currently being a MAJOR issue/need with Survey's and Assessments, this solution has proven to work as designed.

Mwatkins
ServiceNow Employee
ServiceNow Employee

Glad to hear that you have designed it to minimize churn - that was my main concern. I admit I didn't read too deeply. I saw automatic create/delete of BR and thought I should pipe up; especially since others might come along and do something similar, while not being as careful as you apparently have been.

Glad it is working for you!

Another thing to consider about before query business rules is that they run all over the place, not just end user initiated queries. See my comments about some of the risks of using them here:

https://community.servicenow.com/community?id=community_article&sys_id=8b8f72fd1babd810d2ccea89bd4bc...

Also summarized here:

https://community.servicenow.com/community?id=community_article&sys_id=69bf62661bb060103222ea89bd4bc...

shiv ambati
Tera Explorer

 How can we Add user to the record in metrics table in ITAM? if possible cloud you please help me out. Thanks in advance

johnstettin
Tera Expert

@shiv ambati Not sure I understand your question. Do you want to add a reference to a user on an assessment or survey in ITAM?

Version history
Last update:
‎01-26-2021 12:13 PM
Updated by: