- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
03-24-2025 10:08 AM
Hi everyone,
I’m working with ServiceNow’s Customer Service Management (CSM) portal and would like to enable end users to escalate their own cases directly from the portal. Currently, I understand that cases are typically escalated by support agents, but I want to provide a feature where the end users themselves can request an escalation if they feel their issue requires more urgent attention.
Can someone provide guidance on the best way to do this? Specifically:
How can I add an escalation button or option for end users on the case page?
What configurations or customizations are needed (e.g., UI Actions, Business Rules)?
Are there any best practices for handling notifications and case reassignment when an escalation occurs?
Thanks in advance for any advice or examples you can share!
Solved! Go to Solution.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
03-25-2025 07:12 AM - edited 03-25-2025 07:28 AM
Hi @Tushar280070941 ,
To enable the escalation process for end users, follow these steps:
1. Add an "Escalate" button to the standard ticket page. If a user wants to escalate a case, they can navigate to "My Lists," where they will see all the cases they have requested. Once they open a case, they will find the "Escalate" button under actions.
After the button is clicked a popup is displayed that takes input from the user such as escalation justification and this is mapped to the escalation record created on the backend.
2. Navigate to All -> Widgets -> Case Ticket Action Widget. Clone the widget
3. In the related list of "Case Ticket Action", open the Angular ng templates. Replace the widget with the cloned one or create new angular ng templates with a different name and give the widget as cloned widget.
4. Now, In cloned widget add the following HTML code under div tag of Close Case
<div class="question">
<button type="button" name="escalate" class="btn btn-primary btn-question" ng-click="c.escalateCase()">${Escalate Case}</button>
</div>
5. In Client controller of widget replace the whole code with the below code:
function($scope, spUtil, $uibModal, $location) {
var c = this;
$scope.$on('record.updated', function(name, data) {
for (i = 0; i < data.changes.length; i++){
var field = data.changes[i];
if (field == "state") {
c.data.state = data.record.state.value;
}
}
});
c.createCase = function() {
$location.url(c.data.caseFromProjectEntityHref || '');
};
c.modalMessage = c.data.confirmDeleteMessage;
c.close = function(){
c.showModal('close');
};
c.rejectSolution = function(){
if(c.data.options.mandate_comment_for_reject_solution == "true"){
c.rejectResolutionNote = '';
c.rejectSolutionText = c.data.rejectSolutionText;
c.showModal('reject_solution');
}
else
c.action(c.data.table, c.data.sys_id, '10','reject');
};
c.escalateCase = function() {
c.escalationJustification = '';
c.showModal('escalate');
};
c.submitEscalation = function() {
c.data.action = 'escalateCase';
c.data.sys_id = c.data.sys_id;
c.data.justification = c.escalationJustification;
c.server.update().then(function(response) {
c.cancel();
var activityEl = document.getElementById("Activity");
if(activityEl)
activityEl.focus();
});
};
var instance;
c.showModal = function (type) {
var templateUrl = '';
if(type == 'close')
templateUrl = 'case-close-template.html';
else if(type == 'reject_solution')
templateUrl = 'case-reject-solution-template.html';
else if(type == 'escalate')
templateUrl = 'case-escalate-template.html';
var size = 'md';
try{
var options = {
size: size,
scope: $scope,
backdrop: 'static',
templateUrl: templateUrl
};
instance = $uibModal.open(options);
instance.rendered.then(function() {
var modal = $('div.modal');
modal.attr('aria-labelledby','modal-title');
});
}
catch(err){
console.log(err);
}
};
c.cancel = function () {
if(instance){
instance.dismiss('cancel');
var activityEl = document.getElementById("Activity");
if(activityEl)
activityEl.focus();
}
};
c.action = function(table, id, state, type, comments) {
if($scope.data.state == '1')
c.data.redirect = false;
else
c.data.redirect = true;
c.data.action = 'update';
c.data.state = state;
c.data.sys_id = id;
c.data.table = table;
c.data.type = type;
c.data.comments = comments;
c.server.update().then(function(response) {
if(state==3 && $scope.data.url && c.data.redirect){
window.location = $scope.data.url;
}
else
c.cancel();
var activityEl = document.getElementById("Activity");
if(activityEl)
activityEl.focus();
});
};
c.markTaskComplete = function(id) {
c.data.action = 'markTaskComplete';
c.server.update().then(function(response) {
window.location.reload();
});
};
c.markCaseTaskComplete = function(id){
c.data.action = 'markCaseTaskComplete';
c.server.update().then(function(response){
window.location.reload();
});
};
c.assignCaseTaskToMe = function(id){
c.data.action = 'assignCaseTaskToMe';
c.server.update().then(function(response){
window.location.reload();
});
};
var modal = null;
c.launchCallbackModal = function(e){
var callback_form;
var default_values = {};
var pageRoot = angular.element('.sp-page-root');
modal = $uibModal.open(
{
title : "Request_CallBack",
templateUrl : 'callback-rp-popup.html',
size : 'lg',
keyboard: true,
backdrop: 'static',
scope:$scope,
controller: function($scope) {
$scope.$on("modal.closing", function() {
setTimeout(function() {
c.callbackSuccess = false;
c.callbackSuccessMsg = "";
});
var fieldNames = callback_form.getFieldNames();
for(var i=0; i < fieldNames.length; i++){
callback_form.setValue(fieldNames[i], default_values[fieldNames[i]])
}
pageRoot.attr('aria-hidden', 'false');
if ($("#child-case-tabs #actionList").is(":hidden")) {
$('#child-case-tabs button').dropdown('toggle');
$('#requestCallback').focus();
}
});
}
});
modal.rendered.then(function() {
pageRoot.attr('aria-hidden', 'true');
});
$scope.$on("$sp.sc_cat_item.submitted", function(event, data){
var callback_type = callback_form.getValue('callback_type');
if(data.sys_id){
c.callbackSuccess = true;
c.callbackSuccessMsg = c.data.callbackSuccessMsgs[callback_type];
if(callback_type == 'scheduled'){
var scheduled_start_time = JSON.parse(callback_form.getValue('appointment_time')).actualStart;
c.callbackSuccessMsg = spUtil.format(c.data.callbackSuccessMsgs[callback_type], [scheduled_start_time.replace(" ", " at ")]);
}
c.server.update();
}
});
$scope.$on('spModel.gForm.initialized', function(e, g_form) {
g_form.setValue('parent', c.data.sys_id);
var fieldNames = g_form.getFieldNames();
if(Object.keys(default_values).length === 0){
for(var i=0; i < fieldNames.length; i++){
default_values[fieldNames[i]] = g_form.getValue(fieldNames[i]);
}
}
callback_form = g_form;
var modalSelector = document.querySelector('#cb_popup');
var firstFocusableElement = modalSelector.querySelector("#close-btn");
modalSelector.addEventListener('keydown', function(e) {
var submitBtn = modalSelector.querySelector("#submit-btn");
var doneBtn = modalSelector.querySelector("#done-btn");
var lastFocusableElement = submitBtn || doneBtn;
var isTabPressed = e.key === 'Tab' || e.keyCode === 9;
if (!isTabPressed)
return;
if (e.shiftKey) {
if (e.target.id === firstFocusableElement.id) {
lastFocusableElement.focus();
e.preventDefault();
}
} else {
if (e.target.id === lastFocusableElement.id) {
firstFocusableElement.focus();
e.preventDefault();
}
}
});
});
e.stopPropagation();
}
$scope.$on("sp.update.breadcrumbs", function(e, list) {
e.stopPropagation();
});
c.cancelRP = function () {
if(modal){
modal.dismiss('cancel');
}
if(c.callbackSuccess){
var currentParams = $location.search();
currentParams['show_callback_tab'] = 'true';
$location.search(currentParams);
}
};
}
5. In the server-side script, add the code below code under existing if condition that checks for input.action === 'update'.
else if (input.action === "escalateCase") {
gs.info("escalation: " + input.sys_id);
var escalation = new GlideRecord("sn_customerservice_escalation");
escalation.initialize();
escalation.source_record = input.sys_id; //mapping the mandatory fields required for escalation
escalation.escalation_justification = input.justification;
escalation.request_source = 0;
escalation.reason = 1;
escalation.type = '08cdf0ca87500300fe4433d4c6cb0b89';
var escalationId = escalation.insert();
if (escalationId) {
showInfoMessage(gs.getMessage("Case has been escalated successfully"));
} else {
gs.addErrorMessage(gs.getMessage("Failed to escalate case"));
}
6. Create a new angular ng template as shown below and map it to the cloned widget. If you change name of any angular template make sure you change in client controller code also.
7. Navigate to All -> Standard Ticket Configuration. Open the record with table "sn_customerservice_case"
Change the widget you have cloned as shown below
8. Send a notification to the Assigned To if populated; otherwise, notify the Assignment Group when a case is escalated by a customer.
Thanks,
Likitha
If my response helped please mark it correct and close the thread so that it benefits future readers.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
03-25-2025 07:12 AM - edited 03-25-2025 07:28 AM
Hi @Tushar280070941 ,
To enable the escalation process for end users, follow these steps:
1. Add an "Escalate" button to the standard ticket page. If a user wants to escalate a case, they can navigate to "My Lists," where they will see all the cases they have requested. Once they open a case, they will find the "Escalate" button under actions.
After the button is clicked a popup is displayed that takes input from the user such as escalation justification and this is mapped to the escalation record created on the backend.
2. Navigate to All -> Widgets -> Case Ticket Action Widget. Clone the widget
3. In the related list of "Case Ticket Action", open the Angular ng templates. Replace the widget with the cloned one or create new angular ng templates with a different name and give the widget as cloned widget.
4. Now, In cloned widget add the following HTML code under div tag of Close Case
<div class="question">
<button type="button" name="escalate" class="btn btn-primary btn-question" ng-click="c.escalateCase()">${Escalate Case}</button>
</div>
5. In Client controller of widget replace the whole code with the below code:
function($scope, spUtil, $uibModal, $location) {
var c = this;
$scope.$on('record.updated', function(name, data) {
for (i = 0; i < data.changes.length; i++){
var field = data.changes[i];
if (field == "state") {
c.data.state = data.record.state.value;
}
}
});
c.createCase = function() {
$location.url(c.data.caseFromProjectEntityHref || '');
};
c.modalMessage = c.data.confirmDeleteMessage;
c.close = function(){
c.showModal('close');
};
c.rejectSolution = function(){
if(c.data.options.mandate_comment_for_reject_solution == "true"){
c.rejectResolutionNote = '';
c.rejectSolutionText = c.data.rejectSolutionText;
c.showModal('reject_solution');
}
else
c.action(c.data.table, c.data.sys_id, '10','reject');
};
c.escalateCase = function() {
c.escalationJustification = '';
c.showModal('escalate');
};
c.submitEscalation = function() {
c.data.action = 'escalateCase';
c.data.sys_id = c.data.sys_id;
c.data.justification = c.escalationJustification;
c.server.update().then(function(response) {
c.cancel();
var activityEl = document.getElementById("Activity");
if(activityEl)
activityEl.focus();
});
};
var instance;
c.showModal = function (type) {
var templateUrl = '';
if(type == 'close')
templateUrl = 'case-close-template.html';
else if(type == 'reject_solution')
templateUrl = 'case-reject-solution-template.html';
else if(type == 'escalate')
templateUrl = 'case-escalate-template.html';
var size = 'md';
try{
var options = {
size: size,
scope: $scope,
backdrop: 'static',
templateUrl: templateUrl
};
instance = $uibModal.open(options);
instance.rendered.then(function() {
var modal = $('div.modal');
modal.attr('aria-labelledby','modal-title');
});
}
catch(err){
console.log(err);
}
};
c.cancel = function () {
if(instance){
instance.dismiss('cancel');
var activityEl = document.getElementById("Activity");
if(activityEl)
activityEl.focus();
}
};
c.action = function(table, id, state, type, comments) {
if($scope.data.state == '1')
c.data.redirect = false;
else
c.data.redirect = true;
c.data.action = 'update';
c.data.state = state;
c.data.sys_id = id;
c.data.table = table;
c.data.type = type;
c.data.comments = comments;
c.server.update().then(function(response) {
if(state==3 && $scope.data.url && c.data.redirect){
window.location = $scope.data.url;
}
else
c.cancel();
var activityEl = document.getElementById("Activity");
if(activityEl)
activityEl.focus();
});
};
c.markTaskComplete = function(id) {
c.data.action = 'markTaskComplete';
c.server.update().then(function(response) {
window.location.reload();
});
};
c.markCaseTaskComplete = function(id){
c.data.action = 'markCaseTaskComplete';
c.server.update().then(function(response){
window.location.reload();
});
};
c.assignCaseTaskToMe = function(id){
c.data.action = 'assignCaseTaskToMe';
c.server.update().then(function(response){
window.location.reload();
});
};
var modal = null;
c.launchCallbackModal = function(e){
var callback_form;
var default_values = {};
var pageRoot = angular.element('.sp-page-root');
modal = $uibModal.open(
{
title : "Request_CallBack",
templateUrl : 'callback-rp-popup.html',
size : 'lg',
keyboard: true,
backdrop: 'static',
scope:$scope,
controller: function($scope) {
$scope.$on("modal.closing", function() {
setTimeout(function() {
c.callbackSuccess = false;
c.callbackSuccessMsg = "";
});
var fieldNames = callback_form.getFieldNames();
for(var i=0; i < fieldNames.length; i++){
callback_form.setValue(fieldNames[i], default_values[fieldNames[i]])
}
pageRoot.attr('aria-hidden', 'false');
if ($("#child-case-tabs #actionList").is(":hidden")) {
$('#child-case-tabs button').dropdown('toggle');
$('#requestCallback').focus();
}
});
}
});
modal.rendered.then(function() {
pageRoot.attr('aria-hidden', 'true');
});
$scope.$on("$sp.sc_cat_item.submitted", function(event, data){
var callback_type = callback_form.getValue('callback_type');
if(data.sys_id){
c.callbackSuccess = true;
c.callbackSuccessMsg = c.data.callbackSuccessMsgs[callback_type];
if(callback_type == 'scheduled'){
var scheduled_start_time = JSON.parse(callback_form.getValue('appointment_time')).actualStart;
c.callbackSuccessMsg = spUtil.format(c.data.callbackSuccessMsgs[callback_type], [scheduled_start_time.replace(" ", " at ")]);
}
c.server.update();
}
});
$scope.$on('spModel.gForm.initialized', function(e, g_form) {
g_form.setValue('parent', c.data.sys_id);
var fieldNames = g_form.getFieldNames();
if(Object.keys(default_values).length === 0){
for(var i=0; i < fieldNames.length; i++){
default_values[fieldNames[i]] = g_form.getValue(fieldNames[i]);
}
}
callback_form = g_form;
var modalSelector = document.querySelector('#cb_popup');
var firstFocusableElement = modalSelector.querySelector("#close-btn");
modalSelector.addEventListener('keydown', function(e) {
var submitBtn = modalSelector.querySelector("#submit-btn");
var doneBtn = modalSelector.querySelector("#done-btn");
var lastFocusableElement = submitBtn || doneBtn;
var isTabPressed = e.key === 'Tab' || e.keyCode === 9;
if (!isTabPressed)
return;
if (e.shiftKey) {
if (e.target.id === firstFocusableElement.id) {
lastFocusableElement.focus();
e.preventDefault();
}
} else {
if (e.target.id === lastFocusableElement.id) {
firstFocusableElement.focus();
e.preventDefault();
}
}
});
});
e.stopPropagation();
}
$scope.$on("sp.update.breadcrumbs", function(e, list) {
e.stopPropagation();
});
c.cancelRP = function () {
if(modal){
modal.dismiss('cancel');
}
if(c.callbackSuccess){
var currentParams = $location.search();
currentParams['show_callback_tab'] = 'true';
$location.search(currentParams);
}
};
}
5. In the server-side script, add the code below code under existing if condition that checks for input.action === 'update'.
else if (input.action === "escalateCase") {
gs.info("escalation: " + input.sys_id);
var escalation = new GlideRecord("sn_customerservice_escalation");
escalation.initialize();
escalation.source_record = input.sys_id; //mapping the mandatory fields required for escalation
escalation.escalation_justification = input.justification;
escalation.request_source = 0;
escalation.reason = 1;
escalation.type = '08cdf0ca87500300fe4433d4c6cb0b89';
var escalationId = escalation.insert();
if (escalationId) {
showInfoMessage(gs.getMessage("Case has been escalated successfully"));
} else {
gs.addErrorMessage(gs.getMessage("Failed to escalate case"));
}
6. Create a new angular ng template as shown below and map it to the cloned widget. If you change name of any angular template make sure you change in client controller code also.
7. Navigate to All -> Standard Ticket Configuration. Open the record with table "sn_customerservice_case"
Change the widget you have cloned as shown below
8. Send a notification to the Assigned To if populated; otherwise, notify the Assignment Group when a case is escalated by a customer.
Thanks,
Likitha
If my response helped please mark it correct and close the thread so that it benefits future readers.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
04-16-2025 06:54 PM - edited 04-16-2025 06:56 PM
@Likitha PatelThank you so much for the above. It worked like a charm.
I found one issue or a line of code missing in Server Side script which is
Without the source_table line - reference case record is not being set on the Escalation record created.
I also changed the HTML code a little so that Escalate Case button appears under Actions as an option. Above code shows the Escalate Case as a separate button below Actions Drop down.