Skip to content

Commit 4c9d36c

Browse files
authored
Merge branch 'ServiceNowDevProgram:main' into fxhack
2 parents 0d20b8a + fa447a4 commit 4c9d36c

File tree

29 files changed

+648
-114
lines changed

29 files changed

+648
-114
lines changed

CONTRIBUTING.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,25 @@ If you plan to submit another pull request while your original is still pending,
3131
- **Descriptive Pull Request Titles**: Your pull request must have explicit and descriptive titles that accurately represent the changes made.
3232
- **Scope Adherence**: Changes that fall outside the described scope will result in the entire pull request being rejected.
3333
- **Quality Over Quantity**: Low-effort or spam pull requests will be marked accordingly.
34-
- **Expanded Snippets**: Code snippets reused from the [ServiceNow Documentation](https://docs.servicenow.com/) or [API References](https://developer.servicenow.com/dev.do#!/reference/) are acceptable only if they are expanded in a meaningful way (e.g., with additional context, documentation, or variations). Remember: *QUANTITY IS FUN, QUALITY IS KEY.*
34+
- **Expanded Snippets**: Code snippets reused from the [ServiceNow Documentation](https://docs.servicenow.com/) or [API References](https://developer.servicenow.com/dev.do#!/reference/) are acceptable only if they are expanded in a meaningful way (e.g., with additional context, documentation, or variations). Remember: *"QUANTITY IS FUN, QUALITY IS KEY."*
3535
- **Relevance**: Code should be relevant to ServiceNow Developers.
3636
- **ES2021 Compatibility**: While ES2021 is allowed, we encourage you to disclose if your code is using ES2021 features, as not everyone may be working with ES2021-enabled applications.
3737

38+
## Core Documentation File Changes
39+
40+
**IMPORTANT**: For changes to core documentation files (README.md, CONTRIBUTING.md, LICENSE, etc.), contributors must:
41+
42+
1. **Submit an Issue First**: Before making any changes to core documentation files, create an issue describing:
43+
- What you intend to edit
44+
- Why the change is needed
45+
- Your proposed approach
46+
47+
2. **Get Assignment**: Wait to be assigned to the issue by a maintainer before submitting a PR.
48+
49+
3. **Reference the Issue**: Include the issue number in your PR title and description.
50+
51+
This process helps prevent merge conflicts when multiple contributors want to update the same documentation files and ensures all changes align with the project's direction.
52+
3853
## Repository Structure
3954

4055
**IMPORTANT**: The repository has been reorganized into major categories. All new contributions MUST follow this structure for PR approval.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
## Overview
2+
This script retrieves incidents that were opened more than X days ago using **GlideDateTime** and **GlideRecord**.
3+
Useful for reporting, escalations, notifications, and cleanup tasks.
4+
5+
## Table and Field
6+
- **Table:** `incident`
7+
- **Field:** `opened_at`
8+
9+
## Parameters
10+
- **X (number of days):** Defines the threshold for old incidents (e.g., 30 days).
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
(function() {
2+
var days = 30; // Change this to your required number of days
3+
4+
// Calculate the date X days ago
5+
var cutoffDate = new GlideDateTime();
6+
cutoffDate.addDaysUTC(-days);
7+
8+
// Query incidents opened before the cutoff date
9+
var gr = new GlideRecord('incident');
10+
gr.addQuery('opened_at', '<', cutoffDate);
11+
gr.query();
12+
13+
gs.info('Incidents opened more than ' + days + ' days ago:');
14+
15+
while (gr.next()) {
16+
gs.info('Incident Number: ' + gr.number + ', Opened At: ' + gr.opened_at.getDisplayValue());
17+
}
18+
})();
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Querying JSON with JSONPath to extract values
2+
3+
GlideJsonPath is a class which can be used to use JSONPath in ServiceNow. It can be useful when working with JSON Payloads, especially highly nested or in general complex structures.
4+
5+
References:
6+
- [RFC 9535: JSONPath: Query Expressions for JSON](https://datatracker.ietf.org/doc/rfc9535/)
7+
- [Play with JSONPath outside of ServiceNow](https://jsonpath.com/)
8+
- [Good Examples to start with](https://restfulapi.net/json-jsonpath/)
9+
- [ServiceNow API Documentation](https://www.servicenow.com/docs/bundle/zurich-api-reference/page/app-store/dev_portal/API_reference/GlideJsonPath/concept/GlideJsonPathAPI.html)
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Run in background script
2+
var json = {
3+
"store":
4+
{
5+
"book": [
6+
{
7+
"category": "reference",
8+
"author": "Nigel Rees",
9+
"title": "Sayings of the Century",
10+
"price": 8.95
11+
},
12+
{
13+
"category": "fiction",
14+
"author": "Evelyn Waugh",
15+
"title": "Sword of Honour",
16+
"price": 12.99
17+
},
18+
{
19+
"category": "fiction",
20+
"author": "Herman Melville",
21+
"title": "Moby Dick",
22+
"isbn": "0-553-21311-3",
23+
"price": 8.99
24+
},
25+
{
26+
"category": "fiction",
27+
"author": "J. R. R. Tolkien",
28+
"title": "The Lord of the Rings",
29+
"isbn": "0-395-19395-8",
30+
"price": 22.99
31+
}
32+
],
33+
"bicycle": {
34+
"color": "red",
35+
"price": 19.95
36+
}
37+
}
38+
};
39+
40+
var path1 = 'store.book[0].author'; // The author of the first book
41+
var path2 = 'store.book[*].author'; // All authors
42+
var path3 = 'store..price'; // All prices
43+
var path4 = '$..book[?(@.price<10)]'; // All books cheaper than 10
44+
var path5 = '$..book[?(@.isbn)]'; // All books with an ISBN number
45+
var path6 = '$..*'; // All members of JSON structure
46+
47+
var gjp = new GlideJsonPath(JSON.stringify(json));
48+
gs.info('Path: ' + path1 + ' Result: ' + gjp.read(path1));
49+
gs.info('Path: ' + path2 + ' Result: ' + gjp.read(path2));
50+
gs.info('Path: ' + path3 + ' Result: ' + gjp.read(path3));
51+
gs.info('Path: ' + path4 + ' Result: ' + gjp.read(path4));
52+
gs.info('Path: ' + path5 + ' Result: ' + gjp.read(path5));
53+
gs.info('Path: ' + path6 + ' Result: ' + gjp.read(path6));
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/**
2+
* Finds and reports records that have duplicate values across a combination of specified fields instead of a single field
3+
* Useful for finding for example duplicate items where unique key is not just 1 field
4+
*/
5+
6+
// --- UPDATE ONLY THE VALUES BELOW ---
7+
var tableName = 'cmdb_model'; // ADD: The table you want to check for duplicates.
8+
var fieldNames = ['name', 'model_number', 'manufacturer']; // ADD: An array of fields that create the unique combination.
9+
10+
// --- DO NOT EDIT BELOW THIS LINE ---
11+
findDuplicateCombinations(tableName, fieldNames);
12+
13+
function findDuplicateCombinations(tableName, fieldNames) {
14+
/**************************************/
15+
/*** Basic Error Handling ***/
16+
/**************************************/
17+
18+
// Check if the table exists
19+
if (!gs.tableExists(tableName)) {
20+
gs.print('Error: Table "' + tableName + '" does not exist.');
21+
return;
22+
}
23+
24+
// Check if fieldNames is a valid, non-empty array
25+
if (!Array.isArray(fieldNames) || fieldNames.length === 0) {
26+
gs.print('Error: The "fieldNames" variable must be an array with at least one field.');
27+
return;
28+
}
29+
30+
// Check if all specified fields exist on the table
31+
var gr = new GlideRecord(tableName);
32+
gr.initialize();
33+
for (var i = 0; i < fieldNames.length; i++) {
34+
var currentField = fieldNames[i];
35+
if (!gr.isValidField(currentField)) {
36+
gs.print('Error: The field "' + currentField + '" does not exist on the "' + tableName + '" table.');
37+
return;
38+
}
39+
}
40+
41+
/***************************************/
42+
/*** Find Duplicate Combinations ***/
43+
/***************************************/
44+
var duplicateJson = {}; // Stores the duplicate records and their counts
45+
var duplicateGroupCount = 0; // Counts the number of groups of duplicates
46+
47+
var duplicateAggregate = new GlideAggregate(tableName);
48+
duplicateAggregate.addAggregate('COUNT');
49+
50+
// Loop through the array to group by each field, creating the combination
51+
for (var j = 0; j < fieldNames.length; j++) {
52+
var field = fieldNames[j];
53+
duplicateAggregate.groupBy(field);
54+
duplicateAggregate.addNotNullQuery(field); // Ignore records where any part of the combination is empty
55+
}
56+
57+
duplicateAggregate.addHaving('COUNT', '>', 1); // More than 1 means it is a duplicate combination
58+
duplicateAggregate.query();
59+
60+
while (duplicateAggregate.next()) {
61+
duplicateGroupCount++;
62+
var combinationDisplay = [];
63+
64+
for (var k = 0; k < fieldNames.length; k++) {
65+
var fieldName = fieldNames[k];
66+
var fieldValue = duplicateAggregate.getDisplayValue(fieldName) || duplicateAggregate.getValue(fieldName);
67+
combinationDisplay.push(fieldName + ': "' + fieldValue + '"');
68+
}
69+
70+
var displayKey = combinationDisplay.join(', ');
71+
var countInGroup = duplicateAggregate.getAggregate('COUNT');
72+
duplicateJson[displayKey] = countInGroup;
73+
}
74+
75+
/***************************************/
76+
/*** Print the Results ***/
77+
/***************************************/
78+
79+
// No duplicates found
80+
var fieldCombinationString = '"' + fieldNames.join('", "') + '"';
81+
if (Object.keys(duplicateJson).length === 0) {
82+
gs.print('No duplicates found for the field combination [' + fieldCombinationString + '] on table "' + tableName + '".');
83+
return;
84+
}
85+
86+
// Duplicates were found
87+
gs.print("Found " + duplicateGroupCount + " groups of duplicates based on the combination [" + fieldCombinationString + "]:");
88+
89+
for (var key in duplicateJson) {
90+
gs.print('Combination {' + key + '} has ' + duplicateJson[key] + ' occurrences.');
91+
}
92+
}

Server-Side Components/Background Scripts/Duplicate Finder/readme.md

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
## ServiceNow Duplicate Record Finder
2-
A simple server-side script for ServiceNow that finds and reports on duplicate values for any field on any table. It uses an efficient GlideAggregate query and formats the results into a clean, readable report.
3-
1+
# ServiceNow Duplicate Record Finder
2+
A simple server-side script for ServiceNow that finds and reports on duplicate values for any field on any table. It uses an efficient `GlideAggregate` query.
3+
## Find Duplicates by Single Field - `findDuplicatesBySingleField.js`
4+
This finds duplicates based on a single field which matches
45
### How to Use
56
This script is designed to be run in **Scripts - Background** or as a Fix Script.
67
1. Navigate: Go to **System Definition > Scripts - Background** (or type sys.scripts.do in the filter navigator).
7-
2. Copy Script: Copy the entire contents of the script.js file.
8+
2. Copy Script: Copy the entire contents of the `findDuplicatesBySingleField.js` file.
89
3. Paste and Configure: Paste the script into the "Run script" text box. Add the table to search in `tableName` and the field to search for duplicates in `fieldName`
910
```js
1011
// Update ONLY below values to find duplicates
@@ -22,6 +23,25 @@ This script is designed to be run in **Scripts - Background** or as a Fix Script
2223
var fieldName = 'short_description';
2324
```
2425

25-
2626
4. Run Script: Click the "Run script" button. The results will be displayed on the screen below the editor.
2727

28+
## Find Duplicates by Field Combination - `findDuplicatesByCombination.js`
29+
This is a more powerful script that finds duplicate records based on a unique combination of one or more fields.
30+
31+
### How to use
32+
This script is also designed to be run in **Scripts - Background**
33+
1. Navigate: Go to **System Definition > Scripts - Background**
34+
2. Copy Script: Copy the entire contents of the `findDuplicatesByCombination.js` file.
35+
3. Paste and Configure: Paste the script into the "Run script" text box. Update the `tableName` field and the `fieldNames` array. The `fieldNames` variable is an array, so even a single field must be enclosed in square brackets `[]`
36+
37+
```js
38+
// --- UPDATE ONLY THE VALUES BELOW ---
39+
var tableName = '<table_name>'; // The table you want to check.
40+
var fieldNames = ['field_1', 'field_2']; // An array of fields for the unique combination.
41+
```
42+
43+
For example, to find models with the same name, model number and manufacturer
44+
```js
45+
var tableName = 'cmdb_model';
46+
var fieldNames = ['name', 'model_number', 'manufacturer'];
47+
```
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
The script retrieves the top-level manager for the currently logged-in user by traversing the manager hierarchy in the sys_user table.
2+
3+
It starts from the current user and moves up through each manager until it reaches a user who does not have a manager.
4+
5+
The script starts with the current user (e.g., Employee).
6+
7+
It checks if the user has a manager.
8+
9+
If yes, it moves up the hierarchy to the manager.
10+
11+
It repeats this process until it reaches a user who does not have a manager.
12+
13+
That user is considered the Top-Level Manager.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
var currentUser = gs.getUser(); // Current logged-in user
2+
var userGR = new GlideRecord('sys_user');
3+
var maxLevels = 7;
4+
var currentLevel = 0;
5+
6+
if (userGR.get(currentUser.getID())) {
7+
8+
// Loop until we find a user who has no manager or reach max level
9+
while (userGR.manager && currentLevel < maxLevels) {
10+
var managerID = userGR.getValue('manager');
11+
var managerGR = new GlideRecord('sys_user');
12+
13+
if (managerGR.get(managerID)) {
14+
userGR = managerGR; // Move up one level
15+
currentLevel++;
16+
// gs.print(" Level " + currentLevel + " Manager: " + userGR.getDisplayValue('name'));
17+
} else {
18+
break; // Manager record not found
19+
}
20+
}
21+
22+
gs.print("Top-level (or Level " + currentLevel + ") Manager: " + userGR.getDisplayValue('name'));
23+
} else {
24+
gs.print("User not found.");
25+
}

0 commit comments

Comments
 (0)