Name

global.AutoResolutionContextHelper

Description

Helper for handling everything related to sys_cs_auto_resolution_context.

Script

var AutoResolutionContextHelper = Class.create();
AutoResolutionContextHelper.prototype = {
  
  initialize: function() {
  	this.RESOLVED_MSG = gs.getMessage('The user decided to close the issue since the topic resolved the issue.');
  	this.RESOLVED_CATALOG_MSG = gs.getMessage('I am closing the task because the user submitted a catalog item and the SLA is breached.');
  	this.UNRESOLVED_MSG = gs.getMessage('The user decided to leave the issue open since the topic does not resolve the issue.');
  	this.DECLINED_MSG = gs.getMessage('The user declined the notification.');
  	this.UNABLE_TO_PROCEED = gs.getMessage('The user accepted the notification, but Auto-Resolution was not able to help resolve the issue.');
  	this.GENERAL_UNASSIGN_TASK_MSG = gs.getMessage("Auto-Resolution can't resolve the task and un-assigned the task. The context timed out or the user abandoned the conversation.");
  	
  	this.RESOLVED = 'resolved';
  	this.UNRESOLVED = 'unresolved';
  	this.TOPIC_COMPLETED = 'topic_completed';
  	this.ERROR = 'error';
  	this.DECLINED = 'declined';
  	this.CATALOG_TIMEOUT = 'catalog_timeout';
  },
  
  getTaskDetails: function(contextId) {
  	var contextGr = this._getContextGr(contextId);
  	var taskGr = contextGr.getElement('task').getRefRecord();
  	return taskGr;
  },	
  
  getContextDetails: function(contextId) {
  	var contextGr;
  	var configurationGr;
  	var taskId;
  	var taskTable;
  	var topicId;

  	this.LOGGER = new AutoResolutionLoggingUtils()
  		.withName(this.type)
  		.withContextId(contextId)
  		.createLogger();
  	
  	/*** Context Validation ***/
  	var contextValidation = this._validateContext(contextId);
  	if (!contextValidation.isValid) 
  		return this._createInvalidResponse(contextValidation.validationReason, 'Context validation failed');

  	this.LOGGER.debug('Passed context validation for context: {0}', contextId);
  	contextGr = contextValidation.contextGr;
  	
  	/*** Configuartion Validation ***/
  	var configurationValidation = this._validateConfiguration(contextGr);
  	if (!configurationValidation.isValid)
  		return this._createInvalidResponse(configurationValidation.validationReason, 'Configuration validation failed');

  	this.LOGGER.debug('Passed configuration validation for context: {0}', contextId);
  	configurationGr = configurationValidation.configurationGr;
  	
  	/*** Topic Validation ***/
  	var topicValidation = this._validateTopic(contextGr);
  	if (!topicValidation.isValid)
  		return this._createInvalidResponse(topicValidation.validationReason, 'Topic validation failed');

  	this.LOGGER.debug('Passed topic validation for context: {0}', contextId);
  	topicId = topicValidation.topicId;
  	
  	/*** Task Validation ***/
  	var taskValidation = this._validateTask(contextGr, configurationGr);
  	if (!taskValidation.isValid)
  		return this._createInvalidResponse(taskValidation.validationReason, 'Task validation failed');

  	this.LOGGER.debug('Passed task validation for context: {0}', contextId);
  	
  	taskId = taskValidation.taskId;
  	taskTable = taskValidation.taskTable;
  	
  	// Validation has passed for the Auto Resolution Context and
  	// we can continue with the Resolution Topic
  	return {
  		isValid: true,
  		taskId:  taskId,
  		taskTable: taskTable,
  		topicId: topicId,
  		language: contextGr.getValue('task_creation_language_code'),
  		configId: configurationGr.getUniqueValue(),
  		validationReason: ''
  	};
  },
  
  executeTemplate: function(type, contextId, loggingContext, message) {
  	this.LOGGER = new AutoResolutionLoggingUtils()
  		.withName(this.type)
  		.withContext(loggingContext)
  		.createLogger();
  	var templateFunction;
  	
  	// If there is some undefined type passed the topic will fail.
  	// Lets mark the task and context accordingly
  	if (gs.nil(type)) {
  		var msg = 'No template type passed to ' + this.type + '.executeTemplate()';
  		this.LOGGER.error('No template passed to executeTemplate()');
  		this.executeErrorTemplate(contextId);
  		throw msg;
  	}
  	
  	switch (type) {
  		case this.RESOLVED:
  			templateFunction = this.executeTaskResolutionTemplate;
  			break;
  		case this.UNRESOLVED:
  			templateFunction = this.executeUnresolvedTaskTemplate;
  			break;
  		case this.DECLINED:
  			templateFunction = this.executeDeclinedTemplate;
  			break;
  		case this.TOPIC_COMPLETED:
  			templateFunction = this.executeTopicCompletionTemplate;
  			break;
  		case this.ERROR:
  			templateFunction = this.executeErrorTemplate;
  			break;
  		case this.CATALOG_TIMEOUT:
  			templateFunction = this.closeTaskViaCatalogTimeout;
  			break;
  		default:
  			this.LOGGER.error('Invalid template type: {0}', type);
  			return false;
  	}
  	
  	this._executeFunctionAsBotUser(templateFunction, contextId, message);
  },
  
  executeTaskResolutionTemplate: function(contextId, message) {
  	this._executeTaskResolutionTemplate(contextId, message || this.RESOLVED_MSG);
  },

  closeTaskViaCatalogTimeout: function(contextId, message) {
  	this._executeTaskResolutionTemplate(contextId, message || this.RESOLVED_CATALOG_MSG);
  },

  _executeTaskResolutionTemplate: function(contextId, message) {
  	var contextGr = this._getContextGr(contextId);
  	var taskGr = AutoResolutionTaskDataBroker.getTaskRecordFromContextRecord(contextGr);
  	var configurationGr = this.getConfigurationGr(contextGr);
  	taskGr.applyTemplate(configurationGr.task_resolution_template.name);
  	taskGr.work_notes = message;
  	AutoResolutionTaskDataBroker.updateTaskRecord(taskGr);

  	this.setTaskResolved(contextGr, true, false);
  	this.setActive(contextGr, false, false);
  	this.setSlaState(contextGr, AutoResolutionConstants.SLA_STATE.COMPLETED, false);
  	contextGr.update();
  },

  executeTopicCompletionTemplate: function(contextId, message) {
  	var contextGr = this._getContextGr(contextId);
  	var configurationGr = this.getConfigurationGr(contextGr);
  	var taskGr = AutoResolutionTaskDataBroker.getTaskRecordFromContextRecord(contextGr);
  	var topicGr = contextGr.getElement('matched_topic').getRefRecord();
  	taskGr.applyTemplate(configurationGr.topic_completion_template.name);
  	taskGr.work_notes = message || gs.getMessage('Auto-Resolution topic "{0}" completed.', topicGr.getValue('name'));
  	AutoResolutionTaskDataBroker.updateTaskRecord(taskGr);
  },
  
  executeUnresolvedTaskTemplate: function(contextId, message) {
  	this._executeUnresolvedTemplate(contextId, message || this.UNRESOLVED_MSG);
  },
  
  executeDeclinedTemplate: function(contextId, message) {
  	this._executeUnresolvedTemplate(contextId, message || this.DECLINED_MSG);
  },
  
  executeErrorTemplate: function(contextId, message) {
  	var contextGr = this._getContextGr(contextId);
  	var configurationGr = this.getConfigurationGr(contextGr);
  	var taskGr = AutoResolutionTaskDataBroker.getTaskRecordFromContextRecord(contextGr);
  	new AutoResolutionTaskHelper().unassignTask(taskGr,  message || this.UNABLE_TO_PROCEED, configurationGr.unresolved_task_template.name);
  },
  
  _executeUnresolvedTemplate: function(contextId, msg) {
  	var contextGr = this._getContextGr(contextId);
  	var configurationGr = this.getConfigurationGr(contextGr);
  	var taskGr = AutoResolutionTaskDataBroker.getTaskRecordFromContextRecord(contextGr);
  	new AutoResolutionTaskHelper().unassignTask(taskGr, msg, configurationGr.unresolved_task_template.name);
  	this.setActive(contextGr, false, false);
  	this.setSlaState(contextGr, AutoResolutionConstants.SLA_STATE.CANCELED, false);
  	contextGr.update();
  },
  
  setWorkNotesOnTask: function(contextId, workNote) {
  	var contextGr = this._getContextGr(contextId);
  	var topicGr = contextGr.getElement('matched_topic').getRefRecord();
  	var taskGr = AutoResolutionTaskDataBroker.getTaskRecord('task', contextGr.getValue('task'));
  	if (gs.nil(workNote))
  		workNote = gs.getMessage('Auto-Resolution topic "{0}" started.', topicGr.getValue('name'));
  	new AutoResolutionTaskHelper().setWorkNotesOnTask(taskGr, workNote);
  },

  /**
   * Updates context record with the field value map thats passed as input
   * @param contextSysId
   * @param contextFieldValueMap map of {"fieldname":"fieldvalue"}
   */
  updateContext: function(contextSysId, contextFieldValueMap) {
  	var contextGr = new GlideRecord('sys_cs_auto_resolution_context');
  	contextGr.get(contextSysId);

  	Object.keys(contextFieldValueMap).forEach(function(key) {
  		contextGr.setValue(key, contextFieldValueMap[key]);
  	});

  	contextGr.update();
  },

  /**
   *
   * @param configSysId
   * @param taskSysId
   * @param languageCode
   * @returns {GlideRecord}
   */
  createContext: function(configSysId, taskSysId, languageCode) {
  	var contextGr = new GlideRecord('sys_cs_auto_resolution_context');
  	contextGr.initialize();
  	contextGr.setValue('configuration', configSysId);
  	contextGr.setValue('task', taskSysId);
  	contextGr.setValue('task_creation_language_code', languageCode);
  	contextGr.insert();
  	return contextGr;
  },

  updateNotificationResponded: function(contextId, state) {
  	var contextGr = this._getContextGr(contextId);
  	if (contextGr.isValidRecord()) {
  		contextGr.setValue('notification_state', state);
  		contextGr.update();
  		
  		if (state === "accepted")
  			this._executeFunctionAsBotUser(this.setWorkNotesOnTask, contextId, "");
  	}
  },

  /**
   * Sets task_resolved, optionally updates
   * @param {GlideRecord} contextGr
   * @param {boolean} resolved
   * @param {boolean} update
   */
  setTaskResolved: function(contextGr, resolved, update) {
  	contextGr.setValue('task_resolved', resolved);
  	if (update)
  		contextGr.update();
  },

  /**
   * Sets active, optionally updates
   * @param {GlideRecord} contextGr
   * @param {boolean} resolved
   * @param {boolean} update
   */
  setActive: function(contextGr, active, update) {
  	contextGr.setValue('active', active);
  	if (update)
  		contextGr.update();
  },

  /**
   * Sets sla_state, optionally updates
   * @param {GlideRecord} contextGr
   * @param {boolean} resolved
   * @param {boolean} update
   */
  setSlaState: function(contextGr, state, update) {
  	contextGr.setValue('sla_state', state);
  	if (update)
  		contextGr.update();
  },
  
  setInteraction: function(contextId, interactionId) {
  	var contextGr = this._getContextGr(contextId);
  	if (contextGr.isValidRecord()) {
  		contextGr.setValue('interaction', interactionId);
  		contextGr.update();
  		
  		var interactionGr = new GlideRecord('interaction');
  		interactionGr.get(interactionId);
  		interactionGr.setValue('auto_resolution', true);
  		interactionGr.update();
  	}
  },

  /**
   * Unassign the task from bot user and set context inactive for any or all of these below mentioned cases:
   * Any Context record that has been in waiting state (user hasn't responded to notification) - sets it to timeout
   * Any context record that has an abandoned conversation (user has accepted the notification and some run time error has occurred during the conversation)
   * Any context record that has completed conversation but task hasn't been updated via that conversation
   */
  processContextErrorsOrTimeoutsOrClosedInteraction: function() {
  	var contextGr = new GlideRecord(AutoResolutionConstants.CONTEXT_TABLE_NAME);
  	contextGr.addEncodedQuery("notification_state=waiting^NQ" +
  		"interaction.state=closed_abandoned^NQ" +
  		"notification_stateINaccepted,declined^interaction.state=closed_complete");
  	contextGr.addQuery('sys_class_name', AutoResolutionConstants.CONTEXT_TABLE_NAME);
  	contextGr.addQuery('configuration.use_sla', '0');
  	contextGr.addActiveQuery();
  	contextGr.query();
  	var gdt = new GlideDateTime();
  	while (contextGr.next()) {
  		if (contextGr.getValue("notification_state") === "waiting") {
  			var durationMs = contextGr.configuration.task_sla.dateNumericValue();
  			gdt.subtract(durationMs);
  			if (contextGr.sys_created_on.getGlideObject().compareTo(gdt) < 0) {
  				var contextFieldValueMap = {};
  				contextFieldValueMap.notification_state = "timeout";
  				contextFieldValueMap.sla_state = "timeout";
  				contextFieldValueMap.active = false;
  				contextFieldValueMap.reason = this._prepareContextReason(contextGr, "SLA is breached.");
  				this.updateContext(contextGr.getUniqueValue(), contextFieldValueMap);
  				this._unassignTaskFromContext(contextGr);
  			}
  		} else {
  			this.setContextInactive(contextGr, "User has abandoned the conversation.");
  			this._unassignTaskFromContext(contextGr);
  		}
  	}
  },

  /**
   * @param {GlideRecord} taskGr
   * @returns {GlideRecord|null} Context record for task or null
   */
  getContextFromTask: function(taskGr) {
  	var contextGr = new GlideRecord(AutoResolutionConstants.CONTEXT_TABLE_NAME);
  	contextGr.addQuery('task', taskGr.getUniqueValue());
  	contextGr.addQuery('sys_class_name', AutoResolutionConstants.CONTEXT_TABLE_NAME);
  	contextGr.query();
  	if (contextGr.next())
  		return contextGr;
  	return null;
  },

  /**
   * @param {GlideRecord} contextGr
   * @param {string} reason
   */
  setContextInactive: function(contextGr, reason) {
  	contextGr.setValue('active', false);
  	contextGr.setValue('reason', this._prepareContextReason(contextGr, reason));
  	contextGr.update();
  },

  _prepareContextReason: function(contextGr, reason) {
  	if (gs.nil(reason))
  		return "";

  	var existingReason = contextGr.getValue('reason');
  	if (!gs.nil(existingReason) && existingReason.length > 0) {
  		reason += "\n";
  		reason += existingReason;
  	}
  	return reason;
  },

  handleCatalogSubmitted: function(contextId) {
  	var contextGr = this._getContextGr(contextId);

  	// Do nothing if a catalog has already been submitted
  	if (contextGr.getValue('catalog_submitted') === '1') {
  		new AutoResolutionLoggingUtils()
  			.withName(this.type)
  			.withContextId(contextId)
  			.createLogger()
  			.info('Catalog has already been submitted for context: {0}', contextId);
  		return;
  	}

  	this.setCatalogSubmitted(contextGr);
  	var configGr = this.getConfigurationGr(contextGr);
  	var userGr = contextGr.getElement('notification_user').getRefRecord();
  	var notificationHelper = new AutoResolutionNotificationHelper(userGr.getUniqueValue(), userGr.getTableName(), configGr.getUniqueValue());
  	var emailNotificationId = configGr.getValue('catalog_submitted_email');
  	var smsTemplateGr = notificationHelper.getSMSTemplateFromGR(configGr, 'catalog_sms');
  	var taskGr = contextGr.getElement('task').getRefRecord();
  	notificationHelper.sendEmailAndSMSNotificationForTask(taskGr, emailNotificationId, smsTemplateGr);
  },

  setCatalogSubmitted: function(contextGr) {
  	contextGr.setValue('catalog_submitted', true);
  	contextGr.update();
  },

  /**
   * Gets the session language that is stored on context record during context creation
   * @param contextGr
   * @returns {string} language code
   */
  getLanguageFromContext: function(contextGr) {
  	return contextGr.getValue("task_creation_language_code");
  },

  /**
   * Gets the matched topic name that came from an intent (not AIS)
   * @param taskSysId
   * @returns {string} matched topic name
   */
  getMatchedIntentTopicNameFromTask: function(taskSysId) {
  	var contextGr = new GlideRecord(AutoResolutionConstants.CONTEXT_TABLE_NAME);
  	contextGr.addQuery("task", taskSysId);
  	contextGr.query();

  	if (!contextGr.next())
  		return "";

  	var matchedTopic = contextGr.getValue("matched_topic");

  	if (gs.nil(matchedTopic))
  		return "";

  	var configurationGr = this.getConfigurationGr(contextGr);

  	if (!configurationGr.isValidRecord())
  		return "";

  	if (configurationGr.getValue("ais_topic") == matchedTopic)
  		return "";

  	return contextGr.matched_topic.name;
  },

  _unassignTaskFromContext: function(contextGr) {
  	var taskGr = AutoResolutionTaskDataBroker.getTaskRecordFromContextRecord(contextGr);
  	var configurationGr = this.getConfigurationGr(contextGr);
  	var templateName = configurationGr.unresolved_task_template.name;
  	new AutoResolutionTaskHelper().unassignTask(taskGr, this.GENERAL_UNASSIGN_TASK_MSG, templateName);
  },
  
  _validateContext: function(contextId) {
  	var contextGr = this._getContextGr(contextId);
  	
  	if (!contextGr.isValidRecord())
  		return this._createInvalidResponse('Context record is not found: ' + contextId);
  	
  	if (contextGr.getValue('notification_state') === 'timeout')
  		return this._createInvalidResponse('Notification expired');
  	
  	if (contextGr.getValue('active') === '0') {
  		var reason = contextGr.getValue("reason");

  		if (!gs.nil(reason))
  			return this._createInvalidResponse(reason);

  		if (contextGr.getValue("notification_state") === "accepted")
  			return this._createInvalidResponse("Notification accepted");

  		return this._createInvalidResponse('Context record is not active: ' + contextId);
  	}
  	
  	return {
  		isValid: true,
  		contextGr: contextGr
  	};
  },
  
  _validateConfiguration: function(contextGr) {
  	var configurationGr = this.getConfigurationGr(contextGr);
  	if (!configurationGr.isValidRecord())
  		return this._createInvalidResponse('Configuration record is not found');
  	
  	return {
  		isValid: true,
  		configurationGr: configurationGr
  	};
  },
  
  _validateTopic: function(contextGr) {
  	return {
  		isValid: true,
  		topicId: this._getTopicId(contextGr)
  	};
  },
  
  _validateTask: function(contextGr, configurationGr) {
  	var taskGr = contextGr.getElement('task').getRefRecord();

  	// Task exists
  	if (!taskGr.isValidRecord())
  		return this._createInvalidResponse('Task record is not found: ' + taskGr.getUniqueValue());
  	
  	// Task assigned to Virtual Agent
  	if (taskGr.getValue('assigned_to') !== AutoResolutionUtil.getBotUserId(taskGr.sys_class_name))
  		return this._createInvalidResponse('Task is not assigned to Virtual Agent');
  		
  	return {
  		isValid: true,
  		taskId: taskGr.getUniqueValue(),
  		taskTable: taskGr.getTableName(),
  	};
  },
  
  _getContextGr: function(contextId) {
  	var contextGr = new GlideRecord('sys_cs_auto_resolution_context');
  	contextGr.get(contextId);
  	return contextGr;
  },
  
  getConfigurationGr: function(contextGr) {
  	return contextGr.getElement('configuration').getRefRecord();
  },
  
  _getTopicId: function(contextGr) {
  	return contextGr.getValue('matched_topic');
  },
  
  _getTaskId: function(contextGr) {
  	return contextGr.getValue('task');
  },
  
  _getTaskTable: function(configurationGr) {
  	return configurationGr.getValue('target_table_name');
  },
  
  _getVAUserID: function() {
  	var gr = new GlideRecord('sys_user');
  	gr.addQuery('user_name', 'virtual.agent');
  	gr.query();
  	gr.next();
  	
  	return gr.getUniqueValue();
  },
  
  _createInvalidResponse: function(validationReason, validationMessage) {
  	if (!gs.nil(validationMessage))
  		this._logError(validationReason, validationMessage);
  	
  	return {
  		isValid: false,
  		validationReason: validationReason
  	};
  },
  
  /*
  * @param templateFunction - any function to be called
  * @param contextId - IAR context Id
  */
  _executeFunctionAsBotUser: function(templateFunction, contextId, message) {
  	var sessionUserId = gs.getUserID();
  	
  	var contextGr = this._getContextGr(contextId);
  	var taskGr = contextGr.getElement('task').getRefRecord();
  	var botUserId = AutoResolutionUtil.getBotUserId(taskGr.sys_class_name);
  	var shouldImpersonate = botUserId !== sessionUserId;
  	var success = true;
  	
  	if (shouldImpersonate) {
  		var gi = new GlideImpersonate();
  		gi.impersonate(botUserId);
  	}
  	
  	try {
  		templateFunction.call(this, contextId, message);
  	} catch (err) {
  		this.LOGGER.error('Error executing template function={0}: {1}', templateFunction.name, err);
  		success = false;
  	} finally {
  		if (shouldImpersonate)
  			gi.impersonate(sessionUserId);
  	}
  	
  	return success;
  },
  
  _logError: function(validationReason, validationMessage) {
  	var messages = [this.type, validationReason, validationMessage];
  	this.LOGGER.error('{0}: {1}', validationMessage, validationReason);
  },
  
  type: 'AutoResolutionContextHelper'
};

Sys ID

5d67399853e6101055eeddeeff7b1251

Offical Documentation

Official Docs: