Skip to content

Commit ae01540

Browse files
authored
Merge branch 'main' into feature/incident-smart-field-suggestions
2 parents 993574c + 2914895 commit ae01540

File tree

316 files changed

+10824
-391
lines changed

Some content is hidden

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

316 files changed

+10824
-391
lines changed

.github/pull_request_template.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1+
# PR Description:
2+
replace this with your description
3+
14
# Pull Request Checklist
25

36
## Overview
7+
- [x] Put an x inside of the square brackets to check each item.
48
- [ ] I have read and understood the [CONTRIBUTING.md](CONTRIBUTING.md) guidelines
5-
- [ ] 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.
610
- [ ] I've included only files relevant to the changes described in the PR title and description
711
- [ ] I've created a new branch in my forked repository for this contribution
812

.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
}
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

.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({

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: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
function onChange(control, oldValue, newValue, isLoading) {
2+
if (isLoading || newValue == '') {
3+
return;
4+
}
5+
6+
var faxRegex = /^\d{10}$/; //allow only 10 digit in fax number field
7+
if (!faxRegex.test(newValue)) {
8+
g_form.addErrorMessage('Please enter a valid 10-digit fax number');
9+
g_form.clearValue('fax_num');
10+
}
11+
12+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
This script ensures that the fax number entered consists of exactly 10 digits.
2+
Any input that doesn’t meet this requirement — such as numbers with fewer or more digits, letters, or special characters — will be automatically rejected to maintain proper validation and data consistency.

0 commit comments

Comments
 (0)