Skip to content

Commit cb03b45

Browse files
committed
Improve Google Sheets integration with error handling and retry logic
- Add robust error handling with detailed error messages - Implement retry mechanism for API operations - Refactor service methods for better reliability and structure - Add proper validation for Google Sheets configuration
1 parent a7f3112 commit cb03b45

1 file changed

Lines changed: 111 additions & 18 deletions

File tree

website/modules/@apostrophecms/form/lib/googleSheetsService.js

Lines changed: 111 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,14 @@ const googleSheetsService = {
2424
range: 'Sheet1!A1:Z1',
2525
});
2626

27-
return (
28-
!checkResponse.data.values || checkResponse.data.values.length === 0
29-
);
27+
const needHeaders =
28+
!checkResponse.data.values || checkResponse.data.values.length === 0;
29+
30+
return needHeaders;
3031
} catch (err) {
31-
throw new Error(`Headers check error: ${err.message}`);
32+
// eslint-disable-next-line no-console
33+
console.error(`[SHEETS] Headers check error: ${err.message}`);
34+
return true;
3235
}
3336
},
3437

@@ -60,32 +63,122 @@ const googleSheetsService = {
6063
},
6164

6265
async appendToSheet(sheets, spreadsheetId, values) {
63-
return await sheets.spreadsheets.values.append({
64-
spreadsheetId,
65-
range: 'Sheet1!A1',
66-
valueInputOption: 'RAW',
67-
resource: { values },
66+
try {
67+
const response = await sheets.spreadsheets.values.append({
68+
spreadsheetId,
69+
range: 'Sheet1!A1',
70+
valueInputOption: 'RAW',
71+
resource: { values },
72+
});
73+
return response;
74+
} catch (err) {
75+
// eslint-disable-next-line no-console
76+
console.error(`[SHEETS] Append error: ${err.message}`);
77+
throw err;
78+
}
79+
},
80+
81+
sleep(ms) {
82+
return new Promise((resolve) => {
83+
setTimeout(resolve, ms);
6884
});
6985
},
7086

71-
async sendFormDataToGoogleSheets(formData) {
72-
const { spreadsheetId, auth } = this.getGoogleSheetsClient();
73-
if (!auth) {
74-
throw new Error('Google Sheets auth failed');
87+
async retryOperation(operation, maxRetries = 3, delayMs = 1000) {
88+
let lastError = null;
89+
90+
for (let attempt = 0; attempt < maxRetries; attempt += 1) {
91+
try {
92+
// eslint-disable-next-line no-await-in-loop
93+
return await operation();
94+
} catch (error) {
95+
lastError = error;
96+
const retriesLeft = maxRetries - attempt - 1;
97+
98+
// eslint-disable-next-line no-console
99+
console.error(`Operation failed, retries left: ${retriesLeft}`);
100+
101+
if (retriesLeft <= 0) {
102+
break;
103+
}
104+
105+
// eslint-disable-next-line no-await-in-loop
106+
await this.sleep(delayMs);
107+
}
108+
}
109+
110+
throw lastError;
111+
},
112+
113+
async checkHeadersWithRetry(sheets, spreadsheetId) {
114+
try {
115+
return await this.retryOperation(
116+
() => this.checkNeedHeaders(sheets, spreadsheetId),
117+
3,
118+
1000,
119+
);
120+
} catch (error) {
121+
// eslint-disable-next-line no-console
122+
console.error(
123+
`[SHEETS] Headers check failed after all attempts: ${error.message}`,
124+
);
125+
return null;
75126
}
127+
},
76128

77-
const sheets = google.sheets({ version: 'v4', auth });
129+
async addHeadersIfNeeded(sheets, spreadsheetId, headers) {
130+
const needHeaders = await this.checkHeadersWithRetry(sheets, spreadsheetId);
78131

79-
const { headers, rowData } = this.formatFormData(formData);
80-
const needHeaders = await this.checkNeedHeaders(sheets, spreadsheetId);
132+
if (needHeaders === null) {
133+
return false;
134+
}
81135

82136
if (needHeaders) {
83-
await this.appendToSheet(sheets, spreadsheetId, [headers]);
137+
try {
138+
await this.appendToSheet(sheets, spreadsheetId, [headers]);
139+
} catch (error) {
140+
// eslint-disable-next-line no-console
141+
console.error(`[SHEETS] Failed to add headers: ${error.message}`);
142+
return false;
143+
}
84144
}
85145

86-
await this.appendToSheet(sheets, spreadsheetId, [rowData]);
87146
return true;
88147
},
148+
149+
async sendFormDataToGoogleSheets(formData) {
150+
try {
151+
const { spreadsheetId, auth } = this.getGoogleSheetsClient();
152+
if (!spreadsheetId || !auth) {
153+
// eslint-disable-next-line no-console
154+
console.error('[SHEETS] Missing Google Sheets configuration');
155+
return false;
156+
}
157+
158+
const sheets = google.sheets({ version: 'v4', auth });
159+
const { headers, rowData } = this.formatFormData(formData);
160+
161+
const headersAdded = await this.addHeadersIfNeeded(
162+
sheets,
163+
spreadsheetId,
164+
headers,
165+
);
166+
if (!headersAdded) return false;
167+
168+
try {
169+
await this.appendToSheet(sheets, spreadsheetId, [rowData]);
170+
return true;
171+
} catch (error) {
172+
// eslint-disable-next-line no-console
173+
console.error(`[SHEETS] Failed to send form data: ${error.message}`);
174+
return false;
175+
}
176+
} catch (error) {
177+
// eslint-disable-next-line no-console
178+
console.error(`[SHEETS] Unexpected error: ${error.message}`);
179+
return false;
180+
}
181+
},
89182
};
90183

91184
module.exports = googleSheetsService;

0 commit comments

Comments
 (0)