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

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;
    },
0

Execute operation on script include ‘todoPageUtils’ from scope ‘GRC: Risk Management’ was denied – how to fix it? 5 (2)

ServiceNow support didn’t provide too much information about the steps to fix
Execute operation on script include ‘hr_PortalUtil’ from scope ‘Global’ was denied – Support and Troubleshooting (servicenow.com)

Error:
Execute operation on script include ‘todoPageUtils’ from scope ‘GRC: Risk Management’ was denied. The application ‘GRC: Risk Management’ must declare a cross scope access privilege. Please contact the application admin to update their access requests.

How to fix this issue?

  1. Go to “Application Restricted Caller Access” (sys_restricted_caller_access) table
  2. Change status to “Allowed”.

* Make sure you’ve captured the entry into your update set.

0

Ended up on Top 25% of Hacktoberfest 2021 contributors 0 (0)

In response to my Hacktoberfest 2021 – NOWJedi by #ServiceNowDeveloper participation, I focused on developing code snippets.

You can find all my pull requests here and drill down to Service Portal Widgets.

Record process flow

Create diagram using Highcharts

Creating diagram using GoJS library

File Upload widget

With this participation I ended up in the Top 25% of contributors.

0

Prevent Entities to get inactivate by mistake 0 (0)

Hi all,

I had few incidents in the past when entities get inactivated by mistake so I decided to leverage our entity management and improve UX to allow them (managers) to receive a popup message before they proceed with inactivation of an entity

Steps:

  1. Mark “Active” as read-only at dictionary level.
  2. Change your scope to “GRC: Profile”.
  3. Go to Profile [sn_grc_profile] and create a UI action called “Mark as inactive” with the following conditions:

    Name: Mark as inactive
    Action name: entity_retire
    Form button: true
    Show insert: true
    Show update: true
    Form style: destructive
    List style: destructive
    Client:  true

    Onclick: confirmAndRetireEntity()
    Condition: gs.getUser().hasRole(‘sn_grc.manager’) && current.active == true
    Script: 
    function confirmAndRetireEntity() { var gwt = new GwtMessage(); var title = gwt.getMessage('When a entity is retired, all related risks and controls are retired. Are you sure you want to continue?'); var modal = new GlideModal("sn_grc_retire_popup", false, 600, 450); modal.setTitle(gwt.getMessage('Confirmation')); modal.setPreference('sysparm_title', title); modal.setPreference('sysparm_sysid', g_form.getUniqueValue()); modal.setBackdropStatic(true); modal.setPreference('focusTrap', true); modal.setPreference('action_name', 'entity_retire'); modal.render(); return false; } if (typeof window == 'undefined') updateStatus(); function updateStatus() { current.setValue('active', 'false'); current.update(); action.setRedirectURL(current); }​

Result:
image

Note:

1. This pop up can be widely used (its generic) but it was originally created for Policies.

2. You need to create another button called “Mark as active” to perform the opposite behavior of this UI action. I gave “entity_enroll” as action name, change condition to run only on inactive records, change function name “confirmAndEnrollEntity()”, change line to “current.setValue(‘active’,’true’)” and thats it.

0

How to recreate a Glide List using last icons 0 (0)

Hi,

Most recently I tried to use the glide list and apparently the “macro” available is using the old version. Gosh no.

In order to improve it, I have created the following UI macro.

Name: GlideList2

Description: This is an optimized Glide List   ready for bootstrap. It was based on old macro lightweight_glide_list and recent HTML code.

Code:

<?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:evaluate var="jvar_current_user" expression="gs.getUserDisplayName()" />

<g:evaluate var="jvar_current_user_id" expression="gs.getUserID()" />

<!-- start bootstrap --> 

   <button data-original-title="Edit ${jvar_label}" id="${jvar_control_name}_unlock" data-target="#glide\.${jvar_control_name}" data-placement="auto" title="" data-auto-close="false" style="margin-right: 5px; display: inline-block;" data-type="glide_list_unlock" tabindex="0" class="btn btn-default btn-ref" data-ref="${jvar_control_name}">

   <span aria-hidden="true" class="icon icon-locked"></span>

   <span class="sr-only">Edit ${jvar_label}</span>

   </button>

   <!-- start mode edit -->

   <span id="${jvar_control_name}_edit" style="display: none;">

     <div class="glide-list">

     <select id="select_0${jvar_control_name}" style="direction:ltr; ;" name="select_0${jvar_control_name}" onchange="toggleGlideListIcons('${jvar_control_name}', false);" class="form-control" multiple="yes" size="6"></select>

     <div class="button-column">

     <j:if test="${jvar_can_add_me}">

     <button data-original-title="Add me" id="add_me.${jvar_control_name}" title="" data-user-id="${jvar_current_user_id}" style="display: inline-block;" data-type="glide_list_add_me" class="btn btn-default btn-ref" data-user="${jvar_current_user}" data-ref="${jvar_control_name}">

     <span aria-hidden="true" class="icon-user-add icon"></span><span class="sr-only">${jvar_label} Add me</span>

     </button>

     </j:if>

     

     <button data-original-title="Remove selected item" disabled="" id="remove.${jvar_control_name}" class="btn btn-default btn-ref" title="" style="display: inline-block;" data-type="glide_list_remove" data-ref="${jvar_control_name}">

     <span aria-hidden="true" class="icon icon-cross"></span>

     <span class="sr-only">Remove selected item</span>

     </button>

     <button data-original-title="View selected item" disabled="" id="view2link.${jvar_control_name}" title="" onmouseout="lockPopup(event)" name="view2.${jvar_control_name}" tabindex="-1" onclick="glideListViewSelection('${jvar_control_name}','deapr_glide','sys_user', 'false')" class="btn btn-default btn-ref" type="button" onmouseover="glideListPopupSelection(event, '${jvar_control_name}', 'sys_user')"><span aria-hidden="true" class="icon-view icon"></span>

     <span class="sr-only">View selected item</span>

     </button>

     

     <button data-original-title="Lookup using list" id="lookup.${jvar_control_name}" onclick="reflistOpen( '${jvar_control_name}','select_0${jvar_control_name}','${jvar_table}','null', 'false', ''); mousePositionSave(event); event.stop();" class="btn btn-default btn-ref" title="" name="lookup.{{ref}}">

   <span aria-hidden="true" class="icon icon-search"></span><span class="sr-only">Lookup using list</span>

   </button>

     <button data-original-title="Lock" id="${jvar_control_name}_lock" title="" style="align: left; padding-top: 3px; cursor: pointer; display: none" data-type="glide_list_lock" tabindex="0" class="btn btn-default btn-ref" type="button" data-ref="${jvar_control_name}">

     <span aria-hidden="true" class="icon icon-unlocked"></span><span class="sr-only">Lock"</span>

     </button>

     </div>

     </div>

   </span>

<!-- finish edit -->

<p data-original-title="${jvar_label}" id="${jvar_control_name}_nonedit" class="form-control-static" title="" style="direction:ltr; display: inline-block; "></p>

<input mandatory="false" id="${jvar_control_name}" value="" name="${jvar_control_name}" type="HIDDEN" />

<!--

<input value="" id="sys_original.${jvar_control_name}" name="sys_original.${jvar_control_name}" type="HIDDEN" />

 -->

<!-- finish bootstrap -->

<!-- End List -->

</j:jelly>

On your UI page, you just need to invoke it.

<!-- List -->	
<j:set var="jvar_label" value="Users" />

<j:set var="jvar_control_name" value="zListCollectorUsers" />

<j:set var="jvar_can_add_me" value="true" />

<j:set var="jvar_table" value="sys_user" />

<g:macro_invoke macro="GlideList2" />
0

Design a Powerful and Scalable Workflow with System Properties 0 (0)

The article below is intended for any person customise your Workflows. Javascript knowledge is useful but not required.

As I mentioned in other articles, regardless of your task, there aren’t always enough hours in the day to get everything done. As a result, you constantly feel like you’re always behind. And that’s just not good for your productivity or your health. Instead of putting in those extra hours, you can become more effective at work by focusing on what really matters. And you can get started with that ASAP by applying this quick scalable solution to your scripts. 



Here’s a few things we will cover:

Use CaseExample
Automate properties (sys_properties)System properties store configuration information that rarely or never changes. Each time you change or add a system property, the system flushes the cache to keep all nodes in the cluster in synch. This cache flush has a very high performance cost for one to 10 minutes, which can potentially cause an outage if done excessively. To prevent such outages, do not use a system property to store configuration information that changes more than once or twice a month. I usually identify what can change in the future, like “group id”, “names”, etc.

Use Case Walkthrough

  1. In the Navigation filter, enter sys_properties.list.The entire list of properties in the System Properties [sys_properties] table appears.
  2. Verify that the property does not exist by searching for the property name.
  3. Click New.
    Usually I use “company.workflow.default.L1″.
  4. Complete the System Property form.
    Type: String
    Value: <sys_user_group_id>
  5. Navigate to Workflow > Workflow Editor
  6. Edit your workflow and change script as appropriate.

Script
gs.getProperty(‘company.workflow.default.L1′);

Unfortunately there is no magic formula to identify which task or script or variable should be considered as system properties but to start you should think what can change over the time. 

Example #1:

Found some quite simple use case in the communities (link). A user would like to add a IF condition in the workflow to verify if the sys_property value is equal to true and then, the requester assigned will be XXX company and eventually will fire the next sc task. 

answer = ifScript();

function ifScript() {
    var prop = gs.getProperty('ab.provision.workflow');
    var comp = current.u_requestor.company.getDisplayValue(); //I am assuming u_requestor is the field company name

    if (prop == 'true' && comp == 'company name') {
        return 'yes';
    }
    return 'no';
}

Conclusion

System properties are not limited to just Notifications or Workflows, they’re part of the NOW Platform and can include anything we have in the Platform. Try to automate your instance and try to centralize all your “customer” data in one place. Use all the tools at your disposal to make that happen.

I will try to collect a use case for each type in order to help you to identify on which cases you should apply the sys property technique. 

0

Building a Dropdown in Jelly 0 (0)

If you want to create your dropdown list based on an array, please check the example below:

HTML

<!-- Values for select dropdown list-->

<g:evaluate>
var methods = [
{ name: 'Option number 1', value: 'Opt1' },
{ name: 'Option Number 2', value: 'Opt2' },
{ name: 'Option Number 3', value: 'Opt3' }
];
</g:evaluate>


<!-- Step #1 - Create dropdown for process type-->

<div class="form-group">
<label for="inputdefault" class="col-sm-2 control-label">Workflow Type:</label>
<div class="col-sm-10">
<!-- Step #1 - Start Select-->
<select class="form-control-static" id="dropdownId" name="dropdownId"   onchange="toggle_visibility(this); return false;">  
<option value="" selected="selected">-- Select --</option>  
<j:forEach items="${methods}" var="jvar_choice" indexVar="jvar_choice_id">
<g:evaluate jelly="true">
var name = jelly.jvar_choice.name;  
var value = jelly.jvar_choice.value;  
</g:evaluate>
</j:forEach>  
</select>  

<!-- Step #1 - End Select-->
  <div class="help-block">
  <j:if test="${!empty(jvar_choice)}">
  <label for="help_method_selected" id="help_method_selected"></label>
  </j:if>
  </div>      
</div>
</div>

Client script

getDrop = document.getElementById('dropdownId').value; process = trim(dropdownId);
console.log('var Demand Number: ' +getDrop);

Processing script

var p_dropdown = request.getParameter('dropdownId');
// For debugging
gs.info("MyDebug -INFO- Received form value for process ->" + p_dropdown);
gs.log("MyDebug -INFO- Received form, value for process ->" + p_dropdown);