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')
onclick:
condition:
RP.isRelatedList() && (new ChangeTaskRelUtility()).isValidTable(parent.sys_class_name)
Script:
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
HTML:
<?xml version="1.0" encoding="utf-8" ?> <!-- get parameters into variables --> // 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 (grTaskRelTask.next()) { aExistingRel.push(grTaskRelTask.getValue('child')); } var sExisting = (aExistingRel.length > 0) ? "sys_idNOT IN" + aExistingRel.join(',').toString() : ""; sExisting; /g:evaluate // 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 (grRelAllowedType.next()) { aAllowedTypes.push(grRelAllowedType.getValue('type')); } var sAllowed = (aAllowedTypes.length > 0) ? "sys_idIN" + aAllowedTypes.join(',').toString() : ""; sAllowed; /g:evaluate <!-- reserving spot for error messages -->
${gs.getMessage('Change Request Lookup')}
${gs.getMessage('Selected Change Requests')}
${gs.getMessage('Select relationship type')}
Client script:
var aChangeRequests = []; // globally store the list of selected change requests // Hide selected list UI on load as it will be emptyvar oChangeListContainer = $j('#display_change_list').parent(); oChangeListContainer.hide(); //hide on load // Hide the alert box on load as it will be emptyvar 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 + "
"; } document.getElementById('display_change_list').innerHTML = sTextArea; // Toggle display of select change container (aChangeRequests.length > 0) ? oChangeListContainer.show() : 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("
" + msg + "
"); } function showAlertBox() { oAlertBox.prepend("Please address the following issues:
"); oAlertBox.show(); }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 duplicatesfunction 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(); } } }
Haven't tested these all recently within global/local scopes, so feel free to have a play! option 1 use an encoded query embedded in the GlideRecord , e.g. var grProf = new GlideRecord ( 'x_cls_clear_skye_i_profile' ); grProf . addQuery ( 'status=1^ owner=NULL ' ); grProf . query (); even better use the glideRecord addNotNullQuery or addNullQuery option 2 JSUtil.nil / notNil (this might be the most powerful. See this link ) example: if ( current . operation () == 'insert' && JSUtil . notNil ( current . parent ) && ! current . work_effort . nil ()) option 3 there might be times when you need to get inside the GlideRecord and perform the check there, for example if the code goes down 2 optional routes depending on null / not null can use gs.nil : var grAppr = new GlideRecord ( 'sysapproval_approver' ); var grUser = new GlideRecord ( 'sys_user' ); if ( grUser . get ( 'sys_id' , current . approver )){
Classic UI : var sURL_editparam = gs . action . getGlideURI (). getMap (). get ( ' sysparm_aparameter ' ); if ( sURL_editparam == 'true' ) { gs . addInfoMessage ( 'parameter passed ); } Portal : var sURL_editparam = $sp . getParameter ( " sysparm_aparameter " ); if ( sURL_editparam == 'true' ) { gs . addInfoMessage ( 'parameter passed ); }
Call a script include to apply a reference qualifier on a catalog item variable: - variable reference qualifier dependent on another variable selection, in this case a variable referencing sys_user (requested_for) On the catalog item form. variable name to apply ref qual filter : retail_equipment variable reference qualifier (on cmdb table ): javascript : new refqual_functions (). lostStolen_getAssignedCIs (); client-callable script include ( refqual_functions) function : lostStolen_getAssignedCIs : function (){ //--called from variable set client script, for lost/stolen request (service catalog) gs . log ( current . variables . requested_for , 'retail_lostStolen_getAssignedCIs' ); return ( 'install_statusNOT IN8,7owned_by=' + current . variables . requested_for ); //owned_by=1269b79937f1060041c5616043990e41install_statusNOT IN8,7 },
http://www.cloudminus89.com/2021/05/incident-caused-by-change-part-2.html