Skip to content

Commit 179dd58

Browse files
authored
Merge branch 'main' into check-user-impersonation-details
2 parents 15c0347 + f8120aa commit 179dd58

File tree

89 files changed

+2118
-11
lines changed

Some content is hidden

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

89 files changed

+2118
-11
lines changed

.github/pull_request_template.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# PR Description:
2+
3+
14
# Pull Request Checklist
25

36
## Overview

.github/scripts/validate-structure.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,34 @@ function validateFilePath(filePath) {
4949
const normalized = filePath.replace(/\\/g, '/');
5050
const segments = normalized.split('/');
5151

52+
// Check for invalid characters that break local file systems
53+
for (let i = 0; i < segments.length; i++) {
54+
const segment = segments[i];
55+
56+
// Check for trailing periods (invalid on Windows)
57+
if (segment.endsWith('.')) {
58+
return `Invalid folder/file name '${segment}' in path '${normalized}': Names cannot end with a period (.) as this breaks local file system sync on Windows.`;
59+
}
60+
61+
// Check for trailing spaces (invalid on Windows)
62+
if (segment.endsWith(' ')) {
63+
return `Invalid folder/file name '${segment}' in path '${normalized}': Names cannot end with a space as this breaks local file system sync on Windows.`;
64+
}
65+
66+
// Check for reserved Windows names
67+
const reservedNames = ['CON', 'PRN', 'AUX', 'NUL', 'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9', 'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9'];
68+
const nameWithoutExt = segment.split('.')[0].toUpperCase();
69+
if (reservedNames.includes(nameWithoutExt)) {
70+
return `Invalid folder/file name '${segment}' in path '${normalized}': '${nameWithoutExt}' is a reserved name on Windows and will break local file system sync.`;
71+
}
72+
73+
// Check for invalid characters (Windows and general file system restrictions)
74+
const invalidChars = /[<>:"|?*\x00-\x1F]/;
75+
if (invalidChars.test(segment)) {
76+
return `Invalid folder/file name '${segment}' in path '${normalized}': Contains characters that are invalid on Windows file systems (< > : " | ? * or control characters).`;
77+
}
78+
}
79+
5280
if (!allowedCategories.has(segments[0])) {
5381
return null;
5482
}

.github/workflows/validate-structure.yml

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ jobs:
6565
cat "$tmp_error" >&2
6666
6767
if grep -q 'Folder structure violations found' "$tmp_output" "$tmp_error"; then
68+
# Save validation output for use in PR comment
69+
cat "$tmp_output" "$tmp_error" > "$RUNNER_TEMP/validation_output.txt"
6870
echo "status=failed" >> "$GITHUB_OUTPUT"
6971
exit 0
7072
fi
@@ -87,11 +89,37 @@ jobs:
8789
const owner = context.repo.owner;
8890
const repo = context.repo.repo;
8991
92+
const fs = require('fs');
93+
const output = fs.readFileSync(process.env.RUNNER_TEMP + '/validation_output.txt', 'utf8');
94+
95+
let commentBody = `Thank you for your contribution. However, it doesn't comply with our contributing guidelines.\n\n`;
96+
97+
// Check if the error is about invalid file/folder names
98+
if (output.includes('Names cannot end with a period') ||
99+
output.includes('Names cannot end with a space') ||
100+
output.includes('is a reserved name on Windows') ||
101+
output.includes('Contains characters that are invalid')) {
102+
commentBody += `**❌ Invalid File/Folder Names Detected**\n\n`;
103+
commentBody += `Your contribution contains file or folder names that will break when syncing to local file systems (especially Windows):\n\n`;
104+
commentBody += `\`\`\`\n${output}\n\`\`\`\n\n`;
105+
commentBody += `**Common issues:**\n`;
106+
commentBody += `- Folder/file names ending with a period (.) - not allowed on Windows\n`;
107+
commentBody += `- Folder/file names ending with spaces - not allowed on Windows\n`;
108+
commentBody += `- Reserved names like CON, PRN, AUX, NUL, COM1-9, LPT1-9 - not allowed on Windows\n`;
109+
commentBody += `- Invalid characters: < > : " | ? * or control characters\n\n`;
110+
commentBody += `Please rename these files/folders to be compatible with all operating systems.\n\n`;
111+
} else {
112+
commentBody += `As a reminder, the general requirements (as outlined in the [CONTRIBUTING.md file](https://github.com/ServiceNowDevProgram/code-snippets/blob/main/CONTRIBUTING.md)) are the following: follow the folder+subfolder guidelines and include a README.md file explaining what the code snippet does.\n\n`;
113+
commentBody += `**Validation errors:**\n\`\`\`\n${output}\n\`\`\`\n\n`;
114+
}
115+
116+
commentBody += `Review your contribution against the guidelines and make the necessary adjustments. Closing this for now. Once you make additional changes, feel free to re-open this Pull Request or create a new one.`;
117+
90118
await github.rest.issues.createComment({
91119
owner,
92120
repo,
93121
issue_number: pullNumber,
94-
body: `Thank you for your contribution. However, it doesn't comply with our contributing guidelines. As a reminder, the general requirements (as outlined in the [CONTRIBUTING.md file](https://github.com/ServiceNowDevProgram/code-snippets/blob/main/CONTRIBUTING.md)) are the following: follow the folder+subfolder guidelines and include a README.md file explaining what the code snippet does. Review your contribution against the guidelines and make the necessary adjustments. Closing this for now. Once you make additional changes, feel free to re-open this Pull Request or create a new one.`.trim()
122+
body: commentBody.trim()
95123
});
96124
97125
await github.rest.pulls.update({
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
In ServiceNow, Open catalog client Scripts [catalog_script_client] and paste the code snippet of [spModalSweetAlerts.js] file.
2+
3+
Setup:
4+
1. A catalog item having variable name Rewards[rewards] of type 'Select Box'(include none as true) and 2 choices(Yes and No)
5+
2. A Single line type field named 'Reward Selected' [reward_selected] which will hold the value selected by user from the spModal popup.
6+
3. The onLoad catalog client script setup as below:
7+
4. Type: onChange
8+
5. Variable: rewards (as per step 1)
9+
6. Script: [[spModalSweetAlerts.js]]
10+
11+
12+
13+
Screenshots:
14+
15+
16+
<img width="1338" height="268" alt="image" src="https://github.com/user-attachments/assets/f7f22b83-7e0e-47bb-bbed-2a8f38783a4d" />
17+
18+
19+
Rewards selected as 'Yes'
20+
21+
<img width="1353" height="327" alt="image" src="https://github.com/user-attachments/assets/1bb55339-36b4-4a9c-8b65-2b254b87cf5b" />
22+
23+
From the spModal popup select anyone of the reward, it should be populated in the Reward Selected field.
24+
Along with that a message shows the same of the selection.
25+
26+
<img width="1350" height="319" alt="image" src="https://github.com/user-attachments/assets/1b23c766-51f8-4b01-9073-f836f390deb2" />
27+
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
function onChange(control, oldValue, newValue) {
2+
if (newValue == 'Yes') {
3+
spModal.open({
4+
title: "Reward Type",
5+
message: "Please select the category of Reward",
6+
buttons: [{
7+
label: "Star Performer",
8+
value: "Star Performer"
9+
},
10+
{
11+
label: "Emerging Player",
12+
value: "Emerging Player"
13+
},
14+
{
15+
label: "High Five Award",
16+
value: "High Five Award"
17+
},
18+
{
19+
label: "Rising Star",
20+
value: "Rising Star"
21+
}
22+
]
23+
}).then(function(choice) {
24+
if (choice && choice.value) {
25+
g_form.addInfoMessage('Selected Reward: '+ choice.label);
26+
g_form.setValue('reward_selected', choice.value);
27+
}
28+
});
29+
} else {
30+
g_form.clearValue('reward_selected');
31+
}
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
2+
if (isLoading) {
3+
return;
4+
}
5+
6+
var prevValue;
7+
if (g_scratchpad.prevValue == undefined)
8+
prevValue = oldValue;
9+
else {
10+
prevValue = g_scratchpad.prevValue;
11+
}
12+
g_scratchpad.prevValue = newValue;
13+
14+
15+
var oldGlideValue = prevValue.split(',');
16+
var newGlideValue = newValue.split(',');
17+
18+
var operation;
19+
20+
if (oldGlideValue.length > newGlideValue.length || newValue == '') {
21+
operation = 'remove';
22+
} else if (oldGlideValue.length < newGlideValue.length || oldGlideValue.length == newGlideValue.length) {
23+
operation = 'add';
24+
} else {
25+
operation = '';
26+
}
27+
28+
var ajaxGetNames = new GlideAjax('watchListCandidatesUtil');
29+
ajaxGetNames.addParam('sysparm_name', 'getWatchListUsers');
30+
ajaxGetNames.addParam('sysparm_old_values', oldGlideValue);
31+
ajaxGetNames.addParam('sysparm_new_values', newGlideValue);
32+
ajaxGetNames.getXMLAnswer(function(response) {
33+
34+
var result = JSON.parse(response);
35+
36+
g_form.clearMessages();
37+
g_form.addSuccessMessage('Operation Performed : ' + operation);
38+
g_form.addSuccessMessage('OldValue : ' + result.oldU);
39+
g_form.addSuccessMessage('NewValue : ' + result.newU);
40+
41+
});
42+
43+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
In Client Scripts, oldValue will display the value of last value/record which is stored in that field.
2+
For new records, it is generally empty and for existing records it displays the value which is stored after load.
3+
If we will try to change the value in that field, it will still show oldValue the same value which was there during the load of form.
4+
5+
So, In order to identify the oldValue on change(as it does in business rule(previous)), This script comes handy and also it will
6+
detect the action performed.
7+
8+
9+
This onChange Client script comes handy when dealing with Glide List type fields where we have to detect whether the value was added or removed and returns the display name of users who were added/removed along with name of operation performed.
10+
11+
Setup details:
12+
13+
onChange client Script on [incident] table
14+
Field Name: [watch_list]
15+
Script: Check [detectOldValuenewValueOperation.js] file
16+
Script Include Name: watchListCandidatesUtil (client callable/GlideAjax Enabled - true), Check [watchListCandidatesUtil.js] file for script
17+
18+
19+
20+
Output:
21+
22+
Currently there is no one in the watchlist field:
23+
24+
<img width="873" height="298" alt="image" src="https://github.com/user-attachments/assets/a46ca53a-f031-4bf9-9f85-2056c408b66b" />
25+
26+
27+
Adding [Bryan Rovell], It shows the operation was addition, oldValue as 'No record found' as there was no value earlier.New Value shows the name of the user (Bryan Rovell)
28+
<img width="870" height="443" alt="image" src="https://github.com/user-attachments/assets/484284b6-846e-424c-b9c8-a53278f48c72" />
29+
30+
31+
Adding 2 users one by one:
32+
33+
<img width="987" height="484" alt="image" src="https://github.com/user-attachments/assets/35dfe96a-c932-4f95-9c8e-bdb48b1c7b5f" />
34+
35+
36+
Removing 2 at once:
37+
38+
<img width="879" height="496" alt="image" src="https://github.com/user-attachments/assets/c83d4e01-f150-44cb-9078-9841072ec949" />
39+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
var watchListCandidatesUtil = Class.create();
2+
watchListCandidatesUtil.prototype = Object.extendsObject(AbstractAjaxProcessor, {
3+
4+
5+
getWatchListUsers: function() {
6+
7+
var oldUsers = this.getParameter('sysparm_old_values');
8+
var newUsers = this.getParameter('sysparm_new_values');
9+
10+
var result = {
11+
oldU: this._getUserNames(oldUsers),
12+
newU: this._getUserNames(newUsers)
13+
};
14+
15+
return JSON.stringify(result);
16+
},
17+
18+
19+
_getUserNames: function(userList) {
20+
var names = [];
21+
22+
var grUserTab = new GlideRecord('sys_user');
23+
grUserTab.addQuery('sys_id', 'IN', userList);
24+
grUserTab.query();
25+
if (grUserTab.hasNext()) {
26+
while (grUserTab.next()) {
27+
names.push(grUserTab.getDisplayValue('name'));
28+
}
29+
return names.toString();
30+
} else {
31+
return 'No record found';
32+
}
33+
},
34+
35+
36+
type: 'watchListCandidatesUtil'
37+
});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
## Dynamically Switch Form View Based on Field Value
2+
3+
This client script demonstrates how to **automatically switch form views** based on the value of a field.
4+
5+
**Use case:**
6+
For example, if the **Category** field is set to *Hardware*, the form view switches to **ess**.
7+
You can extend this by updating the mapping object to support additional fields and values (e.g., *Software → itil*, *Network → support*).
8+
9+
**Benefit:**
10+
Improves user experience by guiding users to the **most relevant form view**, ensuring the right fields are shown for the right scenario.
11+
12+
**Test:**
13+
- Change the **Category** field to *Hardware* → Form view should switch to **ess**.
14+
- Update mapping to add new conditions (e.g., *Software → itil*) and verify the view switches accordingly.
15+
16+
**How to Use:**
17+
1. **Modify the table name** in the `switchView` function to match your target table:
18+
```javascript
19+
switchView("section", "<your_table_name>", targetView);
20+
2. **Modify the view mapping**
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/**
2+
* dynamic-form-view-onchange.js
3+
*
4+
* Dynamically switches the form view automatically depending on the value of a specific field.
5+
* Example: If Category = Hardware → switch to ess view.
6+
* Extendable by modifying the mapping object for different fields/values.
7+
*
8+
*/
9+
10+
function onChange(control, oldValue, newValue, isLoading) {
11+
if (isLoading || !newValue) {
12+
return;
13+
}
14+
15+
// Field value → view name mapping
16+
var viewMapping = {
17+
"hardware": "ess",
18+
"software": "itil",
19+
"network": "support"
20+
};
21+
22+
var fieldValue = newValue.toLowerCase();
23+
var targetView = viewMapping[fieldValue];
24+
25+
if (targetView) {
26+
try {
27+
// Here for example the table name is incident
28+
switchView("section", "incident", targetView);
29+
} catch (e) {
30+
console.error("View switch failed: ", e);
31+
}
32+
}
33+
}

0 commit comments

Comments
 (0)