Skip to content

Commit 7c24ccc

Browse files
Merge branch 'ServiceNowDevProgram:main' into Use_case_-Base64-Encode-Before-Save-And-Decode-on-Display
2 parents 5f4d826 + de3ed58 commit 7c24ccc

File tree

28 files changed

+712
-1
lines changed

28 files changed

+712
-1
lines changed

.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: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# The Glide User (g_user) is a global object available within the client side. It provides information about the logged-in user.
2+
3+
Property Description
4+
5+
g_user.userID Sys ID of the currently logged-in user
6+
g_user.name User's Full name
7+
g_user.firstName User's First name
8+
g_user.lastName User's Last name
9+
10+
# It also has some methods available within the client side.
11+
12+
Method Description
13+
14+
g_user.hasRole() Determine whether the logged-in user has a specific role
15+
g_user.hasRoleExactly() Do not consider the admin role while evaluating the method
16+
g_user.hasRoles() You can pass two or more roles in a single method
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
if (g_user.hasRole('admin') || g_user.hasRole('itil')) {
2+
// User has at least one of the roles
3+
g_form.setDisplay('internal_notes', true);
4+
}
5+
6+
if (g_user.hasRole('admin') && g_user.hasRole('itil')) {
7+
// User must have both roles
8+
g_form.setDisplay('advanced_settings', true);
9+
}
10+
11+
//Using the parameters to set a field value
12+
g_form.setValue('short_description', g_user.firstName);
13+
14+
g_form.setValue('short_description', g_user.lastName);
15+
16+
g_form.setValue('short_description', g_user.name);
17+
18+
g_form.setValue('short_description', g_user.userID);
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Mandatory Field Highlighter
2+
3+
## Use Case
4+
Provides visual feedback for empty mandatory fields on ServiceNow forms by showing error messages when the form loads. Helps users quickly identify which required fields need to be completed.
5+
6+
## Requirements
7+
- ServiceNow instance
8+
- Client Script execution rights
9+
- Forms with mandatory fields
10+
11+
## Implementation
12+
1. Create a new Client Script with Type "onLoad"
13+
2. Copy the script code from script.js
14+
3. **Customize the fieldsToCheck string** with your form's mandatory field names
15+
4. Apply to desired table/form
16+
5. Save and test on a form with mandatory fields
17+
18+
## Configuration
19+
Edit the `fieldsToCheck` variable to include your form's mandatory fields as a comma-separated string:
20+
21+
```javascript
22+
// Example configurations for different forms:
23+
24+
// For Incident forms:
25+
var fieldsToCheck = 'short_description,priority,category,caller_id,assignment_group';
26+
27+
// For Request forms:
28+
var fieldsToCheck = 'short_description,requested_for,category,priority';
29+
30+
// For Change Request forms:
31+
var fieldsToCheck = 'short_description,category,priority,assignment_group,start_date,end_date';
32+
33+
// For Problem forms:
34+
var fieldsToCheck = 'short_description,category,priority,assignment_group';
35+
36+
// Custom fields (add as needed):
37+
var fieldsToCheck = 'short_description,priority,u_business_justification,u_cost_center';
38+
```
39+
40+
## Features
41+
- Shows error messages under empty mandatory fields on form load
42+
- Easy configuration with comma-separated field names
43+
- Automatically skips fields that don't exist on the form
44+
- Only processes fields that are actually mandatory and visible
45+
- Uses proper ServiceNow client scripting APIs
46+
- No DOM manipulation or unsupported methods
47+
48+
## Common Field Names
49+
- `short_description` - Short Description
50+
- `priority` - Priority
51+
- `category` - Category
52+
- `caller_id` - Caller
53+
- `requested_for` - Requested For
54+
- `assignment_group` - Assignment Group
55+
- `assigned_to` - Assigned To
56+
- `state` - State
57+
- `urgency` - Urgency
58+
- `impact` - Impact
59+
- `start_date` - Start Date
60+
- `end_date` - End Date
61+
- `due_date` - Due Date
62+
- `location` - Location
63+
- `company` - Company
64+
- `department` - Department
65+
66+
## Notes
67+
- Uses g_form.showFieldMsg() method to display error messages
68+
- Uses g_form.hasField() to safely check field existence (built into the safety checks)
69+
- Only runs on form load - provides initial validation feedback
70+
- Easy to customize for different forms by modifying the field list
71+
- Compatible with all standard ServiceNow forms
72+
- Lightweight and focused on essential functionality
73+
74+
## Example Usage
75+
For a typical incident form, simply change the configuration line to:
76+
```javascript
77+
var fieldsToCheck = 'short_description,priority,category,caller_id,assignment_group';
78+
```
79+
Save the Client Script and test on an incident form to see error messages appear under empty mandatory fields.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
function onLoad() {
2+
3+
// USER CONFIGURATION: Add field names you want to check (comma-separated)
4+
var fieldsToCheck = 'short_description,priority,category,caller_id';
5+
6+
// Convert to array and process
7+
var fieldArray = fieldsToCheck.split(',');
8+
9+
// Check each field
10+
for (var i = 0; i < fieldArray.length; i++) {
11+
var fieldName = fieldArray[i];
12+
13+
// Skip if field is not mandatory or not visible
14+
if (!g_form.isMandatory(fieldName) || !g_form.isVisible(fieldName)) {
15+
continue;
16+
}
17+
18+
// Get current field value
19+
var value = g_form.getValue(fieldName);
20+
21+
// Show error message if field is empty
22+
if (!value || value === '') {
23+
g_form.showFieldMsg(fieldName, 'This field is required', 'error');
24+
}
25+
}
26+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Field Character Counter
2+
3+
## Use Case
4+
Provides real-time character count feedback for text fields in ServiceNow forms. Shows remaining characters with visual indicators to help users stay within field limits.
5+
6+
## Requirements
7+
- ServiceNow instance
8+
- Client Script execution rights
9+
- Text fields with character limits
10+
11+
## Implementation
12+
1. Create a new Client Script with Type "onChange"
13+
2. Copy the script code from `script.js`
14+
3. Configure the field name and character limit in the script
15+
4. Apply to desired table/form
16+
5. Save and test
17+
18+
## Configuration
19+
Edit these variables in the script:
20+
21+
```javascript
22+
var fieldName = 'short_description'; // Your field name
23+
var maxLength = 160; // Your character limit
24+
```
25+
26+
## Features
27+
- Real-time character counting as user types
28+
- Visual indicators: info (blue), warning (yellow), error (red)
29+
- Shows "X characters remaining" or "Exceeds limit by X characters"
30+
- Automatically clears previous messages
31+
32+
## Common Examples
33+
```javascript
34+
// Short Description (160 chars)
35+
var fieldName = 'short_description';
36+
var maxLength = 160;
37+
38+
// Description (4000 chars)
39+
var fieldName = 'description';
40+
var maxLength = 4000;
41+
42+
// Work Notes (4000 chars)
43+
var fieldName = 'work_notes';
44+
var maxLength = 4000;
45+
```
46+
47+
## Message Thresholds
48+
- **50+ remaining**: Info message (blue)
49+
- **1-20 remaining**: Warning message (yellow)
50+
- **Over limit**: Error message (red)
51+
52+
## Notes
53+
- Uses standard ServiceNow APIs: `g_form.showFieldMsg()` and `g_form.hideFieldMsg()`
54+
- Create separate Client Scripts for multiple fields
55+
- Works with all text fields and text areas
56+
- Character count includes all characters (spaces, punctuation, etc.)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
function onChange(control, oldValue, newValue, isLoading) {
2+
if (isLoading) return;
3+
4+
// USER CONFIGURATION: Set field name and character limit
5+
var fieldName = 'Description'; // Change to your field name
6+
var maxLength = 80; // Change to your character limit
7+
8+
var currentLength = newValue ? newValue.length : 0;
9+
var remaining = maxLength - currentLength;
10+
11+
// Clear any existing messages
12+
g_form.hideFieldMsg(fieldName);
13+
14+
// Show appropriate message based on remaining characters
15+
if (remaining < 0) {
16+
g_form.showFieldMsg(fieldName, 'Exceeds limit by ' + Math.abs(remaining) + ' characters', 'error');
17+
} else if (remaining <= 20) {
18+
g_form.showFieldMsg(fieldName, remaining + ' characters remaining', 'warning');
19+
} else if (remaining <= 50) {
20+
g_form.showFieldMsg(fieldName, remaining + ' characters remaining', 'info');
21+
}
22+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
2+
Script Usage :
3+
4+
This function takes the parameters such as source table, source record sys_id, target table, fields that needs to be copied to target table.
5+
6+
As a validation check, the fields from source table should be similar to target else abort inserting the record.
7+
8+
9+
Same Code to invoke the function:
10+
copyFieldsValidated(
11+
'dmn_demand',
12+
'8c10306edbc00810f777526adc961976',
13+
'pm_project',
14+
['name', 'short_description'] //will throw error since name field not common in both tables
15+
);
16+
17+
18+
copyFieldsValidated(
19+
'dmn_demand',
20+
'8c10306edbc00810f777526adc961976',
21+
'pm_project',
22+
['short_description'] //Insert the record since short_description is common in both tables
23+
);
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/**
2+
* Copies specified fields from a source record to a newly created target record,
3+
* validating all fields before performing the insert operation.
4+
*
5+
* @param {string} sourceTable - The name of the source table.
6+
* @param {string} sourceId - The sys_id of the source record.
7+
* @param {string} targetTable - The name of the target table.
8+
* @param {string[]} fields - An array of field names to copy.
9+
* @returns {string|null} The sys_id of the newly created target record, or null on failure.
10+
*/
11+
function copyFieldsValidated(sourceTable, sourceId, targetTable, fields) {
12+
var src = new GlideRecord(sourceTable);
13+
if (!src.get(sourceId)) {
14+
gs.error("Source record not found in " + sourceTable + " with sys_id: " + sourceId);
15+
return null;
16+
}
17+
18+
var tgt = new GlideRecord(targetTable);
19+
tgt.initialize();
20+
var allFieldsAreValid = true;
21+
// First, validate all fields before doing any work
22+
fields.forEach(function(f) {
23+
if (!src.isValidField(f) || !tgt.isValidField(f)) {
24+
gs.warn("Field [" + f + "] is not valid in both " + sourceTable + " and " + targetTable + ". Aborting insert.");
25+
allFieldsAreValid = false;
26+
}
27+
});
28+
29+
// Proceed with copying and inserting only if all fields are valid
30+
if (allFieldsAreValid) {
31+
fields.forEach(function(f) {
32+
tgt.setValue(f, src.getValue(f));
33+
});
34+
var newId = tgt.insert();
35+
if (newId) {
36+
gs.info("Record copied from " + sourceTable + " → " + targetTable + ". New sys_id: " + newId);
37+
return newId;
38+
} else {
39+
gs.error("Failed to insert new record into " + targetTable);
40+
return null;
41+
}
42+
} else {
43+
gs.error("Aborting record insert due to invalid fields.");
44+
return null;
45+
}
46+
}

0 commit comments

Comments
 (0)