Skip to content

Commit 7186965

Browse files
authored
Merge branch 'ServiceNowDevProgram:main' into user-impersonation-activity-logger
2 parents e85e26f + 087ff75 commit 7186965

File tree

18 files changed

+486
-43
lines changed

18 files changed

+486
-43
lines changed
Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,36 @@
1-
Auto-Populate Short Description (Client Script)
2-
Overview
1+
# Auto-Populate Short Description (Client Script)
2+
## Overview
33

44
This client script automatically updates the Short Description field in ServiceNow whenever a user selects a category on the form. It improves data consistency, saves time, and ensures that short descriptions remain meaningful and standardized.
55

6-
How It Works
6+
## How It Works
77

88
When a user selects a category such as Hardware, Software, or Network, the script automatically prefixes the existing short description with a corresponding label (for example, “Hardware Issue –”).
99
This makes incident records easier to identify and improves the quality of data entry.
1010

11-
Configuration Steps
11+
## Configuration Steps
1212

13-
Log in to your ServiceNow instance with admin or developer access.
14-
15-
Navigate to System Definition → Client Scripts.
16-
17-
Create a new Client Script with the following details:
13+
1. Log in to your ServiceNow instance with admin or developer access.
14+
2. Navigate to System Definition → Client Scripts.
15+
3. Create a new Client Script with the following details:
1816

17+
```
1918
Table: Incident
20-
2119
Type: onChange
22-
2320
Field name: category
24-
2521
Copy and paste the provided script into the Script field.
26-
2722
Save the record and make sure the script is active.
23+
```
2824

29-
Testing
25+
## Testing
3026

31-
Open any existing or new Incident record.
27+
1. Open any existing or new Incident record.
28+
2. Select a category such as Hardware or Software.
29+
3. The Short Description field will automatically update with the corresponding prefix.
3230

33-
Select a category such as Hardware or Software.
34-
35-
The Short Description field will automatically update with the corresponding prefix.
36-
37-
Benefits
31+
## Benefits
3832

3933
Speeds up data entry for users.
40-
4134
Maintains consistency in short descriptions.
42-
4335
Reduces manual effort and human errors.
44-
4536
Enhances clarity in incident listings and reports.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Introduction
2+
Generating a PDF using PDFGenerationAPI
3+
Calling an OOTB convertToPDFWithHeaderFooter() method to generate a PDF with your header and footer.
4+
PDFGenerationAPI – convertToPDFWithHeaderFooter(String html, String targetTable, String targetTableSysId, String pdfName, Object headerFooterInfo, String fontFamilySysId, Object documentConfiguration)
5+
6+
# Example:
7+
<img width="704" height="496" alt="image" src="https://github.com/user-attachments/assets/9d086ee3-db76-482d-ab11-4b0540a5e2fb" />
8+
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//Table: Change Request
2+
// UI action button on the change form that exports all the related incidents into a PDF format.
3+
//ServiceNows PDFGenerationAPI allows you to customize the page size, header, footer, header image, page orientation, and more.
4+
5+
var inc = new GlideRecord('incident'),
6+
incidentsList = [],
7+
v = new sn_pdfgeneratorutils.PDFGenerationAPI,
8+
html = '',
9+
hfInfo = new Object(),
10+
result;
11+
inc.addQuery('rfc', current.sys_id);
12+
inc.query();
13+
while (inc.next()) {
14+
incidentsList.push(inc.number);
15+
incidentsList.push(inc.getDisplayValue('caller_id'));
16+
incidentsList.push(inc.getDisplayValue('category'));
17+
incidentsList.push(inc.getDisplayValue('subcategory'));
18+
incidentsList.push(inc.getValue('priority'));
19+
incidentsList.push(inc.getValue('short_description'));
20+
incidentsList.push(inc.getValue('description'));
21+
incidentsList.push(inc.getDisplayValue('assignment_group'));
22+
}
23+
var json = {
24+
incidents: incidentsList.toString()
25+
};
26+
27+
JSON.stringify(json);
28+
html = '<h1 style="margin: 0in; text-align: center; line-height: 107%; break-after: avoid; font-size: 20pt; font-family: Calibri Light, sans-serif; color: #2e74b5; font-weight: normal;" align="center"><strong><span style="font-size: 16.0pt; line-height: 107%; font-family: Arial Narrow, sans-serif; color: #002060;">Incidents Related to the Change: ' + current.number + ' </span></strong></h1><br>';
29+
30+
31+
html += '<div style="width: 100%"><p><strong><span style="font-size: 11pt; font-family: Arial Arrow; color: #002060;">Incidents List</span><span style="font-size: 2pt; font-family: Palatino; color: #002060;">&nbsp;&nbsp;</span></strong></p></div>';
32+
html += '<table style="table-layout: fixed; width: 100%; border-collapse: collapse;" border="1"><tr style="text-align: center;background-color: #B4C6E7;font-size: 10pt; font-family: Arial Arrow; word-wrap: break-word;"><td><strong>Number</strong></td><td><strong>Caller</strong></td><td><strong>Category</strong></td><td><strong>Sub Category</strong></td><td><strong>Priority</strong></td><td><strong>Short Description</strong></td><td><strong>Description</strong></td><td><strong>Assignment Group</strong></td></tr>' + getIncidentsTable(json.incidents) + '</table>';
33+
hfInfo["FooterTextAlignment"] = "BOTTOM_CENTER";
34+
hfInfo["FooterText"] = "Incidents List";
35+
hfInfo["PageOrientation"] = "LANDSCAPE";
36+
hfInfo["GeneratePageNumber"] = "true";
37+
38+
result = v.convertToPDFWithHeaderFooter(html, 'change_request', current.sys_id, "Incidents Related to the Change:_" + current.number, hfInfo);
39+
action.setRedirectURL(current);
40+
41+
function getIncidentsTable(incidents) {
42+
if (incidents == '')
43+
return '';
44+
var table = '',
45+
i;
46+
incidents = incidents.split(',');
47+
for (i = 0; i < incidents.length; i += 8)
48+
table += '<tr style="text-align: center;font-size: 10pt; font-family: Arial Arrow; word-wrap: break-word;"><td>' + incidents[i] + '</td><td>' + incidents[i + 1] + '</td><td>' + incidents[i +2] + '</td><td>' + incidents[i + 3] + '</td><td>' + incidents[i + 4] + '</td><td>' + incidents[i + 5] + '</td><td>' + incidents[i + 6] + '</td><td>' + incidents[i + 7] + '</td></tr>';
49+
return table;
50+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Script Include: PercentileMetrics
2+
// Purpose: Compute percentile resolution times by group using nearest-rank selection.
3+
// Scope: global or scoped. Client callable false.
4+
5+
var PercentileMetrics = Class.create();
6+
PercentileMetrics.prototype = {
7+
initialize: function() {},
8+
9+
/**
10+
* Compute percentiles for incident resolution times by group.
11+
* @param {Object} options
12+
* - windowDays {Number} lookback window (default 30)
13+
* - groupField {String} field to group by (default 'assignment_group')
14+
* - percentiles {Array<Number>} e.g. [0.5, 0.9]
15+
* - table {String} table name (default 'incident')
16+
* @returns {Array<Object>} [{ group: <sys_id>, count: N, avgMins: X, p: { '0.5': v, '0.9': v } }]
17+
*/
18+
resolutionPercentiles: function(options) {
19+
var opts = options || {};
20+
var table = opts.table || 'incident';
21+
var groupField = opts.groupField || 'assignment_group';
22+
var windowDays = Number(opts.windowDays || 30);
23+
var pct = Array.isArray(opts.percentiles) && opts.percentiles.length ? opts.percentiles : [0.5, 0.9];
24+
25+
// Build date cutoff for resolved incidents
26+
var cutoff = new GlideDateTime();
27+
cutoff.addDaysUTC(-windowDays);
28+
29+
// First pass: find candidate groups with counts and avg
30+
var ga = new GlideAggregate(table);
31+
ga.addQuery('resolved_at', '>=', cutoff);
32+
ga.addQuery('state', '>=', 6); // resolved/closed states
33+
ga.addAggregate('COUNT');
34+
ga.addAggregate('AVG', 'calendar_duration'); // average of resolution duration
35+
ga.groupBy(groupField);
36+
ga.query();
37+
38+
var results = [];
39+
while (ga.next()) {
40+
var groupId = ga.getValue(groupField);
41+
var count = parseInt(ga.getAggregate('COUNT'), 10) || 0;
42+
if (!groupId || count === 0) continue;
43+
44+
// Second pass: ordered sample to pick percentile ranks
45+
var ordered = new GlideRecord(table);
46+
ordered.addQuery('resolved_at', '>=', cutoff);
47+
ordered.addQuery('state', '>=', '6');
48+
ordered.addQuery(groupField, groupId);
49+
ordered.addNotNullQuery('closed_at');
50+
// Approx resolution minutes using dateDiff: closed_at - opened_at in minutes
51+
ordered.addQuery('opened_at', 'ISNOTEMPTY');
52+
ordered.addQuery('closed_at', 'ISNOTEMPTY');
53+
ordered.orderBy('closed_at'); // for stability
54+
ordered.query();
55+
56+
var durations = [];
57+
while (ordered.next()) {
58+
var opened = String(ordered.getValue('opened_at'));
59+
var closed = String(ordered.getValue('closed_at'));
60+
var mins = gs.dateDiff(opened, closed, true) / 60; // seconds -> minutes
61+
durations.push(mins);
62+
}
63+
durations.sort(function(a, b) { return a - b; });
64+
65+
var pvals = {};
66+
pct.forEach(function(p) {
67+
var rank = Math.max(1, Math.ceil(p * durations.length)); // nearest-rank
68+
pvals[String(p)] = durations.length ? Math.round(durations[rank - 1]) : 0;
69+
});
70+
71+
results.push({
72+
group: groupId,
73+
count: count,
74+
avgMins: Math.round(parseFloat(ga.getAggregate('AVG', 'calendar_duration')) / 60),
75+
p: pvals
76+
});
77+
}
78+
return results;
79+
},
80+
81+
type: 'PercentileMetrics'
82+
};
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Incident resolution percentile by assignment group
2+
3+
## What this solves
4+
Leaders often ask for P50 or P90 of incident resolution time by assignment group. Out-of-box reports provide averages, but percentiles are more meaningful for skewed distributions. This utility computes configurable percentiles from incident resolution durations.
5+
6+
## Where to use
7+
- Script Include callable from Background Scripts, Scheduled Jobs, or Flow Actions
8+
- Example Background Script is included
9+
10+
## How it works
11+
- Uses `GlideAggregate` to get candidate groups with resolved incidents in a time window
12+
- For each group, queries resolved incidents ordered by resolution duration (ascending)
13+
- Picks percentile ranks (for example 0.5, 0.9) using nearest-rank method
14+
- Returns a simple object per group with count, average minutes, and requested percentiles
15+
16+
## Configure
17+
- `WINDOW_DAYS`: number of days to look back (default 30)
18+
- `GROUP_FIELD`: field to group by (default `assignment_group`)
19+
- Percentiles array (for example `[0.5, 0.9]`)
20+
21+
## References
22+
- GlideAggregate API
23+
https://www.servicenow.com/docs/bundle/zurich-api-reference/page/app-store/dev_portal/API_reference/GlideAggregate/concept/c_GlideAggregateAPI.html
24+
- GlideRecord API
25+
https://www.servicenow.com/docs/bundle/zurich-api-reference/page/app-store/dev_portal/API_reference/GlideRecord/concept/c_GlideRecordAPI.html
26+
- GlideDateTime API
27+
https://www.servicenow.com/docs/bundle/zurich-api-reference/page/app-store/dev_portal/API_reference/GlideDateTime/concept/c_GlideDateTimeAPI.html
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Background Script: example usage for PercentileMetrics
2+
(function() {
3+
var util = new PercentileMetrics();
4+
var out = util.resolutionPercentiles({
5+
windowDays: 30,
6+
groupField: 'assignment_group',
7+
percentiles: [0.5, 0.9, 0.95]
8+
});
9+
10+
out.forEach(function(r) {
11+
gs.info('Group=' + r.group + ' count=' + r.count + ' avg=' + r.avgMins + 'm P50=' + r.p['0.5'] + 'm P90=' + r.p['0.9'] + 'm P95=' + r.p['0.95'] + 'm');
12+
});
13+
})();
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Background script to check if a specific user has access to UI page
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/*
2+
Below background script can be used to analyze if user has access to UI page
3+
*/
4+
5+
var user = '5381f6fbdb5f4d14567a8e7a4896192e'; // Gets current user ID
6+
var pageId = "manage_access"; // Replace with your actual page ID
7+
8+
var spScriptable = new GlideSPScriptable();
9+
var canAccess = spScriptable.canSeePage(pageId);
10+
11+
gs.print("User " + user + " can access page '" + pageId + "': " + canAccess);

0 commit comments

Comments
 (0)