From e0d4d190e9170f67deed87ce8d28fbb2e1be0f3a Mon Sep 17 00:00:00 2001 From: Poojitha Valli Pogaku Date: Mon, 20 Oct 2025 19:02:38 +0530 Subject: [PATCH 1/3] Field Progress Bar Counter --- .../Field Progress Bar Counter/README.md | 66 +++++++++++++++++ .../Field Progress Bar Counter/script.js | 72 +++++++++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 Client-Side Components/Client Scripts/Field Progress Bar Counter/README.md create mode 100644 Client-Side Components/Client Scripts/Field Progress Bar Counter/script.js diff --git a/Client-Side Components/Client Scripts/Field Progress Bar Counter/README.md b/Client-Side Components/Client Scripts/Field Progress Bar Counter/README.md new file mode 100644 index 0000000000..435c06cce9 --- /dev/null +++ b/Client-Side Components/Client Scripts/Field Progress Bar Counter/README.md @@ -0,0 +1,66 @@ +# Field Progress Bar Counter + +## Overview +This client script adds a visual progress bar indicator when users type in text fields, showing how much of the field's maximum length has been used. It provides a more intuitive way to track character limits compared to simple numeric counters. + +## Features +- Visual progress bar that updates in real-time +- Color-coded feedback (green, yellow, red) based on usage +- Percentage indicator alongside the progress bar +- Smooth transitions between states +- Works with any text field with a character limit + +## Requirements +- ServiceNow instance +- Client Script execution rights +- Text fields with character limits (e.g., short_description, description) + +## Implementation Steps +1. Navigate to System Definition → Client Scripts +2. Create a new Client Script with these settings: + - Table: Choose your target table (e.g., incident, sc_req_item) + - Type: onChange + - Field name: Your target field (e.g., short_description) +3. Copy the code from script.js into your client script +4. Configure the maxLength variable if needed +5. Save and test + +## Configuration +The script can be customized by modifying these variables: +```javascript +var maxLength = 160; // Maximum characters allowed +var warningThreshold = 0.7; // Show yellow at 70% capacity +var criticalThreshold = 0.9; // Show red at 90% capacity +``` + +## How It Works +1. The script creates a progress bar div element below the field +2. As the user types, it calculates the percentage of used characters +3. The progress bar fills proportionally to character usage +4. Colors change based on defined thresholds: + - Green: Normal usage + - Yellow: Approaching limit + - Red: Near/at limit + +## Benefits +- Improved user experience with visual feedback +- Reduces likelihood of hitting character limits unexpectedly +- Helps users self-regulate content length +- Modern, professional appearance +- Zero server calls - all client-side + +## Usage Example +When implementing on an Incident's short description: +```javascript +function onChange(control, oldValue, newValue, isLoading) { + if (isLoading || newValue === oldValue) { + return; + } + showProgressBar(control, newValue); +} +``` + +## Compatibility +- Works in all modern browsers +- Compatible with both classic and next-experience UIs +- Responsive design adapts to field width \ No newline at end of file diff --git a/Client-Side Components/Client Scripts/Field Progress Bar Counter/script.js b/Client-Side Components/Client Scripts/Field Progress Bar Counter/script.js new file mode 100644 index 0000000000..9e6abdfae2 --- /dev/null +++ b/Client-Side Components/Client Scripts/Field Progress Bar Counter/script.js @@ -0,0 +1,72 @@ +function onLoad() { + var fieldName = 'short_description'; // Change to your field name + var maxLength = 160; + var warningThreshold = 0.7; + var criticalThreshold = 0.9; + + var fieldElement = g_form.getControl(fieldName); + if (!fieldElement) return; + + // Add progress bar initially + addProgressBar(fieldElement, fieldElement.value); + + // Attach keyup event for real-time updates + fieldElement.addEventListener('keyup', function() { + addProgressBar(fieldElement, fieldElement.value); + }); +} + +function addProgressBar(fieldElement, value) { + var maxLength = 160; + var warningThreshold = 0.7; + var criticalThreshold = 0.9; + + // Remove any existing progress bar + var existingBar = document.getElementById(fieldElement.name + '_progress'); + if (existingBar) { + existingBar.parentNode.removeChild(existingBar); + } + + // Create progress bar container + var container = document.createElement('div'); + container.id = fieldElement.name + '_progress'; + container.style.cssText = 'width: 100%; height: 4px; background: #e0e0e0; margin-top: 4px; border-radius: 2px; transition: all 0.3s ease;'; + + // Create progress bar fill + var fill = document.createElement('div'); + fill.style.cssText = 'height: 100%; width: 0%; border-radius: 2px; transition: all 0.3s ease;'; + + // Calculate percentage + var percent = (value.length / maxLength) * 100; + percent = Math.min(percent, 100); + + // Set fill width and color + fill.style.width = percent + '%'; + if (percent >= criticalThreshold * 100) { + fill.style.backgroundColor = '#ff4444'; + } else if (percent >= warningThreshold * 100) { + fill.style.backgroundColor = '#ffbb33'; + } else { + fill.style.backgroundColor = '#00C851'; + } + + // Create percentage label + var label = document.createElement('div'); + label.style.cssText = 'font-size: 11px; color: #666; margin-top: 2px; text-align: right;'; + label.textContent = Math.round(percent) + '% used'; + + // Assemble and insert the progress bar + container.appendChild(fill); + container.appendChild(label); + + // Insert after the field element + var parent = fieldElement.parentNode; + parent.insertBefore(container, fieldElement.nextSibling); + + // Add warning message if over limit + if (value.length > maxLength) { + g_form.addErrorMessage('This field exceeds the maximum length of ' + maxLength + ' characters'); + } else { + g_form.clearMessages(); + } +} \ No newline at end of file From e9cd3088fe01a63d80b737cf78472e72716feee6 Mon Sep 17 00:00:00 2001 From: Poojitha Valli Pogaku Date: Mon, 20 Oct 2025 19:03:24 +0530 Subject: [PATCH 2/3] Delete Client-Side Components/Client Scripts/Field Progress Bar Counter directory --- .../Field Progress Bar Counter/README.md | 66 ----------------- .../Field Progress Bar Counter/script.js | 72 ------------------- 2 files changed, 138 deletions(-) delete mode 100644 Client-Side Components/Client Scripts/Field Progress Bar Counter/README.md delete mode 100644 Client-Side Components/Client Scripts/Field Progress Bar Counter/script.js diff --git a/Client-Side Components/Client Scripts/Field Progress Bar Counter/README.md b/Client-Side Components/Client Scripts/Field Progress Bar Counter/README.md deleted file mode 100644 index 435c06cce9..0000000000 --- a/Client-Side Components/Client Scripts/Field Progress Bar Counter/README.md +++ /dev/null @@ -1,66 +0,0 @@ -# Field Progress Bar Counter - -## Overview -This client script adds a visual progress bar indicator when users type in text fields, showing how much of the field's maximum length has been used. It provides a more intuitive way to track character limits compared to simple numeric counters. - -## Features -- Visual progress bar that updates in real-time -- Color-coded feedback (green, yellow, red) based on usage -- Percentage indicator alongside the progress bar -- Smooth transitions between states -- Works with any text field with a character limit - -## Requirements -- ServiceNow instance -- Client Script execution rights -- Text fields with character limits (e.g., short_description, description) - -## Implementation Steps -1. Navigate to System Definition → Client Scripts -2. Create a new Client Script with these settings: - - Table: Choose your target table (e.g., incident, sc_req_item) - - Type: onChange - - Field name: Your target field (e.g., short_description) -3. Copy the code from script.js into your client script -4. Configure the maxLength variable if needed -5. Save and test - -## Configuration -The script can be customized by modifying these variables: -```javascript -var maxLength = 160; // Maximum characters allowed -var warningThreshold = 0.7; // Show yellow at 70% capacity -var criticalThreshold = 0.9; // Show red at 90% capacity -``` - -## How It Works -1. The script creates a progress bar div element below the field -2. As the user types, it calculates the percentage of used characters -3. The progress bar fills proportionally to character usage -4. Colors change based on defined thresholds: - - Green: Normal usage - - Yellow: Approaching limit - - Red: Near/at limit - -## Benefits -- Improved user experience with visual feedback -- Reduces likelihood of hitting character limits unexpectedly -- Helps users self-regulate content length -- Modern, professional appearance -- Zero server calls - all client-side - -## Usage Example -When implementing on an Incident's short description: -```javascript -function onChange(control, oldValue, newValue, isLoading) { - if (isLoading || newValue === oldValue) { - return; - } - showProgressBar(control, newValue); -} -``` - -## Compatibility -- Works in all modern browsers -- Compatible with both classic and next-experience UIs -- Responsive design adapts to field width \ No newline at end of file diff --git a/Client-Side Components/Client Scripts/Field Progress Bar Counter/script.js b/Client-Side Components/Client Scripts/Field Progress Bar Counter/script.js deleted file mode 100644 index 9e6abdfae2..0000000000 --- a/Client-Side Components/Client Scripts/Field Progress Bar Counter/script.js +++ /dev/null @@ -1,72 +0,0 @@ -function onLoad() { - var fieldName = 'short_description'; // Change to your field name - var maxLength = 160; - var warningThreshold = 0.7; - var criticalThreshold = 0.9; - - var fieldElement = g_form.getControl(fieldName); - if (!fieldElement) return; - - // Add progress bar initially - addProgressBar(fieldElement, fieldElement.value); - - // Attach keyup event for real-time updates - fieldElement.addEventListener('keyup', function() { - addProgressBar(fieldElement, fieldElement.value); - }); -} - -function addProgressBar(fieldElement, value) { - var maxLength = 160; - var warningThreshold = 0.7; - var criticalThreshold = 0.9; - - // Remove any existing progress bar - var existingBar = document.getElementById(fieldElement.name + '_progress'); - if (existingBar) { - existingBar.parentNode.removeChild(existingBar); - } - - // Create progress bar container - var container = document.createElement('div'); - container.id = fieldElement.name + '_progress'; - container.style.cssText = 'width: 100%; height: 4px; background: #e0e0e0; margin-top: 4px; border-radius: 2px; transition: all 0.3s ease;'; - - // Create progress bar fill - var fill = document.createElement('div'); - fill.style.cssText = 'height: 100%; width: 0%; border-radius: 2px; transition: all 0.3s ease;'; - - // Calculate percentage - var percent = (value.length / maxLength) * 100; - percent = Math.min(percent, 100); - - // Set fill width and color - fill.style.width = percent + '%'; - if (percent >= criticalThreshold * 100) { - fill.style.backgroundColor = '#ff4444'; - } else if (percent >= warningThreshold * 100) { - fill.style.backgroundColor = '#ffbb33'; - } else { - fill.style.backgroundColor = '#00C851'; - } - - // Create percentage label - var label = document.createElement('div'); - label.style.cssText = 'font-size: 11px; color: #666; margin-top: 2px; text-align: right;'; - label.textContent = Math.round(percent) + '% used'; - - // Assemble and insert the progress bar - container.appendChild(fill); - container.appendChild(label); - - // Insert after the field element - var parent = fieldElement.parentNode; - parent.insertBefore(container, fieldElement.nextSibling); - - // Add warning message if over limit - if (value.length > maxLength) { - g_form.addErrorMessage('This field exceeds the maximum length of ' + maxLength + ' characters'); - } else { - g_form.clearMessages(); - } -} \ No newline at end of file From 70c8cd0af49a36c33f0570e5bec4ce27afcec36c Mon Sep 17 00:00:00 2001 From: Poojitha Valli Pogaku Date: Mon, 20 Oct 2025 19:28:08 +0530 Subject: [PATCH 3/3] Email Template Debugger --- .../Email Template Debugger/README.md | 143 +++++++ .../Email Template Debugger/debugger_page.js | 349 ++++++++++++++++++ .../Email Template Debugger/script.js | 228 ++++++++++++ 3 files changed, 720 insertions(+) create mode 100644 Server-Side Components/Script Includes/Email Template Debugger/README.md create mode 100644 Server-Side Components/Script Includes/Email Template Debugger/debugger_page.js create mode 100644 Server-Side Components/Script Includes/Email Template Debugger/script.js diff --git a/Server-Side Components/Script Includes/Email Template Debugger/README.md b/Server-Side Components/Script Includes/Email Template Debugger/README.md new file mode 100644 index 0000000000..20ae879771 --- /dev/null +++ b/Server-Side Components/Script Includes/Email Template Debugger/README.md @@ -0,0 +1,143 @@ +# Email Template Debugger + +## Overview +A powerful utility for ServiceNow developers to debug and preview email notifications in real-time. This tool helps visualize how email templates will render with different data contexts, test notification conditions, and troubleshoot email-related issues without sending actual emails. + +## Features +- Live preview of email templates +- Variable substitution testing +- HTML/Plain text toggle view +- Attachment validation +- Template syntax checking +- Recipient list validation +- Condition script testing +- Email script debugging +- Performance metrics + +## Requirements +- ServiceNow instance with admin access +- Notification management rights +- Script Include access +- Email administration rights + +## Implementation Steps +1. Create a new Script Include using script.js +2. Set up the debugging page using debugger_page.js +3. Configure access controls +4. Import any required style sheets +5. Test with sample notifications + +## Components + +### Script Include +- Handles template processing +- Manages variable substitution +- Validates email scripts +- Processes attachments +- Checks recipient lists + +### Debugging Interface +- Template preview panel +- Variable input section +- Script testing area +- Results display +- Error highlighting + +## Usage Example +```javascript +var emailDebugger = new EmailTemplateDebugger(); +var result = emailDebugger.debugTemplate({ + notificationId: 'sys_id_of_notification', + testRecord: 'sys_id_of_test_record', + recipientList: ['user1@example.com'], + variables: { + 'incident.number': 'INC0010001', + 'incident.short_description': 'Test incident' + } +}); +``` + +## Features in Detail + +### Template Analysis +- Syntax validation +- Missing variable detection +- Script error identification +- HTML structure verification +- CSS compatibility check + +### Performance Monitoring +- Template processing time +- Script execution metrics +- Database query impact +- Attachment processing time +- Overall generation time + +### Security Checks +- Recipient validation +- Domain verification +- Script injection prevention +- Attachment size validation +- Permission verification + +### Debugging Tools +- Step-by-step template processing +- Variable resolution tracking +- Script execution logging +- Error stack traces +- Query optimization hints + +## Best Practices +1. Always test with sample data first +2. Verify all variable substitutions +3. Check both HTML and plain text versions +4. Validate attachment handling +5. Test with different record types +6. Monitor performance metrics +7. Review security implications + +## Error Handling +The debugger provides detailed error information for: +- Syntax errors in templates +- Missing or invalid variables +- Script execution failures +- Recipient list issues +- Attachment problems +- Permission errors + +## Performance Considerations +- Cache frequently used templates +- Optimize script execution +- Batch process attachments +- Minimize database queries +- Use efficient variable substitution + +## Security Notes +- Validate all input data +- Check recipient permissions +- Sanitize variable content +- Verify attachment types +- Monitor script execution + +## Troubleshooting +Common issues and solutions: +1. Template not found + - Verify notification sys_id + - Check access permissions +2. Variable substitution fails + - Confirm variable names + - Check data types +3. Script errors + - Review script syntax + - Check variable scope +4. Attachment issues + - Verify file permissions + - Check size limits + +## Extensions +The debugger can be extended with: +- Custom validation rules +- Additional preview formats +- New debugging tools +- Performance analyzers +- Security checkers \ No newline at end of file diff --git a/Server-Side Components/Script Includes/Email Template Debugger/debugger_page.js b/Server-Side Components/Script Includes/Email Template Debugger/debugger_page.js new file mode 100644 index 0000000000..2756d5e4aa --- /dev/null +++ b/Server-Side Components/Script Includes/Email Template Debugger/debugger_page.js @@ -0,0 +1,349 @@ +// Client-side debugger interface +var g_emailDebugger = Class.create({ + initialize: function() { + this.debuggerUI = this._createDebuggerUI(); + this.currentTemplate = null; + this._attachEventHandlers(); + }, + + _createDebuggerUI: function() { + var container = new Element('div', { + 'class': 'email-debugger-container' + }); + + // Create header + var header = new Element('div', { + 'class': 'debugger-header' + }); + header.insert(new Element('h2').update('Email Template Debugger')); + container.insert(header); + + // Create main content area + var content = new Element('div', { + 'class': 'debugger-content' + }); + + // Template selector + content.insert(this._createTemplateSelector()); + + // Test data section + content.insert(this._createTestDataSection()); + + // Preview section + content.insert(this._createPreviewSection()); + + // Debug log section + content.insert(this._createDebugSection()); + + container.insert(content); + + // Add styles + this._addStyles(); + + return container; + }, + + _createTemplateSelector: function() { + var section = new Element('div', { + 'class': 'debugger-section' + }); + + section.insert(new Element('h3').update('Select Template')); + + var selector = new Element('select', { + 'class': 'template-selector' + }); + this._loadTemplates(selector); + + section.insert(selector); + return section; + }, + + _createTestDataSection: function() { + var section = new Element('div', { + 'class': 'debugger-section' + }); + + section.insert(new Element('h3').update('Test Data')); + + // Record selector + var recordInput = new Element('input', { + 'type': 'text', + 'placeholder': 'Record sys_id', + 'class': 'test-record-input' + }); + section.insert(recordInput); + + // Variables editor + var variablesEditor = new Element('textarea', { + 'class': 'variables-editor', + 'placeholder': 'Enter test variables in JSON format' + }); + section.insert(variablesEditor); + + // Test button + var testButton = new Element('button', { + 'class': 'test-button' + }).update('Test Template'); + section.insert(testButton); + + return section; + }, + + _createPreviewSection: function() { + var section = new Element('div', { + 'class': 'debugger-section preview-section' + }); + + section.insert(new Element('h3').update('Preview')); + + // View toggles + var toggles = new Element('div', { + 'class': 'view-toggles' + }); + toggles.insert(new Element('button', { + 'class': 'toggle-html active' + }).update('HTML')); + toggles.insert(new Element('button', { + 'class': 'toggle-plain' + }).update('Plain Text')); + section.insert(toggles); + + // Preview iframe + var preview = new Element('iframe', { + 'class': 'preview-frame' + }); + section.insert(preview); + + return section; + }, + + _createDebugSection: function() { + var section = new Element('div', { + 'class': 'debugger-section debug-section' + }); + + section.insert(new Element('h3').update('Debug Log')); + + var log = new Element('div', { + 'class': 'debug-log' + }); + section.insert(log); + + return section; + }, + + _loadTemplates: function(selector) { + // Load available email templates + var ga = new GlideAjax('EmailTemplateDebugger'); + ga.addParam('sysparm_name', 'getTemplateList'); + ga.getXMLAnswer(function(answer) { + var templates = JSON.parse(answer); + templates.forEach(function(template) { + selector.insert(new Element('option', { + 'value': template.sys_id + }).update(template.name)); + }); + }); + }, + + _attachEventHandlers: function() { + var self = this; + + // Template selection + this.debuggerUI.down('.template-selector').observe('change', function(e) { + self._loadTemplate(e.target.value); + }); + + // Test button + this.debuggerUI.down('.test-button').observe('click', function() { + self._runTest(); + }); + + // View toggles + this.debuggerUI.down('.view-toggles').observe('click', function(e) { + if (e.target.hasClassName('toggle-html')) { + self._showHtmlView(); + } else if (e.target.hasClassName('toggle-plain')) { + self._showPlainView(); + } + }); + }, + + _loadTemplate: function(templateId) { + var ga = new GlideAjax('EmailTemplateDebugger'); + ga.addParam('sysparm_name', 'debugTemplate'); + ga.addParam('sysparm_template_id', templateId); + ga.getXMLAnswer(this._updatePreview.bind(this)); + }, + + _runTest: function() { + var testData = { + record: this.debuggerUI.down('.test-record-input').value, + variables: this._parseVariables() + }; + + var ga = new GlideAjax('EmailTemplateDebugger'); + ga.addParam('sysparm_name', 'debugTemplate'); + ga.addParam('sysparm_test_data', JSON.stringify(testData)); + ga.getXMLAnswer(this._updatePreview.bind(this)); + }, + + _parseVariables: function() { + try { + return JSON.parse(this.debuggerUI.down('.variables-editor').value); + } catch (e) { + this._logError('Invalid variables JSON: ' + e.message); + return {}; + } + }, + + _updatePreview: function(response) { + var result = JSON.parse(response); + + if (result.status === 'success') { + this._updatePreviewContent(result.data.preview); + this._updateDebugLog(result.data.debug); + this._updateMetrics(result.data.metrics); + } else { + this._logError(result.message); + } + }, + + _updatePreviewContent: function(preview) { + var frame = this.debuggerUI.down('.preview-frame'); + var doc = frame.contentDocument || frame.contentWindow.document; + doc.open(); + doc.write(preview.html); + doc.close(); + }, + + _updateDebugLog: function(log) { + var logContainer = this.debuggerUI.down('.debug-log'); + logContainer.update(''); + + log.forEach(function(entry) { + var logEntry = new Element('div', { + 'class': 'log-entry' + }); + logEntry.insert(new Element('span', { + 'class': 'timestamp' + }).update('[' + entry.timestamp + 'ms] ')); + logEntry.insert(new Element('span', { + 'class': 'message' + }).update(entry.message)); + + if (entry.data) { + logEntry.insert(new Element('pre', { + 'class': 'data' + }).update(JSON.stringify(entry.data, null, 2))); + } + + logContainer.insert(logEntry); + }); + }, + + _updateMetrics: function(metrics) { + var metricsHtml = '
'; + Object.keys(metrics).forEach(function(key) { + metricsHtml += '
' + + '' + key + ': ' + + '' + metrics[key] + 'ms' + + '
'; + }); + metricsHtml += '
'; + + this.debuggerUI.down('.debug-section').insert({ + top: new Element('div').update(metricsHtml) + }); + }, + + _logError: function(message) { + var logContainer = this.debuggerUI.down('.debug-log'); + logContainer.insert({ + top: new Element('div', { + 'class': 'error-entry' + }).update(message) + }); + }, + + _addStyles: function() { + var styles = ` + .email-debugger-container { + padding: 20px; + background: #f5f5f5; + border-radius: 4px; + font-family: 'Helvetica Neue', Arial, sans-serif; + } + + .debugger-section { + margin-bottom: 20px; + padding: 15px; + background: white; + border-radius: 4px; + box-shadow: 0 1px 3px rgba(0,0,0,0.1); + } + + .preview-frame { + width: 100%; + height: 500px; + border: 1px solid #ddd; + border-radius: 4px; + } + + .debug-log { + max-height: 300px; + overflow-y: auto; + font-family: monospace; + background: #2b2b2b; + color: #e6e6e6; + padding: 10px; + border-radius: 4px; + } + + .log-entry { + margin-bottom: 5px; + line-height: 1.4; + } + + .error-entry { + color: #ff6b6b; + font-weight: bold; + } + + .view-toggles button { + margin-right: 10px; + padding: 5px 15px; + border: 1px solid #ddd; + border-radius: 4px; + background: white; + cursor: pointer; + } + + .view-toggles button.active { + background: #007bff; + color: white; + border-color: #0056b3; + } + + .metrics-summary { + margin-bottom: 15px; + padding: 10px; + background: #e9ecef; + border-radius: 4px; + } + + .metric { + display: inline-block; + margin-right: 20px; + } + + .metric-name { + font-weight: bold; + } + `; + + var styleSheet = new Element('style').update(styles); + $$('head')[0].insert(styleSheet); + } +}); \ No newline at end of file diff --git a/Server-Side Components/Script Includes/Email Template Debugger/script.js b/Server-Side Components/Script Includes/Email Template Debugger/script.js new file mode 100644 index 0000000000..7b780d82ee --- /dev/null +++ b/Server-Side Components/Script Includes/Email Template Debugger/script.js @@ -0,0 +1,228 @@ +var EmailTemplateDebugger = Class.create(); +EmailTemplateDebugger.prototype = Object.extendsObject(AbstractAjaxProcessor, { + initialize: function() { + this.resultCache = {}; + this.debugLog = []; + this.startTime = 0; + this.metrics = {}; + }, + + debugTemplate: function(params) { + try { + this.startDebugSession(); + + // Validate input parameters + if (!this._validateParams(params)) { + throw new Error('Invalid parameters provided'); + } + + // Get notification template + var template = this._getNotificationTemplate(params.notificationId); + this.logDebug('Template retrieved', template); + + // Process variables + var processedTemplate = this._processVariables(template, params.variables); + this.logDebug('Variables processed', processedTemplate); + + // Validate recipients + this._validateRecipients(params.recipientList); + this.logDebug('Recipients validated', params.recipientList); + + // Check conditions + if (!this._evaluateConditions(template, params.testRecord)) { + return this._formatResult('Notification conditions not met', 'warning'); + } + + // Process attachments + var attachments = this._processAttachments(template, params.testRecord); + this.logDebug('Attachments processed', attachments); + + // Generate preview + var preview = this._generatePreview(processedTemplate); + this.logDebug('Preview generated', preview); + + return this._formatResult('Success', 'success', { + preview: preview, + metrics: this.getMetrics(), + debug: this.getDebugLog() + }); + + } catch (e) { + return this._formatResult(e.message, 'error', { + stack: e.stack, + debug: this.getDebugLog() + }); + } + }, + + _validateParams: function(params) { + if (!params || !params.notificationId) { + return false; + } + return true; + }, + + _getNotificationTemplate: function(notificationId) { + var startTime = new Date().getTime(); + + var notification = new GlideRecord('sysevent_email_template'); + if (!notification.get(notificationId)) { + throw new Error('Template not found: ' + notificationId); + } + + this._addMetric('templateRetrieval', new Date().getTime() - startTime); + + return { + subject: notification.getValue('subject'), + body: notification.getValue('message_html'), + plainText: notification.getValue('message'), + conditions: notification.getValue('condition') + }; + }, + + _processVariables: function(template, variables) { + var startTime = new Date().getTime(); + + var processed = { + subject: template.subject, + body: template.body, + plainText: template.plainText + }; + + // Process each variable + Object.keys(variables || {}).forEach(function(key) { + var regex = new RegExp('\\$\\{' + key + '\\}', 'g'); + processed.subject = processed.subject.replace(regex, variables[key]); + processed.body = processed.body.replace(regex, variables[key]); + processed.plainText = processed.plainText.replace(regex, variables[key]); + }); + + this._addMetric('variableProcessing', new Date().getTime() - startTime); + + return processed; + }, + + _validateRecipients: function(recipients) { + var startTime = new Date().getTime(); + + if (!recipients || !recipients.length) { + throw new Error('No recipients specified'); + } + + recipients.forEach(function(email) { + if (!this._isValidEmail(email)) { + throw new Error('Invalid email format: ' + email); + } + }, this); + + this._addMetric('recipientValidation', new Date().getTime() - startTime); + }, + + _isValidEmail: function(email) { + return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); + }, + + _evaluateConditions: function(template, testRecord) { + var startTime = new Date().getTime(); + + if (!template.conditions) { + return true; + } + + try { + var condition = template.conditions; + var gr = new GlideRecord(testRecord.table); + gr.get(testRecord.sys_id); + + var evaluator = new GlideRecordConditionEvaluator(); + var result = evaluator.evaluateCondition(gr, condition); + + this._addMetric('conditionEvaluation', new Date().getTime() - startTime); + + return result; + } catch (e) { + this.logDebug('Condition evaluation error', e.message); + return false; + } + }, + + _processAttachments: function(template, testRecord) { + var startTime = new Date().getTime(); + + var attachments = []; + var gr = new GlideRecord('sys_attachment'); + gr.addQuery('table_sys_id', testRecord.sys_id); + gr.query(); + + while (gr.next()) { + attachments.push({ + name: gr.getValue('file_name'), + size: gr.getValue('size_bytes'), + type: gr.getValue('content_type') + }); + } + + this._addMetric('attachmentProcessing', new Date().getTime() - startTime); + + return attachments; + }, + + _generatePreview: function(processedTemplate) { + var startTime = new Date().getTime(); + + var preview = { + html: this._sanitizeHTML(processedTemplate.body), + plain: processedTemplate.plainText, + subject: processedTemplate.subject + }; + + this._addMetric('previewGeneration', new Date().getTime() - startTime); + + return preview; + }, + + _sanitizeHTML: function(html) { + // Basic HTML sanitization + return html.replace(/)<[^<]*)*<\/script>/gi, '') + .replace(/on\w+="[^"]*"/g, ''); + }, + + startDebugSession: function() { + this.startTime = new Date().getTime(); + this.debugLog = []; + this.metrics = {}; + }, + + logDebug: function(message, data) { + this.debugLog.push({ + timestamp: new Date().getTime() - this.startTime, + message: message, + data: data + }); + }, + + _addMetric: function(name, duration) { + this.metrics[name] = duration; + }, + + getMetrics: function() { + var totalTime = new Date().getTime() - this.startTime; + this.metrics.total = totalTime; + return this.metrics; + }, + + getDebugLog: function() { + return this.debugLog; + }, + + _formatResult: function(message, status, data) { + return { + message: message, + status: status, + timestamp: new Date().getTime(), + data: data || {} + }; + }, + + type: 'EmailTemplateDebugger' +}); \ No newline at end of file