Skip to content

Commit ef7eaa7

Browse files
Merge branch 'ServiceNowDevProgram:main' into myFirstContribution
2 parents 24c52a3 + b59f64f commit ef7eaa7

File tree

317 files changed

+11381
-577
lines changed

Some content is hidden

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

317 files changed

+11381
-577
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

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: 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
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+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
var AccountUtils = Class.create();
2+
AccountUtils.prototype = Object.extendsObject(AbstractAjaxProcessor, {
3+
4+
//Populate the department name from the account in the session data for the reference qualifier to use:
5+
6+
setSessionData: function() {
7+
var acct = this.getParameter('sysparm_account');
8+
var dept = '';
9+
var acctGR = new GlideRecord('customer_account'); //reference table for Account variable
10+
if (acctGR.get(acct)) {
11+
dept = '^dept_name=' + acctGR.dept_name; //department field name on account table
12+
}
13+
14+
var session = gs.getSession().putClientData('selected_dept', dept);
15+
return;
16+
},
17+
18+
type: 'AccountUtils'
19+
});
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
This Catalog Client Script and Script Include are used with a reference qualifier similar to
2+
javascript: 'disable=false' + session.getClientData('selected_dept');
3+
4+
The scenario is a MRVS with a reference variable to the customer account table. When an (active) account is selected in the first row, subsequent rows should only be able to select active accounts in the same department as the first account.
5+
6+
The Catalog Client Script will pass the first selected account (if there is one) to a Script Include each time a MRVS row is added or edited. The Script Include will pass the department name to the reference qualifier.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
function onLoad() {
2+
//applies to MRVS, not Catalog Item. This will pass the first selected account (if there is one) to a Script Include each time a MRVS row is added or edited
3+
var mrvs = g_service_catalog.parent.getValue('my_mrvs'); //MRVS internal name
4+
var acct = '';
5+
if (mrvs.length > 2) { //MRVS is not empty
6+
var obj = JSON.parse(mrvs);
7+
acct = obj[0].account_mrvs;
8+
}
9+
var ga = new GlideAjax('AccountUtils');
10+
ga.addParam('sysparm_name', 'setSessionData');
11+
ga.addParam('sysparm_account', acct);
12+
ga.getXMLAnswer(getResponse);
13+
}
14+
15+
function getResponse(response) {
16+
//do nothing
17+
}

0 commit comments

Comments
 (0)