Displaying Related Attachments ‐ Parent, Child, and Sibling - ben-vargas/servicenow-wiki GitHub Wiki
This article explains how to display attachments from related records on a ServiceNow form. It addresses the common need to view attachments not only on the current record but also on its parent, child, and sibling records within a hierarchical task structure. This helps users easily access all relevant documentation without navigating through multiple records.
The Challenge
When working with tasks in ServiceNow, it's common for attachments to be distributed across parent, child, and sibling records. For example, a change request might have attachments at the request level, the change task level, and even sub-task levels. Without a dedicated mechanism to view related attachments, users must manually navigate through these related records to find the necessary files. This process is cumbersome and inefficient.
Use Case
This solution is ideal for scenarios where attachments are used at multiple levels in a hierarchical task structure, such as:
- Change Management: Viewing attachments from a change request, change tasks, and related implementation tasks.
- Service Catalog Requests: Seeing attachments from the requested item, parent request, and associated tasks.
- Incident Management: Tracking attachments from incidents and related child tasks.
Solution Overview
This solution involves three main components:
- Script Include: The core logic resides in a Script Include, which identifies related task records (parents, children, and siblings) and returns a comma-separated string of their sys_ids.
- Relationship: A System Definition Relationship is used to display the related attachments in a related list on a form.
- Mail Script: A Mail Script is used to include related attachments in email notifications.
1. Script Include: RelatedTaskAttachments
Create a new Script Include named RelatedTaskAttachments
with the following code:
var RelatedTaskAttachments = Class.create();
RelatedTaskAttachments.prototype = {
initialize: function() {},
commaSeparatedTaskFamily: function(fromtable, recid) {
var family = '';
var gr = new GlideRecord('task');
if (fromtable == 'sysapproval_approver') { //sysapprovals are different since they don't have parents
var aprv = new GlideRecord('sysapproval_approver');
if (aprv.get(recid)) {
if(aprv.sysapproval){
gr.get(aprv.sysapproval);
family += (gr.sys_id + ', ');
}
} else {
gs.error("RelatedTaskAttachments: Record with sys_id: " + recid + " not found on table sysapproval_approver");
return '';
}
} else {
if(!gr.get(recid)){ //gr will be the source record
gs.error("RelatedTaskAttachments: Record with sys_id: " + recid + " not found on table task");
return '';
}
}
//STEP 1 up & across
if (gr.parent) {
family += (gr.parent + ', ');
var pfind = new GlideRecord('task');
if (pfind.get(gr.parent)) {
while (pfind.parent) { //not sure if .get() inside the loop will change this or not
family += (pfind.parent + ', ');
if(!pfind.get(pfind.parent)){
gs.error("RelatedTaskAttachments: Record with sys_id: " + pfind.parent + " not found on table task");
break; // Exit loop if the parent record is not found
}
}
if (fromtable != 'sysapproval_approver') { //we don't want approvals seeing siblings
var sfind = new GlideRecord('task');
sfind.addQuery('parent', gr.parent);
sfind.query();
while (sfind.next()) {
if (sfind.sys_id != gr.sys_id) //don't want it finding itself
family += (sfind.sys_id + ', ');
}
}
} else {
gs.error("RelatedTaskAttachments: Record with sys_id: " + gr.parent + " not found on table task");
}
}
//STEP 2 down & down
var cfind = new GlideRecord('task');
cfind.addQuery('parent', gr.sys_id);
cfind.query();
while (cfind.next()) {
family += (cfind.sys_id + ', ');
var gcfind = new GlideRecord('task');
gcfind.addQuery('parent', cfind.sys_id);
gcfind.query();
while (gcfind.next())
family += (gcfind.sys_id + ', ');
}
return family;
},
type: 'RelatedTaskAttachments'
};
Explanation:
commaSeparatedTaskFamily(fromtable, recid)
: This function is the core of the script.- It takes the table name and record sys_id as input.
- It uses
GlideRecord
to query thetask
table. - It handles
sysapproval_approver
records differently since approvals don't have a parent in the same way. - It retrieves the immediate parent, as well as all ancestors (grandparent, great-grandparent, etc.).
- It retrieves sibling records with the same parent.
- It retrieves direct children, and grandchildren.
- It has error handling when retrieving records to prevent errors and provide context on what may have failed.
- It returns a comma-separated string of sys_ids of all found related records.
2. System Relationship:
Create a new System Definition Relationship with these settings:
- Name: Related Attachments
- Applies to Table: Select the table on which you want to display the related attachments (e.g.,
sc_task
,change_task
, etc.). - Queries from Table:
sys_attachment
- Query with: JavaScript
- Query:
(function() {
var csfam = new RelatedTaskAttachments().commaSeparatedTaskFamily(parent.sys_class_name, parent.sys_id);
current.addQuery('table_sys_id', 'IN', csfam);
})()
Explanation:
- The script creates a new instance of the
RelatedTaskAttachments
Script Include. - It calls the
commaSeparatedTaskFamily
function, passing in the current record's table name and sys_id. - It uses the returned comma-separated string to filter the
sys_attachment
table and returns all attachments associated with all records.
3. Mail Script:
Create a new Mail Script with the following code, and name it something relevant like related_attachments
:
(function() {
var gr = new GlideRecord('sys_attachment');
var rectype = current.sys_class_name;
if (!current.sys_class_name) //APPROVALS DONT HAVE CLASS NAMES
rectype = 'sysapproval_approver';
var csfam = new RelatedTaskAttachments().commaSeparatedTaskFamily(rectype, current.sys_id);
gr.addQuery('table_sys_id', current.sys_id).addOrCondition('table_sys_id', 'IN', csfam);
gr.query();
if (gr.hasNext()) {
template.print("Attachments: ");
while (gr.next()) {
var attachLink = '<a href="' + gs.getProperty("glide.servlet.uri") + gs.generateURL(gr.getTableName(), gr.sys_id) + '">' + gr.file_name + '</a>';
template.print(attachLink);
if (gr.hasNext())
template.print(", ");
else
template.print("\n");
}
}
})();
Explanation:
- Creates a
GlideRecord
for thesys_attachment
table. - Determines the record type using
current.sys_class_name
. - If not found, defaults to
sysapproval_approver
. - Calls the
commaSeparatedTaskFamily
function from the Script Include to generate a list of related record sys_ids. - Adds a query to the
GlideRecord
using either the current records sys_id or the related record sys_ids. - Generates the links of all related attachments.
- Prints the formatted attachment links in the email.
Using the Mail Script:
You can use this mail script by including it in your email notification template using: ${mail_script:related_attachments}
.
How to Implement
- Create Script Include: Create the Script Include named
RelatedTaskAttachments
and copy the code provided. - Create System Relationship: Create the System Definition Relationship as specified.
- Create Mail Script: Create the Mail Script named
related_attachments
and copy the provided code. - Add Related List: On the form where you want to see related attachments, go to the Form Layout, and add the newly created relationship to the form in its own tab.
- Include Mail Script: Add the mail script to your email notifications to include the attachment links.
- Test Thoroughly: Always test your implementation before moving it into production.
Best Practices
- Script Include:
- Follow the naming convention for Script Includes (CamelCase with a Class name).
- Add error handling when retrieving records to prevent errors.
- Relationships:
- Use descriptive relationship names that reflect the functionality.
- Test your relationships after creation.
- Mail Script:
- Use Mail Scripts to keep logic out of email templates.
- Always test mail scripts to verify that attachments are included as expected.
- Always use a
try catch
block and log any errors that occur.
- Performance: For large task hierarchies, consider adding caching to the Script Include to improve performance and reduce database load.
Conclusion
This comprehensive approach enables users to easily view and access all attachments related to a task, across its entire hierarchy. By combining a Script Include, a System Relationship, and a Mail Script, you can significantly improve the efficiency and user experience of your ServiceNow instance. This solution is flexible and can be adapted to various scenarios where attachments are spread across parent-child relationships within the platform. Always test all changes thoroughly in a non-production instance before deploying to production.