Name
global.AutoResolutionAISearchResult
Description
An object that represents a search result that contains many Genius and search results
Script
var AutoResolutionAISearchResult = Class.create();
AutoResolutionAISearchResult.prototype = {
record:null,
geniusSearchMaxLimit: null, // overriden max number
searchMaxLimit: null, // overriden max number
initialize: function(param) {
if (typeof param === 'string') {
// if the param is string, it's the sysId of the record
this.record = new GlideRecord(global.AutoResolutionConstants.AI_SEARCH_RESULT_TABLE_NAME);
if (!this.record.get(param))
throw 'Record not found';
} else if (param instanceof GlideRecord) {
this.record = param;
}
},
/**
* after the record was updated from outside, the method will fetch the same record from db
* After this call, the record will be up to date
*/
refresh: function() {
this.initialize(this.record.getUniqueValue(), this.vaContext);
},
/**
* Enforces the max search result size for Genius search
*/
setGeniusSearchMaxLimit: function(geniusSearchMaxLimit) {
this.geniusSearchMaxLimit = parseInt(geniusSearchMaxLimit);
},
/**
* Enforces the max search result size for regular search
*/
setSearchMaxLimit: function(searchMaxLimit) {
this.searchMaxLimit = parseInt(searchMaxLimit);
},
/**
* Returns the allowed max size of genius results
*/
getAllowedMaxGeniusSearchLimit: function() {
// if the max size is set from outside, use the value.
if (!gs.nil(this.geniusSearchMaxLimit))
return this.geniusSearchMaxLimit;
// if the app config is not set, use the default size
if (gs.nil(this.record.search_app_config))
return global.AISearchConstants.DEFAULT_GENIUS_SEARCH_RESULT_LIMIT;
var limit = this.record.search_app_config.genius_results_limit;
return gs.nil(limit) ?
global.AISearchConstants.DEFAULT_GENIUS_SEARCH_RESULT_LIMIT :
parseInt(this.record.search_app_config.genius_results_limit);
},
/**
* Returns the allowed max size of search results
*/
getAllowedMaxSearchLimit: function() {
// if the max size is set from outside, use the value.
if (!gs.nil(this.searchMaxLimit))
return this.searchMaxLimit;
// if the app config is not set, use the default size
if (gs.nil(this.record.search_app_config))
return global.AISearchConstants.DEFAULT_SEARCH_RESULT_LIMIT;
var limit = this.record.search_app_config.search_results_limit;
return gs.nil(limit) ?
global.AISearchConstants.DEFAULT_SEARCH_RESULT_LIMIT :
parseInt(this.record.search_app_config.search_results_limit);
},
getSysId : function() {
return this.record.getUniqueValue();
},
getSearchAppConfigId : function() {
return this.record.getValue('search_app_config');
},
getARContextId : function() {
return this.record.getValue('ar_context');
},
getUserId : function() {
var context = this.record.ar_context;
if (gs.nil(context))
return null;
var user = context.notification_user;
if (gs.nil(user))
return null;
return user.sys_id+'';
},
getSessionId : function() {
return this.record.getValue('session_id');
},
getExecutionTime : function() {
return this.record.getValue('execution_time');
},
getSearchTerm : function() {
return this.record.getValue('search_term');
},
getResultPayload: function() {
return this.record.getValue('result_payload');
},
getSearchMetadata: function() {
return this.record.getValue('search_metadata');
},
getStatus: function() {
return this.record.getValue('status');
},
getMessage: function() {
return this.record.getValue('message');
},
getGeniusSearchResults: function() {
return this._getSearchResults(
global.AISearchConstants.RESULT_TYPE_GENIUS, this.getAllowedMaxGeniusSearchLimit());
},
getSearchResults: function() {
return this._getSearchResults(
global.AISearchConstants.RESULT_TYPE_SEARCH, this.getAllowedMaxSearchLimit());
},
getFeedbackType: function() {
return this.record.getValue('feedback_type');
},
setFeedbackType: function(feedbackType) {
this.record.setValue('feedback_type', feedbackType);
},
/**
* Traverse all line items and fire an event for GlideSignals.
* This method also updates the feedback status on each line item record.
*/
processFeedbacks: function(force) {
if (!global.AutoResolutionAISearchHelper.isAISearchFeedbackEnabled())
return;
// find line items tht have feedback_type set but not submitted yet.
var gr = new GlideRecord(global.AutoResolutionConstants.AI_SEARCH_RESULT_LINE_ITEM_TABLE_NAME);
gr.addQuery('ai_search_result', this.getSysId());
gr.addNotNullQuery('feedback_type');
if (!force)
gr.addQuery('feedback_submitted', false); // process only the records that have not been processed yet.
gr.query();
var isSubmitted = false;
while(gr.next()) {
// updated feedback_submitted as true.
var eventType = global.AISearchHelper.getGlideSignalsEventTypeByResultType(gr.getValue('result_type'));
var feedbackPayload = gr.getValue('feedback_payload');
if (gs.nil(eventType) || gs.nil(feedbackPayload) )
continue;
global.AISearchHelper.sendMetricsToGlideSignalsAPI(eventType, JSON.parse(feedbackPayload));
gr.setValue('feedback_submitted', true);
gr.update();
isSubmitted = true;
}
// if any line-item feedback is submitted and the flag is not set previously, update the flag.
if (isSubmitted && !this.isFeedbackSubmitted()) {
this.setFeedbackSent();
this.update();
}
},
/**
* Return the number of search results
*/
getSearchResultSize: function() {
return this._getResultSize(global.AISearchConstants.RESULT_TYPE_SEARCH);
},
/**
* Return the number of genius search results
*/
getGeniusSearchResultSize: function() {
return this._getResultSize(global.AISearchConstants.RESULT_TYPE_GENIUS);
},
/**
* Returns the SEARCH_EVENT feedback payload. This payload is for GlideSignals API
*/
getFeedbackPayload: function() {
return this.record.getValue('feedback_payload');
},
/**
* After the event fired, mark this column true.
*/
setFeedbackSent: function() {
this.record.setValue('feedback_submitted', true);
},
/**
* Tests if the feedback is submitted.
*/
isFeedbackSubmitted: function() {
return this.record.getValue('feedback_submitted') == '1';
},
_getResultSize: function(resultType) {
var actualSize = 0;
var ga = new GlideAggregate(global.AutoResolutionConstants.AI_SEARCH_RESULT_LINE_ITEM_TABLE_NAME);
ga.addQuery('ai_search_result', this.getSysId());
ga.addQuery('result_type', resultType);
ga.addAggregate('COUNT');
ga.query();
if (ga.next())
actualSize = ga.getAggregate("COUNT");
var allowedMaxSize = 0;
if (resultType == global.AISearchConstants.RESULT_TYPE_GENIUS)
allowedMaxSize = this.getAllowedMaxGeniusSearchLimit();
else if (resultType == global.AISearchConstants.RESULT_TYPE_SEARCH)
allowedMaxSize = this.getAllowedMaxSearchLimit();
return actualSize > allowedMaxSize ? allowedMaxSize : actualSize;
},
_getSearchResults: function(resultType, maxSize) {
var gr = new GlideRecord(global.AutoResolutionConstants.AI_SEARCH_RESULT_LINE_ITEM_TABLE_NAME);
gr.addQuery('ai_search_result', this.getSysId());
gr.addQuery('result_type', resultType);
// if need to enforce the result size.
if(!gs.nil(maxSize))
gr.setLimit(maxSize);
gr.orderBy('item_index');
var arr = [];
gr.query();
while(gr.next()) {
// pass the sysId instead of the glide record
// since the same record keeps getting reused.
var result = new AutoResolutionAISearchResultLineItem(gr.getUniqueValue());
arr.push(result);
}
return arr;
},
/**
* Send SEARCH_EVENT feedback to GlideSignals
* This method should be executed by the current user, not by the system user.
*/
sendSearchEventToGlideSignalsAPI: function(force) {
// if already submitted, don't proceed
if(!force && this.isFeedbackSubmitted())
return;
var feedback = this.getFeedbackPayload();
global.AISearchHelper.sendMetricsToGlideSignalsAPI(
global.AISearchConstants.EVENT_TYPE_SEARCH_EVENT, JSON.parse(feedback));
},
/**
* To flush the changes back to db
*/
update: function() {
this.record.update();
},
type: 'AutoResolutionAISearchResult'
};
/**
* Creates a new AI Search result
* @param contextGr
* @param searchTerm
* @param searchResultsStr : the search result in string format
* @param executionTime : the time taken for the search execution in milliseconds
* @param status : the status of the execution
* @param message : the message from the execution
* @param language : the language used for search execution
*/
AutoResolutionAISearchResult.create = function(contextGr, searchTerm, searchResultsStr, executionTime, status, message, language) {
var resultGr = new GlideRecord(global.AutoResolutionConstants.AI_SEARCH_RESULT_TABLE_NAME);
resultGr.setValue('ar_context', contextGr.getUniqueValue());
resultGr.setValue('result_payload', searchResultsStr);
resultGr.setValue('status', status);
resultGr.setValue('message', message);
var configuration = contextGr.configuration;
if (gs.nil(configuration)) //TODO : use logger
throw "Auto-Resolution configuration must exist";
var searchAppConfig = configuration.search_application;
if (gs.nil(searchAppConfig)) //TODO : use logger
throw "Search application configuration must exist";
resultGr.setValue('search_app_config', searchAppConfig.getValue());
resultGr.setValue('search_profile', searchAppConfig.search_profile.getValue());
var task = contextGr.task;
if (gs.nil(task)) //TODO : use logger
throw "task must exist";
resultGr.setValue('task_table', task.sys_class_name.getValue());
resultGr.setValue('task_id', task.getValue());
resultGr.setValue('search_term', searchTerm);
resultGr.setValue('execution_time', executionTime);
resultGr.setValue('status', status);
resultGr.setValue('message', message);
var limits = global.AISearchHelper.getResultLimits(searchAppConfig);
var processedResults
= global.AutoResolutionAISearchHelper.parseAISearchResults(
searchResultsStr, limits.genius_results_limit, limits.search_results_limit);
var searchMetadataStr = processedResults.searchMetadata; // note this is string
resultGr.setValue('search_metadata', searchMetadataStr);
var searchMetadata = JSON.parse(searchMetadataStr);
// this paramBag is required for all GlideSignalsEvent
resultGr.setValue('session_id', gs.generateGUID()); // random id for glide signals
var eventParamBag
= global.AutoResolutionAISearchHelper.createParamBagForGlideSignalsEvent(resultGr, language);
// get the feedback payload for the search event.
var searchEventPayload = createSearchEventPayload(eventParamBag, processedResults, searchMetadata);
resultGr.setValue('feedback_payload', JSON.stringify(searchEventPayload));
// Create a new record.
var resultId = resultGr.insert();
var contextId = contextGr.getUniqueValue();
// creates line items for Genius Results and Search Results
createLineItemsForGeniusResult(
eventParamBag, resultId, contextId, searchMetadata, JSON.parse(processedResults.geniusSearchResults));
createLineItemsForSearchResult(
eventParamBag, resultId, contextId, searchMetadata, JSON.parse(processedResults.searchResults));
//once creation is done, send the search-event payload to glide signals API.
var result = new AutoResolutionAISearchResult(resultGr);
result.sendSearchEventToGlideSignalsAPI();
result.update();
return result;
};
/**
* Returns AISearch results found by the context Id.
*/
AutoResolutionAISearchResult.getByContextId = function(contextId) {
var gr = new GlideRecord(AutoResolutionConstants.AI_SEARCH_RESULT_TABLE_NAME);
gr.addQuery('ar_context', contextId);
gr.query();
return gr.next()? new AutoResolutionAISearchResult(gr): null;
};
/**
* Creates a payload object for the GlideSignals SEARCH_EVENT with the given parameters.
*/
function createSearchEventPayload(paramBag, processedResults, searchMetadata) {
var searchEvent = new global.AISearchGlideSignalsSearchEvent(searchMetadata.searchResultMetadata, paramBag);
var geniusSearchResults = JSON.parse(processedResults.geniusSearchResults) || [];
for (var i=0; i<geniusSearchResults.length; i++)
searchEvent.buildPayload(global.AISearchConstants.RESULT_TYPE_GENIUS, geniusSearchResults[i]);
var searchResults = JSON.parse(processedResults.searchResults) || [];
for (var i=0; i<searchResults.length; i++)
searchEvent.buildPayload(global.AISearchConstants.RESULT_TYPE_SEARCH, searchResults[i]);
return searchEvent.getPayload();
}
/**
* Creates line-items for search results
*/
function createLineItemsForSearchResult(paramBag, resultId, contextId, searchMetadata, results) {
// don't proceed if there's no result
if (gs.nil(results))
return;
var resultType = global.AISearchConstants.RESULT_TYPE_SEARCH;
var signalType = global.AISearchConstants.CLICK_ACTION_SIGNAL_TYPE;
var gr = new GlideRecord(global.AutoResolutionConstants.AI_SEARCH_RESULT_LINE_ITEM_TABLE_NAME);
for(var i=0; i<results.length; i++) {
var result = results[i];
var resultEvent
= new global.AISearchGlideSignalsSearchResultEvent(searchMetadata.searchResultMetadata, paramBag);
// signalValue starts from 1 , not 0
var itemIndex = i + 1;
resultEvent.buildPayload(result, signalType, itemIndex);
var feedbackPayload = resultEvent.getPayload();
gr.initialize();
setLineItemValues(itemIndex, gr, contextId, resultId, result, resultType, JSON.stringify(feedbackPayload));
gr.insert();
}
}
/**
* Creates line-items for genius results
*/
function createLineItemsForGeniusResult(paramBag, resultId, contextId, searchMetadata, results) {
// don't proceed if there's no result
if (gs.nil(results))
return;
var resultType = global.AISearchConstants.RESULT_TYPE_GENIUS;
var gr = new GlideRecord(global.AutoResolutionConstants.AI_SEARCH_RESULT_LINE_ITEM_TABLE_NAME);
for(var i=0; i<results.length; i++) {
var result = results[i];
var resultEvent
= new global.AISearchGlideSignalsGeniusResultEvent(searchMetadata.searchResultMetadata, paramBag);
resultEvent.buildPayload(result);
var payload = resultEvent.getPayload();
gr.initialize();
var itemIndex = i+1; // Note this starts from 1.
setLineItemValues(itemIndex, gr, contextId, resultId, result, resultType, JSON.stringify(payload));
gr.insert();
}
}
/**
* Populates the given line-item record with the given parameters
*/
function setLineItemValues(currentIndex, currentRecord, contextId, resultId, result, resultType, feedbackPayload) {
currentRecord.setValue('ai_search_result', resultId);
currentRecord.setValue('item_index', currentIndex+''); // make the value string to avoid any decimal issue
currentRecord.setValue('feedback_payload', feedbackPayload);
currentRecord.setValue('result_payload', JSON.stringify(result));
currentRecord.setValue('result_type', resultType);
var model = result.propValues.model;
currentRecord.setValue('resource_table', global.AISearchHelper.getResourceTableFromModel(model));
currentRecord.setValue('resource_id', global.AISearchHelper.getResourceIdFromModel(model));
currentRecord.setValue('description', global.AISearchHelper.getDescriptionFromModel(model));
currentRecord.setValue('kb_article', global.AISearchHelper.getKBArticleIdFromModel(model));
var clickURL = global.AutoResolutionAISearchHelper.getAISearchResultLink(contextId, result);
currentRecord.setValue('click_url', clickURL);
}
Sys ID
037387c5538101105400ddeeff7b120b