How to create a custom Reject Major Incident Candidate modal in Service Operations Workspace?
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
2 hours ago
Hi Community,
I’m working on a customization in Service Operations Workspace for the Incident table.
I need to replace/customize the Reject Major Incident Candidate action with a custom modal/popup. I do not want to modify the OOTB implementation directly; I want to create a separate custom implementation following the ServiceNow recommended UX Framework approach.
Requirement
When an Incident Analyst clicks Reject Major Incident Candidate, I want to open a custom modal containing:
Reject Reason – mandatory textarea
A simple text:
“Do you want to modify Impact & Urgency?”Impact – dropdown
Urgency – dropdown
Cancel button
Reject button
Expected behavior
When the user clicks Cancel, the modal should close and no Incident changes should be made.
When the user clicks Reject:
Update the Incident Impact with the value selected in the modal.
Update the Incident Urgency with the value selected in the modal.
Set the Major Incident state to Rejected.
Preserve/update the relevant Proposed by / Proposed fields using the existing Major Incident rejection logic.
Add the entered Reject Reason to the Incident Work notes.
Save/update the Incident.
Server-side logic
I have already created a custom Script Include using AbstractAjaxProcessor:
var RejectMajorIncidentAjax = Class.create();
RejectMajorIncidentAjax.prototype = Object.extendsObject(AbstractAjaxProcessor, {
rejectMajorIncident: function() {
var sysId = this.getParameter("sysparm_sys_id");
var workNotes = this.getParameter("sysparm_work_notes");
var impact = this.getParameter("sysparm_impact");
var urgency = this.getParameter("sysparm_urgency");
var gr = new GlideRecord("incident");
if (!gr.get(sysId))
return "ERROR";
gr.impact = impact;
gr.urgency = urgency;
var mim = new sn_major_inc_mgmt.MajorIncidentTriggerRules(gr);
mim.rejectMIC(workNotes);
return "SUCCESS";
},
type: "RejectMajorIncidentAjax"
});What I am looking for
I would like guidance on the recommended ServiceNow Workspace/UX Framework implementation for this requirement.
Specifically:
How should I create the custom modal?
Should I use a custom Macroponent + sys_modal?
How should the custom Declarative Action open the modal?
What is the recommended way to pass the current Incident sys_id to the modal?
How should the Impact, Urgency, and Reject Reason values be passed from the modal to a UX Client Script/GlideAjax call?
How should the modal be closed after successful rejection?
I found the OOTB Problem → Assess Declarative Action, which uses:
declarative_action_type = uxf_client_action
However, I have not been able to trace the referenced client_action to determine exactly how the OOTB modal is being launched in my instance.
I would prefer a custom implementation without modifying the OOTB Reject Major Incident Candidate action or other OOTB components.
Any guidance on the correct architecture and step-by-step configuration would be greatly appreciated.
Thanks!
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
58m ago
Hi @sirishanavu,
For this requirement, you do not need to create or modify a custom macroponent, sys_modal, the OOTB SOW record page, or the OOTB Reject Major Incident Candidate action.
A simpler and more upgrade-safe approach is:
Custom Form Declarative Action
→ Workspace client script
→ g_modal.showFields()
→ Client-callable Script Include
→ Refresh the Incident form
Declarative Actions allow you to add custom functionality without taking ownership of the SOW record page. The Workspace g_modal.showFields() API supports textarea, choice fields, mandatory validation, instructions, custom button labels and button styles.
1. Create the Form Declarative Action
Navigate to:
Now Experience Framework > Declarative Actions > Create New Action
Select Form and configure:
Action label: Custom Reject Major Incident Candidate
Action name: custom_reject_major_incident_candidate
Table: Incident
Implemented as: Client Script
Experience restricted: true
Required write access: true
Required role: Use the same role configured on the OOTB Reject Major Incident Candidate action
Record condition: Major incident state is Proposed
Add the action to the SOW action configuration used by your Incident form, normally SOW Actions.
Open the automatically created Form Action Layout Item and configure the required icon, order and button style.
Declarative Form Actions support client-side JavaScript and can be added to Workspace without changing the underlying page.
2. Declarative Action client script
Use the following client script:
function onClick(g_form) {
var impactChoices = [
{
displayValue: '1 - High',
value: '1'
},
{
displayValue: '2 - Medium',
value: '2'
},
{
displayValue: '3 - Low',
value: '3'
}
];
var urgencyChoices = [
{
displayValue: '1 - High',
value: '1'
},
{
displayValue: '2 - Medium',
value: '2'
},
{
displayValue: '3 - Low',
value: '3'
}
];
var fields = [
{
type: 'textarea',
name: 'reject_reason',
label: 'Reject Reason',
mandatory: true,
autoFocus: true
},
{
type: 'choice',
name: 'impact',
label: 'Impact',
value: g_form.getValue('impact'),
choices: impactChoices,
mandatory: true
},
{
type: 'choice',
name: 'urgency',
label: 'Urgency',
value: g_form.getValue('urgency'),
choices: urgencyChoices,
mandatory: true
}
];
g_modal.showFields({
title: 'Reject Major Incident Candidate',
instruction: 'Do you want to modify Impact & Urgency?',
fields: fields,
size: 'md',
cancelTitle: 'Cancel',
cancelType: 'default',
confirmTitle: 'Reject',
confirmType: 'destructive'
}).then(
function(result) {
var values = {};
var updatedFields = result.updatedFields || [];
updatedFields.forEach(function(field) {
values[field.name] = field.value;
});
if (!values.reject_reason) {
g_form.addErrorMessage(
'Reject Reason is required.'
);
return;
}
var ga = new GlideAjax('RejectMajorIncidentAjax');
ga.addParam(
'sysparm_name',
'rejectMajorIncident'
);
ga.addParam(
'sysparm_sys_id',
g_form.getUniqueValue()
);
ga.addParam(
'sysparm_reject_reason',
values.reject_reason
);
ga.addParam(
'sysparm_impact',
values.impact
);
ga.addParam(
'sysparm_urgency',
values.urgency
);
ga.getXMLAnswer(function(answer) {
var response;
try {
response = JSON.parse(answer || '{}');
} catch (e) {
g_form.addErrorMessage(
'An invalid response was received from the server.'
);
return;
}
if (!response.success) {
g_form.addErrorMessage(
response.message ||
'The Major Incident Candidate could not be rejected.'
);
return;
}
g_form.addInfoMessage(
response.message ||
'The Major Incident Candidate was rejected successfully.'
);
if (typeof g_form.reload === 'function') {
g_form.reload();
}
});
},
function() {
// Cancel was clicked.
// No server call or Incident update is performed.
}
);
return false;
}
The Incident sys_id does not need to be placed in the modal manually. It is obtained from the current record by using:
g_form.getUniqueValue()
The modal returns its values through the updatedFields array. Mandatory validation is handled by g_modal.showFields().
3. Create the client-callable Script Include
Create the following Script Include in the same application scope as the Declarative Action.
Name: RejectMajorIncidentAjax
Client callable: true
Accessible from: All application scopes, only when cross-scope access is required
var RejectMajorIncidentAjax = Class.create();
RejectMajorIncidentAjax.prototype =
Object.extendsObject(AbstractAjaxProcessor, {
rejectMajorIncident: function() {
var response = {
success: false,
message: ''
};
try {
var sysId = String(
this.getParameter('sysparm_sys_id') || ''
);
var rejectReason = String(
this.getParameter('sysparm_reject_reason') || ''
).trim();
var impact = String(
this.getParameter('sysparm_impact') || ''
);
var urgency = String(
this.getParameter('sysparm_urgency') || ''
);
if (!gs.hasRole('major_incident_manager') &&
!gs.hasRole('admin')) {
response.message =
'You are not authorized to reject a Major Incident Candidate.';
return JSON.stringify(response);
}
if (!sysId.match(/^[0-9a-f]{32}$/)) {
response.message =
'A valid Incident sys_id was not provided.';
return JSON.stringify(response);
}
if (!rejectReason) {
response.message =
'Reject Reason is mandatory.';
return JSON.stringify(response);
}
var allowedValues = {
'1': true,
'2': true,
'3': true
};
if (!allowedValues[impact]) {
response.message =
'The selected Impact value is invalid.';
return JSON.stringify(response);
}
if (!allowedValues[urgency]) {
response.message =
'The selected Urgency value is invalid.';
return JSON.stringify(response);
}
var incidentGR =
new GlideRecordSecure('incident');
if (!incidentGR.get(sysId)) {
response.message =
'The Incident could not be found or accessed.';
return JSON.stringify(response);
}
if (!incidentGR.canWrite()) {
response.message =
'You do not have permission to update this Incident.';
return JSON.stringify(response);
}
if (!incidentGR.impact.canWrite() ||
!incidentGR.urgency.canWrite()) {
response.message =
'You do not have permission to update Impact or Urgency.';
return JSON.stringify(response);
}
var mimRules =
new sn_major_inc_mgmt
.MajorIncidentTriggerRules();
var mimStates =
mimRules.MAJOR_INCIDENT_STATE;
if (incidentGR.getValue(
'major_incident_state'
) != mimStates.PROPOSED) {
response.message =
'This Incident is no longer a proposed Major Incident Candidate.';
return JSON.stringify(response);
}
incidentGR.setValue(
'impact',
impact
);
incidentGR.setValue(
'urgency',
urgency
);
incidentGR.setValue(
'work_notes',
rejectReason
);
incidentGR.setValue(
'major_incident_state',
mimStates.REJECTED
);
var updatedSysId = incidentGR.update();
if (!updatedSysId) {
response.message =
'The Incident update failed.';
return JSON.stringify(response);
}
response.success = true;
response.message =
'Major Incident Candidate ' +
incidentGR.getDisplayValue() +
' was rejected successfully.';
return JSON.stringify(response);
} catch (ex) {
gs.error(
'[RejectMajorIncidentAjax] ' +
ex.message
);
response.message =
'An unexpected error occurred while rejecting the Major Incident Candidate.';
return JSON.stringify(response);
}
},
type: 'RejectMajorIncidentAjax'
});
4. Important implementation note
Before using the final server-side update block, compare it with the server-side portion of the OOTB Reject Major Incident Candidate action in the same instance.
Some releases directly set the Major Incident state to Rejected and update the Incident. Other releases may call an internal Major Incident Management helper.
If the OOTB action in your release uses:
var mim =
new sn_major_inc_mgmt
.MajorIncidentTriggerRules(incidentGR);
mim.rejectMIC(rejectReason);
replace only this block:
incidentGR.setValue(
'work_notes',
rejectReason
);
incidentGR.setValue(
'major_incident_state',
mimStates.REJECTED
);
var updatedSysId = incidentGR.update();
with the OOTB helper call verified in your instance. Do not assume an undocumented helper method is identical across releases.
The proposed-by and proposed-date fields should not be manually cleared or overwritten. The normal Incident state must also remain unchanged. OOTB rejection changes the Major Incident state to Rejected while leaving the standard Incident state unchanged.
Expected result
When Cancel is selected:
No GlideAjax call is made.
The modal closes.
No Incident fields are changed.
When Reject is selected:
Reject Reason is validated as mandatory.
Impact and Urgency are validated.
The current Incident sys_id is passed securely to the server.
The server revalidates role, ACL access and Major Incident state.
Impact and Urgency are updated.
The Reject Reason is added to Work notes.
The Major Incident state is changed to Rejected.
The standard Incident state remains unchanged.
The record is refreshed in Service Operations Workspace.
For this field-based requirement, g_modal.showFields() is significantly simpler than creating a custom macroponent, page route, UX add-on event mapping and sys_modal configuration. A custom UI Builder modal should be used only when complex layouts, conditional sections, custom components or asynchronous validation inside the open modal are required.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
28m ago
Hi @sirishanavu ,
If you want to do this by Declarative action of type UXF Client Action.
Follow this link Easy way to deal with modals in Workspace
The above article provides a step-by-step guide on creating Declarative Actions and opening a modal when the Declarative Action is clicked. It also explains an easier approach to creating modals using Views instead of Macroponents, making the process more accessible for developers who are not familiar with UI Builder.
If my response helped, mark it as helpful and accept the solution.
Thanks,
Dinesh