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 = '