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

Offical Documentation

Official Docs: