Skip to content

Commit 49162ad

Browse files
Merge branch 'ServiceNowDevProgram:main' into main
2 parents 6602ce0 + 4523c2b commit 49162ad

File tree

175 files changed

+3355
-9
lines changed

Some content is hidden

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

175 files changed

+3355
-9
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
Use Case: Find the Total Number of Records in a Table Using the ATF Step "Run Server Side Script"
2+
3+
Using existing ATF steps (without scripting), it is very difficult to find the record count of a table.
4+
5+
By using the ATF test step "Run Server Side Script" with a simple script, we can count the records and also log/pass the count to the next ATF step if required.
6+
7+
Steps:
8+
9+
Navigate to Automated Test Framework >> Tests >> Click on New Test.
10+
Give a name to the test and provide a description.
11+
Go to the Test Steps related list and click Add Test Step.
12+
Navigate to Server and choose Run Server Side Script.
13+
Add the script (the script is in the script.js file).
14+
Save the test and run it to see the results.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
(function(outputs, steps, params, stepResult, assertEqual) {
2+
// add test script here
3+
var gr = new GlideAggregate('incident');
4+
gr.addAggregate('COUNT');
5+
gr._query();
6+
if (gr.next()) {
7+
return gr.getAggregate('COUNT'); // pass the step
8+
stepResult.setOutputMessage("Successfully Calculated the Count");
9+
} else {
10+
stepResult.setOutputMessage("Failed to Count");
11+
return false; // fail the step
12+
}
13+
14+
})(outputs, steps, params, stepResult, assertEqual);
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
## Description
2+
The planned lines of the ServiceNow burndown chart do not take holidays into account.
3+
By using this Python snippet, you can create a burndown chart with planned lines that take holidays into account.
4+
The generated burndown chart can also be automatically deployed as an image to Slack and other tools.
5+
6+
## Requirements
7+
OS: Windows/MacOS/Unix
8+
Python: Python3.x
9+
ServiceNow: More than Vancouver
10+
Plugins: Agile Development 2.0 (com.snc.sdlc.agile.2.0) is installed
11+
12+
## Installation
13+
Clone the repository and place the "Burndown Chart" directory in any location.
14+
Execute the following command to create a virtual environment.
15+
<code>python3 -m venv .venv
16+
macOS/Unix: source .venv/bin/activate
17+
Windows: .venv\Scripts\activate
18+
pip install -r requirements.txt
19+
</code>
20+
21+
## Usage
22+
1. Go to the Burndown Chart directory.
23+
2. Prepare the following values ​​according to your environment:
24+
- InstanceName: Your instance name (e.g. dev000000 for PDI)
25+
- Credentials: Instance login information in Base64 (Read permission to the rm_sprint table is required.)
26+
- Sprint Name: Target sprint name from the Sprint[rm_sprint] table
27+
28+
3. Run the command to install the required modules.
29+
<code>pip install -r equirements.txt</code>
30+
31+
5. Run sprint_burndown_chart.py.
32+
<code>python3 sprint_burndown_chart.py INSTANCE_NAME BASE64_ENCODED_STRING(USERID:PASSWORD) SPRINT_NAME</code>
33+
example:
34+
<code>python3 sprint_burndown_chart.py dev209156 YXBpOmpkc0RhajNAZDdKXnNmYQ== Sprint1</code>
35+
36+
When you run it, a burndown chart image like the one shown will be created.
37+
![figure](https://github.com/user-attachments/assets/50d3ffc2-4c66-4f4d-bb69-c2b98763621d)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
jpholiday==0.1.10
2+
matplotlib==3.9.2
3+
numpy==2.0.2
4+
pandas==2.2.3
5+
pytz==2024.2
6+
requests==2.32.3
7+
urllib3==1.26.13
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import argparse
2+
import pprint
3+
import requests
4+
import datetime
5+
import matplotlib.dates as mdates
6+
import pandas as pd
7+
import matplotlib.pyplot as plt
8+
import urllib.request
9+
import urllib.parse
10+
import json
11+
import datetime
12+
from pytz import timezone
13+
14+
# ---- #
15+
# init #
16+
# ---- #
17+
point_dict = {}
18+
total_points = 0
19+
done = 0
20+
undone = 0
21+
parser = argparse.ArgumentParser()
22+
parser.add_argument('instancename')
23+
parser.add_argument('authstring')
24+
parser.add_argument('sprintname')
25+
args = parser.parse_args()
26+
BASIC = 'Basic ' + args.authstring
27+
28+
# ---------- #
29+
# Get Sprint #
30+
# ---------- #
31+
params = {
32+
'sysparm_query': 'short_description=' + args.sprintname
33+
}
34+
param = urllib.parse.urlencode(params)
35+
url = "https://" + args.instancename + ".service-now.com/api/now/table/rm_sprint?" + param
36+
req = urllib.request.Request(url)
37+
req.add_header("authorization", BASIC)
38+
with urllib.request.urlopen(req) as res:
39+
r = res.read().decode("utf-8")
40+
obj = json.loads(r)
41+
# Get the start and end dates of a Sprint
42+
start_date = obj['result'][0]['start_date']
43+
start_date = (datetime.datetime.strptime(start_date, '%Y-%m-%d %H:%M:%S') + datetime.timedelta(hours=9)).date()
44+
print(start_date)
45+
end_date = obj['result'][0]['end_date']
46+
end_date = (datetime.datetime.strptime(end_date, '%Y-%m-%d %H:%M:%S') + datetime.timedelta(hours=9)).date()
47+
# Initializing the points array
48+
while start_date <= end_date:
49+
point_dict[str(start_date)] = 0
50+
start_date = start_date + datetime.timedelta(days=1)
51+
# --------- #
52+
# Get Story #
53+
# --------- #
54+
params = {
55+
'sysparm_query': 'sprint.short_descriptionLIKE' + args.sprintname
56+
}
57+
param = urllib.parse.urlencode(params)
58+
url = "https://" + args.instancename + ".service-now.com/api/now/table/rm_story?" + param
59+
req = urllib.request.Request(url)
60+
req.add_header("authorization", BASIC)
61+
with urllib.request.urlopen(req) as res:
62+
r = res.read().decode("utf-8")
63+
obj = json.loads(r)
64+
# Story Loop
65+
for name in obj['result']:
66+
if len(name['story_points']) > 0:
67+
total_points += int(name['story_points'])
68+
if name['closed_at'] != '':
69+
close_date = datetime.datetime.strptime(
70+
name['closed_at'], '%Y-%m-%d %H:%M:%S')
71+
close_date = close_date.date()
72+
if name['state'] == '3':
73+
if str(close_date) in point_dict:
74+
point_dict[str(close_date)] += int(name['story_points'])
75+
else:
76+
point_dict[str(close_date)] = int(name['story_points'])
77+
if name['state'] == '3':
78+
done += int(name['story_points'])
79+
else:
80+
undone += int(name['story_points'])
81+
counta = 0
82+
for i in point_dict.items():
83+
counta += int(i[1])
84+
point_dict[i[0]] = total_points - counta
85+
plt.xkcd()
86+
fig, ax = plt.subplots()
87+
# Creating a performance line
88+
x = []
89+
y = []
90+
plt.ylim(0, total_points + 5)
91+
counta = 0
92+
for key in point_dict.keys():
93+
if datetime.datetime.today() >= datetime.datetime.strptime(key, '%Y-%m-%d'):
94+
x.append(datetime.datetime.strptime(key, '%Y-%m-%d'))
95+
y.append(point_dict[key])
96+
# Holiday determination
97+
DATE = "yyyymmdd"
98+
def isBizDay(DATE):
99+
Date = datetime.date(int(DATE[0:4]), int(DATE[4:6]), int(DATE[6:8]))
100+
if Date.weekday() >= 5:
101+
return 0
102+
else:
103+
return 1
104+
# Get the number of weekdays
105+
total_BizDay = 0
106+
for key in point_dict.keys():
107+
if isBizDay(key.replace('-', '')) == 1:
108+
total_BizDay += 1
109+
# Creating an ideal line
110+
x2 = []
111+
y2 = []
112+
point_dict_len = len(point_dict)
113+
average = total_points / (total_BizDay - 1)
114+
for key in point_dict.keys():
115+
dtm = datetime.datetime.strptime(key, '%Y-%m-%d')
116+
x2.append(dtm)
117+
y2.append(total_points)
118+
# If the next day is a weekday, consume the ideal line.
119+
if isBizDay((dtm + datetime.timedelta(days=1)).strftime("%Y%m%d")) == 1:
120+
total_points -= average
121+
days = mdates.DayLocator()
122+
daysFmt = mdates.DateFormatter('%m/%d')
123+
ax.xaxis.set_major_locator(days)
124+
ax.xaxis.set_major_formatter(daysFmt)
125+
plt.title("" + args.sprintname + " Burndown")
126+
plt.plot(x2, y2, label="Ideal", color='green')
127+
plt.plot(x2, y2, marker='.', markersize=20, color='green')
128+
plt.plot(x, y, label="Actual", color='red')
129+
plt.plot(x, y, marker='.', markersize=20, color='red')
130+
plt.grid()
131+
plt.xlabel("Days")
132+
plt.ylabel("Remaining Effort(pts)")
133+
plt.subplots_adjust(bottom=0.2)
134+
plt.legend()
135+
# Viewing the graph
136+
# plt.show()
137+
# Saving a graph
138+
plt.savefig('figure.png')
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Script to be used to add bookmark for ITIL users. This will help add favorites for SLAs for
2+
- My Group Tasks
3+
- SLAs for My Tasks
4+
- Tasks Assigned to Me
5+
- My approvals
6+
to all ITIL users.
7+
Replace the addQuery value to get the favorites applied from the backend to required audience
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
var jsonFavList = {
2+
"SLA for My Group Tasks": "task_list.do?sysparm_query=assignment_groupDYNAMICd6435e965f510100a9ad2572f2b47744&sysparm_first_row=1&sysparm_view=",
3+
"SLA for My Tasks": "task_list.do?sysparm_query=assigned_toDYNAMIC90d1921e5f510100a9ad2572f2b477fe&sysparm_first_row=1&sysparm_view=",
4+
"Tasks Assigned to Me": "task_list.do?sysparm_query=stateNOT INclosed_complete,closed_abandoned^assigned_toDYNAMIC90d1921e5f510100a9ad2572f2b477fe",
5+
"My approvals": "sysapproval_approver_list.do?sysparm_query=approverDYNAMIC90d1921e5f510100a9ad2572f2b477fe&sysparm_first_row=1&sysparm_view="
6+
};
7+
8+
var g = new GlideRecord("sys_user_has_role");
9+
g.addEncodedQuery("role=282bf1fac6112285017366cb5f867469");//considering sys_id for ITIL role is 282bf1fac6112285017366cb5f867469
10+
g.query();
11+
while (g.next()) {
12+
for (var fav in jsonFavList) {
13+
var grBookMark = new GlideRecord("sys_ui_bookmark");
14+
grBookMark.addEncodedQuery("user=" + g.user + "^title=" + fav + "^url=" + jsonFavList[fav]);
15+
grBookMark.query();
16+
if (!grBookMark.next()) {
17+
grBookMark.initialize();
18+
grBookMark.pinned = true;
19+
grBookMark.title = fav;
20+
grBookMark.url = jsonFavList[fav];
21+
grBookMark.user = g.user;
22+
grBookMark.insert();
23+
}
24+
}
25+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
function addComments(tableName,recSysId, userName, fieldName){
2+
var rec = new GlideRecord(tableName);
3+
if(rec.get(recSysId)){
4+
rec[fieldName].setJournalEntry('This is my comment',userName);
5+
rec.update();
6+
}
7+
}
8+
9+
addComments(tableName,recSysId,userName,fieldName);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
There have been scenarios where you are working on an integration and one of the usecase is to add comments on a record. In this scenario once you add comments directly
2+
to the record by assigning the value in the comments or work_notes field, it captures it as a integration user in the activity formatter. But with setJournalEntry()
3+
method you can pass the user_name and it will add the comment on behalf of the user whose user_name is passed. In this way it easy to track who is actually modifying
4+
the record or adding the comments. Hope this will be a helpful snippet.
5+
6+
In this function the paramters are defined as:
7+
tableName: Name of the table on which you are adding the comments.
8+
recSysId: sys_id of the record for which the comment is getting added.
9+
userName: user_name of the user who is adding the comments
10+
fieldName: It can be either comments or work_notes or any custom journal entry field.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Using GlideAggregate function to find out tickets (tasks) with same number. OOB there happens to be a Unique checkbox at dictionary level
2+
and if in case not set to True it might create duplicate numbered tickets.
3+
Script will help find, ticekts if any.

0 commit comments

Comments
 (0)