Computer

JellyScript: A language with limited documentation

Today we are going to learn about a little-known language with limited documentation: JellyScript.

I was asked to create a KB article template for automatically generated Technology Process policies that would recursively go through all of the policies that had the Technology Process policy as the parent record and then use each of their related control objectives to print out a bulleted list containing their technology process, starting reference ID, the objective statement ID, and the objective statements.

This is the final product below:

(Potentially sensitive content redacted.)

I made the KB article template for our client and wanted to share my findings with you, the reader. Below is the full code I used for this task.

<?xml version="1.0" encoding="utf-8" ?>
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">
	<g:evaluate object="true" var="jvar_statements">				
		var controlPolicies = new GlideRecord('sn_compliance_policy');
		controlPolicies.addQuery('parent', current.sys_id +'');
		controlPolicies.query();
							
		var controlObj = new GlideRecord('sn_compliance_policy_statement');					
		var mtom = controlObj.addJoinQuery('sn_compliance_m2m_policy_policy_statement', 'sys_id', 'content');
		mtom.addCondition('document', current.sys_id + '');
		while(controlPolicies.next()){		
			mtom.addOrCondition('document', controlPolicies.sys_id + '');		
		}
		controlObj.orderBy('reference');
		controlObj.query();		
		
		controlObj;						
	</g:evaluate>
	
	<table border="5" style="width:100%;border-collapse: separate">
		<caption ><h3 style="font-weight: bold">${current.name} - Control Statements</h3></caption>	
		<tr>
			<th style="width:10%;text-align:center">Control Objective</th>
			<th style="width:5%;text-align:center">Objective ID</th>
			<th style="width:5%;text-align:center">Statement ID</th>			
			<th style="text-align:center">Control Statements</th>    
		</tr>
		<j:while test="${jvar_statements.next()}">
			<tr>
				<td style="padding-left: 10px;padding-right: 10px;padding-top: 10px;padding-bottom: 10px;text-align:center"><a href="/sn_compliance_policy?sys_id=${jvar_statements.getValue('u_policy_control_objective')}" target="_blank">${jvar_statements.getDisplayValue('u_policy_control_objective')}</a></td>
				<td style="padding-left: 10px;padding-right: 10px;padding-top: 10px;padding-bottom: 10px;text-align:center">${jvar_statements.getValue('u_policy_control_objective.u_tcf_id')}</td>		
				<td style="font-weight: bold;text-decoration: underline;text-align:center"><a href="/sn_compliance_policy_statement?sys_id=${jvar_statements.getUniqueValue()}" target="_blank">${jvar_statements.getValue('reference')}</a></td>				
				<td style="padding-left: 10px;padding-right: 10px;padding-top: 10px;padding-bottom: 10px">${jvar_statements.getValue('name')}</td>		
			</tr>
		</j:while>
	</table>		
	
</j:jelly>

We will start with the languages involved. We have XML as a wrapper, JellyScript as the main operator, JavaScript as the logic, with HTML and CSS for formatting and display. 

XML:

This is the standard header for an XML document and simply signals to the compiler to recognize the script as such.

<?xml version="1.0" encoding="utf-8" ?>

JellyScript:

  • <j:jelly> tag
    • This is the Jelly tag all the other code is encapsulated in. You can see there are three variables; g, j2, and g2. There is a lot to unpack here.
    • First off, there are two phases of the jelly script, the first being cached (except for UI pages) and the latter not.
    • ‘G’ represents scripts unique to ServiceNow, allowing for use of JavaScript.
    • ‘J’ represents Apache Jelly, a whole other language I’m not getting into in this article as I am not using it in this example.
    • The first ‘G’ has a value of ‘glide’, which is the return type of the variable, indicating we are using, you guessed it, a Glide function.
    • The other two are ‘null’ that are for the second phase of the script and are not used. You can learn more about Jelly here
      <j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">
          //code
      </j:jelly>

  • <g:evaluate> tag
    • Where the JavaScript you know and love lives. All your logic for finding the data you want is done here.
    • On this tag, ‘object’ is set to true, indicating there is an object as a return value.
    • We also have a ‘var’, which is incredibly important for our dynamic list presentation later.
    • The tag itself can be dissected into <NAMESPACE:COMMAND> as in the example where we are using the G namespace and we want to have it evaluate some code.
      	<g:evaluate object="true" var="jvar_statements">				
      		//code		
      	</g:evaluate>
  • <j:while> tag
    • Logic that is outside the evaluation, but inside the jelly tag that performs the same logic as when doing a GlideRecord While [ while(gr.next()) ]
    • The ‘test’ variable is for telling the tag what to loop through. In this case we use the ‘var’ designation we defined in the <g:evaluate> tag.
    • When referencing variables and their values, you need to wrap the variables in a ${} just like in notification scripts. 
      <j:while test="${jvar_statements.next()}">
          //code
      </j:while>

Now that we have a grasp of what Jelly does in relation to the other code, we will dig into the JavaScript logic that drives the data you see in the image above.

JavaScript:

I’ll try to simplify this as best I can, it’s a semi-complex process.

The Ask:

  • On a high level, the task is to take a record on ‘sn_compliance_policy’ [Policy] with a type of Technology Process [Parent] that gets approved and have it generate a KB article that lists out each Parent’s control objectives [CO].
  • A Technology Process policy [Parent] is approved.
  • This generates the KB article.
  • The KB article needs to list out each control objective relating to the Parent record. 
    • Control objectives are housed on a separate table, accessible via a many-to-many related list from Policy>Control Objective
    • To get to this related list, you have to find all other policies with Parent as the parent record and the type as ‘Control Objective’ and then iterate through them to come up with all control objectives from all policies relating to the parent

The Breakdown:

  • First you must look at the Policy table to find all records where Parent is the current record being approved. 
  • Then you need to look at the Control Objective table and create a second query that joins the many-to-many table using the proper field as the foreign key for matching up records. 
  • You look for any control objectives where the sys_ids match the many-to-many value (you will not find any using the current.sys_id, but this sets up the next part)
  • Then using the policies query, loop through as an or condition to find all the control objectives relating to the Parent. 
  • Order by the internal reference ID, ascending (this is a string field called ‘reference’, it is not an actual reference field)
  • Don’t forget to query!
  • Lastly, instead of the keyword ‘return’, you simply write the variable as a stand alone statement. 
var controlPolicies = new GlideRecord('sn_compliance_policy');
controlPolicies.addQuery('parent', current.sys_id +'');
controlPolicies.query();
							
var controlObj = new GlideRecord('sn_compliance_policy_statement');					
var mtom = controlObj.addJoinQuery('sn_compliance_m2m_policy_policy_statement', 'sys_id', 'content');
mtom.addCondition('document', current.sys_id + '');
while(controlPolicies.next()){		
	mtom.addOrCondition('document', controlPolicies.sys_id + '');		
}
controlObj.orderBy('reference');
controlObj.query();		
		
controlObj;						

HTML / CSS:

These things go hand-in-hand so I’ll be brief about them. Here are the highlights:

  • The ${current} object is still accessible from here for display purposes, which is a plus
  • When you want to reference a value inside the <g:evaluate> tag, you MUST use the variable name assigned in the tag, in this case it is ‘jvar_statements’
  • I have invoked the <j:while> tag here to take advantage of dynamic data population.
  • As you can see on my <td> tags, one of them looks like ‘${jvar_statements.getValue(‘u_policy_control_objective.u_tcf_id’)}’
    • This is a game changer, because unlike in client scripts, you can actually dot-walk reference values in a getValue() method here. 

<table border="5" style="width:100%;border-collapse: separate">
	<caption ><h3 style="font-weight: bold">${current.name} - Control Statements</h3></caption>	
	<tr>
		<th style="width:10%;text-align:center">Control Objective</th>
		<th style="width:5%;text-align:center">Objective ID</th>
		<th style="width:5%;text-align:center">Statement ID</th>			
		<th style="text-align:center">Control Statements</th>    
	</tr>
	<j:while test="${jvar_statements.next()}">
			<tr>
				<td style="padding-left: 10px;padding-right: 10px;padding-top: 10px;padding-bottom: 10px;text-align:center"><a href="/sn_compliance_policy?sys_id=${jvar_statements.getValue('u_policy_control_objective')}" target="_blank">${jvar_statements.getDisplayValue('u_policy_control_objective')}</a></td>
				<td style="padding-left: 10px;padding-right: 10px;padding-top: 10px;padding-bottom: 10px;text-align:center">${jvar_statements.getValue('u_policy_control_objective.u_tcf_id')}</td>		
				<td style="font-weight: bold;text-decoration: underline;text-align:center"><a href="/sn_compliance_policy_statement?sys_id=${jvar_statements.getUniqueValue()}" target="_blank">${jvar_statements.getValue('reference')}</a></td>				
				<td style="padding-left: 10px;padding-right: 10px;padding-top: 10px;padding-bottom: 10px">${jvar_statements.getValue('name')}</td>		
			</tr>
		</j:while>
	</table>		
	

I hope this makes more sense than when you first started looking at JellyScript, and from the bottom of my heart I hope you never have to write anything in it. If you do, this is here to reference