Skip to content

Commit acf0dd4

Browse files
Merge branch 'ServiceNowDevProgram:main' into main
2 parents b80e4fc + 4311771 commit acf0dd4

File tree

26 files changed

+603
-0
lines changed

26 files changed

+603
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
function onChange(control, oldValue, newValue, isLoading) {
2+
if (isLoading) return;
3+
4+
if (newValue === 'hardware') {
5+
g_form.setMandatory('asset_tag', true);
6+
g_form.setDisplay('asset_tag', true);
7+
g_form.setValue('assignment_group', 'Hardware Support Group');
8+
} else if (newValue === 'software') {
9+
g_form.setMandatory('asset_tag', false);
10+
g_form.setDisplay('asset_tag', false);
11+
g_form.setValue('assignment_group', 'Software Support Group');
12+
} else {
13+
g_form.setMandatory('asset_tag', false);
14+
g_form.setDisplay('asset_tag', true);
15+
}
16+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
If an Incident Category = Hardware, make Asset Tag mandatory and automatically assign to Hardware Support Group.
2+
If Software, assign to Software Support Group and hide Asset Tag.
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
var GenericEmailUtility = Class.create();
2+
GenericEmailUtility.prototype = {
3+
initialize: function() {},
4+
5+
// Generate an Outlook (mailto) link with watermark tracking
6+
get_Outlook_link: function() {
7+
try {
8+
const email_payload = JSON.stringify({
9+
"REQUESTOR_ID": "",
10+
"TITLE": "",
11+
"BODY": "",
12+
"REQUEST_ID": "",
13+
"TABLE_ID": ""
14+
});
15+
16+
var mailtoLink = false;
17+
const raw_data = this.getParameter("sysparm_email_body") || email_payload;
18+
19+
if (global.JSUtil.notNil(raw_data)) {
20+
var email_data = JSON.parse(raw_data);
21+
22+
const to = this.getEmail(email_data.REQUESTOR_ID);
23+
const cc = gs.getProperty("instanceEmailAddress"); // instance default CC
24+
const subject = email_data.TITLE || '';
25+
const body = email_data.BODY || '';
26+
27+
const watermark = this.getWatermark(email_data.REQUEST_ID, email_data.TABLE_ID);
28+
29+
// Construct mailto link
30+
mailtoLink = 'mailto:' + to + '?cc=' + cc;
31+
32+
if (subject)
33+
mailtoLink += '&subject=' + encodeURIComponent(subject);
34+
35+
if (body)
36+
mailtoLink += '&body=' + encodeURIComponent(body);
37+
38+
if (watermark)
39+
mailtoLink += encodeURIComponent("\n\nRef: " + watermark);
40+
}
41+
42+
return mailtoLink;
43+
44+
} catch (ex) {
45+
gs.error("Error in get_Outlook_link(): " + ex.message);
46+
return false;
47+
}
48+
},
49+
50+
// Fetch watermark ID (creates one if missing)
51+
getWatermark: function(record_id, table_name) {
52+
var wm = new GlideRecord('sys_watermark');
53+
wm.addQuery('source_id', record_id);
54+
wm.orderByDesc('sys_created_on');
55+
wm.query();
56+
57+
if (wm.next()) {
58+
return wm.getValue('number');
59+
}
60+
61+
wm.initialize();
62+
wm.source_id = record_id;
63+
wm.source_table = table_name;
64+
wm.insert();
65+
66+
return wm.getValue('number');
67+
},
68+
69+
// Retrieve user’s email address
70+
getEmail: function(user_id) {
71+
if (global.JSUtil.notNil(user_id)) {
72+
var user = new GlideRecordSecure('sys_user');
73+
if (user.get(user_id))
74+
return user.email.toString();
75+
}
76+
return '';
77+
},
78+
79+
type: 'GenericEmailUtility'
80+
};
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Outlook Email Watermark Utility for ServiceNow
2+
3+
# Overview
4+
This reusable utility allows users to send emails **outside ServiceNow** (e.g., using Outlook or any default mail client) while still maintaining the conversation within ServiceNow.
5+
By embedding a unique watermark reference, any replies to the email will automatically append to the original record's activity feed.
6+
7+
This helps teams collaborate externally without losing internal record visibility — ideal for customers or vendors who communicate via Outlook.
8+
9+
---
10+
11+
# Objective
12+
- Enable ServiceNow users to send Outlook emails directly from a record.
13+
- Maintain conversation history in ServiceNow using watermark tracking.
14+
- Make the solution **generic**, reusable across tables (Incident, Change, Request, etc.).
15+
- Prevent dependency on outbound mail scripts or custom integrations.
16+
17+
# Components
18+
19+
## 1. Script Include: GenericEmailUtility
20+
Handles the logic for:
21+
- Constructing the mailto: link.
22+
- Fetching recipient and instance email addresses.
23+
- Generating or retrieving the watermark ID.
24+
- Returning a formatted Outlook link to the client script.
25+
26+
## Key Methods
27+
1. get_Outlook_link() - Builds the full Outlook mail link with subject, body, and watermark.
28+
2. getWatermark(record_id, table_name) - Ensures a watermark exists for the record.
29+
3. getEmail(user_id) - Fetches the email address for the target user.
30+
31+
## 2. UI Action (Client Script)
32+
Executes on the record form when the button/link is clicked.
33+
It gathers record data, constructs a payload, calls the Script Include using GlideAjax, and opens Outlook.
34+
35+
## Key Steps
36+
1. Collect field data like requestor, short description, and description.
37+
2. Pass record details to the Script Include (GenericEmailUtility).
38+
3. Receive a ready-to-use Outlook link.
39+
4. Open the mail client with prefilled details and watermark reference.
40+
41+
## How It Works
42+
1. User clicks "Send Outlook Email" UI Action on a record.
43+
2. Script gathers record data and passes it to GenericEmailUtility.
44+
3. The utility builds a 'mailto:' link including the watermark.
45+
4. Outlook (or default mail client) opens with pre-filled To, CC, Subject, and Body fields.
46+
5. When the recipient replies, ServiceNow uses the watermark to append comments to the correct record.
47+
48+
## Example Usage
49+
**User clicks “Send Outlook Email”** on a Request record:
50+
Outlook opens prefilled like this:
51+
52+
<img width="288" height="65" alt="image" src="https://github.com/user-attachments/assets/b58c5e0a-d80a-40ca-9ab5-f188a1203169" />
53+
54+
55+
<img width="710" height="496" alt="image" src="https://github.com/user-attachments/assets/5cbc7645-4233-4826-99f7-e2948bb5ab78" />
56+
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
function onClick(g_form) {
2+
var separator = "\n--------------------------------\n";
3+
var email_body = "Record URL:\n" + g_form.getDisplayValue('number') + separator;
4+
email_body += "Short Description:\n" + g_form.getValue('short_description') + separator;
5+
email_body += "Description:\n" + g_form.getValue('description') + separator;
6+
7+
var email_data = {};
8+
email_data.REQUESTOR_ID = g_form.getValue('caller_id') || g_form.getValue('opened_by') || g_form.getValue('requested_for');
9+
email_data.TITLE = g_form.getValue('short_description') || 'ServiceNow Communication';
10+
email_data.BODY = email_body;
11+
email_data.REQUEST_ID = g_form.getUniqueValue();
12+
email_data.TABLE_ID = g_form.getTableName();
13+
14+
var ga = new GlideAjax('GenericEmailUtility');
15+
ga.addParam('sysparm_name', 'get_Outlook_link');
16+
ga.addParam('sysparm_email_body', JSON.stringify(email_data));
17+
ga.getXMLAnswer(function(response) {
18+
var mailto_link = response;
19+
if (mailto_link && mailto_link != 'false') {
20+
window.open(mailto_link);
21+
} else {
22+
g_form.addErrorMessage('Unable to generate Outlook link.');
23+
}
24+
});
25+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Language Selector with Flags
2+
3+
A language selector widget for the Portal.
4+
The user can change the instance language without having to leave the Portal.
5+
6+
<img width="255" height="95" alt="image" src="https://github.com/user-attachments/assets/af130ec4-d724-4b07-a38f-afd858b7eba2" />
7+
<img width="234" height="195" alt="image" src="https://github.com/user-attachments/assets/a2de8161-a922-4376-904d-b16f81dcc573" />
8+
9+
10+
## What it does
11+
- Displays a dropdown with flags and language names.
12+
- Automatically updates the user's language in the `sys_user` table.
13+
- Reloads the page to apply the new language immediately.
14+
15+
## Files
16+
- **HTML Template:** renders the dropdown with flag emojis and labels.
17+
- **Client Script:** handles language selection and sends the PATCH request.
18+
- **Server Script:** provides the current user ID and stored language.
19+
20+
## Example
21+
When the user selects **🇪🇸 Spanish**, the widget updates their user record and reloads the Portal in Spanish.
22+
23+
## Prerequisites
24+
- The language selected **must be installed and active** in the instance.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
function($http) {
2+
var c = this;
3+
4+
c.languages = [
5+
{ code: 'en', label: 'English', flag: '🇬🇧' },
6+
{ code: 'pb', label: 'Portuguese (Brazil)', flag: '🇧🇷' },
7+
{ code: 'es', label: 'Spanish', flag: '🇪🇸' },
8+
{ code: 'fr', label: 'French', flag: '🇫🇷' },
9+
{ code: 'de', label: 'German', flag: '🇩🇪' },
10+
{ code: 'it', label: 'Italian', flag: '🇮🇹' }
11+
];
12+
13+
c.userId = c.data.user_id;
14+
c.selected = c.data.language || 'en';
15+
16+
c.changeLang = function() {
17+
$http.patch('/api/now/table/sys_user/' + c.userId, { preferred_language: c.selected })
18+
.then(function(response) {
19+
location.reload();
20+
});
21+
};
22+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
.lang-selector {
2+
display: flex;
3+
align-items: center;
4+
gap: 8px;
5+
}
6+
select, button {
7+
padding: 6px 8px;
8+
border-radius: 6px;
9+
border: 1px solid #ccc;
10+
}
11+
button {
12+
cursor: pointer;
13+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<div class="lang-selector">
2+
<select ng-model="c.selected"
3+
ng-options="l.code as (l.flag + ' ' + l.label) for l in c.languages"
4+
ng-change="c.changeLang()">
5+
</select>
6+
</div>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
(function() {
2+
var user = gs.getUser();
3+
data.user_id = user.getID();
4+
5+
var grUser = new GlideRecord('sys_user');
6+
if (grUser.get(data.user_id)) {
7+
data.language = grUser.getValue('preferred_language') || 'en';
8+
}
9+
})();

0 commit comments

Comments
 (0)