-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathutils.js
More file actions
191 lines (167 loc) · 5.88 KB
/
utils.js
File metadata and controls
191 lines (167 loc) · 5.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
/**
* Utility Functions for Automated Proposal Generation System
*
* This module provides common utility functions used throughout the application:
* - UUID generation for unique identifiers
* - Email validation
* - Retry logic with exponential backoff
* - Admin notification system
* - Data formatting helpers
*/
/**
* Generate a UUID v4 (random UUID)
* Uses a simple implementation suitable for Google Apps Script
*
* @return {String} UUID in format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
*
* @example
* const id = generateUUID();
* // Returns: "a1b2c3d4-e5f6-4789-a012-b3c4d5e6f7a8"
*/
function generateUUID() {
// UUID v4 format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
// where x is any hexadecimal digit and y is one of 8, 9, A, or B
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
const r = Math.random() * 16 | 0;
const v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
/**
* Validate email address format
* Uses RFC 5322 compliant regex pattern
*
* @param {String} email - Email address to validate
* @return {Boolean} True if email is valid, false otherwise
*
* @example
* validateEmail('user@example.com'); // Returns: true
* validateEmail('invalid.email'); // Returns: false
* validateEmail(''); // Returns: false
* validateEmail(null); // Returns: false
*/
function validateEmail(email) {
// Return false for null, undefined, or empty strings
if (!email || typeof email !== 'string') {
return false;
}
// RFC 5322 compliant email regex (simplified but robust)
// Matches: local-part@domain
// - local-part: alphanumeric, dots, hyphens, underscores, plus signs
// - domain: alphanumeric, dots, hyphens with valid TLD
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
return emailRegex.test(email.trim());
}
/**
* Retry an operation with exponential backoff
* Useful for handling transient failures in Google Workspace API calls
*
* @param {Function} operation - Function to execute (should return a value or throw on error)
* @param {Number} maxRetries - Maximum number of retry attempts (default: 3)
* @param {Number} initialDelayMs - Initial delay in milliseconds (default: 1000)
* @return {*} Result of the operation if successful
* @throws {Error} Last error if all retries fail
*
* @example
* const result = retryOperation(() => {
* return SpreadsheetApp.openById(sheetId);
* }, 3, 1000);
*
* @example
* // With custom retry count
* const doc = retryOperation(() => {
* return DocumentApp.openById(docId);
* }, 5, 500);
*/
function retryOperation(operation, maxRetries, initialDelayMs) {
// Set defaults
maxRetries = maxRetries || 3;
initialDelayMs = initialDelayMs || 1000;
let lastError;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
// Attempt the operation
return operation();
} catch (error) {
lastError = error;
// If this was the last attempt, throw the error
if (attempt === maxRetries) {
Logger.log(`Operation failed after ${maxRetries + 1} attempts: ${error.toString()}`);
throw error;
}
// Calculate exponential backoff delay: initialDelay * 2^attempt
const delayMs = initialDelayMs * Math.pow(2, attempt);
Logger.log(`Attempt ${attempt + 1} failed: ${error.toString()}. Retrying in ${delayMs}ms...`);
// Wait before retrying
Utilities.sleep(delayMs);
}
}
// This should never be reached, but just in case
throw lastError;
}
/**
* Send notification email to system administrators
* Used for error reporting and system alerts
*
* @param {String} subject - Email subject line
* @param {String} message - Email body (plain text)
* @param {Object} options - Optional parameters
* @param {String} options.htmlBody - HTML version of the message
* @param {Boolean} options.includeStackTrace - Include stack trace if available (default: true)
* @param {Error} options.error - Error object to include in notification
*
* @example
* notifyAdmin('Proposal Generation Failed', 'Unable to create document for client XYZ');
*
* @example
* // With error object
* try {
* // some operation
* } catch (error) {
* notifyAdmin('Error in Proposal Generator', 'An error occurred', { error: error });
* }
*/
function notifyAdmin(subject, message, options) {
options = options || {};
try {
// Get admin email from CONFIG
const adminEmail = CONFIG.EMAIL.adminEmail;
if (!adminEmail || !validateEmail(adminEmail)) {
Logger.log('Cannot send admin notification: Invalid or missing admin email in CONFIG');
return;
}
// Build email body
let emailBody = message;
// Add error details if provided
if (options.error) {
emailBody += '\n\n--- Error Details ---\n';
emailBody += `Error: ${options.error.toString()}\n`;
if (options.includeStackTrace !== false && options.error.stack) {
emailBody += `\nStack Trace:\n${options.error.stack}`;
}
}
// Add timestamp
emailBody += `\n\n--- System Information ---\n`;
emailBody += `Timestamp: ${new Date().toISOString()}\n`;
emailBody += `Script: Proposal Generator\n`;
// Prepare email options
const emailOptions = {
name: CONFIG.BRANDING.companyName + ' - Proposal Generator System'
};
// Add HTML body if provided
if (options.htmlBody) {
emailOptions.htmlBody = options.htmlBody;
}
// Send the notification
GmailApp.sendEmail(
adminEmail,
`[Proposal Generator] ${subject}`,
emailBody,
emailOptions
);
Logger.log(`Admin notification sent: ${subject}`);
} catch (error) {
// Log error but don't throw - we don't want notification failures to break the main flow
Logger.log(`Failed to send admin notification: ${error.toString()}`);
}
}