Blog

Ticket Actions Widget

How to add a "Resolve Incident", “Request Status” and “Cancel” buttons with modal popup for Service Portal.

This was updated for ServiceNow Orlando 2020.

Results

Here is what the final result looks like:

INSTRUCTIONS

1. Create Widget

Name: Ticket Actions

Body HTML Template

<div class="panel b" ng-if="data.showWidget">
  <div class="panel-heading bg-primary">Actions</div>
  <div class="panel-body">
    <button type="button" class="btn btn-primary btn-block" ng-click="c.openModalResolve()" ng-if="data.showResolve">Resolve</button>
    <button type="button" class="btn btn-default btn-block" ng-click="c.openModalStatus()" ng-if="data.showStatus">Request Status</button>
    <button type="button" class="btn btn-danger btn-block" ng-click="c.openModalCancel()" ng-if="data.showCancel">Cancel</button>
    <button type="button" class="btn btn-default btn-block" ng-click="c.openModalReopen()" ng-if="data.showReopen">Reopen</button>
  </div>
</div>

<script type="text/ng-template" id="modalTemplateResolve">
	<div class="panel panel-default">
		<div class="panel-heading">
			<h4 class="panel-title">Provide a reason for the resolve</h4>
  </div>
      <div class="panel-body wrapper-xl">
      <form name="modalTemplateResolve" ng-submit="c.uiAction('resolve')">
        <div class="form-group">
          <textarea required sp-autosize="true" ng-required="true" ng-model="data.resolveComments" id="resolveComments" placeholder="Comments required" class="form-control ng-pristine ng-valid ng-scope ng-empty ng-touched" aria-invalid="false" style="overflow: hidden; word-wrap: break-word; resize: horizontal;"></textarea>
  </div>
        <input class="btn btn-primary" type="submit" />
  </form>
  </div>
  </div>
</script>

<script type="text/ng-template" id="modalTemplateCancel">
	<div class="panel panel-default">
		<div class="panel-heading">
			<h4 class="panel-title">Provide a reason for the cancel</h4>
  </div>
      <div class="panel-body wrapper-xl">
      <form name="modalTemplateResolve" ng-submit="c.uiAction('cancel')">
        <div class="form-group">
          <textarea required sp-autosize="true" ng-required="true" ng-model="data.cancelComments" id="cancelComments" placeholder="Comments required" class="form-control ng-pristine ng-valid ng-scope ng-empty ng-touched" aria-invalid="false" style="overflow: hidden; word-wrap: break-word; resize: horizontal;"></textarea>
  </div>
        <input class="btn btn-primary" type="submit" />
  </form>
  </div>
  </div>
</script>

<script type="text/ng-template" id="modalTemplateStatus">
	<div class="panel panel-default">
		<div class="panel-heading">
			<h4 class="panel-title">Provide any additional information</h4>
  </div>
      <div class="panel-body wrapper-xl">
      <form name="modalTemplateStatus" ng-submit="c.uiAction('status')">
        <div class="form-group">
          <textarea required sp-autosize="true" ng-required="true" ng-model="data.statusComments" id="statusComments" placeholder="Comments required" class="form-control ng-pristine ng-valid ng-scope ng-empty ng-touched" aria-invalid="false" style="overflow: hidden; word-wrap: break-word; resize: horizontal;"></textarea>
  </div>
        <input class="btn btn-primary" type="submit" />
  </form>
  </div>
  </div>
</script>

<script type="text/ng-template" id="modalTemplateReopen">
  <div class="panel panel-default">
    <div class="panel-heading">
      <h4 class="panel-title">Provide a reason for reopening the Incident</h4>
  </div>
    <div class="panel-body wrapper-xl">
      <form name="modalTemplateReopen" ng-submit="c.uiAction('reopen')">
        <div class="form-group">
          <textarea required sp-autosize="true" ng-required="true" ng-model="data.reopenComments" id="reopenComments" placeholder="Comments required" class="form-control ng-pristine ng-valid ng-scope ng-empty ng-touched" aria-invalid="false" style="overflow: hidden; word-wrap: break-word; resize: horizontal;"></textarea>
  </div>
        <input class="btn btn-primary" type="submit" />
  </form>
  </div>
  </div>
</script>

Server Script

(function() {

 // Get table & sys_id
 data.table = input.table || $sp.getParameter("table");
 data.sys_id = input.sys_id || $sp.getParameter("sys_id");

 // Valid GlideRecord
 gr = new GlideRecord(data.table);
 if (!gr.isValid())
   return;

 // Valid sys_id
 if (!gr.get(data.sys_id))
   return;

  //Button Visibility
  if(data.table == 'incident' && gr.active == true && gr.incident_state != 6 && (gr.caller_id == gs.getUserID() ||  gs.hasRole("admin"))){
    data.showWidget = true;
    data.showResolve = true;
    data.showCancel = true;
    data.showStatus = true;
    data.showReopen = false;
  }
  else if(data.table == 'incident' && gr.incident_state == 6 && (gr.caller_id == gs.getUserID() ||  gs.hasRole("admin"))){
    data.showWidget = true;
    data.showResolve = false;
    data.showCancel = false;
    data.showStatus = false;
    data.showReopen = true;
  }
  else if(data.table == 'sc_req_item' && gr.active == true && (gr.request.requested_for == gs.getUserID() ||  gs.hasRole("admin"))){
    data.showWidget = true;
    data.showResolve = false;
    data.showCancel = true;
    data.showStatus = true;
    data.showReopen = false;
  }
  else {
    data.showWidget = false;
    data.showResolve = false;
    data.showCancel = false;
    data.showStatus = false;
    data.showReopen = false;
  }

 //input
 if (input && input.action) {
   var action = input.action;

   //Incident table
   if (data.table == 'incident') {
     if (action == 'resolve' && input.resolveComments !='') {
			 gr.setValue('incident_state', 6);
			 gr.setValue('state', 6);
			 gr.setValue('resolved_by', gs.getUserID());
			 gr.setValue('close_code', 'Closed/Resolved by Caller');
			 gr.setValue('close_notes', "Closed by caller with comment: " + input.resolveComments);
			 //gr.work_notes = 'Closed by caller with comment: ' + input.resolveComments;
			 gr.update();
     }
     if (action == 'status' && input.statusComments !='') {
       gr.comments.setJournalEntry("Status request by user: "+ input.statusComments);
       gr.setValue('u_request_status',gr.u_request_status++);
       gr.update();
     }
     if (action == 'cancel') {
       gr.setValue('incident_state', 8);
       gr.setValue('state', 8);
       gr.setValue('resolved_by', gs.getUserID());
			 gr.setValue('close_code','Customer Cancelled');
       gr.setValue('close_notes','Cancelled by caller with comment: '+ input.cancelComments);
			 //gr.work_notes = 'Cancelled by caller with comment: ' + input.resolveComments;
       gr.update();
     }
     if (action == 'reopen') {
       gr.setValue('incident_state', 9);
       gr.setValue('state', 9);
       gr.setDisplayValue('comments', 'Reopened by caller with comment: '+ input.reopenComments);
       gr.update();
     }
   }

   //Requested Item table
   if (data.table == 'sc_req_item' && input.cancelComments !='') {
     if (action == 'cancel') {
       var workflow = new Workflow();
       workflow.cancel(gr);
       gr.setValue('approval','withdrawn');
       gr.setValue('stage','Request Cancelled');
       gr.setValue('state','4');
       gr.setValue('active','false');
			 gr.comments.setJournalEntry("Closed by Requested For with comment: "+ input.cancelComments);
       gr.update();
     }
     if (action == 'status' && input.statusComments !='') {
       gr.comments.setJournalEntry("Status request by user: "+ input.statusComments);
       gr.update();
     }
   }

 }
})();

Client Controller

function($uibModal, $scope, spUtil) {
	var c = this;

	$scope.$on('record.updated', function(name, data) {
		c.data.cancelComments = '';
		c.data.statusComments = '';
		c.data.resolveComments = '';
		c.data.reopenComments = '';
		spUtil.update($scope);
	})

	c.uiAction = function(action) {
		c.data.action = action;
		c.server.update().then(function() {
			c.data.action = undefined;
		})
		c.modalInstance.close();
	}
	c.openModalResolve = function() {
		c.modalInstance = $uibModal.open({
			templateUrl: 'modalTemplateResolve',
			scope: $scope
		});
	}
	c.openModalCancel = function() {
		c.modalInstance = $uibModal.open({
			templateUrl: 'modalTemplateCancel',
			scope: $scope
		});
	}
	c.openModalStatus = function() {
		c.modalInstance = $uibModal.open({
			templateUrl: 'modalTemplateStatus',
			scope: $scope
		});
	}
	c.openModalReopen = function() {
		c.modalInstance = $uibModal.open({
			templateUrl: 'modalTemplateReopen',
			scope: $scope
		});
	}
	c.closeModal = function() {
		c.modalInstance.close();
	}
}

2. Drop Widget on Form

  1. Go to the Ticket Page in SP, go look at an existing incident.

  2. Ctrl-Right click and select "Page in Designer

  3. On the Widgets Sidebar drag-n-drop the "Ticket Actions" widget on the form.