commit 787d6bc85e9e1640fc9b82235cd8d34ed3a5f1c1 Author: marco Date: Wed Jan 18 19:53:20 2023 +0100 Initial commit diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..ca93b3f --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 marco@sdf.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..5f76a91 --- /dev/null +++ b/README.md @@ -0,0 +1,51 @@ +# Apps Script Showcase + +![screenshot showing ](./screenshot.png) + +--- +This is an example how one could build a frontend based on Google Sheets and Apps Script to generate a JSON document which serves as a configuration input for an SLA monitoring service. + +The SLA monitoring service expects a JSON document in the following structure (array of objects containing configuration parameters). + +```json +[ + { + "defg-5678__used_payment_methods" : { + "schedule" : "* * * * 1", + "account_identifier" : "", + "included_payment_methods" : "", + "check_name" : "used_payment_methods", + "additional_configuration" : "", + "organisation_identifier" : "defg-5678", + "excluded_payment_methods" : "" + } + }, + { + "hijk-1234__used_payment_methods" : { + "schedule" : "* * * * 2-5,7", + "account_identifier" : "", + "included_payment_methods" : "", + "organisation_identifier" : "hijk-1234", + "additional_configuration" : "", + "check_name" : "used_payment_methods", + "excluded_payment_methods" : "ECA" + } + }, + { + "abcd-1234__used_payment_methods" : { + "additional_configuration" : "VIS:3,ECA:5", + "organisation_identifier" : "abcd-1234", + "check_name" : "used_payment_methods", + "excluded_payment_methods" : "", + "schedule" : "* * * * 1-5", + "included_payment_methods" : "VIS,ECA,FOO", + "account_identifier" : "efgh-567" + } + } +] +``` + +The idea is that a support-agent can enter the configuration parameters (per customer/organisation and account) into a Google Sheet and then "deploy" them. +Valid configurations will then be available via an API (see `doGet()` in the [apps-script/web](https://developers.google.com/apps-script/guides/web) documentation) for the SLA monitoring service to poll. + +See [src/Script.js](./src/Script.js) for how the deployment and validation works and [src/SidebarTemplate.html](./src/SidebarTemplate.html) for how the sidebar is built. diff --git a/screenshot.png b/screenshot.png new file mode 100644 index 0000000..f86c1e8 Binary files /dev/null and b/screenshot.png differ diff --git a/src/Script.js b/src/Script.js new file mode 100644 index 0000000..832e0e2 --- /dev/null +++ b/src/Script.js @@ -0,0 +1,116 @@ +let scriptProperties = PropertiesService.getScriptProperties(); +let supportedChecks = [ + 'used_payment_methods', +]; + +function onOpen() { + SpreadsheetApp.getUi() + .createMenu('Monitoring 👀') + .addItem('Deploy configurations', 'deploy') + .addToUi(); +} + +function doGet() { + return ContentService.createTextOutput(JSON.stringify(getStoredChecks())).setMimeType(ContentService.MimeType.JSON); +} + +function storeChecksById(checksById) { + scriptProperties.setProperties(checksById); +} + +function getStoredChecks() { + arr = []; + Object.entries(scriptProperties.getProperties()).forEach(([key, value]) => { + obj = {}; + obj[key] = JSON.parse(value); + arr.push(obj); + }); + return arr; +} + +function getStoredCheckIds() { + return Object.keys(getStoredChecks()); +} + +function getCheckId(check) { + return check['organisation_identifier'] + '__' + check['check_name']; +} + +function validateCheck(check) { + let errors = []; + if (getCheckId(check).split('__').includes('')) { + errors.push('Check is missing `organisation_identifier` or `check_name`'); + } + if (!supportedChecks.includes(check['check_name']) && check['check_name'] !== '') { + errors.push('Check is not supported: `' + check['check_name'] + '`'); + } + if (check['schedule'] && !/^[1-7]+((,[1-7])|(-[1-7]))*$/.test(check['schedule'])) { + errors.push('Please specify the schedule as a comma separated list and/or range of weekday numbers between 1 and 7, like for example: `1-5`, `1,3,5,7` or `1-3,5`'); + } else { + check['schedule'] = '* * * * ' + check['schedule']; + } + if ('' !== check['included_payment_methods'] !== check['excluded_payment_methods']) { + re = /^([A-Z]+,)*[A-Z]+$|^$/; + if (!re.test(check['included_payment_methods']) || !re.test(check['excluded_payment_methods'])) { + errors.push('Please specify payment methods as a comma separated list, like for example: `VIS,ECA` '); + } + if (!check['included_payment_methods'] === !check['excluded_payment_methods']) { + errors.push('Please specify only either `included_payment_methods` or `excluded_payment_methods`, or leave both fields empty to check all payment methods'); + } + } + return errors; +} + +function getValidatedChecksById(checks) { + let arr = []; + checks.forEach(function(check) { + let obj = {}; + obj[getCheckId(check)] = {} + obj[getCheckId(check)]['check'] = check; + obj[getCheckId(check)]['errors'] = validateCheck(check); + arr.push(obj) + }); + return arr; +} + +function validateAndStoreChecks(checks) { + let validatedChecksById = getValidatedChecksById(checks); + let validChecks = validatedChecksById.filter(check => {return check[Object.keys(check)[0]]['errors'].length === 0}); + + let checksToStore = {}; + validChecks.forEach(function (check) { + let key = Object.keys(check)[0]; + let value = JSON.stringify(check[key]['check']); + checksToStore[key] = value; + }); + + storeChecksById(checksToStore); + + let storedCheckIdsToRemove = getStoredCheckIds().map(c => c).filter(check => !validChecks.map(c => Object.keys(c)[0]).includes(check)); + storedCheckIdsToRemove.forEach(function (check_name) { + scriptProperties.deleteProperty(check_name); + }); + + return validatedChecksById; +} + +function deploy() { + let html = HtmlService.createTemplateFromFile('DeploymentsSidebarTemplate'); + let data = SpreadsheetApp.getActive().getSheetByName("Payments Monitoring Configurations").getDataRange().getValues(); + + scriptProperties.deleteAllProperties(); + + let checks = []; + for (let i = 0; i < data.length; i++) { + check = {}; + for (let j = 0; j < data[0].length; j++) { + check[data[0][j]] = data[i][j]; + } + checks.push(check); + } + checks.shift(); + + html.deployments = validateAndStoreChecks(checks); + + SpreadsheetApp.getUi().showSidebar(html.evaluate()); +} diff --git a/src/SidebarTemplate.html b/src/SidebarTemplate.html new file mode 100644 index 0000000..64fcfa7 --- /dev/null +++ b/src/SidebarTemplate.html @@ -0,0 +1,42 @@ + + + + + + + + +

🚀 Deployments

+
+ +
+
+ (row ) +
+ +
+ +
+ + +