0

Joined Impact as an Influencer! 0 (0)

Verification code

Impact-Site-Verification: 5a032c67-f9dc-4f7c-9069-98a0ef620803

Excited to share that I’ve officially joined Impact as an Influencer! Being part of a tech community that champions authentic voices, meaningful partnerships, and real impact is something I truly value.

As someone who believes in the power of storytelling and connection, I’m looking forward to collaborating with brands that align with my values—and helping others do the same.

Let’s use our platforms to inspire, inform, and influence for good.
#ImpactInfluencer #CreatorCommunity #PartnershipsWithPurpose

0

Action Tasks with Smart Assessment on Compliance Case Management (CCM) 0 (0)

There is an issue with the Smart Assessments on the Compliance Case Management when you try to use smart assessments in the Action Tasks – it’s returning an error: “Incorrect setup”. The assessment gets generated so it’s working as expected. When I try to initiate them, it returns me an error.

Official docs:
https://www.servicenow.com/docs/bundle/yokohama-governance-risk-compliance/page/product/grc-compliance-case-mgmt/task/perform-smart-assessment-on-action-task.html

1. Go to the Assessment Workspace and configure/create a new assessment template called “My Questionnaire”.
Assessment categories = Compliance Case Assessment
Assessment targets = Compliance Case
Assessment reader = sn_comp_case.compliance_case_business_user
2. Go to All > Case type
3. Open “Compliance Case” or any active case type record.
4. In the assessment configuration, use the “My Questionnaire”.
5. Go to the Compliance Case Workspace and open any record where type is “Compliance Case”
6. Create an Action Task and use smart assessment.
7. When you move the task to Assigned state, it will trigger the assessment.
8. Click in View Assessment and the issue will get displayed when you try to take the assessment.

How to solve it?

Create the smart assessment under the right scope (CCM) 🙂

0

How to build a Food Recipe App using Creator Studio? #BuildWithCreatorStudio 0 (0)

Today, Creator Studio: Creating applications without writing code provide a fast and convenient way to develop an application. Whether you’re building a small tool-based application or a big portal, these tools can be very serviceable.

I built a simple Recipe App app using Creator Studio called Gourmet – designed to inspire and guide both novice cooks and seasoned chefs alike. With an extensive library of recipes from around the world, Gourmet simplifies meal planning, shopping, and cooking with easy-to-follow instructions, personalized recommendations, and helpful cooking tips.

Key features include:

  • Recipe Discovery: Explore thousands of recipes by cuisine, dietary preferences, ingredients, or meal type.
  • Step-by-Step Instructions: Detailed cooking instructions with photos and videos to guide you through each step.

The request form captures all the relevant details such as ingredients, quantity, direction and any special instructions. Once submitted the request is automatically routed to an approval group composed by chefs around the world. This ensures all recipes are reviewed and validated quickly. Upon approval, the system creates a task for the chefs to replicate , ensuring the recipe is valid and can be used by anyone.

Requirements

  1. Create a PDI on developer.service-now.com.
  2. The plugin ‘Creator Studio’ is automatically entitled on all ServiceNow instances and all ServiceNow developer portal instances. Check if its installed in your instance.

Lets start

This should be your homepage.

Click in the button “Open Creator Studio” or in the filter navigator find App Engine Studio > Creator Studio. It should look like this:

Click in “Create app” and type the following:

  • Name: Gourmet
  • Description: Gourmet is your ultimate kitchen companion, designed to inspire and guide both novice cooks and seasoned chefs alike. With an extensive library of recipes from around the world, Gourmet simplifies meal planning, shopping, and cooking with easy-to-follow instructions, personalized recommendations, and helpful cooking tips.

The next menu will help you to choose a template to start: Creator Studio Default Template. (documentation here Creator Studio apps and tables).

Lets skip it for now, we need to create a new catalog and new categories after we finish the form.

Time to design the form

We want to create a Catalog Item where the user can request a recipe. You can preview the form or submit for review.

In the form submission tab, you have a view of your table.

Now mark the form as “Ready”. Now its time to confirm if your recent form is publish and available.

  1. Go to the Employee Center.
  2. Try to search for the Recipe Submission Request and you will find your recent form.
  3. Fill up all the fields and press submit. This will create a Gourmet Record where you can confirm using the Creator Studio.

And in less than 10min you just created your first record producer.
Thank you and drop any comment 🙂

0

THE SERVICENOW #BuildWith CHALLENGE IS BACK! 0 (0)

Last year we showed everyone how easy it is to build an application on the #ServiceNow platform using App Engine Studio (AES).

This year the focus will be on the capabilities of #ServiceNowCreatorStudio – this tool is designed with non-developers in mind, offering a guided, intuitive environment for building request-based applications. It breaks down the traditional barriers to app development, making it accessible for business process experts to bring their automation ideas to life. This tool is not intended to replace existing development tools like App Engine Studio, but to complement them by providing a new entry point for app creation.

Transform your ideas into real-world solutions with #BuildWithCreatorStudio. Whether you’re new to ServiceNow, a low-code developer, a business analyst, or a full-stack engineer, anyone is allowed to participate. Ready to get creative and have some fun together? 😁

Join me this year for this exciting challenge. Rise to the #BuildWithCreatorStudio Challenge and earn exclusive swag!

September 9 – October 8th

0

Creating spider web using Highcharts 0 (0)

In this snippet you will create a custom spider web using a custom page and populating data using Highcharts native library

Step 1: Create a new Widget

Go to Service Portal > Widget > Click New

  • Name: Custom productionProcess
  • Id: custom-gojs-productionProcess
  • Click on submit

Body HTML template

  • Copy and paste below HTML Code in Widget’s HTML Template section
<div>  
	<!-- chart -->
    <div id="container"></div>
    <p class="highcharts-description">
        A spiderweb chart or radar chart is a variant of the polar chart.
        Spiderweb charts are commonly used to compare multivariate data sets,
        like this demo using six variables of comparison.
    </p>
</div>

CSS/SCSS

  • Copy and paste below CSS in Widget’s CSS/SCSS Section
/* to be completed */

Client Side Scripts

  • Copy and Paste below Script in Widget’s Client Side Section
api.controller=function($rootScope, $scope, $window, $interval, spUtil) {
  /* widget controller */
	var c = this;

	/** Chart source: https://www.highcharts.com/demo/polar-spider*/
	var options = {
        credits: {
            enabled: false
        },

        chart: {
            renderTo: 'container', // change chart_id if needed
            polar: true,
            type: 'line'
        },

    accessibility: {
        description: 'A spiderweb chart compares the allocated budget against actual spending within an organization. The spider chart has six spokes. Each spoke represents one of the 6 departments within the organization: sales, marketing, development, customer support, information technology and administration. The chart is interactive, and each data point is displayed upon hovering. The chart clearly shows that 4 of the 6 departments have overspent their budget with Marketing responsible for the greatest overspend of $20,000. The allocated budget and actual spending data points for each department are as follows: Sales. Budget equals $43,000; spending equals $50,000. Marketing. Budget equals $19,000; spending equals $39,000. Development. Budget equals $60,000; spending equals $42,000. Customer support. Budget equals $35,000; spending equals $31,000. Information technology. Budget equals $17,000; spending equals $26,000. Administration. Budget equals $10,000; spending equals $14,000.'
    },

    title: {
        text: 'Budget vs spending',
        x: -80
    },

    pane: {
        size: '80%'
    },

    xAxis: {
        categories: ['Sales', 'Marketing', 'Development', 'Customer Support',
            'Information Technology', 'Administration'],
        tickmarkPlacement: 'on',
        lineWidth: 0
    },

    yAxis: {
        gridLineInterpolation: 'polygon',
        lineWidth: 0,
        min: 0
    },

    tooltip: {
        shared: true,
        pointFormat: '<span style="color:{series.color}">{series.name}: <b>${point.y:,.0f}</b><br/>'
    },

    legend: {
        align: 'right',
        verticalAlign: 'middle',
        layout: 'vertical'
    },

    series: [{
        name: 'Allocated Budget',
        data: [43000, 19000, 60000, 35000, 17000, 10000],
        pointPlacement: 'on'
    }, {
        name: 'Actual Spending',
        data: [50000, 39000, 42000, 31000, 26000, 14000],
        pointPlacement: 'on'
    }],

    responsive: {
        rules: [{
            condition: {
                maxWidth: 500
            },
            chartOptions: {
                legend: {
                    align: 'center',
                    verticalAlign: 'bottom',
                    layout: 'horizontal'
                },
                pane: {
                    size: '70%'
                }
            }
        }]
    }
};
	
  /*Generate chart*/
	var chart = new Highcharts.Chart(options);
  
  /* improvements: next step would be to have a ng-selector in HTML and use record watcher to keep data up do date */
	
};

Step 2: Add native Highcharts library to your widget as widget dependencies

***Go to Service Portal > Widget ***

  • Search for your previous widget created “Custom Spider Web” (custom-spider-web) and open the record.
  • On the related tab Dependencies, click on Edit button.
  • Search for PA Widget (4fbe3df5673322002c658aaad485ef29) and add to your list.
  • Click on Save button to save the change.

Step 3: Create a new Page

Go to Service Portal > Page > Click New

  • Name: spiderweb – Test Page
  • ID: spiderweb
  • Click on Submit button.
  • Once submitted, Click on Open in Page Designer related link
  • In Page designer, Place custom-spider-web widget inside a container > row > Column at top location.
  • View paget from following link http://instance-name.service-now.com/sp?id=spiderweb.

Sources

Any of following links are not under my surveilance or maintenance

https://github.com/NorthwoodsSoftware/GoJS/blob/master/samples/productionProcess.html https://gojs.net/latest/intro/toolTips.html http://g-mops.net/epica_gojs/api/symbols/Diagram.html

0

Create Current Scoring for Risk Management 0 (0)

In this snippet you will create a current scoring (similar to inherent or residual) using same functions and logic.

Step 1: Create the following fields in Risk table

Go to sn_risk_risk.config > Dictionary entries > Click New

Make sure you have the update in the correct application. (GRC: Risk Management).

FieldTypeReference
u_current_aleCurrency
u_current_aroCurrency
u_current_sleCurrency
u_current_impactReferenceRisk Criteria
u_current_likelihoodReferenceRisk Criteria
u_current_scoreReferenceRisk Criteria

Step 2: Reporting – Clone Risk Overview dashboard

Go to pa_dashboard and get ready to clone a out of the box record

Make sure you have the update in the correct application. (GRC: Risk Management).

  • Clone Risk Overview widget and rename to Custom Risk Overview.
  • Add a new tab named “Current Scoring”
  • Click on Save button to save the change.

Step 3: Reporting – Clone inherent_heatmap UI page

Go to sys_widgets and get ready to clone a out of the box record

Make sure you have the update in the correct application. (GRC: Risk Management).

  • Clone Inherent Heatmap widget by clicking Insert and Stay menu action.
  • Name: Current Risk Heatmap
  • Click on Save button to save the change.

Script

  • Copy and paste below Script in Widget’s Script section
function sections() {
    return {
		'Current Risk Heatmap' : { 'name' : 'current_heatmap' }
    };
}

function render() {
	return renderer.getRenderedPage( 'sn_risk_current_heatmap');  
}

function getEditLink() {
	return "sys_ui_page.do?sysparm_query=name=current_heatmap";
}

Step 4: Reporting – Clone inherent_heatmap UI page

Go to sys_ui_page and get ready to clone a out of the box record

Make sure you have the update in the correct application. (GRC: Risk Management).

  • Clone inherent_heatmap UI page by clicking Insert and Stay menu action.
  • Name: current_heatmap
  • Click on Save button to save the change.

Endpoint

  • Copy and paste below Endpoint in UI page section
sn_risk_current_heatmap.do

Body HTML template

  • Copy and paste below HTML Code in Widget’s HTML Template section
<?xml version="1.0" encoding="utf-8" ?>
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">
	<g:requires name="styles/heisenberg/heisenberg_all.css" includes="true" />
	<script language="javascript" src="sn_risk.RiskHeatmapUtils.jsdbx" />
	<style>
		#currentRiskHighChartContainer text tspan {
			stroke-width: 1px;
		}
	</style>
	
	<g2:evaluate var="jvar_riskHighChartData">
		var risk = new sn_risk.RiskHeatmap().getRiskHeatmapData('current', '', true); 
		new global.JSON().encode(risk);
	</g2:evaluate>
	<g2:evaluate var="jvar_riskXAxisCategories">
		var xAxis = new sn_risk.RiskHeatmap().getRiskHeatmapXAxisCategories();  
		new global.JSON().encode(xAxis);
	</g2:evaluate>
	<g2:evaluate var="jvar_riskYAxisCategories">
		var yAxis = new sn_risk.RiskHeatmap().getRiskHeatmapYAxisCategories(); 
		new global.JSON().encode(yAxis);
	</g2:evaluate>
	
    <div id="currentRiskHighChartContainer" name="currentRiskHighChartContainer" style="min-width: 310px; height: 400px; max-width: 600px; margin: 0 auto"> 
    </div>
	
   
</j:jelly>

Client script

  • Copy and Paste below script in Client Script Section
(function($){
	var chart;

	chart = new Highcharts.Chart({
		chart: {
			renderTo: "currentRiskHighChartContainer",
			type: 'heatmap',
			marginTop: 12,
			marginBottom: 60,
			plotBackgroundColor: 'none',
			events:{
				load:function() {
					var points = this.series[0].data,
					lenY = this.yAxis[0].tickPositions.length - 1,
					lenX = this.xAxis[0].tickPositions.length - 1,
					x = lenX,
					tmpX = 0,
					y = 0,
					j = 0;
					
					$.each(points, function(i, p){
						var color = p.color;
						p.update({
							color: color
						},false);
					});
					
					this.isDirty = true;
					this.redraw();
				}
			}
		},
		plotOptions: {
			series: {
				events: {
					click: function (event) {
						document.location.href = event.point.url;
					}
				}
			}
		},
		title: {
			align:'center',
			text: '',
			
			style: {
				color: 'black',
				fontSize: '12pt'
			}
			
		},
		
		xAxis: {
			categories: JSON.parse('$[HTML, JS:jvar_riskXAxisCategories]'),
			title: {
				enabled: true,
				text: '<span>Impact</span>'
			}
		},
		
		yAxis: {
			categories: JSON.parse('$[HTML, JS:jvar_riskYAxisCategories]'),
			title: {
				enabled: true,
				text: '<span>Likelihood</span>'
			}
		},
		
		tooltip: {
			formatter: function () {
				var msg = new GwtMessage().getMessage('Count: {0}', this.point.value)
				+ '<br>' + 
				new GwtMessage().getMessage('Impact: {0}', this.series.yAxis.categories[this.point.y])
				+ '<br>' + 
				new GwtMessage().getMessage('Likelihood: {0}', this.series.xAxis.categories[this.point.x]);
				return msg;
			}
		},
		credits: {
			enabled: false
		},
		/*want to make this part dynamically populated*/
		series:[{
			data:[],
			cursor: 'pointer',
			showInLegend: false,			
			dataLabels: {
				enabled: true,
				color: 'black',
				type: 'heatmap',
				backgroundColor: 'none',
				style: {
					textShadow: 'none'
				}
			}
			
		}]
		
	});
	
	sn_risk.RiskHeatmapUtils.updateHeatmap(chart, 'current', true);
	
	CustomEvent.on('dashboard_filter.removed', function(filterMessage){	
		sn_risk.RiskHeatmapUtils.onRemoveEvent(chart, filterMessage, 'current', true);		
	});
	
	CustomEvent.on('dashboard_filter.added', function(filterMessage){
		sn_risk.RiskHeatmapUtils.onAddEvent(chart, filterMessage, 'current', true);
	});	
	
})(jQuery);

Processing Script

  • Copy and Paste below Server-Side Script in Processing Script Section
// empty
  • Click on Save button to save the change.

Step 5: Scripting – Edit RiskHeatMap

Go to System Definition > Script Include

  • Search for RiskHeatMapand open the record.
  • Copy and Paste below Script in Script Include Script Section
var RiskHeatmap = Class.create();
RiskHeatmap.prototype = Object.extendsObject(RiskHeatmapBase, {

    // Overwrite current method to include current as heatmapType
	_getRiskHeatmapData: function(heatmapType, filter, includeColor){
		var riskData = [];
		var riskImpactFieldName = '';
		var riskLikelihoodFieldName = '';
		
		if (heatmapType == 'inherent'){
			riskImpactFieldName = 'impact';
			riskLikelihoodFieldName = 'likelihood';
		}
		else if(heatmapType == 'residual'){
			riskImpactFieldName = 'residual_impact';
			riskLikelihoodFieldName = 'residual_likelihood';
		} else if(heatmapType == 'current'){
			riskImpactFieldName = 'u_current_impact';
			riskLikelihoodFieldName = 'u_current_likelihood';
		} else
		return riskData;
		
		var likelihood =  new GlideRecord('sn_risk_criteria');
		likelihood.addQuery('type', 'likelihood');
		likelihood.orderByDesc('order');
		
		likelihood.query();
		
		for(var i = 0; i < likelihood.getRowCount(); i++){
			likelihood.next();
			
			var impact =  new GlideRecord('sn_risk_criteria');
			impact.addQuery('type', 'impact');
			impact.orderByDesc('order');
			
			impact.query();
			for(var j = 0; j < impact.getRowCount(); j++){
				
				impact.next();
				var risk = new GlideAggregate('sn_risk_risk');
				var count = 0;
				risk.addQuery(riskImpactFieldName, impact.getUniqueValue());
				risk.addQuery(riskLikelihoodFieldName, likelihood.getUniqueValue());
				
				if (filter)				
					risk.addEncodedQuery(filter);

				risk.addAggregate('COUNT');
				risk.query();
				if (risk.next())
					count = risk.getAggregate('COUNT');
				
				var riskColor = this._getRiskColor(impact.getUniqueValue(), likelihood.getUniqueValue());
				var riskUrl = 'sn_risk_risk_list.do?sysparm_query=' + riskImpactFieldName + '%3D' + impact.getUniqueValue()
				+ '%5E' + riskLikelihoodFieldName + '%3D' + likelihood.getUniqueValue();
				
				if (filter)
					riskUrl += '%5E' + filter;
				if (includeColor)
					riskData.push({x:i, y:j, value:count, color: riskColor, url:riskUrl});
				else
					riskData.push({x:i, y:j, value:count, url:riskUrl});
				}
			}
			
			return riskData;
		},





    /** Override base class functions here **/

    type: 'RiskHeatmap'
});
  • Click on Save button to save the change.

Step 6: Scripting – Create new RiskFormUtils

Go to System UI > UI Scripts > Click New

  • Id: WR_RiskFormUtilsV2
  • Copy and Paste below Script in UI Script Script Section
var sn_risk = sn_risk || {};

sn_risk.WR_RiskFormUtilsV2 = (function() {
"use strict";

return {

	calculateALE: function(currency) {

		var resALE = g_form.getValue('sys_readonly.sn_risk_risk.residual_ale');
		var inALE = g_form.getValue('sys_readonly.sn_risk_risk.inherent_ale');
		var ccr = g_form.getValue('calculated_risk_factor');

		resALE = parseFloat(resALE.replace(/[^\d\.]/g,''));
		inALE = parseFloat(inALE.replace(/[^\d\.]/g,''));

		var calcALE = parseFloat(resALE + ((inALE - resALE) * (ccr / 100)));

		calcALE = calcALE.toFixed(2);
		calcALE = currency + calcALE;

		g_form.setValue('calculated_ale', calcALE.toString());
	},

	synchSymbol: function(fromSLE, toSLE, tableName){	
		var toSLESymbol = g_form.getValue(toSLE + '.currency');
		var fromSLESymbol = g_form.getValue(fromSLE + '.currency');  
		if (toSLESymbol == fromSLESymbol)
			return;

		var val = g_form.getValue(toSLE + '.display');	
		// Strip any special characters in the value
		val = val.replace(new RegExp(g_user_grouping_separator, "g"), "");

		var newSLEVal = fromSLESymbol + ';' + val;
		g_form.setValue(toSLE + ".currency", fromSLESymbol);
		g_form.setValue(toSLE + ".display", val);
		g_form.setValue(toSLE, newSLEVal);

		var id = tableName + '.' + toSLE;
		// these two lines are used to set the right currency value, like GBP;100.00
		// Otherwise the value is set to be 100.00, which won't save the currenct
		var sleElement= document.getElementById(id);
		sleElement.value = newSLEVal;			

	},	

	hideQualitativeFields: function(hideQualitativeFields) {
		var fieldsToHide = [];
		if (hideQualitativeFields) {
			fieldsToHide = ['significance', 'residual_significance', 
							'likelihood', 'residual_likelihood',
							'score', 'residual_score',
							'calculated_score'
							];
		}
		else {
			fieldsToHide = ['inherent_sle', 'residual_sle',
							'inherent_aro', 'residual_aro',
							'inherent_ale', 'residual_ale',
							'calculated_ale',

							'default_inherent_sle', 'default_residual_sle',
							'default_inherent_aro', 'default_residual_aro'
							];
		}
		for (var i = 0; i < fieldsToHide.length; i++)
			g_form.setDisplay(fieldsToHide[i], false);
	}, 

	  updateRiskScore: function(scoreType, fieldName, fieldType, value) {

		            var setFieldName, impactField, likelihooField;
		            var ga = new GlideAjax('RiskUtilsAJAXV2');
		
		            switch(fieldName) {
		
		                case 'u_current_impact':
		                    setFieldName = 'u_current_sle';
		                    ga.addParam('sysparm_name', 'usingQualitative');
		                    ga.addParam('sysparm_prop_name', 'sn_risk.qualitative_impact');
		                    ga.getXML(setFields);
		                    break;
		
		                case 'u_current_likelihood':
		                    setFieldName = 'u_current_aro';
		                    ga.addParam('sysparm_name', 'usingQualitative');
		                    ga.addParam('sysparm_prop_name', 'sn_risk.qualitative_likelihood');
		                    ga.getXML(setFields);
		            }
		
		            function setFields(response) {
		                var answer = response.responseXML.documentElement.getAttribute("answer");
		                if(answer == 'true') {
		
		                    ga = new GlideAjax('RiskUtilsAJAXV2');
		                    ga.addParam('sysparm_name', 'getRiskCriteria');
		                    ga.addParam('sysparm_field_type', fieldType);
		                    ga.addParam('sysparm_field_value', value);
		                    ga.getXMLAnswer(function(answer) {
		
		                        if(answer != false || answer == '0')
		                            g_form.setValue(setFieldName, answer);
		                    });
		
		                    ga = new GlideAjax('RiskUtilsAJAXV2');
		                    ga.addParam('sysparm_name', 'getRiskScore');
		                    ga.addParam('sysparm_impact', g_form.getValue('u_current_impact'));
		                    ga.addParam('sysparm_likelihood', g_form.getValue('u_current_likelihood'));
		                    ga.getXMLAnswer(function(answer) {
		                        g_form.setValue('u_current_score', answer);
		                    });
		                }
		            }
		        },
	
	useQualitativeImpact: function(useQualitativeImpact) {
		jslog("Calling use qualitative impact " + useQualitativeImpact);
		if(useQualitativeImpact) {
			g_form.setDisplay('inherent_sle', false);
			g_form.setDisplay('residual_sle', false);
			g_form.setDisplay('default_inherent_sle', false);
			g_form.setDisplay('default_residual_sle', false);
		}
		else {
			g_form.setDisplay('impact', false);
			g_form.setDisplay('residual_impact', false);
		}
	},

	useQualitativeLikelihood: function(useQualitativeLikelihood) {
		jslog("Calling use qualitative likelihood " + useQualitativeLikelihood);
		if(useQualitativeLikelihood) {
			g_form.setDisplay('inherent_aro', false);
			g_form.setDisplay('residual_aro', false);
			g_form.setDisplay('default_inherent_aro', false);
			g_form.setDisplay('default_residual_aro', false);
		}
		else {
			g_form.setDisplay('likelihood', false);
			g_form.setDisplay('residual_likelihood', false);
		}
	},

	validateRisk: function(fieldType) {
		jslog("Started: " + fieldType);
		fieldType = fieldType + '';
		var isDefault = false;
		var fieldArr = fieldType.split('_');

		if(fieldArr[0] == 'default')
			isDefault = true;

		var inherentField;
		var residualField;
		var inherent; 
		var residual;

		switch(fieldType) {
			case 'likelihood':
			case 'impact':
				residualField = 'residual_' + fieldType;
				inherentField = fieldType;
				setQualitativeValues(inherentField, residualField);
				break;

			case 'residual_likelihood':
			case 'residual_impact':
				residualField = fieldType;
				inherentField = fieldArr[1];
				setQualitativeValues(inherentField, residualField);
				break;

			case 'default_inherent_sle':
			case 'inherent_sle':
				setInherentQuantitativeValues(fieldType, fieldArr);
				setQuantitativeValues(inherentField, residualField);
				doCheck();
				break;

			case 'default_inherent_aro':
			case 'inherent_aro':
				setInherentQuantitativeValues(fieldType, fieldArr);
				setAROValues(inherentField, residualField);
				break;

			case 'default_residual_sle':
			case 'residual_sle':
				setResidualQuantitativeValues(fieldType, fieldArr);
				setQuantitativeValues(inherentField, residualField);
				doCheck();
				break;
			case 'default_residual_aro':
			case 'residual_aro':
				setResidualQuantitativeValues(fieldType, fieldArr);
				setAROValues(inherentField, residualField);
				break;
		}

		function hideErrorBoxes(inherentField, residualField) {
			g_form.hideErrorBox(inherentField);
			g_form.hideErrorBox(residualField);
		}

		function setInherentQuantitativeValues(fieldType, fieldArr) {
			inherentField = fieldType;
			if(isDefault)
				residualField = 'default_residual_' + fieldArr[2];
			else
				residualField = 'residual_' + fieldArr[1];
			hideErrorBoxes(inherentField, residualField);
		}

		function setResidualQuantitativeValues(fieldType, fieldArr) {
			if(isDefault)
				inherentField = 'default_inherent_' + fieldArr[2];
			else
				inherentField = 'inherent_' + fieldArr[1];
			residualField = fieldType;
			hideErrorBoxes(inherentField, residualField);
		}

		function setQuantitativeValues(inherentField, residualField) {
			var rawInherent = g_form.getValue(inherentField);
			var inherentArray = rawInherent.split(';');
			inherent = parseFloat(inherentArray[1].replace(/,/g , ''));
			var rawResidual = g_form.getValue(residualField);
			var residualArray = rawResidual.split(';');
			residual = parseFloat(residualArray[1].replace(/,/g , ''));
		}

		function setQualitativeValues(inherentField, residualField) {
			hideErrorBoxes(inherentField, residualField);
			inherent = false;
			residual = false;
			if(g_form.getValue(inherentField) == '') {
				inherent = 0;
			}
			else {
				g_form.getReference(inherentField, setInherent);
			}
			g_form.getReference(residualField, setResidual);
		}

		function setAROValues(inherentField, residualField) {
			hideErrorBoxes(inherentField, residualField);
			inherent = g_form.getValue(inherentField);
			residual = g_form.getValue(residualField);
			doCheck();
		}

		function setInherent(criteria) {
			if(criteria.type == 'likelihood')
				inherent = criteria.percentage_max_value;
			else 
				inherent = parseInt(criteria.currency_max_value);
			doCheck();
		}

		function setResidual(criteria) {
			if(criteria.type == 'likelihood')
				residual = criteria.percentage_max_value;
			else
				residual = parseInt(criteria.currency_max_value);	
			doCheck();
		}

		function doCheck() {
			if(inherent !== false && residual !== false && (parseFloat(residual)) > parseFloat(inherent)) {
				switch(fieldType) {
					case 'likelihood' :
						g_form.showErrorBox(inherentField,getMessage('sn_risk_likelihood_inherent_lower_than_residual'));
						break;
					case 'residual_likelihood' :
						g_form.showErrorBox(residualField,getMessage('sn_risk_likelihood_residual_higher_than_inherent'));
						break;
					case 'impact' :
						g_form.showErrorBox(inherentField,getMessage('sn_risk_impact_inherent_lower_than_residual'));
						break;
					case 'residual_impact' :
						g_form.showErrorBox(residualField,getMessage('sn_risk_impact_residual_higher_than_inherent'));
						break;
					case 'inherent_sle' : 
						g_form.showErrorBox('inherent_sle', getMessage('sn_risk_sle_inherent_lower_than_residual'));
						break;
					case 'default_inherent_sle' :
						g_form.showErrorBox('default_inherent_sle', getMessage('sn_risk_sle_inherent_lower_than_residual'));
						break;
					case 'residual_sle' : 
						g_form.showErrorBox('residual_sle', getMessage('sn_risk_sle_residual_higher_than_inherent'));
						break;
					case 'default_residual_sle' : 
						g_form.showErrorBox('default_residual_sle', getMessage('sn_risk_sle_residual_higher_than_inherent'));
						break;
					case 'inherent_aro' : 
						g_form.showErrorBox('inherent_aro', getMessage('sn_risk_aro_inherent_lower_than_residual'));
						break;
					case 'default_inherent_aro' : 
						g_form.showErrorBox('default_inherent_aro', getMessage('sn_risk_aro_inherent_lower_than_residual'));
						break;
					case 'residual_aro' : 
						g_form.showErrorBox('residual_aro', getMessage('sn_risk_aro_residual_higher_than_inherent'));
						break;
					case 'default_residual_aro' : 
						g_form.showErrorBox('default_residual_aro', getMessage('sn_risk_aro_residual_higher_than_inherent'));
						break;
				}
				return false;
			}
			else
				return true;
		}
	},

	type: 'WR_RiskFormUtilsV2'
};
})();
  • Click on Save button to save the change.

Step 7: Edit Custom Risk Overview dashboard

Go to Service Portal > Page > Click New

Adapt the dashboard according to your taste.

Search for Custom Risk Overview dashboard.

In the “Current Scoring” tab search for widget current and add to the dashboard.

0

How to report using our Entity model? 0 (0)

 Governance, Risk, and Compliance, entities can be people, processes, departments, applications, or objects, whose exposure must be managed. These entities have controls that are defined to view the status.

To understand entities, consider the following example. Assume you are a new GRC user and you want to implement a change management process to all your critical financial systems. All the systems can be considered as individual entities. Map all the systems to an entity class called Financial. Have an entity type filter for critical financial systems to determine the systems that are identified as critical.

The primary benefit of creating entities is that you can maintain accountability because each entity has an owner. To understand this benefit, assume that you want to configure all the servers in a new way. After you finish the configuration, you perform an audit and then discover that only one server failed to comply with the new configuration. If you had not defined all the entities, then the entire audit result would have been deemed as failed. But because you have the entities defined, then only the non-compliant server entity and its identified owner are held accountable instead of all the servers.

Having defined entities ensures that the entity owners can be identified and that appropriate controls can be applied to those entities. It also helps in tracking the entities that are non-compliant. Any entity that has child entities can be said to have downstream entities. Any entity that has parent entities can be said to have upstream entities.

Take the following entity class model as example:

We have a Company that contains Departments that contains Business Services, Business Applications and Business Process. In GRC this are called “downstream entities”. This model is called “Dependency Model” and can be achieved by setting up the “Entity Class hierarchy”. You can visualise the model by accessing the “GRC Workbench”.

For this exercise, we will create 3 entities to match the above model and generate their respective risks.

EntityClass
WranguCompany
Information SecurityDepartment
Enterprise Risk ManagementBusiness Process
List of entities
RiskEntityStatement
Disruption to the business due to critical infrastructure unavailabilityWranguBusiness Continuity and Disruption
Inability to retain key employees may result in the departments inability to achieve objectives.Information SecurityLoss of Key Personnel
Unauthorized actions have been detectedEnterprise RiskUnauthorized actions
List of risks

Steps

  1. Create a client callable script include called “GRCReporting” – make sure its you’ve checked the ‘Client callable’.
  2. Copy the following code into it.
var GRCReporting = Class.create();
GRCReporting.prototype = Object.extendsObject(global.AbstractAjaxProcessor, {

	getDownstreamProfileIDs: function(profileID) {
		return new sn_grc.GRCProfileLists().getDownstreamProfileIDs(profileID);
	},

	type: 'GRCReporting'
});

Create your reports

  1. Create a report against the Risk table (sn_risk_risk)
  2. In the filter condition type the following “Entity > Sys Id” contains “javascript:new sn_grc.sn_grc.GRCReporting().getDownstreamProfileIDs(<Entity SysId>);”. In this example, I’ve used the entity Wrangu.
  3. Click “Run”.
0

The method getUniqueValue() does not always return an id – TRUE or FALSE? 0 (0)

During Live Coding Happy Hour (LCHH) last week session about Decision APIs I got intrigued when Earl Duque used gr.getValue(‘sys_id’) instead of gr.getUniqueValue(), and in the comments was mentioned getUniqueValue() does not always return the sys id, so lets confirm that.

Lets take a look at the API Documentation says (here).

It says “get the primary key of the record which is usually sys_id unless otherwise specific”. Interesting, what they mean by primary key of a record? There’s a field called “Primary” that is only true for the Sys ID.

Made “PK” field as primary, did a quick GlideRecord query() and it always returned the sys id. I couldn’t prove their hypothesis – getUniqueValue is returning always the sysId. What did I miss?

It seems you can have multiple fields with primary = true (?).

var grTest = new GlideRecord('x_299742_nowjedi_example');
grTest.query();
while (grTest.next()) {
      gs.info(grTest.getUniqueValue());    
}

If you know the answer to this please drop in the comments.


Thank you,
Raf

0

ServiceNow Community Rising Star ⭐ for the class of 2023 4.6 (9)

Since I joined Wrangu I decided to become more involved in the ServiceNow community and since 2019 I have been in Top 5 GRC community leaderboard. Today this effort paid off in a big way by been honoured and selected as ServiceNow Community Rising Star ⭐ for the class of 2023.

Congrats to the group! I look forward to working with you to support and grow our amazing community over the next years!

Don’t forget to follow my blog, big news coming soon.

#servicenow #servicenowdeveloper #servicenowrisingstar #nowjedi #servicenowcommunity

https://www.servicenow.com/community/in-other-news/announcing-our-2023-mvp-and-rising-star-awardees/ba-p/2457267

0

Probably one of the best snippets on your Utils: getHistoryWalker 5 (1)

How many times did you write the code to find the previous value on a specific field? Long time ago I found a library called “HistoryWalker” and since then its been on my must-have snippets on my utils. This library sn_hw is not “public” available but you can see lot of scripts using it. Quite useful 🙂

    getHistoryWalker: function(grObject,field) {
        var answer = [];
        var previousValue;

        var hw = new sn_hw.HistoryWalker(grObject.getRecordClassName(), grObject.getUniqueValue());
        hw.walkTo(grObject.sys_mod_count);

        do {
            var wr = hw.getWalkedRecordCopy();
            var currentValue = wr.getValue(field);
            if (currentValue != previousValue) {
                previousValue = currentValue;
                answer.push(wr.getValue(field) + '');
            }
        } while (hw.walkBackward());

        return answer;
    },