After creating a change request, we need to automate the Risk assessment.
Options
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
yesterday
After creating a change request, we need to automate the Risk assessment. We created an Action that contains a script where we pass input variables such as the Change Sys ID. We have attached the script is provided below. The record is being created in the Assessment instance and is linked to the Change Request. However, when we open the Change Request and click on Risk Assessment, the questions are not getting updated.
(function execute(inputs, outputs) {
// -------------------- Config --------------------
// If you know the Metric Type sys_id, put it here for best performance.
var METRIC_TYPE_ID = ''; // e.g., 'b1c2d3e4f5...'
var METRIC_TYPE_NAMES = ['Change Risk Assessment', 'Change risk assessment', 'Change Risk'];
// -------------------- Helpers --------------------
function pick() {
for (var i = 0; i < arguments.length; i++) {
var v = arguments[i];
if (v !== undefined && v !== null && ('' + v).trim() !== '') return v;
}
return '';
}
function normStr(s) { return (s + '').trim().toLowerCase(); }
function nowIso() { return new Date().toISOString(); }
function safeInt(n, dflt) {
var x = parseInt(n, 10);
return isNaN(x) ? dflt : x;
}
function getMetricTypeSysId() {
if (METRIC_TYPE_ID && ('' + METRIC_TYPE_ID).length === 32) return METRIC_TYPE_ID;
for (var i = 0; i < METRIC_TYPE_NAMES.length; i++) {
var mt = new GlideRecord('asmt_metric_type');
mt.addActiveQuery();
mt.addQuery('name', METRIC_TYPE_NAMES[i]);
mt.setLimit(1);
mt.query();
if (mt.next()) return String(mt.getUniqueValue());
}
return '';
}
// -------------------- Inputs --------------------
var changeSysId = String(pick(inputs.changeSysId, inputs.changesysid));
var affectsCritical = String(pick(inputs.affectsCritical, inputs.affectscritical, 'No'));
var complexity = String(pick(inputs.complexity, inputs.complexity, 'Low'));
// Accept both spellings "backout" and "backup"
var backoutDifficulty = String(pick(inputs.backoutDifficulty, inputs.backoutdifficulty, inputs.backupDifficulty, 'Low'));
var redundancyPlan = String(pick(inputs.redundancyPlan, inputs.redundancyplan, 'Yes'));
var verifyDifficulty = String(pick(inputs.verifyDifficulty, inputs.verifydifficulty, 'Low'));
var soxRelated = String(pick(inputs.soxRelated, inputs.soxrelated, 'No'));
// Initialize outputs to safe defaults
outputs.assessmentinstance = '';
outputs.riskscore = '';
outputs.risklevel = '';
// IMPORTANT: If your Action variable "answersapplied" is JSON type, assign an object.
// If it is String type, assign JSON.stringify(obj) instead (see later).
var answersObj = null;
if (!changeSysId) {
gs.error('[AutoRisk] No Change Request sys_id provided.');
// Still return a minimal answers payload for transparency
answersObj = {
error: 'No changeSysId',
inputs: { affectsCritical: affectsCritical, complexity: complexity, backoutDifficulty: backoutDifficulty, redundancyPlan: redundancyPlan, verifyDifficulty: verifyDifficulty, soxRelated: soxRelated },
runAt: nowIso()
};
outputs.answersapplied = answersObj; // If String type, wrap with JSON.stringify(answersObj)
return;
}
// -------------------- Risk score calculation (independent of assessment) --------------------
function weightYesNo(v, yesPts, noPts) {
return normStr(v) === 'yes' ? yesPts : noPts;
}
function weightLMH(v, lowPts, medPts, highPts) {
var k = normStr(v);
if (k === 'low' || k === 'not complex' || k === 'easy') return lowPts;
if (k === 'somewhat complex' || k === 'somewhat' || k === 'medium' || k === 'moderate') return medPts;
if (k === 'high' || k === 'severe' || k === 'hard' || k === 'complex' || k === 'highly complex') return highPts;
return 0;
}
var calcScore = 0;
// 1) Affects critical services?
calcScore += weightYesNo(affectsCritical, 25, 0);
// 2) Complexity
calcScore += weightLMH(complexity, 5, 10, 20);
// 3) Backout difficulty
calcScore += weightLMH(backoutDifficulty, 5, 10, 20);
// 4) Redundancy plan (Yes reduces risk)
calcScore += (normStr(redundancyPlan) === 'yes') ? 0 : 15;
// 5) Verify/test difficulty
calcScore += weightLMH(verifyDifficulty, 5, 10, 20);
// 6) SOX related?
calcScore += weightYesNo(soxRelated, 20, 0);
var calcLevel = 'Low';
if (calcScore >= 70) calcLevel = 'Critical';
else if (calcScore >= 45) calcLevel = 'High';
else if (calcScore >= 20) calcLevel = 'Moderate';
// Pre-fill outputs from our calculation (do NOT rely on assessment instance fields)
outputs.riskscore = String(calcScore);
outputs.risklevel = calcLevel;
// Also prepare our answers object (for the JSON output)
answersObj = {
changeSysId: changeSysId,
inputs: {
affectsCritical: affectsCritical,
complexity: complexity,
backoutDifficulty: backoutDifficulty,
redundancyPlan: redundancyPlan,
verifyDifficulty: verifyDifficulty,
soxRelated: soxRelated
},
calculation: {
score: calcScore,
level: calcLevel
},
runAt: nowIso()
};
// -------------------- Assessment: find or create instance --------------------
var metricTypeSysId = getMetricTypeSysId();
if (!metricTypeSysId) {
gs.error("[AutoRisk] Metric type not found. Looked for: " + METRIC_TYPE_NAMES.join(', '));
// Still return a useful payload
outputs.answersapplied = answersObj; // If String type, use JSON.stringify(answersObj)
return;
}
function findExistingInstance(changeId, mtSysId) {
var ai = new GlideRecord('asmt_assessment_instance');
ai.addQuery('metric_type', mtSysId);
ai.addQuery('trigger_id', changeId); // link to the record being assessed
ai.orderByDesc('sys_created_on');
ai.setLimit(1);
ai.query();
return ai.next() ? String(ai.getUniqueValue()) : '';
}
function createInstanceViaAPI(mtSysId, changeId) {
try {
// Preferred signature: createAssessments(metricTypeSysId, triggerRecordSysId)
if (typeof sn_assessment !== 'undefined' && sn_assessment.AssessmentCreation) {
var ids = new sn_assessment.AssessmentCreation().createAssessments(mtSysId, changeId);
if (ids && ids.length > 0) return String(ids[0]);
}
} catch (e) {
gs.warn('[AutoRisk] AssessmentCreation API path failed: ' + e.message);
}
return '';
}
function createInstanceManually(mtSysId, changeId) {
var ai = new GlideRecord('asmt_assessment_instance');
ai.initialize();
ai.metric_type = mtSysId;
ai.state = 'ready';
// IMPORTANT links
if (ai.isValidField('trigger_table')) ai.trigger_table = 'change_request';
if (ai.isValidField('trigger_id')) ai.trigger_id = changeId;
// Optional assignment
var ch = new GlideRecord('change_request');
if (ch.get(changeId)) {
if (ai.isValidField('user') && ch.assigned_to) ai.user = ch.assigned_to;
if (ai.isValidField('short_description')) ai.short_description = 'Change Risk Assessment: ' + (ch.number || changeId);
}
return ai.insert() || '';
}
var instanceSysId = findExistingInstance(changeSysId, metricTypeSysId);
if (!instanceSysId) instanceSysId = createInstanceViaAPI(metricTypeSysId, changeSysId);
if (!instanceSysId) instanceSysId = createInstanceManually(metricTypeSysId, changeSysId);
if (!instanceSysId) {
gs.error('[AutoRisk] Failed to create/fetch assessment instance.');
outputs.answersapplied = answersObj; // If String type, use JSON.stringify(answersObj)
return;
}
outputs.assessmentinstance = instanceSysId;
gs.info('[AutoRisk] Assessment instance = ' + instanceSysId);
// -------------------- Ensure instance questions exist --------------------
function countInstanceQuestions(instId) {
var ga = new GlideAggregate('asmt_assessment_instance_question');
ga.addQuery('instance', instId);
ga.addAggregate('COUNT');
ga.query();
return ga.next() ? safeInt(ga.getAggregate('COUNT'), 0) : 0;
}
function createQuestionsViaAPI(instId) {
try {
if (typeof sn_assessment !== 'undefined' && sn_assessment.AssessmentCreation) {
var ai = new GlideRecord('asmt_assessment_instance');
if (ai.get(instId)) {
new sn_assessment.AssessmentCreation().createInstanceQuestions(ai);
return true;
}
}
} catch (e) {
gs.warn('[AutoRisk] createInstanceQuestions API failed: ' + e.message);
}
return false;
}
function createQuestionsManually(instId, mtSysId) {
var made = 0;
var m = new GlideRecord('asmt_metric');
m.addActiveQuery();
m.addQuery('metric_type', mtSysId);
m.orderBy('order');
m.query();
while (m.next()) {
var iq = new GlideRecord('asmt_assessment_instance_question');
iq.initialize();
iq.instance = instId;
iq.metric = m.getUniqueValue();
// Stamp source to point back to the change (questions grid shows these)
if (iq.isValidField('source_table')) iq.source_table = 'change_request';
if (iq.isValidField('source_id')) iq.source_id = changeSysId;
else if (iq.isValidField('source')) iq.source = changeSysId;
iq.insert();
made++;
}
return made;
}
var qBefore = countInstanceQuestions(instanceSysId);
if (qBefore === 0) {
var apiMade = createQuestionsViaAPI(instanceSysId);
var qAfter = countInstanceQuestions(instanceSysId);
if (!apiMade || qAfter === 0) {
var manual = createQuestionsManually(instanceSysId, metricTypeSysId);
gs.info('[AutoRisk] Manually created ' + manual + ' question row(s).');
} else {
gs.info('[AutoRisk] Questions created via API: ' + qAfter);
}
} else {
gs.info('[AutoRisk] Instance already has ' + qBefore + ' question(s).');
}
// -------------------- Map our inputs to metrics and write results --------------------
// We will search metrics by "contains" on their label/question
var metrics = []; // {sys_id, key, label}
var mm = new GlideRecord('asmt_metric');
mm.addQuery('metric_type', metricTypeSysId);
mm.addActiveQuery();
mm.query();
while (mm.next()) {
var name = String(mm.getValue('name') || mm.getDisplayValue('name') || '');
var label = name;
if (mm.isValidField('question'))
label = String(mm.getValue('question') || mm.getDisplayValue('question') || name);
else if (mm.isValidField('label'))
label = String(mm.getValue('label') || mm.getDisplayValue('label') || name);
metrics.push({ sys_id: String(mm.getUniqueValue()), key: normStr(label || name), label: label });
}
function findMetricByContains(parts) {
var wants = [].concat(parts || []).map(normStr);
for (var i = 0; i < metrics.length; i++) {
for (var j = 0; j < wants.length; j++) {
if (metrics[i].key.indexOf(wants[j]) > -1) return metrics[i];
}
}
return null;
}
// Map your answers to the likely metric labels (adjust patterns to your metric wording)
var mapping = [
{ want: affectsCritical, find: ['affect', 'critical', 'business service', 'critical ci'] },
{ want: complexity, find: ['complex', 'complexity'] },
{ want: backoutDifficulty, find: ['backout', 'back out', 'revert'] },
{ want: redundancyPlan, find: ['redundancy plan', 'rollback plan', 'backout plan'] },
{ want: verifyDifficulty, find: ['verify successful', 'verify the change', 'verification'] },
{ want: soxRelated, find: ['sox'] }
];
// Value mapping for numeric weight stored with each metric result (optional)
function mapWeight(label) {
var L = (label || '').trim();
var weights = {
'No': 0, 'Some (LOCAL)': 5, 'Yes (GLOBAL)': 10,
'Not complex': 0, 'Somewhat complex': 5, 'Highly complex': 10,
'Easy': 0, 'Somewhat Complex': 5, 'Complex': 10,
'Yes': 0, 'Some redundancy': 5, 'Moderate': 5, 'Hard': 10
};
return weights.hasOwnProperty(L) ? weights[L] : 0;
}
function upsertMetricResult(instanceId, metricSysId, displayValue, numericValue) {
// var results = new GlideRecord('asmt_metric_result');
// results.addEncodedQuery('instance.task_id=' + inputs.changeSysId);
// results.query();
// results.actual_value = 0;
// results.updateMultiple();
// var r = new GlideRecord('asmt_metric_result');
// r.addQuery('instance', instanceId);
// r.addQuery('metric', metricSysId);
// r.setLimit(1);
// r.query();
// var exists = r.next();
// if (!exists) { r.initialize(); r.instance = instanceId; r.metric = metricSysId; }
// if (r.isValidField('display_value')) r.display_value = String(displayValue || '');
// if (r.isValidField('value')) r.value = String(numericValue != null ? numericValue : '');
// if (r.isValidField('answer_text')) r.answer_text = String(displayValue || '');
// if (r.isValidField('answer_value')) r.answer_value = String(numericValue != null ? numericValue : '');
// return exists ? r.update() : r.insert();
}
var a = new GlideRecord('asmt_assessment_instance_question');
a.addQuery('instance', instanceSysId);
a.query();
while (a.next()){
var results = new GlideRecord('asmt_metric_result');
results.initialize();
results.instance = a.instance;
results.metric = a.metric;
results.actual_value = 0;
results.source_id = a.source_id;
results.insert();
}
var answered = 0;
for (var k = 0; k < mapping.length; k++) {
var map = mapping[k];
if (!map.want) continue;
var metric = findMetricByContains(map.find);
if (!metric) {
gs.warn('[AutoRisk] Metric not found for: ' + JSON.stringify(map.find));
continue;
}
var weight = mapWeight(map.want);
upsertMetricResult(instanceSysId, metric.sys_id, map.want, weight);
answered++;
}
gs.info('[AutoRisk] Total metrics answered: ' + answered);
// -------------------- Submit / complete the assessment --------------------
var submitted = false;
try {
if (typeof sn_assessment !== 'undefined' && sn_assessment.AssessmentEvaluation) {
new sn_assessment.AssessmentEvaluation().submit(instanceSysId);
submitted = true;
gs.info('[AutoRisk] Submitted via AssessmentEvaluation API.');
}
} catch (e) {
gs.warn('[AutoRisk] AssessmentEvaluation API failed: ' + e.message);
}
if (!submitted) {
var inst = new GlideRecord('asmt_assessment_instance');
if (inst.get(instanceSysId)) {
if (inst.isValidField('state')) inst.state = 'complete';
if (inst.isValidField('submitted')) inst.submitted = true;
if (inst.isValidField('taken')) inst.taken = true;
inst.update();
gs.info('[AutoRisk] Marked assessment instance complete (fallback).');
}
}
// -------------------- Finalize outputs --------------------
outputs.assessmentinstance = instanceSysId; // String
outputs.riskscore = String(calcScore); // our calculated score (always populated)
outputs.risklevel = String(calcLevel); // our calculated level (always populated)
// If your output variable "answersapplied" is JSON type:
outputs.answersapplied = answersObj;
// If instead your Action defined "answersapplied" as String, comment the previous line and use:
// outputs.answersapplied = JSON.stringify(answersObj);
})(inputs, outputs);
Labels:
- Labels:
-
Change Management
0 REPLIES 0
