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:
addRelationship();
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" ?>
<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 (grTaskRelTask.next()) {
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 (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 -->
<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.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("<p class=\"text-danger\">" + msg + "</p>");
}
function showAlertBox() {
oAlertBox.prepend("<p class=\"text-danger\">Please address the following issues:</p>");
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 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();
}
}
}
Comments
Post a Comment