Name

global.ChangeUtilsSNC

Description

This function contains utility functions related to Change Request . This function should not be modified by the customer. Public functions ajaxFunction_getChangeQueryParams isCopyRulesValid isCopyFlagValid makeRelatedTableCopy copyChangeRelatedLists copyChangeAttachments getCsvValue getRedirectUrlForChangeForm isVerbBack getFieldChoices

Script

var ChangeUtilsSNC = Class.create();
ChangeUtilsSNC.prototype = Object.extendsObject(AbstractAjaxProcessor, {
  CHANGE_REQUEST: 'change_request',
  ALWAYS_IGNORE_ATTRS: ['created_by', 'created', 'updated_by', 'updated', 'sys_mod_count', 'sys_id'],
  CHANGE_TASK: 'change_task',
  TASK_CI: 'task_ci',
  TASK_CREATED_FROM: 'created_from',
  TASK_CREATED_FROM_WORKFLOW: 'workflow',
  TASK_CREATED_FROM_FLOW: 'flow',
  PROP_CHANGE_RELATED_LISTS: 'com.snc.change_request.copy.related_lists',
  PROP_CHANGE_ATTRS: 'com.snc.change_request.copy.attributes',
  PROP_CHANGE_COPY_ENABLED: 'com.snc.change_request.enable_copy',
  PROP_CHANGE_ATTACH_COPY_ENABLED: 'com.snc.change_request.attach.enable_copy',
  PROP_USE_AFFECTED_CI: "com.snc.change_request.refresh_impacted.include_affected_cis",
  PROP_POPULATE_SERVICE_OFFERING: "com.snc.task.populate_service_offering",
  PROP_POPULATE_BUSINESS_APPLICATION: "com.snc.task.populate_business_application",
  PROP_RUN_REFRESH_AS_EVENT: "com.snc.change_request.refresh_impacted.event",
  PROP_CHANGE_CONFLICT_POPULATE_IMPACTED_CIS: "change.conflict.populateimpactedcis",
  PROP_REFRESH_IMPACTED_SERVICES_MESSAGE_SHOW: "com.snc.change_request.refresh_impacted_services.message.show",
  PLUGIN_SERVICE_MAPPING: "com.snc.service-mapping",
  PLUGIN_CHANGE_COLLISION: "com.snc.change.collision",
  REFRESH_TRACKER_NAME: "Refresh impacted services",

  CHANGE_REQUEST_DEFAULT_ATTR_VALUE: 'category,cmdb_ci,priority,risk,impact,type,assignment_group,assigned_to,short_description,description,change_plan,backout_plan,test_plan',
  VERB_BACK: 'sysverb_back',
  RELATED_TABLES_MAP: {
  	'change_task': {
  		property: 'com.snc.change_request.copy.rl.change_task.attributes',
  		parentAttr: 'change_request',
  		defaultValue: 'cmdb_ci,priority,assignment_group,assigned_to,short_description,description',
  		key: '',
  		copyRelated: 'task_ci',
  		copyAttachmentsKey: 'com.snc.change_request.rl.change_task.attach.enable_copy'
  	},
  	'task_cmdb_ci_service': {
  		property: 'com.snc.change_request.copy.rl.task_cmdb_ci_service.attributes',
  		parentAttr: 'task',
  		defaultValue: 'cmdb_ci_service',
  		key: 'cmdb_ci_service'
  	},
  	'task_ci': {
  		property: 'com.snc.change_request.copy.rl.task_ci.attributes',
  		parentAttr: 'task',
  		defaultValue: 'ci_item',
  		key: 'ci_item'
  	},
  	'task_service_offering': {
  		property: 'com.snc.change_request.copy.rl.task_service_offering.attributes',
  		parentAttr: 'task',
  		defaultValue: 'service_offering',
  		key: 'service_offering'
  	},
  	'task_cmdb_ci_business_app': {
  		property: 'com.snc.change_request.copy.rl.task_cmdb_ci_business_app.attributes',
  		parentAttr: 'task',
  		defaultValue: 'cmdb_ci_business_app',
  		key: 'business_application'
  	}
  },
  STAY_ON_CURRENT_CHANGE_REQUEST_MAP: {
  	//Calculate risk - eea1aa78c0a8ce01002aa530a8f1eb65
  	"eea1aa78c0a8ce01002aa530a8f1eb65": true,
  	//Refresh impacted services - refresh_impacted_services
  	"refresh_impacted_services": true
  },

  initialize: function(request, responseXML, gc) {
  	AbstractAjaxProcessor.prototype.initialize.call(this, request, responseXML, gc);
  	this.arrayUtil = new ArrayUtil();
  	this.log = new GSLog('com.snc.change_request.copy.log', 'ChangeUtilsSNC');
  	
  	this.STAY_ON_CURRENT_CHANGE_REQUEST_MAP[this.VERB_BACK] = true;
  },

  //function to return if an actionVerb is supposed to be redirected
  //to stay on the same page
  isStayOnCRPageActionVerb: function(actionVerb) {
  	if (JSUtil.nil(actionVerb))
  		return false;
  	return !!this.STAY_ON_CURRENT_CHANGE_REQUEST_MAP[actionVerb];
  },

  ajaxFunction_getChangeQueryParams: function() {
  	//read attributes list
  	var attributesList = this._getCsvPropertyValue(this.PROP_CHANGE_ATTRS,
  		this.CHANGE_REQUEST_DEFAULT_ATTR_VALUE);
  	var srcSysId = this.getParameter('sysparm_src_sysid');
  	var gr = new GlideRecordSecure(this.CHANGE_REQUEST);
  	if (gr.get(srcSysId)) {
  		return this._getRecordValuesAsEncodedQuery(gr, attributesList);
  	} else {
  		this.log.logErr('Invalid src change_request sysid provided = ' +
  			srcSysId);
  	}
  },

  _getRecordValuesAsEncodedQuery: function(record, attributesList) {
  	var table = j2js(record.getTableName());

  	var gr = new GlideRecordSecure(table);
  	for (var i = 0; i < attributesList.length; ++i) {
  		var name = attributesList[i];
  		if (record.isValidField(name)) {
  			if (record.getValue(name)){
  				if (!gs.nil(record.getElement(name)) && !gs.nil(record.getElement(name).getED())) {
  					var ed = record.getElement(name).getED();
  					// We have to use the display value if it's a date based field for form filter
  					if (ed.getInternalType() + '' === "glide_date_time" || ed.getInternalType() + '' === "glide_time" || ed.isEncrypted())
  						gr.addQuery(name, record.getDisplayValue(name));
  					else
  						gr.addQuery(name, record.getValue(name));
  				} else
  					gr.addQuery(name, record.getValue(name));
  			}
  		}
  		else
  			this.log.logWarning("Invalid field '" + name + "' provided for table '" + table + "'.");
  	}
  	return gr.getEncodedQuery();
  },

  _isSet: function(obj) {
  	// Unlike browsers, Rhino condition evaluation
  	// returns false when the passed obj is array
  	// and has length 0. We want the browser behavior.
  	return obj || JSUtil.type_of(obj) === 'object';
  },

  isCopyFlagValid: function() {
  	//Read property definition
  	var isCopyChangeEnabledProp = j2js(gs.getProperty(this.PROP_CHANGE_COPY_ENABLED));

  	if (isCopyChangeEnabledProp === 'false') {
  		return false;
  	} else {
  		return true;
  	}

  },

  isCopyRulesValid: function(/*GlideRecord*/ changeRequest) {
  	//Read service now rules for enabling copy
  	var rulesValid = true;

  	if (j2js(changeRequest.type) === 'standard') {
  		rulesValid = false;
  	}

  	return rulesValid;
  },

  getCsvValue: function(value) {
  	var val = value.trim();
  	val = val.split(',');
  	for (var i = 0; i < val.length;) {
  		val[i] = val[i].trim();
  		if (!val[i]) {
  			val.splice(i, 1); // Removing empty entries
  		} else {
  			i++;
  		}
  	}
  	return val;
  },

  _getCsvPropertyValue: function(ppty, defaultVal) {
  	var val = gs.getProperty(ppty, defaultVal);
  	return this.getCsvValue(val);
  },

  _copyAttachments: function(srcGr, targetSysId) {
  	var res = [];
  	if (srcGr.hasAttachments() && this.hasReadWriteAccess()) {
  		var table = srcGr.getTableName();
  		res = j2js(GlideSysAttachment.copy(table, srcGr.getUniqueValue(), table, targetSysId));
  	}
  	return res;
  },

  makeRelatedTableCopy: function( /*String*/ srcParentSysId,
  								/*String*/ targetParentSysId,
  								/*String*/ table,
  								/*String*/ key, /*If provided, used to prevent duplicate rows*/
  								/*String*/ parentAttr,
  								/*Array*/ copyAttrs,
  								/*Boolean*/ copyAttachments,
  								/*CSV String*/ copyChildRelatedLists) {
  	if (!copyChildRelatedLists)
  		copyChildRelatedLists = [];
  	else
  		copyChildRelatedLists = this.getCsvValue(copyChildRelatedLists);

  	var ans = [];
  	copyAttrs = this.arrayUtil.diff(copyAttrs, this.ALWAYS_IGNORE_ATTRS, [
  		parentAttr
  	]);
  	var srcGr = new GlideRecordSecure(table);
  	if (srcGr.isValid()) {
  		var existingRecords = [];
  		srcGr.addQuery(parentAttr, srcParentSysId);
  		//Check if table is task_ci and if so exclude dynamic added CI as these will be processed when the dynamic CI is inserted
  		if (table === this.TASK_CI)
  			srcGr.addNullQuery('added_from_dynamic_ci');
  		srcGr.query();
  		if (key) {
  			existingRecords = this._getTargetRelatedRecordKeys(table, key,
  				parentAttr, targetParentSysId);
  		}
  		while (srcGr.next()) {
  			if (key
  				&& this.arrayUtil.contains(existingRecords, srcGr.getValue(key)))
  				continue;
  			if ((table === this.CHANGE_TASK) && (srcGr.getValue(this.TASK_CREATED_FROM) === this.TASK_CREATED_FROM_WORKFLOW || srcGr.getValue(this.TASK_CREATED_FROM) === this.TASK_CREATED_FROM_FLOW))
  				continue;
  			var newSysId = this._makeRelatedRecordCopy(srcGr, copyAttrs,
  				parentAttr, targetParentSysId);
  			if (newSysId) {
  				ans.push(newSysId);

  				if (copyAttachments)
  					this._copyAttachments(srcGr, newSysId);

  				for (var i = 0; i < copyChildRelatedLists.length; ++i) {
  					var relatedTable = copyChildRelatedLists[i];
  					var newAns = this._makeRelatedTableCopy(srcGr.getUniqueValue(),
  															newSysId, relatedTable);
  					if (!this._isSet(newAns)) {
  						this.log.logWarning('makeRelatedTableCopy: Could not copy related\'s related table ' +
  											relatedTable);
  					}
  				}
  			} else {
  				this.log.logWarning('makeRelatedTableCopy: Could not copy related table ' + table);
  			}
  		}
  		return ans;
  	} else {
  		this.log.logWarning('makeRelatedTableCopy: Invalid table ' + table);
  	}
  },

  _makeRelatedTableCopy: function(srcParentSysId, targetParentSysId, table) {
  	var map = this.RELATED_TABLES_MAP[table];
  	if (!map) {
  		this.log.logWarning('_makeRelatedTableCopy: Unsupported related table ' + table);
  		return;
  	}
  	var key = map.key;
  	var parentAttr = map.parentAttr;
  	var attachmentKey = map.copyAttachmentsKey;
  	var copyAttachments = false;
  	if (attachmentKey)
  		copyAttachments = this._getCsvPropertyValue(attachmentKey, 'true') == 'true';
  	var copyAttrs = this._getCsvPropertyValue(map.property,
  		map.defaultValue);
  	var copyChildRelatedLists = map['copyRelated'];
  	return this.makeRelatedTableCopy(srcParentSysId, targetParentSysId, table,
  									key, parentAttr, copyAttrs,
  									copyAttachments, copyChildRelatedLists);
  },

  _makeRelatedRecordCopy: function(srcGr, copyAttrs, parentAttr,
  	targetParentSysId) {
  	var gr = this._makeRecordCopy(srcGr, copyAttrs);
  	gr.setValue(parentAttr, targetParentSysId);
  	return gr.insert();
  },

  _getTargetRelatedRecordKeys: function(table, key, parentAttr,
  	targetParentSysId) {
  	var ans = [];
  	var gr = new GlideRecordSecure(table);
  	gr.addQuery(parentAttr, targetParentSysId);
  	gr.query();
  	while (gr.next()) {
  		ans.push(gr.getValue(key));
  	}
  	return ans;
  },

  _makeRecordCopy: function(srcGr, copyAttrs) {
  	var table = j2js(srcGr.getTableName());
  	var gr = new GlideRecordSecure(table);
  	gr.initialize();
  	for (var i = 0; i < copyAttrs.length; ++i) {
  		var field = copyAttrs[i];
  		if (srcGr.isValidField(field))
  			gr.setValue(field, srcGr.getValue(field));
  		else
  			this.log.logWarning("_makeRecordCopy: Invalid field '" + field +
  								"' provided for table '" + table + "'.");
  	}
  	return gr;
  },

  copyChangeRelatedLists: function(/*String*/ srcChgSysID, /*String*/ newChgSysID) {
  	var ret = true;
  	var relatedTables = this._getCsvPropertyValue(this.PROP_CHANGE_RELATED_LISTS, '');
  	for (var i = 0; i < relatedTables.length; ++i) {
  		var table = relatedTables[i];
  		var ans = this._makeRelatedTableCopy(srcChgSysID, newChgSysID, table);
  		if (!this._isSet(ans)) {
  			this.log.logWarning('copyChangeRelatedLists: Could not copy related table ' + table);
  			ret = false;
  		}
  	}
  	return ret;
  },

  copyChangeAttachments: function(/*String*/ srcSysId, /*String*/ targetSysId) {
  	var isCopyEnabled = gs.getProperty(this.PROP_CHANGE_ATTACH_COPY_ENABLED, 'true') == 'true';
  	if (isCopyEnabled) {
  		var gr = new GlideRecordSecure(this.CHANGE_REQUEST);
  		if (gr.get(srcSysId)) {
  			return this._copyAttachments(gr, targetSysId);
  		} else {
  			this.logErr('copyChangeAttachments: Provided Change Request does not exist - ' + srcSysId);
  		}
  	}
  },

  _getRedirectUrlForForm: function(/*String*/ actionVerb, /*SysId String*/ sysId, /*String*/ table) {
  	var isStayOnPageActionVerb = this.isStayOnCRPageActionVerb(actionVerb);

  	//Logic for deriving URL to redirect to
  	var urlOnStack = '';
  	var suffix = '_and_stay';
  	if (!JSUtil.nil(actionVerb) && ((actionVerb.indexOf(suffix, actionVerb.length - suffix.length) !== -1) || isStayOnPageActionVerb)) {
  		// If the action verb ends with _and_stay then the user intends to submit
  		// the form and stay back on that page. However, the goto url overrides
  		// that behavior and user is taken to this processor. So, the onus is
  		// on processor to redirect the user back to the form.
  		// Or is one of the actions where we should stay on the form.

  		//set the new sys_id to the URL for the form.
  		var gu = new GlideURL(table + '.do');
  		gu.set('sys_id', sysId);
  		var createdChangeUrl = gu.toString();
  		urlOnStack = createdChangeUrl;
  	} else {
  		// It is assumed that the processor's url is not added to back stack,
  		// and before the user is redirected to this, the form's url is popped from
  		// back stack by system. So the stack top contains the page we want to goto.
  		if (!gs.getSession().getStack().isEmpty())
  			urlOnStack = gs.getSession().getStack().pop();
  		if (JSUtil.nil(urlOnStack))
  			urlOnStack = 'welcome.do';
  	}
  	return urlOnStack;
  },

  getRedirectUrlForChangeForm: function (/*String*/ actionVerb, /*SysId String*/ sysId) {
  	return this._getRedirectUrlForForm(actionVerb, sysId, this.CHANGE_REQUEST);
  },
  
  // Checks if the "back" button on the form was pressed
  isVerbBack: function(actionVerb) {
  	return actionVerb === this.VERB_BACK;
  },
  
  // returns a ChoiceList
  getFieldChoices: function(fieldName) {
  	var choices = null;
  	
  	if (!fieldName)
  		return choices;
  	
  	if (!GlideTableDescriptor.fieldExists(this.CHANGE_REQUEST, fieldName))
  		return choices;
  	
  	var clg = new GlideChoiceListGenerator("change_request", fieldName);
  	clg.setNone(false);
  	
  	return clg.get();
  },
  
  hasReadWriteAccess: function() {
  	var tableDesc = GlideTableDescriptor.get(this.CHANGE_REQUEST);
  	return tableDesc.canRead() && tableDesc.canWrite();
  },
  
  /**
  *
  * @param String chgId - The Change Request sys_id
  * @param String userId - The sys_user sys_id to notify on completion. if null, do not send notification
  * @return
  */
  getImpactedServicesFromAffectedCIs: function(chgId, userId) {
  	if (this.log.atLevel(GSLog.DEBUG))
  		this.log.debug("[getImpactedServicesFromAffectedCIs] chgId: " + chgId + " userId: " + userId);

  	var ciu = new CIUtils();
  	var tskUtils = new TaskUtils();
  	//Calling the internal method as CIUtils might be modified and not get a new method added
  	var services = ciu._getImpactedServicesFromAffectedCIs(chgId);  
  	this._addAdditionalServices(services, chgId);

  	if (userId) {
  		var chgGr = new GlideRecord('change_request');
  		if (chgGr.get(chgId)) {
  			var msg = gs.getMessage("Refresh impacted services for change request {0} has completed", chgGr.getDisplayValue('number'));
  			tskUtils.notifyUser(chgId, userId, msg);
  		}
  	}
  },
  
  /**
  *
  * @param String chgId - The Change Request sys_id
  * @param String userId - The sys_user sys_id to notify on completion. if null, do not send notification
  * @return
  */
  getImpactedServicesFromPrimaryCI: function(chgId, userId) {
  	var ciu = new CIUtils();
  	var tskUtils = new TaskUtils();
  	var changeRequestGr = new GlideRecordSecure(global.ChangeRequest.CHANGE_REQUEST);
  	if (changeRequestGr.get(chgId)) {
  		var services = ciu.servicesAffectedByCI(changeRequestGr.cmdb_ci);
  		this._addAdditionalServices(services, changeRequestGr.getUniqueValue());
  	}
  	
  	if (userId) {
  		var chgGr = new GlideRecord('change_request');
  		if (chgGr.get(chgId)) {
  			var msg = gs.getMessage("Refresh impacted services for change request {0} has completed", chgGr.getDisplayValue('number'));
  			tskUtils.notifyUser(chgId, userId, msg);
  		}
  	}
  },

  refreshImpactedServices: function(chgGr, createEvent) {
  	if (typeof chgGr === "string") {
  		var changeRequestGr = new GlideRecordSecure(global.ChangeRequest.CHANGE_REQUEST);
  		if (changeRequestGr.get(chgGr))
  			chgGr = changeRequestGr;
  		else
  			chgGr = null;
  	}

  	if (!chgGr && this.getChgMgtWorker()) {
  		var request = this.getChgMgtWorker().getValue("request");
  		var chgRequestGr = new GlideRecordSecure(global.ChangeRequest.CHANGE_REQUEST);
  		if (request && request.task && chgRequestGr.get(request.task))
  			chgGr = chgRequestGr;
  	}

  	if (!chgGr) {
  		if (this.getChgMgtWorker())
  			this.getChgMgtWorker().addErrorMsg(gs.getMessage("Invalid change request sysId provided"));

  		return;
  	}

  	var refreshImpactedServicesMsg = gs.getMessage("Refresh impacted services has been initiated");

  	if (gs.getProperty(this.PROP_REFRESH_IMPACTED_SERVICES_MESSAGE_SHOW, "true") === "true" && this.getChgMgtWorker())
  		this.getChgMgtWorker().addInfoMsg(refreshImpactedServicesMsg);

  	if (!createEvent)
  		createEvent = false;

  	var useAffectedCi = gs.getProperty(this.PROP_USE_AFFECTED_CI) + "" === "true";

  	if (this.log.atLevel(GSLog.DEBUG))
  		this.log.debug("[refreshImpactedServices] useAffectedCi: " + useAffectedCi + " createEvent: " + createEvent);

  	// chgMgtWorker is async already, do not kick off second level async process
  	if (createEvent && !this.getChgMgtWorker()) {
  		gs.eventQueue("change.refresh_impacted_ci", chgGr, gs.getUserID());
  		if (gs.getProperty(this.PROP_REFRESH_IMPACTED_SERVICES_MESSAGE_SHOW, "true") === "true")
  			gs.addInfoMessage(refreshImpactedServicesMsg);
  	} else {
  		// userId passed as null to prevent notification being sent.
  		// Script Action: Trigger refresh affected cis, also calls this method, but with valid userId to send notification.
  		if (useAffectedCi)
  			this.getImpactedServicesFromAffectedCIs(chgGr.getUniqueValue(), null);
  		else
  			this.getImpactedServicesFromPrimaryCI(chgGr.getUniqueValue(), null);
  	}
  },

  _addAdditionalServices: function(services, chgId) {
  	var ciu = new CIUtils();
  	var arrUtil = new ArrayUtil();	
  	var populateImpactedCis = gs.getProperty(this.PROP_CHANGE_CONFLICT_POPULATE_IMPACTED_CIS) + "" === "true";

  	if (this.log.atLevel(GSLog.DEBUG))
  		this.log.debug("[_addAdditionalServices] populateImpactedCis: " + populateImpactedCis);

  	if (this.getChgMgtWorker())
  		this.getChgMgtWorker().addInfoMsg(gs.getMessage("{0} set to: {1}", [this.PROP_CHANGE_CONFLICT_POPULATE_IMPACTED_CIS, populateImpactedCis]));

  	if (populateImpactedCis && GlidePluginManager.isActive(this.PLUGIN_CHANGE_COLLISION)) {
  		var conflictServices = ChangeCollisionHelper.getImpactedServicesByChangeId(chgId);
  		services = services ? arrUtil.union(services, conflictServices) : conflictServices;
  	}

  	//Check if Service Mapping is active
  	if (GlidePluginManager.isActive(this.PLUGIN_SERVICE_MAPPING)) {
  		var apps = new ChangeRequestDiscovery().getApplicationsByChangeId(chgId);
  		services = services ? arrUtil.union(services, apps) : apps;
  	}

  	ciu.removeImpactedServices(chgId);
  	ciu.addImpactedServices(chgId, arrUtil.unique(services));

  	//Populate the additional related lists
  	var chgGr = new GlideRecord(this.CHANGE_REQUEST);
  	if (chgGr.get(chgId))
  		new TaskUtils().refreshRelatedLists(chgGr);
  },

  _getRunningWorker: function(chgId) {
      var trackerGr = new GlideRecord("sys_execution_tracker");
      trackerGr.addQuery("source", chgId);
  	trackerGr.addQuery("name", this.REFRESH_TRACKER_NAME);
      trackerGr.addQuery("state", "IN", "0,1"); //Pending or Running
      trackerGr.query();
      if (trackerGr.next())
          return trackerGr.getUniqueValue();
      return "";
  },

  createRefreshWorker: function(chgId, userId) {
  	var trackerId = this._getRunningWorker(chgId);
  	if (trackerId) {
  		var execTracker = SNC.GlideExecutionTracker.getBySysID(trackerId);
  		execTracker.cancel(gs.getMessage("Refresh impact services has been cancelled"));
  	}

  	var worker = new GlideScriptedHierarchicalWorker();
      worker.setProgressName(this.REFRESH_TRACKER_NAME);
      worker.setScriptIncludeName("ChangeUtils");

  	if (gs.getProperty(this.PROP_USE_AFFECTED_CI) + "" === "true")
  		worker.setScriptIncludeMethod("getImpactedServicesFromAffectedCIs");
  	else
  		worker.setScriptIncludeMethod("getImpactedServicesFromPrimaryCI");

      worker.putMethodArg("chgId", chgId + '');
  	worker.putMethodArg("userId", userId);
      worker.setBackground(true);
  	worker.setSource(chgId);
  	worker.setSourceTable('change_request');
  	worker.setMaxProgressValue(4);	
  	worker.start();

  	return worker.getProgressID();
  },

  setChgMgtWorker: ChangeManagementWorkerSNC.methods.setChgMgtWorker,

  getChgMgtWorker: ChangeManagementWorkerSNC.methods.getChgMgtWorker,

  type: 'ChangeUtilsSNC'
});

Sys ID

2ddf83eb9fa331002920bde8132e7044

Offical Documentation

Official Docs: