Initial commit

This commit is contained in:
marco 2023-01-18 19:53:20 +01:00 committed by marco
commit 787d6bc85e
5 changed files with 230 additions and 0 deletions

21
LICENSE.md Normal file
View File

@ -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.

51
README.md Normal file
View File

@ -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.

BIN
screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 KiB

116
src/Script.js Normal file
View File

@ -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());
}

42
src/SidebarTemplate.html Normal file
View File

@ -0,0 +1,42 @@
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<style>
body {
font-family: Arial, Helvetica, sans-serif;
}
dl {
font-size: 14px;
}
dd {
display: list-item;
list-style-type: disc;
}
hr {
border: 1px solid black;
}
table, th, td {
border: 1px solid red;
}
</style>
</head>
<body>
<h1>🚀 Deployments</h1>
<hr />
<? for (var i = 0; i < deployments.length; i++) {
var errors = deployments[i][Object.keys(deployments[i])[0]]['errors'];
var sig = errors.length === 0 ? '✅' : '🛑';
?>
<dl>
<dt><?= sig ?> <?= Object.keys(deployments[i]) ?>
(row <?= (i + 2) ?>)
</dt>
<? for (var j = 0; j < errors.length; j++) { ?>
<dd><?= errors[j] ?></dd>
<? } ?>
</dl>
<? } ?>
</body>
</html>