From 948dc848e51a60b70b356dcd2c196e08330e3eba Mon Sep 17 00:00:00 2001 From: Charanjeet <35090930+Charanjet@users.noreply.github.com> Date: Sat, 18 Oct 2025 14:35:48 +0530 Subject: [PATCH 01/10] Implement SimpleGlideAggregate class for aggregations --- .../SimpleGlideAggregate.js | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 Server-Side Components/Script Includes/SimpleGlideAggregate/SimpleGlideAggregate.js diff --git a/Server-Side Components/Script Includes/SimpleGlideAggregate/SimpleGlideAggregate.js b/Server-Side Components/Script Includes/SimpleGlideAggregate/SimpleGlideAggregate.js new file mode 100644 index 0000000000..0334807b61 --- /dev/null +++ b/Server-Side Components/Script Includes/SimpleGlideAggregate/SimpleGlideAggregate.js @@ -0,0 +1,102 @@ +var SimpleGlideAggregate = Class.create(); +SimpleGlideAggregate.prototype = { + initialize: function(tableName) { + if (!tableName) { + throw new Error("Table name is required."); + } + this._table = tableName; + this._ga = new GlideAggregate(tableName); + this._fields = []; + this._conditionsAdded = false; + }, + + /** + * Adds a query condition. + * Usage: addQuery('priority', '=', '1') or addQuery('active', true) + */ + addQuery: function(field, operator, value) { + if (value === undefined) { + this._ga.addQuery(field, operator); + } else { + this._ga.addQuery(field, operator, value); + } + this._conditionsAdded = true; + return this; + }, + + /** + * Adds COUNT aggregate. + */ + count: function() { + this._fields.push({type: 'COUNT', field: null}); + return this; + }, + + /** + * Adds SUM aggregate on a field. + */ + sum: function(field) { + if (!field) throw new Error("Field name required for sum."); + this._fields.push({type: 'SUM', field: field}); + return this; + }, + + /** + * Adds MIN aggregate on a field. + */ + min: function(field) { + if (!field) throw new Error("Field name required for min."); + this._fields.push({type: 'MIN', field: field}); + return this; + }, + + /** + * Adds MAX aggregate on a field. + */ + max: function(field) { + if (!field) throw new Error("Field name required for max."); + this._fields.push({type: 'MAX', field: field}); + return this; + }, + + /** + * Executes the aggregate query and returns results as an object. + * Keys are aggregate type or type_field (for field aggregates). + */ + execute: function() { + var self = this; + + if (this._fields.length === 0) { + throw new Error("At least one aggregate function must be added."); + } + + this._fields.forEach(function(agg) { + if (agg.field) { + self._ga.addAggregate(agg.type, agg.field); + } else { + self._ga.addAggregate(agg.type); + } + }); + + this._ga.query(); + + var results = {}; + if (this._ga.next()) { + this._fields.forEach(function(agg) { + var key = agg.field ? agg.type + '_' + agg.field : agg.type; + var value = agg.field ? self._ga.getAggregate(agg.type, agg.field) : self._ga.getAggregate(agg.type); + results[key] = agg.type === 'COUNT' ? parseInt(value, 10) : parseFloat(value); + }); + } else { + // No rows matched, all aggregates 0 or null + this._fields.forEach(function(agg) { + var key = agg.field ? agg.type + '_' + agg.field : agg.type; + results[key] = 0; + }); + } + + return results; + }, + + type: 'SimpleGlideAggregate' +}; From c71406accdd808c3cf40ce7d17d675bb23eae923 Mon Sep 17 00:00:00 2001 From: Charanjeet <35090930+Charanjet@users.noreply.github.com> Date: Sat, 18 Oct 2025 14:43:13 +0530 Subject: [PATCH 02/10] Add SimpleGlideAggregate utility with usage examples Introduced SimpleGlideAggregate utility to simplify and enhance GlideAggregate usage in ServiceNow. Provides a chainable API for common aggregation operations and includes sample usage. --- .../SimpleGlideAggregate/Readme.md | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 Server-Side Components/Script Includes/SimpleGlideAggregate/Readme.md diff --git a/Server-Side Components/Script Includes/SimpleGlideAggregate/Readme.md b/Server-Side Components/Script Includes/SimpleGlideAggregate/Readme.md new file mode 100644 index 0000000000..e1992df515 --- /dev/null +++ b/Server-Side Components/Script Includes/SimpleGlideAggregate/Readme.md @@ -0,0 +1,40 @@ +SimpleGlideAggregate Utility +**Overview** +SimpleGlideAggregate is a developer utility Script Include for ServiceNow that provides a simplified, chainable API around the native GlideAggregate class. It abstracts complexities of writing aggregation queries and returns results in an easy-to-use JavaScript object format. +Because OOTB glideAggregate API is little bit different so I tried to create a new function with a simper version. +**Purpose** +Simplify aggregate queries such as COUNT, SUM, MIN, and MAX for developers, especially those less familiar with GlideAggregate methods. +Provide an intuitive interface for common aggregation operations with chaining support. +Facilitate viewing aggregate results alongside individual records matching the same criteria for better analysis. + +**Sample Usage of the functions :** + var sga = new SimpleGlideAggregate('incident'); + + // Build query and add all supported aggregates + var results = sga + .addQuery('active', true) // Filter: active incidents only + .addQuery('priority', '>=', 2) // Priority 2 or higher + .count() // Count matching records + .sum('duration') // Sum of duration field instead of impact + .min('priority') // Minimum priority value in results + .max('sys_updated_on') // Most recent update timestamp + .execute(); + + gs.info('Aggregate Results:'); + gs.info('Count: ' + results.COUNT); + gs.info('Sum of Duration: ' + (results.SUM_duration !== undefined ? results.SUM_duration : 'N/A')); + gs.info('Minimum Priority: ' + (results.MIN_priority !== undefined ? results.MIN_priority : 'N/A')); + gs.info('Most Recent Update (max sys_updated_on timestamp): ' + (results.MAX_sys_updated_on !== undefined ? results.MAX_sys_updated_on : 'N/A')); + + // Optionally fetch some matching record details to complement the aggregate data + var gr = new GlideRecord('incident'); + gr.addQuery('active', true); + gr.addQuery('priority', '>=', 2); + gr.orderByDesc('sys_updated_on'); + gr.setLimit(5); + gr.query(); + + gs.info('Sample Matching Incidents:'); + while (gr.next()) { + gs.info('Number: ' + gr.getValue('number') + ', Priority: ' + gr.getValue('priority') + ', Updated: ' + gr.getValue('sys_updated_on')); + } From 2d4d6b44c39a8d3deb1d7a2ad42dd35e04ef583f Mon Sep 17 00:00:00 2001 From: Charanjeet <35090930+Charanjet@users.noreply.github.com> Date: Sat, 18 Oct 2025 15:17:57 +0530 Subject: [PATCH 03/10] Add SimpleGlideAggregate.js to GlideAggregate folder --- .../GlideAggregate/SimpleGlideAggregate.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Server-Side Components/Script Includes/SimpleGlideAggregate/Readme.md => Core ServiceNow APIs/GlideAggregate/SimpleGlideAggregate.js (100%) diff --git a/Server-Side Components/Script Includes/SimpleGlideAggregate/Readme.md b/Core ServiceNow APIs/GlideAggregate/SimpleGlideAggregate.js similarity index 100% rename from Server-Side Components/Script Includes/SimpleGlideAggregate/Readme.md rename to Core ServiceNow APIs/GlideAggregate/SimpleGlideAggregate.js From 19307ead860caacc0659330493c33fcb1b1f20b9 Mon Sep 17 00:00:00 2001 From: Charanjeet <35090930+Charanjet@users.noreply.github.com> Date: Sat, 18 Oct 2025 15:19:23 +0530 Subject: [PATCH 04/10] Move file to GlideAggregate folder --- .../GlideAggregate }/SimpleGlideAggregate.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {Server-Side Components/Script Includes/SimpleGlideAggregate => Core ServiceNow APIs/GlideAggregate }/SimpleGlideAggregate.js (100%) diff --git a/Server-Side Components/Script Includes/SimpleGlideAggregate/SimpleGlideAggregate.js b/Core ServiceNow APIs/GlideAggregate /SimpleGlideAggregate.js similarity index 100% rename from Server-Side Components/Script Includes/SimpleGlideAggregate/SimpleGlideAggregate.js rename to Core ServiceNow APIs/GlideAggregate /SimpleGlideAggregate.js From 7e3234d3c2cfcf780279d389e6b2086ddc60ce86 Mon Sep 17 00:00:00 2001 From: Charanjeet <35090930+Charanjet@users.noreply.github.com> Date: Sat, 18 Oct 2025 15:19:44 +0530 Subject: [PATCH 05/10] Rename SimpleGlideAggregate.js to Readme.md --- .../GlideAggregate/{SimpleGlideAggregate.js => Readme.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Core ServiceNow APIs/GlideAggregate/{SimpleGlideAggregate.js => Readme.md} (100%) diff --git a/Core ServiceNow APIs/GlideAggregate/SimpleGlideAggregate.js b/Core ServiceNow APIs/GlideAggregate/Readme.md similarity index 100% rename from Core ServiceNow APIs/GlideAggregate/SimpleGlideAggregate.js rename to Core ServiceNow APIs/GlideAggregate/Readme.md From dcdbbcad044243a194b969c005bffdba04b6e8bb Mon Sep 17 00:00:00 2001 From: Charanjeet <35090930+Charanjet@users.noreply.github.com> Date: Sat, 18 Oct 2025 15:21:37 +0530 Subject: [PATCH 06/10] Move files to the appropriate folder --- .../GlideAggregate/{ => SimpleGlideAggregate}/Readme.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Core ServiceNow APIs/GlideAggregate/{ => SimpleGlideAggregate}/Readme.md (100%) diff --git a/Core ServiceNow APIs/GlideAggregate/Readme.md b/Core ServiceNow APIs/GlideAggregate/SimpleGlideAggregate/Readme.md similarity index 100% rename from Core ServiceNow APIs/GlideAggregate/Readme.md rename to Core ServiceNow APIs/GlideAggregate/SimpleGlideAggregate/Readme.md From 4820ed51f70e099439754abb19a47599b58cf301 Mon Sep 17 00:00:00 2001 From: Charanjeet <35090930+Charanjet@users.noreply.github.com> Date: Sat, 18 Oct 2025 15:24:01 +0530 Subject: [PATCH 07/10] Move file to appropriate folder --- .../SimpleGlideAggregate}/SimpleGlideAggregate.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Core ServiceNow APIs/{GlideAggregate => GlideAggregate/SimpleGlideAggregate}/SimpleGlideAggregate.js (100%) diff --git a/Core ServiceNow APIs/GlideAggregate /SimpleGlideAggregate.js b/Core ServiceNow APIs/GlideAggregate/SimpleGlideAggregate/SimpleGlideAggregate.js similarity index 100% rename from Core ServiceNow APIs/GlideAggregate /SimpleGlideAggregate.js rename to Core ServiceNow APIs/GlideAggregate/SimpleGlideAggregate/SimpleGlideAggregate.js From b3dc8d9f4ab4ecc910172008ea1f681ef9fa6583 Mon Sep 17 00:00:00 2001 From: Charanjeet <35090930+Charanjet@users.noreply.github.com> Date: Sat, 18 Oct 2025 15:45:18 +0530 Subject: [PATCH 08/10] Add API Token Expiry Warning script This script checks for OAuth tokens nearing expiry and sends a warning email to specified recipients. --- .../API Token Expiry Warning.js | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 Server-Side Components/Scheduled Jobs/API Token Expiry Warning/API Token Expiry Warning.js diff --git a/Server-Side Components/Scheduled Jobs/API Token Expiry Warning/API Token Expiry Warning.js b/Server-Side Components/Scheduled Jobs/API Token Expiry Warning/API Token Expiry Warning.js new file mode 100644 index 0000000000..6b43987d6b --- /dev/null +++ b/Server-Side Components/Scheduled Jobs/API Token Expiry Warning/API Token Expiry Warning.js @@ -0,0 +1,53 @@ +(function() { + + // Configuration via system properties + var warningDays = parseInt(gs.getProperty('api.token.expiry.warning.days', '7'), 10); // Days before expiry to warn + var emailRecipients = gs.getProperty('api.token.expiry.email.recipients', 'admin@example.com'); // Comma-separated emails + + // Current time and warning threshold time + var now = new GlideDateTime(); + var warningDate = new GlideDateTime(); + warningDate.addDays(warningDays); + + // Query oauth_credential records with expires_on between now and warningDate + var gr = new GlideRecord('oauth_credential'); + gr.addQuery('expires_on', '>=', now); + gr.addQuery('expires_on', '<=', warningDate); + gr.addQuery('active', '=', true); // Only active tokens + gr.orderBy('expires_on'); + gr.query(); + + if (!gr.hasNext()) { + gs.info('No OAuth credentials nearing expiry within ' + warningDays + ' days.'); + return; + } + + // Build notification email body + var emailBody = '
The following OAuth credentials are set to expire within ' + warningDays + ' days:
'; + emailBody += '| Name | User | Client ID | Expires On |
|---|---|---|---|
| ' + gr.getDisplayValue('name') + ' | '; + emailBody += '' + gr.getDisplayValue('user') + ' | '; + emailBody += '' + gr.getValue('client_id') + ' | '; + emailBody += '' + gr.getDisplayValue('expires_on') + ' | '; + emailBody += '
Please review and renew tokens to avoid integration failures.
'; + + // Send the email + var mail = new GlideEmailOutbound(); + mail.setFrom('no-reply@yourdomain.com'); + mail.setSubject('[ServiceNow] OAuth API Token Expiry Warning'); + mail.setTo(emailRecipients); + mail.setBody(emailBody); + mail.setContentType('text/html'); + mail.send(); + + gs.info('OAuth token expiry warning email sent to: ' + emailRecipients); + +})(); From 6bb5fe58c593b013339f15ee7b33d7cea3d01c27 Mon Sep 17 00:00:00 2001 From: Charanjeet <35090930+Charanjet@users.noreply.github.com> Date: Sat, 18 Oct 2025 15:47:14 +0530 Subject: [PATCH 09/10] Add Readme for API Token Expiry Warning script Added documentation for the API Token Expiry Warning script, detailing its purpose and benefits. --- .../Scheduled Jobs/API Token Expiry Warning/Readme.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 Server-Side Components/Scheduled Jobs/API Token Expiry Warning/Readme.md diff --git a/Server-Side Components/Scheduled Jobs/API Token Expiry Warning/Readme.md b/Server-Side Components/Scheduled Jobs/API Token Expiry Warning/Readme.md new file mode 100644 index 0000000000..4ec5e8a490 --- /dev/null +++ b/Server-Side Components/Scheduled Jobs/API Token Expiry Warning/Readme.md @@ -0,0 +1,5 @@ +API Token Expiry Warning Script +**Overview** +This script provides proactive monitoring and alerting for OAuth API tokens stored in the ServiceNow oauth_credential table. It identifies tokens nearing expiry within a configurable countdown window and sends notification emails to administrators, helping prevent sudden integration failures due to expired credentials. +**Problem Solved** +API tokens used for integrations have expiration timestamps after which they become invalid. Without early warnings, tokens can expire unnoticed, causing integration outages, failed API calls, and increased support incidents. This solution enables administrators to receive timely alerts allowing proactive token renewal and smoother integration continuity. From 70beb2137cb1a82a2ac6b51840c4084fdc5ec62f Mon Sep 17 00:00:00 2001 From: Charanjeet <35090930+Charanjet@users.noreply.github.com> Date: Sat, 18 Oct 2025 15:51:53 +0530 Subject: [PATCH 10/10] Delete Server-Side Components/Scheduled Jobs/API Token Expiry Warning directory --- .../API Token Expiry Warning.js | 53 ------------------- .../API Token Expiry Warning/Readme.md | 5 -- 2 files changed, 58 deletions(-) delete mode 100644 Server-Side Components/Scheduled Jobs/API Token Expiry Warning/API Token Expiry Warning.js delete mode 100644 Server-Side Components/Scheduled Jobs/API Token Expiry Warning/Readme.md diff --git a/Server-Side Components/Scheduled Jobs/API Token Expiry Warning/API Token Expiry Warning.js b/Server-Side Components/Scheduled Jobs/API Token Expiry Warning/API Token Expiry Warning.js deleted file mode 100644 index 6b43987d6b..0000000000 --- a/Server-Side Components/Scheduled Jobs/API Token Expiry Warning/API Token Expiry Warning.js +++ /dev/null @@ -1,53 +0,0 @@ -(function() { - - // Configuration via system properties - var warningDays = parseInt(gs.getProperty('api.token.expiry.warning.days', '7'), 10); // Days before expiry to warn - var emailRecipients = gs.getProperty('api.token.expiry.email.recipients', 'admin@example.com'); // Comma-separated emails - - // Current time and warning threshold time - var now = new GlideDateTime(); - var warningDate = new GlideDateTime(); - warningDate.addDays(warningDays); - - // Query oauth_credential records with expires_on between now and warningDate - var gr = new GlideRecord('oauth_credential'); - gr.addQuery('expires_on', '>=', now); - gr.addQuery('expires_on', '<=', warningDate); - gr.addQuery('active', '=', true); // Only active tokens - gr.orderBy('expires_on'); - gr.query(); - - if (!gr.hasNext()) { - gs.info('No OAuth credentials nearing expiry within ' + warningDays + ' days.'); - return; - } - - // Build notification email body - var emailBody = 'The following OAuth credentials are set to expire within ' + warningDays + ' days:
'; - emailBody += '| Name | User | Client ID | Expires On |
|---|---|---|---|
| ' + gr.getDisplayValue('name') + ' | '; - emailBody += '' + gr.getDisplayValue('user') + ' | '; - emailBody += '' + gr.getValue('client_id') + ' | '; - emailBody += '' + gr.getDisplayValue('expires_on') + ' | '; - emailBody += '
Please review and renew tokens to avoid integration failures.
'; - - // Send the email - var mail = new GlideEmailOutbound(); - mail.setFrom('no-reply@yourdomain.com'); - mail.setSubject('[ServiceNow] OAuth API Token Expiry Warning'); - mail.setTo(emailRecipients); - mail.setBody(emailBody); - mail.setContentType('text/html'); - mail.send(); - - gs.info('OAuth token expiry warning email sent to: ' + emailRecipients); - -})(); diff --git a/Server-Side Components/Scheduled Jobs/API Token Expiry Warning/Readme.md b/Server-Side Components/Scheduled Jobs/API Token Expiry Warning/Readme.md deleted file mode 100644 index 4ec5e8a490..0000000000 --- a/Server-Side Components/Scheduled Jobs/API Token Expiry Warning/Readme.md +++ /dev/null @@ -1,5 +0,0 @@ -API Token Expiry Warning Script -**Overview** -This script provides proactive monitoring and alerting for OAuth API tokens stored in the ServiceNow oauth_credential table. It identifies tokens nearing expiry within a configurable countdown window and sends notification emails to administrators, helping prevent sudden integration failures due to expired credentials. -**Problem Solved** -API tokens used for integrations have expiration timestamps after which they become invalid. Without early warnings, tokens can expire unnoticed, causing integration outages, failed API calls, and increased support incidents. This solution enables administrators to receive timely alerts allowing proactive token renewal and smoother integration continuity.