dvp
Mega Sage

In this series, I would like to share the solutions that are missing in Service Portal. Today I would to discuss about how to make rejection comments mandatory in Service Portal.

Approvals in ServiceNow are handled either via emails or through an approval record. One of the significant reasons to use approvals via form is the ability to capture comments when rejecting a record. In a form view there is an OOB UI policy Comments mandatory on rejection, that makes comments field mandatory while rejecting a record.

find_real_file.png

When I initially started playing with Portal, I did not find a way to capture comments in OOB approval widgets. So I cloned the OOB Approval info widget and added a new text box

<textarea ng-model="c.data.comment" style="color: grey; width: 100%; margin-top: .5em;" placeholder="Rejection Comments" class="form-control" rows="5"></textarea>

Below   is the screenshot of comments box

find_real_file.png

In order to make the comments field required while rejecting, first validate whether it is empty or not. If it is then stop the approval process.

After adding couple lines in server and client controller I'm able stop the rejection when the comments are NULL and an alert box will pop out as shown in the image below.

find_real_file.png

Here is the code that has to be updated in server and client controller blocks of a cloned widget.

Server Code

if (input.comment){

        gr.comments = input.comment;

        gr.update();

}

Client Controller

c.action = function(state) {

        if( (c.data.comment ==   undefined || c.data.comment ==   '' )&& state == 'rejected'){

                  $window.alert('Rejection Comments cannot be empty');

                  return false;

        }

        c.data.op = state;

        c.data.state = state;

        c.server.update();

}

Please find the attached XML of updated Approval info widget.

Blogs in this series

Portal diaries: Service Portal — Approval Summarizer in Portal

Portal diaries: Service Portal — Multiple Catalogs (Part 1)

84 Comments
Paul McNamara
Mega Guru

This was extremely helpful but I didn't want to add a comment field to the approval record widget, I wanted to use the ootb activity stream so


1) I updated the write acl for the additional comments field of the RITM so that opened by or requested for or anybody assign an approval task could comment (would do this for any record requiring approvals i.e. change, standard change proposals etc)


2) I cloned the ticket conversations widget and added $rootScope.$broadcast("commented"); to the postEntry Function


3) I cloned the Approval Info widget and added


var commented=false;


$rootScope.$on("commented", function(){


      commented=true;


}


c.action = function(state) {


if(commented==false   && state == 'rejected'){


          spUtil.addErrorMessage('Must add a comment before rejecting');


return false;


}


c.data.op = state;


c.data.state = state;


c.server.update();


}



Being sure to add spUtil to the function in the client controller and I moved the ticket Conversation widget above the summary.   Now if you hit reject without having commented you get an error message,   if you have made a comment you don't.   The comment is on the Record Level not the approval so a two way conversation with other users is possible here.



find_real_file.png


Andrew Albury-D
Mega Guru

Thanks heaps for this! With the blog and comments I was able to edit ours so rather than a field on the form we get a $window.prompt() to enter a reason. I've also copied the comment from the Approval to the Request the approval was associated with via Business Rule. Very cool!

I updated both Approvals and Approval Info widgets so this works everywhere on our portal!

My client script looks like this:

function ($scope, $window) {
	var c = this;	
	c.action = function(state) {
		if (state == "rejected") {
			var newcomment = $window.prompt("Why are you rejecting this approval?");
			if (newcomment) {
				c.data.op = state;
				c.data.state = state;
				c.data.comments = newcomment;
				c.server.update();				
			} else {
				return false;
			}
		} else {
			c.data.op = state;
			c.data.state = state;
			c.server.update();		
		}
	}
}

 

and my server script just has  gr.comments = input.comments; in before any update()'s.

Thanks for your ideas!

EDIT: After some feedback (thanks pamtpaul!) I have modified my code to use spModal rather than window.alert(). It looks much more friendly. here is a snippet:

function ($scope, $window, spModal) {
	var c = this;	
	c.action = function(state) {
		if (state == "rejected") {
			spModal.prompt("Why are you rejecting this approval?").then(function(newComments) {
				c.data.op = state;
				c.data.state = state;
				c.data.comments = newComments;
				c.server.update();
			})
		} else {
			c.data.op = state;
			c.data.state = state;
			c.server.update();		
		}
	}
}
Paul McNamara
Mega Guru

I'd recommend using spModal vs $window.prompt as some mobile browsers don't like it.

Andrew Albury-D
Mega Guru

Thanks a lot for the suggestion. I am fairly fresh to Service Portal and didn't know there was an alternative. I'll implement this way ASAP.

sowmyaprabhakar
Tera Contributor

Hi, 

 

Thanks for the post. I was able to achieve the functionality. However, I am facing the following issues.

1. Comments are getting copied multiple times, (minimum twice) upon every update.

2. I wanted to hide the comments field on the widget, after approval./ reject. Currently, I see it is still in the form

 

 

Thanks in advance.

 

Andrew Albury-D
Mega Guru

Hi sowmyaprabhakar,

I had the issue with comments being copied as well. I updated my Server Script in the widget to check if the state was "Requested" before updating the comments:

(from my "Approvals" widget)

if (input.op == 'approved' || input.op == 'rejected') {
		var app = new GlideRecord("sysapproval_approver");
		if (app.get(input.target)) {
			if (input.op == 'rejected' && app.state == 'requested') {
				app.comments = input.comments;
			}
			app.state = input.op;
			app.update();
		}
	}

 

And then you should be able to include an extra line on your HTML template to hide the box with an ng-if. Something like this:(from original example)

<textarea ng-model="c.data.comment" ng-if="c.data.state != 'requested'" style="color: grey; width: 100%; margin-top: .5em;" placeholder="Rejection Comments" class="form-control" rows="5"></textarea>

Hope this helps!

cfrazer
Giga Contributor

Hey Andrew, any chance you can post your entire server script or message it to me?? I am getting the duplicate comments as well, but my approval info widget doesn't look like the example or yours.

 

here's mine:

(function() {
	var gr = $sp.getRecord();
	if (gr == null || !gr.isValid()) {
		data.isValid = false;
		return;
	}
	
	data.isValid = true;
	data.isMine = gr.getValue("approver") == gs.getUserID();

	
	if (!data.isMine && !gr.approver.nil())
		data.approver = gr.approver.getDisplayValue();
	if (input && input.op) { 
		gr.state = input.op;
	gr.update();
	}
	
	if (input.comment){
	
gr.comments = input.comment
gr.update();

}


	var fields = $sp.getFields(gr, 'state,sys_created_on');

	if (gr.sys_mod_count > 0)
		fields.push($sp.getField(gr, 'sys_updated_on'));

	data.fields = fields;
	data.state = gr.state.toString();
	data.sys_updated_on = gr.sys_updated_on.toString();
	data.sys_id = gr.getUniqueValue();
	data.table = gr.getTableName();
	data.label = getRecordBeingApproved(gr).getLabel();
	data.esignature = {
		username:  gs.getUserName(),
		userSysId: gs.getUserID(),
		e_sig_required: checkESig(gr.getValue("source_table"))
};
	function checkESig(table) {
		var esigRegistryGR = new GlideRecord("e_signature_registry");
		if (!esigRegistryGR.isValid())
			return false;
			
		esigRegistryGR.addQuery("enabled", "true");
		esigRegistryGR.addQuery("table_name", table);
		esigRegistryGR.query();
		return esigRegistryGR.hasNext();
	}
	
	function getRecordBeingApproved(gr) {
		if (!gr.sysapproval.nil())
			return gr.sysapproval.getRefRecord();

		return gr.document_id.getRefRecord();
	}
})();
Andrew Albury-D
Mega Guru

Hi cfrazer,

Our scripts looked so different because I posted the "Approvals" widget, not the "Approval Info" widget - but here is my server script for Approval Info:

(function() {
	var gr = $sp.getRecord();
	if (gr == null || !gr.isValid()) {
		data.isValid = false;
		return;
	}
	
	data.isValid = true;
	data.isMine = gr.getValue("approver") == gs.getUserID();
	
	if (!data.isMine && !gr.approver.nil())
		data.approver = gr.approver.getDisplayValue();
	if (input && input.op) { 
		gr.state = input.op;
		gr.comments = input.comments;
		gr.update();
	}

	var fields = $sp.getFields(gr, 'state,sys_created_on');

	if (gr.sys_mod_count > 0)
		fields.push($sp.getField(gr, 'sys_updated_on'));

	data.fields = fields;
	data.state = gr.state.toString();
	data.sys_updated_on = gr.sys_updated_on.toString();
	data.sys_id = gr.getUniqueValue();
	data.table = gr.getTableName();
	data.label = getRecordBeingApproved(gr).getLabel();

	function getRecordBeingApproved(gr) {
		if (!gr.sysapproval.nil())
			return gr.sysapproval.getRefRecord();

		return gr.document_id.getRefRecord();
	}
	
})();

 

It looks like you might have to move your gr.comments inside the if(input && input.op) update section, rather than have them after.

Please mark this helpful if it was 🙂

Carl Fransen1
Kilo Sage

Hi Andrew,

Would you be able to export your 'Approvals' and 'Approvals Info' widgets - I'm having trouble connecting the cots in this post so hoping you could share your final version.

 

Thanks

Carl.

Andrew Albury-D
Mega Guru

Hi Carl,

Attached are my two widgets prepended with my initial (AA). I can't say for certain they will work 100% of the time, but you are welcome to pull them apart!

Please mark my response as helpful if it was 🙂