Skip to content

Commit 4e4b134

Browse files
Merge branch 'main' into CopyBulkIDs
2 parents 676a993 + ce8ea14 commit 4e4b134

File tree

82 files changed

+3248
-141
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

82 files changed

+3248
-141
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
**Set and Lock Variable by Group**
2+
3+
This solution provides a secure and dynamic way to control data entry on a Service Catalog form based on the user's group membership. It is typically used to pre-fill and lock certain justification or approval bypass fields for authorized users (like managers or executive staff), improving their efficiency while maintaining an accurate audit trail.
4+
5+
This functionality requires a combined Client-side (Catalog Client Script) and Server-side (Script Include) approach to ensure the group check is done securely.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// onload Catalog Client Script with Catalog Name
2+
function onLoad() {
3+
var variableName = 'bypass_approval_reason';
4+
var targetGroupName = 'ServiceNow Support'; // The group authorized to skip this step
5+
var ga = new GlideAjax('UserUtils');
6+
ga.addParam('sysparm_name', 'isMemberOf');
7+
ga.addParam('sysparm_group_name', targetGroupName);
8+
ga.getXMLAnswer(checkAndLockVariable);
9+
function checkAndLockVariable(response) {
10+
var isMember = response;
11+
if (isMember == 'true') {
12+
var message = 'Value set and locked due to your ' + targetGroupName + ' membership.';
13+
var setValue = 'Bypassed by authorized ' + targetGroupName + ' member.';
14+
g_form.setValue(variableName, setValue);
15+
g_form.setReadOnly(variableName, true);
16+
g_form.showFieldMsg(variableName, message, 'info');
17+
} else {
18+
g_form.setReadOnly(variableName, false);
19+
}
20+
}
21+
}
22+
23+
//Script Include
24+
var UserUtils = Class.create();
25+
UserUtils.prototype = Object.extendsObject(AbstractAjaxProcessor, {
26+
isMemberOf: function() {
27+
var groupName = this.getParameter('sysparm_group_name');
28+
var isMember = gs.getUser().isMemberOf(groupName);
29+
return isMember.toString();
30+
},
31+
32+
type: 'UserUtils'
33+
});
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
**Dynamic Reference Qualifier with Filtering**
2+
This Client Script provides a solution for dynamically updating the available options in a Reference Field based on the value selected in another field on the same form.
3+
4+
This technique is essential for ensuring data quality and improving the user experience (UX).
5+
6+
A typical use case is filtering the Assignment Group field to show only groups relevant to the selected Service, Category, or Location.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
2+
if (isLoading || newValue === '') {
3+
return;
4+
}
5+
var controlledField = 'assignment_group';
6+
var controllingField = 'u_service';
7+
var serviceSysId = g_form.getValue(controllingField);
8+
var encodedQuery = 'typeLIKEITIL^u_related_service=' + serviceSysId;
9+
g_form.setQuery(controlledField, encodedQuery);
10+
var currentGroupSysId = g_form.getValue(controlledField);
11+
if (currentGroupSysId && oldValue !== '' && currentGroupSysId !== '') {
12+
g_form.setValue(controlledField, '');
13+
}
14+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
**Client script Deatils**
2+
1. Table: sys_script
3+
2. Type: onSubmit
4+
3. UI Type: Desktop
5+
6+
**Benefits**
7+
1. This client script will prevent admin users to do insert/update operation in onBefore business rules.
8+
2. It will help to avoid HealthScan findings thus increasing healthscan score.
9+
3. Will prevent recursive calls.
10+
11+
Link to ServiceNow Business Rules best Practise : https://developer.servicenow.com/dev.do#!/guide/orlando/now-platform/tpb-guide/business_rules_technical_best_practices
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
function onSubmit() {
2+
/*
3+
This client script will prevent insert and update operation in onBefore business rules.
4+
Table: sys_script
5+
Type: onSubmit
6+
Ui Type: Desktop
7+
*/
8+
var whenCond = g_form.getValue('when'); // when condition of business rule
9+
var scriptVal = g_form.getValue('script'); // script value of business rule.
10+
11+
if (whenCond == 'before' && (scriptVal.indexOf('insert()') > -1 || scriptVal.indexOf('update()')) > -1) {
12+
alert("As per ServiceNow best Practise insert and update should be avoided in onBefore BRs. If you still want tp proceed try deactivating client script : " + g_form.getUniqueValue());
13+
return false;
14+
}
15+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# UI Action to kill flow timers
2+
3+
UI page that shows you all timers that a flow context [sys_flow_context] is waiting for. Shows sys_id of the sys_trigger, the datetime when the wait will finish and how long until that time. Select the checkbox and submit the modal form to kill that timer.
4+
5+
## How to use
6+
7+
1. Create ui action on [sys_flow_context] and input the script from *UI action.js* in the script field. Check client and set Onclick as *openTimerDialog()*
8+
2. Create ui page with name *timer_kill_dialog* and copy the HTML, client script and processing script from the *UI page for ui action.js* file
9+
3. Navigate to active flow context that is waiting for timers and click the ui action from step 1 and kill timer/s. Flow will progress as soon as the flow engine processes the flow.fire events
10+
11+
![image](timer.png)
12+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
//Onlick: openTimerDialog()
2+
3+
//open dialog using glidemodal and timer_kill_dialog ui page, pass in current sys_flow_context sysid
4+
function openTimerDialog() {
5+
var dialog = new GlideModal("timer_kill_dialog");
6+
dialog.setTitle("Kill timers");
7+
dialog.setPreference('sysparm_id', g_form.getUniqueValue());
8+
dialog.render();
9+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
//name: timer_kill_dialog
2+
3+
//HTML:
4+
<?xml version="1.0" encoding="utf-8" ?>
5+
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">
6+
<g:evaluate var="jvar_timers" object="true">
7+
// executed server side, gets wait jobs for current flow context and returns object with information about timers
8+
var array = [];
9+
var waitJob = new GlideRecord("sys_trigger");
10+
waitJob.addQuery("state", "0")
11+
.addCondition("name", "flow.fire")
12+
.addCondition("script", "CONTAINS", RP.getParameterValue("sysparm_id"));
13+
waitJob.query();
14+
while (waitJob.next()) {
15+
var obj = {};
16+
obj.sys_id = waitJob.getUniqueValue();
17+
obj.waitUntil = waitJob.getValue("next_action");
18+
var now = new GlideDateTime()
19+
obj.timeLeft = GlideDateTime.subtract(now, new GlideDateTime(waitJob.getValue("next_action"))).getDisplayValue()
20+
array.push(obj)
21+
}
22+
array;
23+
</g:evaluate>
24+
25+
<style>
26+
table td {
27+
padding: 8px;
28+
}
29+
30+
table thead td {
31+
font-weight: bold;
32+
background-color: #f0f0f0;
33+
}
34+
</style>
35+
36+
<!-- submittable form that shows the wait jobs in [sys_trigger] for context and a checkbox to select timers to kill -->
37+
<g:ui_form>
38+
<input type="hidden" id="sysids" name="sysids" value="" />
39+
<div style="padding: 20px; font-family: sans-serif;">
40+
<table>
41+
<tbody>
42+
<thead>
43+
<td>timer sys_id</td>
44+
<td>next action</td>
45+
<td>time left</td>
46+
<td>kill?</td>
47+
</thead>
48+
<j:forEach var="jvar_timer" items="${jvar_timers}">
49+
<tr>
50+
<td>
51+
${jvar_timer.sys_id}
52+
</td>
53+
<td>
54+
${jvar_timer.waitUntil}
55+
</td>
56+
<td>
57+
${jvar_timer.timeLeft}
58+
</td>
59+
<td>
60+
<g:ui_checkbox name="${jvar_timer.sys_id}">
61+
</g:ui_checkbox>
62+
</td>
63+
</tr>
64+
</j:forEach>
65+
</tbody>
66+
</table>
67+
<g:dialog_buttons_ok_cancel ok="return okDialog()" />
68+
</div>
69+
</g:ui_form>
70+
</j:jelly>
71+
72+
//Client script:
73+
// handler for clicking ok on modal. gathers the sys_ids for the timers that have checked checkbox
74+
function okDialog() {
75+
var c = gel('sysids');
76+
var sysids = [];
77+
$j('input[type="checkbox"]:checked').each(function () {
78+
var checkboxId = $j(this).attr('id').replace("ni.", "");
79+
sysids.push(checkboxId);
80+
});
81+
c.value = sysids.toString();
82+
return true;
83+
}
84+
85+
//Processing script:
86+
// queries for timer jobs and sets the job and new flow.fire event to process instantly -> timer on flow completes
87+
var waitJob = new GlideRecord("sys_trigger");
88+
waitJob.addQuery("sys_id", "IN", sysids);
89+
waitJob.query();
90+
while (waitJob.next()) {
91+
var currentScript = waitJob.getValue("script");
92+
var now = new GlideDateTime().getValue();
93+
var replaceScript = currentScript.replace(/gr\.setValue\('process_on',\s*'[^']*'\)/, "gr.setValue('process_on','" + now + "')");
94+
waitJob.setValue("script", replaceScript);
95+
waitJob.setValue("next_action", now);
96+
waitJob.update();
97+
}
98+
//redirect back to bottom of nav stack
99+
var urlOnStack = GlideSession.get().getStack().bottom();
100+
response.sendRedirect(urlOnStack);
44 KB
Loading

0 commit comments

Comments
 (0)