Skip to content

Commit 5dd934e

Browse files
authored
Merge branch 'ServiceNowDevProgram:main' into Hacktoberfest25-Code-snippets
2 parents 0a3f900 + 5770383 commit 5dd934e

File tree

522 files changed

+19491
-506
lines changed

Some content is hidden

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

522 files changed

+19491
-506
lines changed

.github/pull_request_template.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
# PR Description:
2-
2+
replace this with your description
33

44
# Pull Request Checklist
55

66
## Overview
7+
- [x] Put an x inside of the square brackets to check each item.
78
- [ ] I have read and understood the [CONTRIBUTING.md](CONTRIBUTING.md) guidelines
8-
- [ ] My pull request has a descriptive title that accurately reflects the changes
9+
- [ ] My pull request has a descriptive title that accurately reflects the changes and the description has been filled in above.
910
- [ ] I've included only files relevant to the changes described in the PR title and description
1011
- [ ] I've created a new branch in my forked repository for this contribution
1112

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
name: Auto-unassign stale PR assignees
2+
3+
on:
4+
schedule:
5+
- cron: "*/15 * * * *" # run every 15 minutes
6+
workflow_dispatch:
7+
inputs:
8+
enabled:
9+
description: "Enable this automation"
10+
type: boolean
11+
default: true
12+
max_age_minutes:
13+
description: "Unassign if assigned longer than X minutes"
14+
type: number
15+
default: 60
16+
dry_run:
17+
description: "Preview only; do not change assignees"
18+
type: boolean
19+
default: false
20+
21+
permissions:
22+
pull-requests: write
23+
issues: write
24+
25+
env:
26+
# Defaults (can be overridden via workflow_dispatch inputs)
27+
ENABLED: "true"
28+
MAX_ASSIGN_AGE_MINUTES: "60"
29+
DRY_RUN: "false"
30+
31+
jobs:
32+
sweep:
33+
runs-on: ubuntu-latest
34+
steps:
35+
- name: Resolve inputs into env
36+
run: |
37+
# Prefer manual run inputs when present
38+
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
39+
echo "ENABLED=${{ inputs.enabled }}" >> $GITHUB_ENV
40+
echo "MAX_ASSIGN_AGE_MINUTES=${{ inputs.max_age_minutes }}" >> $GITHUB_ENV
41+
echo "DRY_RUN=${{ inputs.dry_run }}" >> $GITHUB_ENV
42+
fi
43+
echo "Effective config: ENABLED=$ENABLED, MAX_ASSIGN_AGE_MINUTES=$MAX_ASSIGN_AGE_MINUTES, DRY_RUN=$DRY_RUN"
44+
45+
- name: Exit if disabled
46+
if: ${{ env.ENABLED != 'true' && env.ENABLED != 'True' && env.ENABLED != 'TRUE' }}
47+
run: echo "Disabled via ENABLED=$ENABLED. Exiting." && exit 0
48+
49+
- name: Unassign stale assignees
50+
uses: actions/github-script@v7
51+
with:
52+
script: |
53+
const owner = context.repo.owner;
54+
const repo = context.repo.repo;
55+
56+
const MAX_MIN = parseInt(process.env.MAX_ASSIGN_AGE_MINUTES || "60", 10);
57+
const DRY_RUN = ["true","True","TRUE","1","yes"].includes(String(process.env.DRY_RUN));
58+
const now = new Date();
59+
60+
core.info(`Scanning open PRs. Threshold = ${MAX_MIN} minutes. DRY_RUN=${DRY_RUN}`);
61+
62+
// List all open PRs
63+
const prs = await github.paginate(github.rest.pulls.list, {
64+
owner, repo, state: "open", per_page: 100
65+
});
66+
67+
let totalUnassigned = 0;
68+
69+
for (const pr of prs) {
70+
if (!pr.assignees || pr.assignees.length === 0) continue;
71+
72+
const number = pr.number;
73+
core.info(`PR #${number}: "${pr.title}" — assignees: ${pr.assignees.map(a => a.login).join(", ")}`);
74+
75+
// Pull reviews (to see if an assignee started a review)
76+
const reviews = await github.paginate(github.rest.pulls.listReviews, {
77+
owner, repo, pull_number: number, per_page: 100
78+
});
79+
80+
// Issue comments (general comments)
81+
const issueComments = await github.paginate(github.rest.issues.listComments, {
82+
owner, repo, issue_number: number, per_page: 100
83+
});
84+
85+
// Review comments (file-level)
86+
const reviewComments = await github.paginate(github.rest.pulls.listReviewComments, {
87+
owner, repo, pull_number: number, per_page: 100
88+
});
89+
90+
// Issue events (to find assignment timestamps)
91+
const issueEvents = await github.paginate(github.rest.issues.listEvents, {
92+
owner, repo, issue_number: number, per_page: 100
93+
});
94+
95+
for (const a of pr.assignees) {
96+
const assignee = a.login;
97+
98+
// Find the most recent "assigned" event for this assignee
99+
const assignedEvents = issueEvents
100+
.filter(e => e.event === "assigned" && e.assignee && e.assignee.login === assignee)
101+
.sort((x, y) => new Date(y.created_at) - new Date(x.created_at));
102+
103+
if (assignedEvents.length === 0) {
104+
core.info(` - @${assignee}: no 'assigned' event found; skipping.`);
105+
continue;
106+
}
107+
108+
const assignedAt = new Date(assignedEvents[0].created_at);
109+
const ageMin = (now - assignedAt) / 60000;
110+
111+
// Has the assignee commented (issue or review comments) or reviewed?
112+
const hasIssueComment = issueComments.some(c => c.user?.login === assignee);
113+
const hasReviewComment = reviewComments.some(c => c.user?.login === assignee);
114+
const hasReview = reviews.some(r => r.user?.login === assignee);
115+
116+
const eligible =
117+
ageMin >= MAX_MIN &&
118+
!hasIssueComment &&
119+
!hasReviewComment &&
120+
!hasReview &&
121+
pr.state === "open";
122+
123+
core.info(` - @${assignee}: assigned ${ageMin.toFixed(1)} min ago; commented=${hasIssueComment || hasReviewComment}; reviewed=${hasReview}; open=${pr.state==='open'} => ${eligible ? 'ELIGIBLE' : 'skip'}`);
124+
125+
if (!eligible) continue;
126+
127+
if (DRY_RUN) {
128+
core.notice(`Would unassign @${assignee} from PR #${number}`);
129+
} else {
130+
try {
131+
await github.rest.issues.removeAssignees({
132+
owner, repo, issue_number: number, assignees: [assignee]
133+
});
134+
totalUnassigned += 1;
135+
// Optional: leave a gentle heads-up comment
136+
await github.rest.issues.createComment({
137+
owner, repo, issue_number: number,
138+
body: `👋 Unassigning @${assignee} due to inactivity (> ${MAX_MIN} min without comments/reviews). This PR remains open for other reviewers.`
139+
});
140+
core.info(` Unassigned @${assignee} from #${number}`);
141+
} catch (err) {
142+
core.warning(` Failed to unassign @${assignee} from #${number}: ${err.message}`);
143+
}
144+
}
145+
}
146+
}
147+
148+
core.summary
149+
.addHeading('Auto-unassign report')
150+
.addRaw(`Threshold: ${MAX_MIN} minutes\n\n`)
151+
.addRaw(`Total unassignments: ${totalUnassigned}\n`)
152+
.write();
153+
154+
result-encoding: string
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
This piece of code is designed for an usecase where you might want to populate a field value that you're passing as a query in the URL which redirects to a catalog item.
2+
In this case, a custom field 'u_date' is chosen as an example to be shown:
3+
4+
1. You open a catalog item record via a URL that carries a date in the query string.
5+
Example:
6+
https://your-instance.service-now.com/your_form.do?sysparm_u_date=2025-10-31
7+
-(This URL includes a parameter named sysparm_u_date with the value 2025-10-31.)
8+
9+
10+
2. The catalog client script reads the page URL and extracts that specific parameter which returns the value "2025-10-31".
11+
12+
3. If the parameter is present, the script populates the form field.
13+
Calling g_form.setValue('u_date', '2025-10-31') sets the date field on the form to 31 October 2025.
14+
15+
16+
Result:
17+
The date field in the form is prefilled from the URL
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
//Logic to fetch the u_date field value passed in the url and setting it in the actual field.
2+
3+
4+
var fetchUrl = top.location.href; //get the URL
5+
6+
var setDate = new URLSearchParams(gUrl).get("sysparm_u_date"); //fetch the value of date from the query parameter
7+
8+
g_form.setValue('u_date', setDate); //set the value to the actual field
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
var GetRecentRequestValues = Class.create();
2+
GetRecentRequestValues.prototype = Object.extendsObject(AbstractAjaxProcessor, {
3+
getValues: function() {
4+
var userID = this.getParameter('sysparm_user');
5+
var itemID = this.getParameter('sysparm_item');
6+
var result = { found: false, values: {} };
7+
8+
var gr = new GlideRecord('sc_req_item');
9+
gr.addQuery('requested_for', userID);
10+
gr.addQuery('cat_item', itemID);
11+
gr.orderByDesc('sys_created_on');
12+
gr.setLimit(1);
13+
gr.query();
14+
15+
if (gr.next()) {
16+
result.found = true;
17+
18+
19+
var vars = gr.variables;
20+
result.values = {
21+
'requested_for': vars.requested_for + '',
22+
'location': vars.location + '',
23+
'department': vars.department + ''
24+
};
25+
}
26+
27+
return JSON.stringify(result);
28+
}
29+
});
30+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
function onLoad() {
2+
var user = g_user.userID;
3+
var itemID = g_form.getUniqueValue();
4+
5+
var ga = new GlideAjax('GetRecentRequestValues');
6+
ga.addParam('sysparm_name', 'getValues');
7+
ga.addParam('sysparm_user', user);
8+
ga.addParam('sysparm_item', itemID);
9+
ga.getXMLAnswer(function(response) {
10+
var data = JSON.parse(response);
11+
if (data && data.found) {
12+
var confirmFill = confirm("We found a similar request. Do you want to autofill fields?");
13+
if (confirmFill) {
14+
for (var field in data.values) {
15+
if (g_form.getControl(field)) {
16+
g_form.setValue(field, data.values[field]);
17+
console.log("Set " + field + " to " + data.values[field]);
18+
} else {
19+
console.log("Field not found: " + field);
20+
}
21+
}
22+
}
23+
} else {
24+
console.log("No previous request found.");
25+
}
26+
});
27+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Recent Request Autofill for ServiceNow Catalog.it automatically offers to fill in fields based on the user's most recent similar request.
2+
Features
3+
- Detects previous requests for the same catalog item
4+
- Prompts user to reuse values from their last submission
5+
- Autofills fields like location, department, and justification
6+
7+
<img width="878" height="395" alt="image" src="https://github.com/user-attachments/assets/33ceabf5-2bbc-43e3-8792-f1f9a99699d2" />
8+
9+
10+
Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
# Clear all fields on a catalog item form
22

3-
This works on both the native platform and service portal / mobile. Typically used with an OnChange catalog client script when you would like to reset all the fields after a certain variable is changed.
3+
This function clears all editable fields on a form, except those explicitly excluded.
4+
It works on both the native platform (Classic UI) and Service Portal / Mobile.
5+
Typically used with an OnChange catalog client script when you want to clear all fields after a certain variable changes.
46

5-
This function does support an exclusion list if there are fields you would like to exclude from being reset, typically you would want to add the field that triggered to the change to the exlusion
7+
The function returns an array of the field names that were cleared, which can be used for logging or further processing.
68

7-
### Example
9+
### Exclusion Support
810

9-
```js
10-
clearFields(['field1', 'field2']);
11-
```
11+
You can pass an array of field names to exclude from being cleared.
12+
This is useful when you want to preserve the value of the field that triggered the change or other important fields.
1213

13-
All fields on the form **except** field1 and field2 will be cleared.
14+
### Example
15+
```
16+
clearFields(['short_description', 'priority']);
17+
```
18+
// Clears all fields except 'short_description' and 'priority'
Lines changed: 45 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,47 @@
1-
/**SNDOC
2-
@name clearFields
3-
@description Clear/reset all fields on a form
4-
@param {Array} [dontClearFieldsArray] - Fields to not clear
5-
@example
6-
clearFields(['field1', 'field2']);
7-
*/
1+
/**
2+
* Clears or resets all editable fields on a form, except those explicitly excluded.
3+
* Compatible with Classic UI and Service Portal/Mobile.
4+
* Intended for use in onChange client scripts.
5+
*
6+
* @function clearFields
7+
* @param {Array} dontClearFieldsArray - Array of field names to exclude from clearing.
8+
* @returns {Array} - Array of field names that were cleared.
9+
*
10+
* @example
11+
* // Clears all fields except 'short_description' and 'priority'
12+
* clearFields(['short_description', 'priority']);
13+
*/
14+
function clearFields(dontClearFieldsArray) {
15+
// Ensure the exclusion list is defined and is an array
16+
dontClearFieldsArray = Array.isArray(dontClearFieldsArray) ? dontClearFieldsArray : [];
817

9-
function clearFields(dontClearFieldsArray){
18+
// Helper function to check if a field should be cleared
19+
function shouldClear(fieldName) {
20+
return dontClearFieldsArray.indexOf(fieldName) === -1;
21+
}
1022

11-
try{ // Classic UI
12-
var pFields = g_form.nameMap;
13-
pFields.forEach(function(field){
14-
if(dontClearFieldsArray.indexOf(field.prettyName) == -1){
15-
g_form.clearValue(field.prettyName);
16-
}
17-
});
18-
}catch(e){ // Service Portal or Mobile
19-
var fields = g_form.getEditableFields();
20-
fields.forEach(function(field){
21-
if(dontClearFieldsArray.indexOf(fields) == -1){
22-
g_form.clearValue(field);
23-
}
24-
});
25-
}
26-
}
23+
var clearedFields = [];
24+
25+
try {
26+
// Classic UI: use g_form.nameMap to get all fields
27+
var allFields = g_form.nameMap;
28+
allFields.forEach(function(field) {
29+
var fieldName = field.prettyName;
30+
if (shouldClear(fieldName)) {
31+
g_form.clearValue(fieldName);
32+
clearedFields.push(fieldName);
33+
}
34+
});
35+
} catch (e) {
36+
// Service Portal or Mobile: use getEditableFields()
37+
var editableFields = g_form.getEditableFields();
38+
editableFields.forEach(function(fieldName) {
39+
if (shouldClear(fieldName)) {
40+
g_form.clearValue(fieldName);
41+
clearedFields.push(fieldName);
42+
}
43+
});
44+
}
45+
46+
return clearedFields;
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
var SentimentAnalyzer = Class.create();
2+
SentimentAnalyzer.prototype = Object.extendsObject(AbstractAjaxProcessor, {
3+
getSentiment: function() {
4+
var text = (this.getParameter('sysparm_text') || '').toLowerCase();
5+
var positive = ['thanks', 'great', 'resolved', 'appreciate'];
6+
var negative = ['issue', 'error', 'not working', 'fail', 'problem'];
7+
8+
var score = 0;
9+
positive.forEach(function(word) { if (text.includes(word)) score++; });
10+
negative.forEach(function(word) { if (text.includes(word)) score--; });
11+
12+
if (score > 0) return 'Positive';
13+
if (score < 0) return 'Negative';
14+
return 'Neutral';
15+
}
16+
});

0 commit comments

Comments
 (0)