Skip to content

Commit c0877b3

Browse files
Merge branch 'ServiceNowDevProgram:main' into main
2 parents 0bc1af4 + 02eaef8 commit c0877b3

File tree

16 files changed

+535
-15
lines changed

16 files changed

+535
-15
lines changed
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
2+
# ServiceNow Catalog Builder API
3+
**Automate Catalog Item Creation with a Single REST Call**
4+
5+
---
6+
7+
## Overview
8+
9+
The **ServiceNow Catalog Builder API** is a custom **Scripted REST API** that dynamically creates Service Catalog Items in your instance — including variables and choices — from a simple JSON payload.
10+
11+
This API eliminates the repetitive manual work of configuring Catalog Items one by one, and makes it possible to **automate catalog creation programmatically** or **integrate it with CI/CD pipelines, GitHub workflows, or external systems**.
12+
13+
---
14+
15+
## Key Features
16+
17+
Automatically create **Catalog Items** in `sc_cat_item`
18+
Dynamically generate **Variables** and **Choices**
19+
Supports **category mapping** and **item ownership**
20+
Extensible design for **flows, icons, and attachments**
21+
Developer-friendly — fully JSON-driven
22+
23+
---
24+
25+
## Use Case
26+
27+
This API is perfect for:
28+
- **Admin Automation:** Auto-build standard catalog forms during environment setup.
29+
- **RPA / CI Pipelines:** Integrate with DevOps or GitHub Actions to deploy catalog definitions.
30+
- **Dynamic Service Portals:** Allow external apps or portals to create items on demand.
31+
32+
Example:
33+
A company wants to auto-create 10 new service catalog items from a GitHub configuration file.
34+
Using this API, they simply call one REST endpoint for each definition — no manual clicks needed.
35+
36+
---
37+
38+
## Scripted REST API Details
39+
40+
| Property | Value |
41+
|-----------|--------|
42+
| **Name** | Catalog Builder API |
43+
| **API ID** | `x_demo.catalog_creator` |
44+
| **Resource Path** | `/create` |
45+
| **Method** | POST |
46+
| **Authentication** | Basic Auth / OAuth |
47+
| **Tables Used** | `sc_cat_item`, `item_option_new`, `question_choice` |
48+
49+
---
50+
51+
## Logic Flow
52+
53+
1. **Receive JSON input** with item name, category, and variables.
54+
2. **Create a new record** in `sc_cat_item`.
55+
3. **Loop through variables** and create them in `item_option_new`.
56+
4. If the variable type is `select_box`, create **choices** automatically.
57+
5. Return a JSON response with the new item’s `sys_id` and success message.
58+
59+
---
60+
61+
## Example Input (POST Body)
62+
63+
```json
64+
{
65+
"name": "Request New Laptop",
66+
"category": "Hardware",
67+
"short_description": "Laptop provisioning request form",
68+
"description": "Allows employees to request a new laptop with model and RAM options.",
69+
"owner": "admin",
70+
"variables": [
71+
{
72+
"name": "Laptop Model",
73+
"type": "select_box",
74+
"choices": "Dell,HP,Lenovo"
75+
},
76+
{
77+
"name": "RAM Size",
78+
"type": "select_box",
79+
"choices": "8GB,16GB,32GB"
80+
},
81+
{
82+
"name": "Business Justification",
83+
"type": "multi_line_text"
84+
}
85+
]
86+
}
87+
88+
89+
## Example Output:
90+
{
91+
"catalog_sys_id": "b2f6329cdb6d0010355b5fb4ca9619e2",
92+
"message": "Catalog item created successfully!"
93+
}
94+
After the API call:
95+
A new Catalog Item appears under Maintain Items.
96+
The item contains:
97+
Short Description: Laptop provisioning form
98+
Variables: Laptop Model, RAM Size, Business Justification
99+
Choices: Auto-populated for select boxes
100+
The item is active and ready to use in the catalog.
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
// Scenario : As a ServiceNow Admin or Developer managing dozens of similar request forms (like “Request Laptop”, “Request Mobile”, “Request Access”, etc.).
2+
// Manually creating each catalog item is repetitive.
3+
4+
// This code will Automate Catalog Item Creation with a Single REST Call
5+
//Script: POST /api/x_demo/catalog_creator/create
6+
7+
(function process(/*RESTAPIRequest*/ request, /*RESTAPIResponse*/ response) {
8+
9+
var body = request.body.data;
10+
var result = {};
11+
12+
try {
13+
// 1. Create Catalog Item
14+
var catItem = new GlideRecord('sc_cat_item');
15+
catItem.initialize();
16+
catItem.name = body.name;
17+
catItem.short_description = body.short_description || '';
18+
catItem.description = body.description || '';
19+
catItem.category = getCategorySysId(body.category);
20+
catItem.owning_group = getOwner(body.owner);
21+
catItem.active = true;
22+
var catSysId = catItem.insert();
23+
24+
result.catalog_sys_id = catSysId;
25+
26+
// 2. Create Variables
27+
if (body.variables && body.variables.length > 0) {
28+
for (var i = 0; i < body.variables.length; i++) {
29+
var v = body.variables[i];
30+
31+
var variable = new GlideRecord('item_option_new');
32+
variable.initialize();
33+
variable.cat_item = catSysId;
34+
variable.name = v.name.toLowerCase().replace(/ /g, '_');
35+
variable.question_text = v.name;
36+
variable.type = getType(v.type);
37+
variable.order = (i + 1) * 100;
38+
var varSysId = variable.insert();
39+
40+
// Add choices for select box variables
41+
if (v.choices && v.choices.length > 0) {
42+
var choices = v.choices.split(',');
43+
for (var j = 0; j < choices.length; j++) {
44+
var choice = new GlideRecord('question_choice');
45+
choice.initialize();
46+
choice.question = varSysId;
47+
choice.value = choices[j].trim();
48+
choice.label = choices[j].trim();
49+
choice.insert();
50+
}
51+
}
52+
}
53+
}
54+
55+
result.message = "Catalog item created successfully!";
56+
response.setStatus(201);
57+
58+
} catch (e) {
59+
gs.error("Error creating catalog item: " + e);
60+
result.message = e.toString();
61+
response.setStatus(500);
62+
}
63+
64+
response.setBody(result);
65+
66+
67+
function getCategorySysId(categoryName) {
68+
var cat = new GlideRecord('sc_category');
69+
cat.addQuery('title', categoryName);
70+
cat.query();
71+
if (cat.next()) return cat.sys_id;
72+
return null;
73+
}
74+
75+
function getOwner(ownerName) {
76+
var usr = new GlideRecord('sys_user');
77+
usr.addQuery('user_name', ownerName);
78+
usr.query();
79+
if (usr.next()) return usr.sys_id;
80+
return gs.getUserID();
81+
}
82+
83+
function getType(typeName) {
84+
var map = {
85+
"single_line_text": 1,
86+
"multi_line_text": 2,
87+
"select_box": 3,
88+
"reference": 8,
89+
"checkbox": 5
90+
};
91+
return map[typeName] || 1;
92+
}
93+
94+
})(request, response);
95+
96+
//Example JSON
97+
//{
98+
"name": "Request New Laptop",
99+
"category": "Hardware",
100+
"short_description": "Laptop provisioning form",
101+
"description": "Allows employees to request a new laptop.",
102+
"owner": "admin",
103+
"variables": [
104+
{
105+
"name": "Laptop Model",
106+
"type": "select_box",
107+
"choices": "Dell,HP,Lenovo"
108+
},
109+
{
110+
"name": "RAM Size",
111+
"type": "select_box",
112+
"choices": "8GB,16GB,32GB"
113+
},
114+
{
115+
"name": "Business Justification",
116+
"type": "multi_line_text"
117+
}
118+
]
119+
}
120+
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
**Usecase**:
2+
This piece of code can be used to check whether a record was created more than 90 days ago or not.
3+
It returns an output of true/false as well as the actual age (in days) of the record.
4+
5+
**Type**:
6+
Background Script
7+
8+
**How it works**:
9+
There is a pastDateString variable which can contain a date-time which you received by querying any particular record's sys_created_on field or any other relevant field.
10+
In the actual code I have hard-coded a value to be used as an example.
11+
-There are two gliderecord objects to store the current date as well as past date
12+
-GlideDateTime.subtract() is used to calculate the time difference as a GlideDuration object
13+
-The time duration in milliseconds is converted to days by dividing with 86400000(milliseconds in a day)
14+
-Comparison is done between the results
15+
-The final age of the record and true/false result of the comparison is the output.
16+
[Note: Output may vary according to timezones]
17+
18+
**Example**:
19+
pastDateString = 2025-05-01 10:00:00
20+
Output:
21+
22+
Record Age (Days): 165
23+
Older than 90 days? true
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//the pastDateString variable can contain a date-time which you received by querying any particular record's sys_created_on field or any other relevant field. Here I'm hard-coding a value.
2+
3+
var pastDateString = '2025-05-01 10:00:00'; //input date and time
4+
var ageThresholdDays = 90;
5+
6+
var gdtPast = new GlideDateTime(pastDateString);
7+
var gdtNow = new GlideDateTime();
8+
var duration = GlideDateTime.subtract(gdtPast, gdtNow);
9+
var durationMs = duration.getNumericValue();
10+
11+
//Calculate the total days (1 day = 86,400,000 milliseconds)
12+
var totalDaysOld = Math.floor(durationMs / 86400000);
13+
14+
var isOlderThanThreshold = false;
15+
if (totalDaysOld > ageThresholdDays) {
16+
isOlderThanThreshold = true;
17+
}
18+
19+
gs.info("Record Age (Days): " + totalDaysOld);
20+
gs.info("Older than " + ageThresholdDays + " days? " + isOlderThanThreshold);
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
(function() {
2+
var priorities = {};
3+
var agg = new GlideAggregate('incident');
4+
agg.addAggregate('COUNT');
5+
agg.groupBy('priority');
6+
agg.query();
7+
while (agg.next()) {
8+
var priority = agg.getDisplayValue('priority') || 'No Priority Set';
9+
var count = agg.getAggregate('COUNT');
10+
priorities[priority] = parseInt(count, 10);
11+
}
12+
for (var priority in priorities) {
13+
gs.info('Priority: ' + priority + ' | Count: ' + priorities[priority]);
14+
}
15+
})();
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
//To get the incident count based on priority.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
How often we come across cases where reports are created with same name by different users. Well to maintain some uniquenss across why not have some controls to get it unified. Below script can be used to suffix 'Created by' to the report to help uniquely identify report.
2+
Execute it as a background script to update it in bulk.
3+
For example Report named ABC will becom ABC - [PQR]v1 and ABC - [XYZ]v2 where PQR and XYZ are created by users
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
var ar = [];
2+
var dupCheck = new GlideAggregate('sys_report');
3+
dupCheck.addEncodedQuery('titleISNOTEMPTY');
4+
dupCheck.addNotNullQuery('title');
5+
dupCheck.groupBy('title');
6+
dupCheck.addAggregate('COUNT', 'title');
7+
dupCheck.addHaving('COUNT', '>', 1);
8+
dupCheck.query();
9+
while (dupCheck.next()) {
10+
ar.push(dupCheck.getValue("title"));
11+
}
12+
13+
for(var i = 0 ; i< ar.length; i++){
14+
var report = new GlideRecord("sys_report");
15+
report.addQuery("title",ar[i]);
16+
report.query();
17+
var c= 0;
18+
while(report.next()){
19+
c++;
20+
var user = new GlideRecord("sys_user");
21+
user.addQuery("email",report.sys_created_by.toString());
22+
user.query();
23+
if(user.next()){
24+
var name = user.name;
25+
}
26+
27+
if(name){
28+
report.title = report.title+" "+' - [' + name + ']'+" "+"v"+c; // Report named ABC will now be ABC - [PQR]v1 and ABC - [XYZ]v2 where PQR and XYZ are created by users
29+
report.setWorkflow(false);
30+
report.autoSysFields(false);
31+
report.update();
32+
}
33+
else {
34+
report.title = report.title+" "+"v"+c;
35+
report.setWorkflow(false);
36+
report.autoSysFields(false);
37+
report.update();
38+
39+
}
40+
}
41+
42+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//Scenario: whenever a user submits a ticket (Incident, HR Case, etc.), the system analyzes the tone of the message - like frustrated, urgent, calm, confused
2+
// and automatically adjusts the ticket priority, or routes it to a specific team (like “Empathy Response Desk)
3+
4+
var EmotionAnalyzer = Class.create();
5+
EmotionAnalyzer.prototype = {
6+
initialize: function() {},
7+
8+
detectEmotion: function(text) {
9+
try {
10+
// ---- Mock Sentiment Logic (No external API) ----
11+
text = text.toLowerCase();
12+
var score = 0;
13+
var negativeWords = ['angry', 'frustrated', 'bad', 'urgent', 'hate', 'delay'];
14+
var positiveWords = ['thank', 'happy', 'great', 'awesome', 'love'];
15+
16+
negativeWords.forEach(function(word) {
17+
if (text.includes(word)) score -= 1;
18+
});
19+
positiveWords.forEach(function(word) {
20+
if (text.includes(word)) score += 1;
21+
});
22+
23+
var sentiment = (score > 0) ? 'positive' : (score < 0) ? 'negative' : 'neutral';
24+
return { sentiment: sentiment, score: Math.abs(score / 3) };
25+
} catch (e) {
26+
gs.error("EmotionAnalyzer error: " + e.message);
27+
return { sentiment: 'neutral', score: 0 };
28+
}
29+
},
30+
31+
type: 'EmotionAnalyzer'
32+
};
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
### Overview
2+
The **Emotion-Aware Ticket Prioritizer** is an AI-driven innovation for ServiceNow that automatically analyzes the tone and emotion of user-submitted tickets (Incidents, HR Cases, etc.) to determine the urgency and emotional state of the user.
3+
If frustration or urgency is detected, the system dynamically increases the **priority**, adds contextual **work notes**, and routes the ticket to the right team — ensuring faster resolution and better user experience.
4+
5+
---
6+
7+
## How It Works
8+
1. When a ticket is created, a **Business Rule** triggers a **Script Include** (`EmotionAnalyzer`).
9+
2. The Script Include analyzes the short description and description text.
10+
3. It detects emotional tone — *positive*, *neutral*, or *negative*.
11+
4. Based on sentiment, the system:
12+
- Adjusts **priority** automatically
13+
- Adds a **work note** with detected emotion
14+
- Optionally, notifies the support team for urgent or frustrated cases
15+
16+
---
17+
## How It Trigger Script Include Via Business Rule
18+
1. Create object of Script Include (Accessible from all scopes)
19+
var util = new global.EmotionAnalyzer();
20+
21+
----
22+
## Example line as input and output
23+
| User Input | Detected Emotion | Auto Priority | Output |
24+
| -------------------------------------- | ---------------- | ------------- | --------------------- |
25+
| “Laptop crashed again, no one helps!” | Negative | 1 (Critical) | Escalate to VIP queue |
26+
| “Thank you, system working great now!” | Positive | 4 (Low) | No action |
27+
| “Need help resetting my password.” | Neutral | 3 (Moderate) | Normal SLA |
28+
29+

0 commit comments

Comments
 (0)