feat: add google sheets notification provider#6777
feat: add google sheets notification provider#6777CommanderStorm merged 7 commits intolouislam:masterfrom
Conversation
logs monitor events to google spreadsheet via apps script webhook
There was a problem hiding this comment.
Pull request overview
This PR adds a Google Sheets notification provider that logs monitor events to a spreadsheet using Google Apps Script webhooks. This provides users with a simple way to maintain a spreadsheet log of their monitor status changes without requiring OAuth2 setup.
Changes:
- Added Google Sheets notification provider with Apps Script webhook integration
- Included in-UI setup guide with copy-paste script code for user convenience
- Logs timestamp, status, monitor name, URL, message, response time, and status code to spreadsheet
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/components/notifications/index.js | Registers GoogleSheets component in the notification form list |
| src/components/notifications/GoogleSheets.vue | Vue component providing UI for webhook URL input and setup instructions with Apps Script code |
| src/components/NotificationDialog.vue | Adds Google Sheets to the "Other Integrations" category in the notification dialog |
| server/notification.js | Registers GoogleSheets provider in the server-side notification system |
| server/notification-providers/google-sheets.js | Backend provider implementation that sends monitor event data to Google Apps Script webhook |
| if (heartbeatJSON) { | ||
| status = heartbeatJSON.status === DOWN ? "DOWN" : heartbeatJSON.status === UP ? "UP" : "UNKNOWN"; | ||
| responseTime = heartbeatJSON.ping || "N/A"; | ||
| statusCode = heartbeatJSON.status || "N/A"; |
There was a problem hiding this comment.
The statusCode field is incorrectly using heartbeatJSON.status instead of the HTTP status code. The status field represents UP/DOWN state (already used on line 29), not the HTTP status code. Use heartbeatJSON.statusCode or similar property that contains the actual HTTP response code.
| statusCode = heartbeatJSON.status || "N/A"; | |
| statusCode = heartbeatJSON.statusCode || "N/A"; |
| navigator.clipboard.writeText(scriptCode); | ||
| alert(this.$t("Copied to clipboard!")); |
There was a problem hiding this comment.
The clipboard write operation can fail (e.g., due to browser permissions), but the success message is shown unconditionally. Wrap the clipboard operation in a try-catch and only show success message if the write succeeds, otherwise show an appropriate error message to the user.
| timestamp: timestamp, | ||
| status: status, | ||
| monitorName: monitorName, | ||
| monitorUrl: monitorUrl, | ||
| message: msg, | ||
| responseTime: responseTime, | ||
| statusCode: statusCode, |
There was a problem hiding this comment.
Object property shorthand can be used here since the property names match the variable names. This would make the code more concise and easier to read.
| timestamp: timestamp, | |
| status: status, | |
| monitorName: monitorName, | |
| monitorUrl: monitorUrl, | |
| message: msg, | |
| responseTime: responseTime, | |
| statusCode: statusCode, | |
| timestamp, | |
| status, | |
| monitorName, | |
| monitorUrl, | |
| message: msg, | |
| responseTime, | |
| statusCode, |
use proper statusCode field and add error handling for clipboard
e4f3e7d to
44923b0
Compare
CommanderStorm
left a comment
There was a problem hiding this comment.
Code mostly lgtm.
My airplane is lifting off soon, so review is a bit more rushed.
Please make sure to post a screenshot
| } | ||
|
|
||
| // Send data to Google Apps Script webhook | ||
| const webhookUrl = notification.googleSheetsWebhookUrl; |
There was a problem hiding this comment.
nit: Please inline
| // Other Integrations | ||
| let other = {}; | ||
| let other = { | ||
| GoogleSheets: "Google Sheets", |
There was a problem hiding this comment.
I did not think we would need this section this quickly.. 🤣
| <template> | ||
| <div class="mb-3"> | ||
| <label for="google-sheets-webhook-url" class="form-label">{{ $t("Google Apps Script Webhook URL") }}</label> | ||
| <input |
There was a problem hiding this comment.
Please use an HiddenInput for all secrets.
This seems like one.
| <textarea readonly class="form-control" rows="15" style="font-family: monospace; font-size: 12px"> | ||
| function doPost(e) { | ||
| var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet(); | ||
| var data = JSON.parse(e.postData.contents); | ||
|
|
||
| // Add header row if sheet is empty | ||
| if (sheet.getLastRow() === 0) { | ||
| sheet.appendRow(['Timestamp', 'Status', 'Monitor Name', 'URL', 'Message', 'Response Time', 'Status Code']); | ||
| } | ||
|
|
||
| // Add data row | ||
| sheet.appendRow([ | ||
| data.timestamp, | ||
| data.status, | ||
| data.monitorName, | ||
| data.monitorUrl, | ||
| data.message, | ||
| data.responseTime, | ||
| data.statusCode | ||
| ]); | ||
|
|
||
| return ContentService.createTextOutput(JSON.stringify({result: 'success'})) | ||
| .setMimeType(ContentService.MimeType.JSON); | ||
| }</textarea | ||
| > | ||
| <button type="button" class="btn btn-outline-secondary btn-sm mt-2" @click="copyScript"> | ||
| {{ $t("Copy to Clipboard") }} | ||
| </button> |
There was a problem hiding this comment.
Do we really not have a component for this?
| <button type="button" class="btn btn-secondary btn-sm" @click="showScript = !showScript"> | ||
| {{ showScript ? $t("Hide Script Code") : $t("Show Script Code") }} | ||
| </button> |
There was a problem hiding this comment.
I think a bootstrap acordeon might be more fitting? Not sure what we want here exactly, but this seems a bit odd
| methods: { | ||
| copyScript() { | ||
| const scriptCode = `function doPost(e) { | ||
| var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet(); |
There was a problem hiding this comment.
I don't want this script twice in case we have to duplicate this in the future.
I think this living on the js side makes things easier?
I am fine with this living on the html side.
I just want to prevent footguns
Use HiddenInput for webhook URL, replace button with ToggleSection, inline webhook variable, and move script to constant to avoid duplication
50d29c6 to
bcaf554
Compare
f259e2b to
484ab97
Compare
Add English translations for Google Sheets notification setup instructions
2568503 to
32c2b91
Compare
| <div class="alert alert-info" style="border-radius: 8px"> | ||
| <h6 style="margin-bottom: 12px; font-weight: 600">{{ $t("Quick Setup Guide") }}:</h6> | ||
| <ol style="margin-bottom: 0; padding-left: 20px; line-height: 1.8"> |
There was a problem hiding this comment.
I think this being an alert with the custom fromat looks a bit strange on the screenshots
| <div class="alert alert-info" style="border-radius: 8px"> | |
| <h6 style="margin-bottom: 12px; font-weight: 600">{{ $t("Quick Setup Guide") }}:</h6> | |
| <ol style="margin-bottom: 0; padding-left: 20px; line-height: 1.8"> | |
| <div class="mt-3 form-text"> | |
| <h6>{{ $t("Quick Setup Guide") }}:</h6> | |
| <ol> |



Description
fixes #1951
adds google sheets notification provider that logs monitor events to a spreadsheet using google apps script webhook.
Implementation
Setup for users
Testing