Name
sn_chg_score.ChangeSuccessScoreGeneratorSNC
Description
Provides functions that generate the metric records that contain the daily change success score for each group. To modify behavior, use the provided ChangeSuccessScoreGenerator extension to override functions defined within this script include.
Script
var ChangeSuccessScoreGeneratorSNC = Class.create();
ChangeSuccessScoreGeneratorSNC.prototype = {
initialize: function() {
this.changeSuccessScoreUtils = new sn_chg_score.ChangeSuccessScoreUtils();
this.changeSuccessScoreGlobalUtils = new global.ChangeSuccessScoreGlobalUtils();
this.minScore = this.changeSuccessScoreUtils.getMinAllowedScore();
this.maxScore = this.changeSuccessScoreUtils.getMaxAllowedScore();
this.entryLevelScore = this.changeSuccessScoreUtils.getEntryLevelScore();
this.previousScoreHistoryLimit = this.changeSuccessScoreUtils.getPreviousScoreHistoryLimit();
this._timezoneId = gs.getSession().getTimeZoneName();
},
setTimeZone: function(timezoneId) {
if (!timezoneId || !this.changeSuccessScoreGlobalUtils.isValidTimeZone(timezoneId))
return;
this._timezoneId = timezoneId;
return this;
},
getTimeZone: function() {
return this._timezoneId;
},
generateLatest: function() {
this.generateForDate();
},
generateForDate: function(scoreDate) {
// if we've been passed a date try and check it's valid
if (scoreDate) {
scoreDate = new GlideDateTime(scoreDate);
if (!scoreDate.isValid())
return;
} else
scoreDate = new GlideDateTime();
// Score date can't be greater than today
var endOfToday = this.changeSuccessScoreGlobalUtils.getEndOfDayInTz(new GlideDateTime(), this.getTimeZone());
if (scoreDate.getValue() > endOfToday.getValue())
return;
var scoreDateInTz = this.changeSuccessScoreGlobalUtils.getDateStrInTz(scoreDate, this.getTimeZone());
var userGroupList = this._getRelevantUserGroups();
var paBreakdownLimit = parseInt(gs.getProperty('com.snc.pa.scorecards.max_breakdown_elements', 1000));
if (isNaN(paBreakdownLimit))
paBreakdownLimit = 1000;
var processedGroups = [];
// Query PA API in chunk mode if number of user groups exceeds the PA max breakdown element limit
for (var k = 0; k < userGroupList.length / paBreakdownLimit; k++) {
if ((k * paBreakdownLimit) + 1 > userGroupList.length)
break;
var page = 1;
var scoreCard = new PAScorecard();
scoreCard.addParam("sysparm_display", "all");
scoreCard.addParam("uuid", this._buildUuidParm(userGroupList.slice(k * paBreakdownLimit, (k * paBreakdownLimit) + paBreakdownLimit)));
scoreCard.addParam("per_page", 100);
scoreCard.addParam("page", page);
scoreCard.addParam("include_scores", true);
scoreCard.addParam("limit", 1);
scoreCard.addParam("from", scoreDateInTz);
scoreCard.addParam("to", scoreDateInTz);
var groupScores = scoreCard.query();
while (groupScores.length > 0) {
processedGroups = processedGroups.concat(this._processGroupScores(groupScores));
page++;
scoreCard.addParam("page", page);
groupScores = scoreCard.query();
}
}
/* Now provide a score of 0 for any groups not returned by the query we made to the PA Scorecard
as this means that group has a 0 score, only for groups that had any Changes assigned */
var unprocessedList = new global.ArrayUtil().diff(userGroupList, processedGroups);
for (var i = 0; i < unprocessedList.length; i++)
this._updateGroupMetric(unprocessedList[i], 0, scoreDateInTz);
},
_processGroupScores: function(groupScores) {
var processedGroups = [];
if (!Array.isArray(groupScores) || groupScores.length === 0)
return processedGroups;
for (var i = 0; i < groupScores.length; i++) {
var groupScore = groupScores[i];
if (!groupScore.hasOwnProperty("scores") || groupScore.scores.length === 0)
continue;
if (!groupScore.hasOwnProperty("element"))
continue;
var groupId = groupScore.element.value;
var score = Math.floor(groupScore.scores[0].value);
var scoreDate = groupScore.scores[0].start_at;
var changeGr = new GlideRecord('change_request');
changeGr.addQuery('assignment_group', groupId);
changeGr.setLimit(1);
changeGr.query();
if (changeGr.hasNext())
this._updateGroupMetric(groupId, score, scoreDate);
processedGroups.push(groupId);
}
return processedGroups;
},
_updateGroupMetric: function(groupId, score, scoreDate) {
if (!groupId || gs.nil(score))
return;
var scoreDateTime = this.changeSuccessScoreGlobalUtils.generateDateTimeInTz(scoreDate, "00:00:00", this.getTimeZone());
var previousMetricDateTime = new GlideDateTime(scoreDateTime);
previousMetricDateTime.addDaysUTC(-1);
var earliestPreviousMetricDateTime = new GlideDateTime(scoreDateTime);
earliestPreviousMetricDateTime.addMonthsUTC(-Math.abs(this.previousScoreHistoryLimit));
var metricGr = this._getMetricForGroup(groupId, earliestPreviousMetricDateTime, previousMetricDateTime);
var newScore = null;
// Try and find a previous score in the records we got back from the Metrics table
if (metricGr && metricGr.next()) {
newScore = parseInt(metricGr.getValue("value"));
if (isNaN(newScore))
newScore = null;
}
if (newScore === null)
newScore = this.entryLevelScore;
newScore += score;
if (newScore < this.minScore)
newScore = this.minScore;
if (newScore > this.maxScore)
newScore = this.maxScore;
newScore = "" + Math.floor(newScore);
// Now check to see if we've already got a metric for our target day and if so we'll overwrite it
metricGr = this._getMetricForGroup(groupId, scoreDateTime, scoreDateTime);
if (!metricGr || !metricGr.next())
metricGr = new GlideRecord("metric_instance");
if (this._setMetricValues(metricGr, groupId, newScore, scoreDateTime))
metricGr.update();
},
_getMetricForGroup: function(groupId, afterDate, beforeDate) {
if (!groupId)
return null;
if (!afterDate || !afterDate.isValid() || !beforeDate || !beforeDate.isValid())
return null;
var metricGr = new GlideRecord("metric_instance");
metricGr.addQuery("definition", this.changeSuccessScoreUtils.METRIC_DEFINITION_ID);
metricGr.addQuery("id", groupId);
metricGr.addQuery("sys_created_on", ">=", this.changeSuccessScoreGlobalUtils.getStartOfDayInTz(afterDate));
metricGr.addQuery("sys_created_on", "<=", this.changeSuccessScoreGlobalUtils.getEndOfDayInTz(beforeDate));
metricGr.orderByDesc("sys_created_on");
metricGr.query();
return metricGr;
},
_setMetricValues: function(metricGr, groupId, score, onDate) {
if (!metricGr || !groupId || !score)
return false;
metricGr.definition = this.changeSuccessScoreUtils.METRIC_DEFINITION_ID;
metricGr.id = groupId;
metricGr.field = null;
metricGr.field_value = score;
metricGr.calculation_complete = true;
if (onDate && onDate.isValid()) {
metricGr.autoSysFields(false);
metricGr.setValue("sys_created_on", onDate.getValue());
metricGr.setValue("sys_created_by", gs.getUserName());
metricGr.setValue("sys_updated_on", onDate.getValue());
metricGr.setValue("sys_updated_by", gs.getUserName());
}
return true;
},
//uuid parm format with breakdown element
//<indicator_sys_id>:<breakdown_sys_id>:<element_sys_id1>, <indicator_sys_id>:<breakdown_sys_id>:<element_sys_id2>, ...
_buildUuidParm: function(userGroupList) {
var parmString = "";
for (var i = 0; i < userGroupList.length; i++)
parmString += this.changeSuccessScoreUtils.PA_CHANGE_SUCCESS_SCORE_ADJUSTMENT_INDICATOR_ID + ":" + this.changeSuccessScoreUtils.PA_CHANGE_SCORE_GROUP_BREAKDOWN_ID + ":" + userGroupList[i] + ((i < userGroupList.length - 1) ? "," : "");
return parmString;
},
_getRelevantUserGroups: function() {
var groupList = [];
var gaChg = new GlideAggregate('change_request');
gaChg.addAggregate('COUNT');
gaChg.addNotNullQuery('assignment_group');
gaChg.groupBy('assignment_group');
gaChg.setCategory(global.ChangeCommon.READ_REPLICA_CATEGORY_CHANGE_MANAGEMENT_HISTORICAL_QUERIES);
gaChg.query();
while (gaChg.next())
groupList.push(gaChg.getValue('assignment_group'));
return groupList;
},
type: 'ChangeSuccessScoreGeneratorSNC'
};
Sys ID
fcee086973730010491d235f04f6a7cb