Incident resolved by change related list - part 2

Building blocks 

Ability of relating multiple changes to an Incident ('incident caused by changes'). To minimise on customisation risks we want to avoid creating multiple fields on the Change Request table and use OOB components where possible. 

POC using: 

OOB Task Relationship [task_rel_task] table, as one related list rather than two 

Custom UI Action list banner button to initiate the relationship via popup 

UI Page to drive the behaviour in the popup 

UI Action 

List Banner button: true

Client: true 

Condition: RP.isRelatedList() && (parent.sys_class_name == 'incident' || parent.sys_class_name == 'problem') 




RP.isRelatedList() && (new ChangeTaskRelUtility()).isValidTable(parent.sys_class_name)


function addRelationship() {function addRelationship() { var oModal = new GlideModal('add_chg_to_task'); oModal.setTitle(new GwtMessage().getMessage('Relate Change Request to {0}', g_form.getDisplayValue())); oModal.setPreference('sysparm_source_dv', g_form.getDisplayValue()); oModal.setPreference('sysparm_source_sys_id', g_form.getUniqueValue()); // track back to originating record oModal.setPreference('sysparm_source_table_name', g_form.getTableName()); // track back to originating record oModal.render(); }

UI Page 

Name: add_chg_to_task 

o Referred to in GlideModal of UI Action 

Hidden form fields to store parameters from UI Action, regarding the ‘parent’/ ‘source’ record 

Generating reference fields (using g:ui_reference) to Change Request [change_request] and Relationship Types [task_rel_type] tables 

Hidden form field to store list of Change Request Sys IDs for processor script 

o Populated by client-side JS using the [+] button 

To do/limitations 

Further validation on reference field to Change Request to ensure duplicates do not appear 

UI friendly validation message when user tries to add same Change Request type 

Validation on submit to ensure Change Requests and Relationship are selected 

Remove Edit and New buttons. 

Further styling 

UI Page script


<?xml version="1.0" encoding="utf-8" ?> <j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null"> <!-- get parameters into variables --> <j:set var="jvar_source_dv" value="${sysparm_source_dv}" /> <j:set var="jvar_source_sys_id" value="${sysparm_source_sys_id}" /> <j:set var="jvar_source_table_name" value="${sysparm_source_table_name}" /> <g:evaluate jelly="true"> // Used to build up query string to change request table and filter out those that already exist var aExistingRel = []; var grTaskRelTask = new GlideRecord('task_rel_task'); grTaskRelTask.addQuery('parent', jelly.jvar_source_sys_id); grTaskRelTask.query(); while ( { aExistingRel.push(grTaskRelTask.getValue('child')); } var sExisting = (aExistingRel.length > 0) ? "^sys_idNOT IN" + aExistingRel.join(',').toString() : ""; sExisting; </g:evaluate> <g:evaluate jelly="true"> // We only want to show the valid types based on the source table and the change request (as a child) var aAllowedTypes = []; var grRelAllowedType = new GlideRecord('task_rel_allowed'); grRelAllowedType.addQuery('parent_task',jelly.jvar_source_table_name); grRelAllowedType.addQuery('child_task', 'change_request'); grRelAllowedType.query(); while ( { aAllowedTypes.push(grRelAllowedType.getValue('type')); } var sAllowed = (aAllowedTypes.length > 0) ? "sys_idIN" + aAllowedTypes.join(',').toString() : ""; sAllowed; </g:evaluate> <!-- reserving spot for error messages --> <div id="chg_errorbox"> </div> <!-- form starts here --> <g:ui_form> <input type="hidden" name="source_dv" id="source_dv" value="${jvar_source_dv}" /> <input type="hidden" name="source_sys_id" id="source_sys_id" value="${jvar_source_sys_id}" /> <input type="hidden" name="source_table_name" id="source_table_name" value="${jvar_source_table_name}" /> <input type="hidden" name="target_change_list" id="target_change_list" value="" /> <div class="row"> <div class="col-md-9"> <h5>${gs.getMessage('Change Request Lookup')}</h5> </div> </div> <div class="row"> <div class="col-md-10"> <g:ui_reference name="change_request" id="change_request" table="change_request" query="active=true${sExisting}" completer="AJAXTableCompleter" columns="short_description;state"/> </div> <div class="col-md-2"> <a href="javascript:void(0)" role="button" class="btn btn-default" onclick="addChange()" title="${gs.getMessage('Select')}"> <span class="glyphicon glyphicon-plus"></span> </a> </div> </div> <div class="change_request_container"> <h5>${gs.getMessage('Selected Change Requests')}</h5> <div class="well" id="display_change_list"> </div> </div> <h5>${gs.getMessage('Select relationship type')}</h5> <g:ui_reference name="rel_type" id="rel_type" table="task_rel_type" query="${sAllowed}" completer="AJAXTableCompleter" /> <div style="padding-top:10px;"> <g:dialog_buttons_ok_cancel ok="return validateAndSubmit();" ok_id="ok_button" cancel_type="button" /> </div> </g:ui_form> </j:jelly>

Client script:

var aChangeRequests = []; // globally store the list of selected change requests // Hide selected list UI on load as it will be empty var oChangeListContainer = $j('#display_change_list').parent(); oChangeListContainer.hide(); //hide on load // Hide the alert box on load as it will be empty var oAlertBox = $j('#chg_errorbox'); oAlertBox.hide(); /* Main function */ function addChange() { wipeAlertBox(); if (changeSelected()) { var chgDV = document.getElementById('sys_display.change_request').value; // display value of selection var chgId = document.getElementById('change_request').value; // sys_id of selection if (checkAlreadySelected(chgId)) { sendToAlertBox("Already in list"); showAlertBox(); return; } if (changeSelected()) { aChangeRequests.push({ 'label': chgDV, 'id': chgId }); } updateList(); clearRef(); } else { sendToAlertBox("Select a change request."); showAlertBox(); } } function changeSelected() { var chg = document.getElementById('sys_display.change_request').value; //var chg = $j('#sys_display.change_request').val(); if (chg != '') return true; return false; } function updateList() { //Display to user the list var sTextArea = ""; for (var c = 0; c < aChangeRequests.length; c++) { oChg = aChangeRequests[c]; sTextArea += oChg.label + "<br/>"; } document.getElementById('display_change_list').innerHTML = sTextArea; // Toggle display of select change container (aChangeRequests.length > 0) ? : oChangeListContainer.hide(); //Preserve to hidden form element as JSON for processing script var sChangeRequests = JSON.stringify(aChangeRequests); document.getElementById('target_change_list').value = sChangeRequests; } function checkAlreadySelected(change_id) { // Used to avoid duplication in user selection var isSelected = false; for (var c = 0; c < aChangeRequests.length; c++) { var checkingChg = aChangeRequests[c].id; if (checkingChg == change_id) { isSelected = true; break; } } return isSelected; } function clearRef() { var oChange = document.getElementById('change_request'); oChange.value = ""; var oDisplay = document.getElementById('sys_display.change_request'); oDisplay.value = ""; setLightWeightLink('change_request'); } function validateAndSubmit() { wipeAlertBox(); var bErrorFound = false; var oRelType = document.getElementById('rel_type'); if (aChangeRequests.length == 0) { sendToAlertBox("Select a change request."); bErrorFound = true; } if (oRelType.value == "") { sendToAlertBox("Select a relationship type."); bErrorFound = true; } if (bErrorFound) { showAlertBox(); return false; } return true; } function wipeAlertBox() { oAlertBox.empty(); oAlertBox.hide(); } function sendToAlertBox(msg) { oAlertBox.append("<p class=\"text-danger\">" + msg + "</p>"); } function showAlertBox() { oAlertBox.prepend("<p class=\"text-danger\">Please address the following issues:</p>");; }

Processing script:

if (target_change_list != '') { var grTaskRel = new GlideRecord('task_rel_task'); var helper = new ChangeTaskRelUtility(); var oTargetChanges = JSON.parse(target_change_list); for (var i = 0; i < oTargetChanges.length; i++) { var sChgId = oTargetChanges[i].id; // avoid duplicates if (helper.relationshipExists()) continue; // create relationship grTaskRel.initialize(); grTaskRel.setValue('parent', source_sys_id); grTaskRel.setValue('child', sChgId); grTaskRel.setValue('type', rel_type); var sRelId = grTaskRel.insert(); if (sRelId != '') { updateSourceRecordRel(grTaskRel.getDisplayValue('type'), sChgId); } } // Redirect to source record var urlRedirect = source_table_name + ".do?sys_id=" + source_sys_id; response.sendRedirect(urlRedirect); } // Prevent duplicates function changeExists(parent_id, change_id) { var grTaskRelTask = new GlideRecord('task_rel_task'); grTaskRelTask.addQuery('parent', parent_id); grTaskRelTask.addQuery('child', change_id); grTaskRelTask.query(); return grTaskRelTask.hasNext(); } function updateSourceRecordRel(rel_display, change_id) { /* Check if the respective fields on the source form are populated. If they are not, * try to set the value now. IF check is in place to ensure values are not overwritten */ var fieldMap = { "incident": { "Caused by::Causes": "caused_by", "Solved by::Solves": "rfc" }, "problem": { "Solved by::Solves": "rfc" } }; var grIncident = new GlideRecord(source_table_name); if (grIncident.get(source_sys_id)) { var targetField = fieldMap[source_table_name][rel_display]; var targetFieldValue = grIncident.getValue(targetField) || ""; if (targetFieldValue == "") { grIncident.setValue(targetField, change_id); grIncident.update(); } } }


Popular posts from this blog

GlideRecord setValue

variable advanced reference qualifier example

Running transform maps asynchronously