From 10d623b20b75fe9e5597b49a754b8f685402495d Mon Sep 17 00:00:00 2001 From: AdrianLxM Date: Tue, 6 Mar 2018 22:56:35 +0100 Subject: [PATCH 001/104] mgdl -> mg/dl --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0dd62cf7173..001cb57a5d0 100644 --- a/README.md +++ b/README.md @@ -483,7 +483,7 @@ To learn more about the Nightscout API, visit https://YOUR-SITE.com/api-docs.htm Treatment Profile Fields: * `timezone` (Time Zone) - time zone local to the patient. *Should be set.* - * `units` (Profile Units) - blood glucose units used in the profile, either "mgdl" or "mmol" + * `units` (Profile Units) - blood glucose units used in the profile, either "mg/dl" or "mmol" * `dia` (Insulin duration) - value should be the duration of insulin action to use in calculating how much insulin is left active. Defaults to 3 hours. * `carbs_hr` (Carbs per Hour) - The number of carbs that are processed per hour, for more information see [#DIYPS](http://diyps.org/2014/05/29/determining-your-carbohydrate-absorption-rate-diyps-lessons-learned/). * `carbratio` (Carb Ratio) - grams per unit of insulin. From 4578e17d687fd156da41200282490c524b4928ca Mon Sep 17 00:00:00 2001 From: Caleb Date: Tue, 17 Dec 2019 12:06:07 -0700 Subject: [PATCH 002/104] Release ref update v2 (#5301) * Updated release name and number * Added missing version number * Added missing version number --- docs/plugins/googlehome-plugin.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/plugins/googlehome-plugin.md b/docs/plugins/googlehome-plugin.md index f4bddb4d9cb..ccffb81f404 100644 --- a/docs/plugins/googlehome-plugin.md +++ b/docs/plugins/googlehome-plugin.md @@ -25,7 +25,7 @@ To add Google Home support for your Nightscout site, here's what you need to do: ## Activate the Nightscout Google Home Plugin -1. Your Nightscout site needs to be new enough that it supports the `googlehome` plugin. It needs to be [version 13.0 (Ketchup)](https://github.com/nightscout/cgm-remote-monitor/releases/tag/13.0) or later. See [updating my version](https://github.com/nightscout/cgm-remote-monitor#updating-my-version) if you need a newer version. +1. Your Nightscout site needs to be new enough that it supports the `googlehome` plugin. It needs to be [version 13.0.0 (Ketchup)](https://github.com/nightscout/cgm-remote-monitor/releases/tag/13.0.0) or later. See [updating my version](https://github.com/nightscout/cgm-remote-monitor#updating-my-version) if you need a newer version. 1. Add `googlehome` to the list of plugins in your `ENABLE` setting. ([Environment variables](https://github.com/nightscout/cgm-remote-monitor#environment) are set in the configuration section for your monitor. Typically Azure, Heroku, etc.) ## Create Your DialogFlow Agent From 6021a0a7cbc48656343f4be853941c1becc7bfe6 Mon Sep 17 00:00:00 2001 From: Jeremy Cunningham <34543464+jpcunningh@users.noreply.github.com> Date: Tue, 17 Dec 2019 17:34:25 -0600 Subject: [PATCH 003/104] Fix auth dialog sizing error (#5315) --- lib/hashauth.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/hashauth.js b/lib/hashauth.js index 1421c076535..8848ca08ef0 100644 --- a/lib/hashauth.js +++ b/lib/hashauth.js @@ -91,7 +91,12 @@ hashauth.init = function init(client, $) { hashauth.requestAuthentication = function requestAuthentication (eventOrNext) { var translate = client.translate; hashauth.injectHtml(); - var clientWidth = Math.min(400, $( '#container')[0].clientWidth); + + var clientWidth = window.innerWidth + || document.documentElement.clientWidth + || document.body.clientWidth; + + clientWidth = Math.min(400, clientWidth); $( '#requestauthenticationdialog' ).dialog({ width: clientWidth From 721aa0226c07472e6f721449a28667eda6310ede Mon Sep 17 00:00:00 2001 From: Jeremy Cunningham <34543464+jpcunningh@users.noreply.github.com> Date: Wed, 18 Dec 2019 00:55:09 -0600 Subject: [PATCH 004/104] Fix auth dialog sizing error (#5314) * Fix auth dialog sizing error * Fix Client Init After Auth (cherry picked from commit 1bf416c3bb964ed555850c3fc55977d69980b8e5) * update NS minor version --- lib/client/index.js | 2 +- npm-shrinkwrap.json | 2 +- package.json | 2 +- swagger.json | 2 +- swagger.yaml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/client/index.js b/lib/client/index.js index eb6c4a9fd93..469009a9a94 100644 --- a/lib/client/index.js +++ b/lib/client/index.js @@ -98,7 +98,7 @@ client.init = function init (callback) { // auth failed, hide loader and request for key $('#centerMessagePanel').hide(); client.hashauth.requestAuthentication(function afterRequest () { - client.init(null, callback); + client.init(callback); }); } }); diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 99dbaa768e3..7d3a1730a7d 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,6 +1,6 @@ { "name": "nightscout", - "version": "13.0.0", + "version": "13.0.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 88e92a6f4ed..32ee90d2a33 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nightscout", - "version": "13.0.0", + "version": "13.0.1", "description": "Nightscout acts as a web-based CGM (Continuous Glucose Montinor) to allow multiple caregivers to remotely view a patients glucose data in realtime.", "license": "AGPL-3.0", "author": "Nightscout Team", diff --git a/swagger.json b/swagger.json index 385f73c068f..dce7854e05b 100755 --- a/swagger.json +++ b/swagger.json @@ -8,7 +8,7 @@ "info": { "title": "Nightscout API", "description": "Own your DData with the Nightscout API", - "version": "13.0.0", + "version": "13.0.1", "license": { "name": "AGPL 3", "url": "https://www.gnu.org/licenses/agpl.txt" diff --git a/swagger.yaml b/swagger.yaml index bdb74b652b9..a08f701d7a5 100755 --- a/swagger.yaml +++ b/swagger.yaml @@ -4,7 +4,7 @@ servers: info: title: Nightscout API description: Own your DData with the Nightscout API - version: 13.0.0 + version: 13.0.1 license: name: AGPL 3 url: 'https://www.gnu.org/licenses/agpl.txt' From 14e8c8c229bbd2d2e2279a8c4b830cef973f8de3 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Wed, 18 Dec 2019 09:07:04 +0200 Subject: [PATCH 005/104] Bump version to 13.0.2-dev --- npm-shrinkwrap.json | 2 +- package.json | 2 +- swagger.json | 2 +- swagger.yaml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 7d3a1730a7d..d80078c1143 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,6 +1,6 @@ { "name": "nightscout", - "version": "13.0.1", + "version": "13.0.2-dev", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 32ee90d2a33..4a9b6ed7496 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nightscout", - "version": "13.0.1", + "version": "13.0.2-dev", "description": "Nightscout acts as a web-based CGM (Continuous Glucose Montinor) to allow multiple caregivers to remotely view a patients glucose data in realtime.", "license": "AGPL-3.0", "author": "Nightscout Team", diff --git a/swagger.json b/swagger.json index dce7854e05b..5ff1e1c251e 100755 --- a/swagger.json +++ b/swagger.json @@ -8,7 +8,7 @@ "info": { "title": "Nightscout API", "description": "Own your DData with the Nightscout API", - "version": "13.0.1", + "version": "13.0.2-dev", "license": { "name": "AGPL 3", "url": "https://www.gnu.org/licenses/agpl.txt" diff --git a/swagger.yaml b/swagger.yaml index a08f701d7a5..4b2b13a8276 100755 --- a/swagger.yaml +++ b/swagger.yaml @@ -4,7 +4,7 @@ servers: info: title: Nightscout API description: Own your DData with the Nightscout API - version: 13.0.1 + version: 13.0.2-dev license: name: AGPL 3 url: 'https://www.gnu.org/licenses/agpl.txt' From e0667c9006556e7b2c4a0a796314c16dcf645857 Mon Sep 17 00:00:00 2001 From: Jeremy Cunningham <34543464+jpcunningh@users.noreply.github.com> Date: Fri, 20 Dec 2019 03:13:02 -0600 Subject: [PATCH 006/104] fix some random test failures (#5341) * fix some random test failures * use typeof to test for existence --- lib/client/browser-utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/client/browser-utils.js b/lib/client/browser-utils.js index 4f920588f80..634afb2ab40 100644 --- a/lib/client/browser-utils.js +++ b/lib/client/browser-utils.js @@ -51,7 +51,7 @@ function init ($) { function queryParms () { var params = {}; - if (location.search) { + if ((typeof location !== 'undefined') && location.search) { location.search.substr(1).split('&').forEach(function(item) { // eslint-disable-next-line no-useless-escape params[item.split('=')[0]] = item.split('=')[1].replace(/[_\+]/g, ' '); From 0c9f819d74f3c2fa32957073e45c4908d0be3b45 Mon Sep 17 00:00:00 2001 From: inventor96 Date: Fri, 27 Dec 2019 00:06:02 -0700 Subject: [PATCH 007/104] Added support for asking delta --- lib/api/alexa/index.js | 8 ++++++ lib/api/googlehome/index.js | 8 ++++++ lib/language.js | 52 +++++++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+) diff --git a/lib/api/alexa/index.js b/lib/api/alexa/index.js index 337ec00f732..bdb0cf84a4f 100644 --- a/lib/api/alexa/index.js +++ b/lib/api/alexa/index.js @@ -113,6 +113,14 @@ function configure (app, wares, ctx, env) { }); }, ['bg', 'blood glucose', 'number']); + ctx.alexa.configureIntentHandler('MetricNow', function (callback, slots, sbx, locale) { + if (sbx.properties.delta && sbx.properties.delta.display) { + callback(translate('virtAsstTitleDelta'), translate('virtAsstDelta', {params: [sbx.properties.delta.display]})); + } else { + callback(translate('virtAsstTitleDelta'), translate('virtAsstUnknown')); + } + }, ['delta']); + ctx.alexa.configureIntentHandler('NSStatus', function (callback, slots, sbx, locale) { ctx.alexa.getRollup('Status', sbx, slots, locale, function (status) { callback(translate('virtAsstTitleFullStatus'), status); diff --git a/lib/api/googlehome/index.js b/lib/api/googlehome/index.js index 2b2caa2a378..0e5a70e8ae9 100644 --- a/lib/api/googlehome/index.js +++ b/lib/api/googlehome/index.js @@ -104,6 +104,14 @@ function configure (app, wares, ctx, env) { }); }, ['bg', 'blood glucose', 'number']); + ctx.googleHome.configureIntentHandler('MetricNow', function (callback, slots, sbx, locale) { + if (sbx.properties.delta && sbx.properties.delta.display) { + callback(translate('virtAsstTitleDelta'), translate('virtAsstDelta', {params: [sbx.properties.delta.display]})); + } else { + callback(translate('virtAsstTitleDelta'), translate('virtAsstUnknown')); + } + }, ['delta']); + ctx.googleHome.configureIntentHandler('NSStatus', function (callback, slots, sbx, locale) { ctx.googleHome.getRollup('Status', sbx, slots, locale, function (status) { callback(translate('virtAsstTitleFullStatus'), status); diff --git a/lib/language.js b/lib/language.js index 87fb871e1d1..f9ae8858f6e 100644 --- a/lib/language.js +++ b/lib/language.js @@ -13563,6 +13563,32 @@ function init() { , zh_cn: 'Full Status' , zh_tw: 'Full Status' }, + 'virtAsstTitleDelta': { + bg: 'Blood Glucose Delta' + , cs: 'Blood Glucose Delta' + , de: 'Blood Glucose Delta' + , dk: 'Blood Glucose Delta' + , el: 'Blood Glucose Delta' + , en: 'Blood Glucose Delta' + , es: 'Blood Glucose Delta' + , fi: 'Blood Glucose Delta' + , fr: 'Blood Glucose Delta' + , he: 'Blood Glucose Delta' + , hr: 'Blood Glucose Delta' + , it: 'Blood Glucose Delta' + , ko: 'Blood Glucose Delta' + , nb: 'Blood Glucose Delta' + , pl: 'Blood Glucose Delta' + , pt: 'Blood Glucose Delta' + , ro: 'Blood Glucose Delta' + , nl: 'Blood Glucose Delta' + , ru: 'Blood Glucose Delta' + , sk: 'Blood Glucose Delta' + , sv: 'Blood Glucose Delta' + , tr: 'Blood Glucose Delta' + , zh_cn: 'Blood Glucose Delta' + , zh_tw: 'Blood Glucose Delta' + }, 'virtAsstStatus': { bg: '%1 and %2 as of %3.' , cs: '%1 %2 čas %3.' @@ -14070,6 +14096,32 @@ function init() { , zh_cn: 'You have %1 carbohydrates on board' , zh_tw: 'You have %1 carbohydrates on board' }, + 'virtAsstDelta': { + bg: 'Your last delta was %1' + , cs: 'Your last delta was %1' + , de: 'Your last delta was %1' + , dk: 'Your last delta was %1' + , el: 'Your last delta was %1' + , en: 'Your last delta was %1' + , es: 'Your last delta was %1' + , fi: 'Your last delta was %1' + , fr: 'Your last delta was %1' + , he: 'Your last delta was %1' + , hr: 'Your last delta was %1' + , it: 'Your last delta was %1' + , ko: 'Your last delta was %1' + , nb: 'Your last delta was %1' + , nl: 'Your last delta was %1' + , pl: 'Your last delta was %1' + , pt: 'Your last delta was %1' + , ro: 'Your last delta was %1' + , ru: 'Your last delta was %1' + , sk: 'Your last delta was %1' + , sv: 'Your last delta was %1' + , tr: 'Your last delta was %1' + , zh_cn: 'Your last delta was %1' + , zh_tw: 'Your last delta was %1' + }, 'virtAsstUnknownIntentTitle': { en: 'Unknown Intent' , cs: 'Unknown Intent' From a4c0425d2b970d0c3d997768131b7652037e4fbc Mon Sep 17 00:00:00 2001 From: inventor96 Date: Fri, 27 Dec 2019 00:21:45 -0700 Subject: [PATCH 008/104] Added time reference --- lib/api/alexa/index.js | 15 ++++++++++-- lib/api/googlehome/index.js | 13 +++++++++- lib/language.js | 48 ++++++++++++++++++------------------- 3 files changed, 49 insertions(+), 27 deletions(-) diff --git a/lib/api/alexa/index.js b/lib/api/alexa/index.js index bdb0cf84a4f..d76593f4449 100644 --- a/lib/api/alexa/index.js +++ b/lib/api/alexa/index.js @@ -102,7 +102,7 @@ function configure (app, wares, ctx, env) { } else { direction = records[0].direction; } - var status = translate('virtAsstStatus', { + var status = translate('virtAsstStatus', { params: [ sbx.scaleMgdl(records[0].sgv), direction, @@ -115,7 +115,18 @@ function configure (app, wares, ctx, env) { ctx.alexa.configureIntentHandler('MetricNow', function (callback, slots, sbx, locale) { if (sbx.properties.delta && sbx.properties.delta.display) { - callback(translate('virtAsstTitleDelta'), translate('virtAsstDelta', {params: [sbx.properties.delta.display]})); + entries.list({count: 1}, function(err, records) { + + callback( + translate('virtAsstTitleDelta'), + translate('virtAsstDelta', { + params: [ + sbx.properties.delta.display, + moment(records[0].date).from(moment(sbx.time)) + ] + }) + ); + }); } else { callback(translate('virtAsstTitleDelta'), translate('virtAsstUnknown')); } diff --git a/lib/api/googlehome/index.js b/lib/api/googlehome/index.js index 0e5a70e8ae9..de96227b6b4 100644 --- a/lib/api/googlehome/index.js +++ b/lib/api/googlehome/index.js @@ -106,7 +106,18 @@ function configure (app, wares, ctx, env) { ctx.googleHome.configureIntentHandler('MetricNow', function (callback, slots, sbx, locale) { if (sbx.properties.delta && sbx.properties.delta.display) { - callback(translate('virtAsstTitleDelta'), translate('virtAsstDelta', {params: [sbx.properties.delta.display]})); + entries.list({count: 1}, function(err, records) { + + callback( + translate('virtAsstTitleDelta'), + translate('virtAsstDelta', { + params: [ + sbx.properties.delta.display, + moment(records[0].date).from(moment(sbx.time)) + ] + }) + ); + }); } else { callback(translate('virtAsstTitleDelta'), translate('virtAsstUnknown')); } diff --git a/lib/language.js b/lib/language.js index f9ae8858f6e..cf2a1dbe49c 100644 --- a/lib/language.js +++ b/lib/language.js @@ -14097,30 +14097,30 @@ function init() { , zh_tw: 'You have %1 carbohydrates on board' }, 'virtAsstDelta': { - bg: 'Your last delta was %1' - , cs: 'Your last delta was %1' - , de: 'Your last delta was %1' - , dk: 'Your last delta was %1' - , el: 'Your last delta was %1' - , en: 'Your last delta was %1' - , es: 'Your last delta was %1' - , fi: 'Your last delta was %1' - , fr: 'Your last delta was %1' - , he: 'Your last delta was %1' - , hr: 'Your last delta was %1' - , it: 'Your last delta was %1' - , ko: 'Your last delta was %1' - , nb: 'Your last delta was %1' - , nl: 'Your last delta was %1' - , pl: 'Your last delta was %1' - , pt: 'Your last delta was %1' - , ro: 'Your last delta was %1' - , ru: 'Your last delta was %1' - , sk: 'Your last delta was %1' - , sv: 'Your last delta was %1' - , tr: 'Your last delta was %1' - , zh_cn: 'Your last delta was %1' - , zh_tw: 'Your last delta was %1' + bg: 'Your delta was %1 as of %2.' + , cs: 'Your delta was %1 as of %2.' + , de: 'Your delta was %1 as of %2.' + , dk: 'Your delta was %1 as of %2.' + , el: 'Your delta was %1 as of %2.' + , en: 'Your delta was %1 as of %2.' + , es: 'Your delta was %1 as of %2.' + , fi: 'Your delta was %1 as of %2.' + , fr: 'Your delta was %1 as of %2.' + , he: 'Your delta was %1 as of %2.' + , hr: 'Your delta was %1 as of %2.' + , it: 'Your delta was %1 as of %2.' + , ko: 'Your delta was %1 as of %2.' + , nb: 'Your delta was %1 as of %2.' + , nl: 'Your delta was %1 as of %2.' + , pl: 'Your delta was %1 as of %2.' + , pt: 'Your delta was %1 as of %2.' + , ro: 'Your delta was %1 as of %2.' + , ru: 'Your delta was %1 as of %2.' + , sk: 'Your delta was %1 as of %2.' + , sv: 'Your delta was %1 as of %2.' + , tr: 'Your delta was %1 as of %2.' + , zh_cn: 'Your delta was %1 as of %2.' + , zh_tw: 'Your delta was %1 as of %2.' }, 'virtAsstUnknownIntentTitle': { en: 'Unknown Intent' From a7e49b37667cec5457ea43c0734289a0c6dcbd22 Mon Sep 17 00:00:00 2001 From: inventor96 Date: Fri, 27 Dec 2019 00:55:31 -0700 Subject: [PATCH 009/104] Made delta reading more informative --- lib/api/alexa/index.js | 6 ++--- lib/api/googlehome/index.js | 6 ++--- lib/language.js | 48 ++++++++++++++++++------------------- 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/lib/api/alexa/index.js b/lib/api/alexa/index.js index d76593f4449..b08a95cc34b 100644 --- a/lib/api/alexa/index.js +++ b/lib/api/alexa/index.js @@ -115,14 +115,14 @@ function configure (app, wares, ctx, env) { ctx.alexa.configureIntentHandler('MetricNow', function (callback, slots, sbx, locale) { if (sbx.properties.delta && sbx.properties.delta.display) { - entries.list({count: 1}, function(err, records) { - + entries.list({count: 2}, function(err, records) { callback( translate('virtAsstTitleDelta'), translate('virtAsstDelta', { params: [ sbx.properties.delta.display, - moment(records[0].date).from(moment(sbx.time)) + moment(records[0].date).from(moment(sbx.time)), + moment(records[1].date).from(moment(records[0].date)) ] }) ); diff --git a/lib/api/googlehome/index.js b/lib/api/googlehome/index.js index de96227b6b4..f3f98ef0e77 100644 --- a/lib/api/googlehome/index.js +++ b/lib/api/googlehome/index.js @@ -106,14 +106,14 @@ function configure (app, wares, ctx, env) { ctx.googleHome.configureIntentHandler('MetricNow', function (callback, slots, sbx, locale) { if (sbx.properties.delta && sbx.properties.delta.display) { - entries.list({count: 1}, function(err, records) { - + entries.list({count: 2}, function(err, records) { callback( translate('virtAsstTitleDelta'), translate('virtAsstDelta', { params: [ sbx.properties.delta.display, - moment(records[0].date).from(moment(sbx.time)) + moment(records[0].date).from(moment(sbx.time)), + moment(records[1].date).from(moment(records[0].date)) ] }) ); diff --git a/lib/language.js b/lib/language.js index cf2a1dbe49c..2348f2ea787 100644 --- a/lib/language.js +++ b/lib/language.js @@ -14097,30 +14097,30 @@ function init() { , zh_tw: 'You have %1 carbohydrates on board' }, 'virtAsstDelta': { - bg: 'Your delta was %1 as of %2.' - , cs: 'Your delta was %1 as of %2.' - , de: 'Your delta was %1 as of %2.' - , dk: 'Your delta was %1 as of %2.' - , el: 'Your delta was %1 as of %2.' - , en: 'Your delta was %1 as of %2.' - , es: 'Your delta was %1 as of %2.' - , fi: 'Your delta was %1 as of %2.' - , fr: 'Your delta was %1 as of %2.' - , he: 'Your delta was %1 as of %2.' - , hr: 'Your delta was %1 as of %2.' - , it: 'Your delta was %1 as of %2.' - , ko: 'Your delta was %1 as of %2.' - , nb: 'Your delta was %1 as of %2.' - , nl: 'Your delta was %1 as of %2.' - , pl: 'Your delta was %1 as of %2.' - , pt: 'Your delta was %1 as of %2.' - , ro: 'Your delta was %1 as of %2.' - , ru: 'Your delta was %1 as of %2.' - , sk: 'Your delta was %1 as of %2.' - , sv: 'Your delta was %1 as of %2.' - , tr: 'Your delta was %1 as of %2.' - , zh_cn: 'Your delta was %1 as of %2.' - , zh_tw: 'Your delta was %1 as of %2.' + bg: 'Your delta was %1 between %2 and %3.' + , cs: 'Your delta was %1 between %2 and %3.' + , de: 'Your delta was %1 between %2 and %3.' + , dk: 'Your delta was %1 between %2 and %3.' + , el: 'Your delta was %1 between %2 and %3.' + , en: 'Your delta was %1 between %2 and %3.' + , es: 'Your delta was %1 between %2 and %3.' + , fi: 'Your delta was %1 between %2 and %3.' + , fr: 'Your delta was %1 between %2 and %3.' + , he: 'Your delta was %1 between %2 and %3.' + , hr: 'Your delta was %1 between %2 and %3.' + , it: 'Your delta was %1 between %2 and %3.' + , ko: 'Your delta was %1 between %2 and %3.' + , nb: 'Your delta was %1 between %2 and %3.' + , nl: 'Your delta was %1 between %2 and %3.' + , pl: 'Your delta was %1 between %2 and %3.' + , pt: 'Your delta was %1 between %2 and %3.' + , ro: 'Your delta was %1 between %2 and %3.' + , ru: 'Your delta was %1 between %2 and %3.' + , sk: 'Your delta was %1 between %2 and %3.' + , sv: 'Your delta was %1 between %2 and %3.' + , tr: 'Your delta was %1 between %2 and %3.' + , zh_cn: 'Your delta was %1 between %2 and %3.' + , zh_tw: 'Your delta was %1 between %2 and %3.' }, 'virtAsstUnknownIntentTitle': { en: 'Unknown Intent' From 7a1fc6d64a59cf4f15ca0491b5cdc336cbefd963 Mon Sep 17 00:00:00 2001 From: inventor96 Date: Fri, 27 Dec 2019 01:01:19 -0700 Subject: [PATCH 010/104] Corrected time reference --- lib/api/alexa/index.js | 2 +- lib/api/googlehome/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/api/alexa/index.js b/lib/api/alexa/index.js index b08a95cc34b..6fdd42b7587 100644 --- a/lib/api/alexa/index.js +++ b/lib/api/alexa/index.js @@ -122,7 +122,7 @@ function configure (app, wares, ctx, env) { params: [ sbx.properties.delta.display, moment(records[0].date).from(moment(sbx.time)), - moment(records[1].date).from(moment(records[0].date)) + moment(records[1].date).from(moment(sbx.time)) ] }) ); diff --git a/lib/api/googlehome/index.js b/lib/api/googlehome/index.js index f3f98ef0e77..ae7b1f95dee 100644 --- a/lib/api/googlehome/index.js +++ b/lib/api/googlehome/index.js @@ -113,7 +113,7 @@ function configure (app, wares, ctx, env) { params: [ sbx.properties.delta.display, moment(records[0].date).from(moment(sbx.time)), - moment(records[1].date).from(moment(records[0].date)) + moment(records[1].date).from(moment(sbx.time)) ] }) ); From c9c9495abefa628cbe1be6fc141166cbadee9ccc Mon Sep 17 00:00:00 2001 From: inventor96 Date: Fri, 27 Dec 2019 01:11:59 -0700 Subject: [PATCH 011/104] Try shortening the response --- lib/api/alexa/index.js | 2 +- lib/api/googlehome/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/api/alexa/index.js b/lib/api/alexa/index.js index 6fdd42b7587..1147898a023 100644 --- a/lib/api/alexa/index.js +++ b/lib/api/alexa/index.js @@ -121,7 +121,7 @@ function configure (app, wares, ctx, env) { translate('virtAsstDelta', { params: [ sbx.properties.delta.display, - moment(records[0].date).from(moment(sbx.time)), + moment(records[0].date).from(moment(sbx.time), false), moment(records[1].date).from(moment(sbx.time)) ] }) diff --git a/lib/api/googlehome/index.js b/lib/api/googlehome/index.js index ae7b1f95dee..2e8f2ca1796 100644 --- a/lib/api/googlehome/index.js +++ b/lib/api/googlehome/index.js @@ -112,7 +112,7 @@ function configure (app, wares, ctx, env) { translate('virtAsstDelta', { params: [ sbx.properties.delta.display, - moment(records[0].date).from(moment(sbx.time)), + moment(records[0].date).from(moment(sbx.time), false), moment(records[1].date).from(moment(sbx.time)) ] }) From 014df8518c3bdb664b675e688e4766f10704f795 Mon Sep 17 00:00:00 2001 From: inventor96 Date: Fri, 27 Dec 2019 01:49:33 -0700 Subject: [PATCH 012/104] Revert attempt --- lib/api/alexa/index.js | 2 +- lib/api/googlehome/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/api/alexa/index.js b/lib/api/alexa/index.js index 1147898a023..6fdd42b7587 100644 --- a/lib/api/alexa/index.js +++ b/lib/api/alexa/index.js @@ -121,7 +121,7 @@ function configure (app, wares, ctx, env) { translate('virtAsstDelta', { params: [ sbx.properties.delta.display, - moment(records[0].date).from(moment(sbx.time), false), + moment(records[0].date).from(moment(sbx.time)), moment(records[1].date).from(moment(sbx.time)) ] }) diff --git a/lib/api/googlehome/index.js b/lib/api/googlehome/index.js index 2e8f2ca1796..ae7b1f95dee 100644 --- a/lib/api/googlehome/index.js +++ b/lib/api/googlehome/index.js @@ -112,7 +112,7 @@ function configure (app, wares, ctx, env) { translate('virtAsstDelta', { params: [ sbx.properties.delta.display, - moment(records[0].date).from(moment(sbx.time), false), + moment(records[0].date).from(moment(sbx.time)), moment(records[1].date).from(moment(sbx.time)) ] }) From 64753106ac59d83f4f4935773f673deb14f55011 Mon Sep 17 00:00:00 2001 From: inventor96 Date: Fri, 27 Dec 2019 03:01:20 -0700 Subject: [PATCH 013/104] Updated templates and documentation --- docs/plugins/alexa-templates/en-us.json | 10 ++++++++++ docs/plugins/google-home-templates/en-us.zip | Bin 13230 -> 6246 bytes .../interacting-with-virtual-assistants.md | 1 + 3 files changed, 11 insertions(+) diff --git a/docs/plugins/alexa-templates/en-us.json b/docs/plugins/alexa-templates/en-us.json index 4cb10aa0643..d9e13174f78 100644 --- a/docs/plugins/alexa-templates/en-us.json +++ b/docs/plugins/alexa-templates/en-us.json @@ -74,6 +74,16 @@ { "name": "LIST_OF_METRICS", "values": [ + { + "name": { + "value": "delta", + "synonyms": [ + "blood glucose delta", + "blood sugar delta", + "bg delta" + ] + } + }, { "name": { "value": "uploader battery", diff --git a/docs/plugins/google-home-templates/en-us.zip b/docs/plugins/google-home-templates/en-us.zip index 6a8498b0b19e0c1822efaf4652f737cf282b350d..b68d7a09b02997f375628281e317983a995dde24 100644 GIT binary patch literal 6246 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 K;qs3&AN~jY03UMz literal 13230 zcmdT~cRUq(*f)>8S4KtID?6jCNYX*(kvP_|M;Y1U7{>_7jAR~?%n~8lB70TMv4Zp<#BR2 zqX{BJTfp(r>qLC7pi`o$BF<2v;UYfN`_7Q;o#B;J2dg{U*+X^@&;D_E{vN+=jWntJ z57AfLPH&HJHjmrl{bkMXs zt>dJzNXHx4Oz{}ZC@F?OQ$go%nU~BDt$XAS(51GEg_cHb0J}qlsW;ONyDn^9!DI3# zopfuVO0rw0l@0Q4ERbXt?|v{O3hsEv%+?j$=J-LRfyz-4M|}J5_h}Go$xu6yTkO;y zUid`6kIIYAR}fYqvN9FJ4}MrjfCH^LLkM1^kDI>Y zYJY`v=&LUf>eDlB939QksrdA!kpko35(HI=3L^k;L45ydE@=AzAl)+;LoSd&OXx>}>on=T~havwF_XHn>6F3u_k{ z%padxvB-!Yh!-=nZFgT^Czc0NDiKB1h>bcY`pGuR)X~;~E~#n=R7AdR@iGg$69H#% znw6rMm2P9UXkzaswNE7Q!@o2!A=?ljiIG=m^F&CxSEG{{FxQ#tKq~FH8zzc4qq8i$iJ6V41bl<9ecJLKlLD0IHSVXI={Y8-lue#UD zmkSh|`M=ruEv|b&7=nMr zH-v1u#hN-RdvnQW;_~a2TM!eflU&AXp2HtKDqdFH%zvNU=J#%SP2@)0()e}=oLESR~hvhEGilUgNVy*?QfL0*&7w#G%IrRa_}ev6&1M^HHmRy z*%fGYB7}r#xK05X!ScbbEb*?72xkZAjbuWSE{zyrbq1^f0MxMFOb%)Q;61fzZyT-r z_tHnb06?a4Y-V0wSzdS>pp2Ee&9*F-x1piTwxOW`77J*AwV7rTZU>sF^QO`Th#*6V zjtay?!Oyvjd@s=4Ff_J8Ag~u|au8wWz3xuPef(ZcRh3hThqJAvUHyrgsz8rmhoI_Y zHug5B^s{*`bO=s}kd6}4_H#OZ801GWJp%MojDXMie5BY67i4`?u0({>N?ZG7`jav7 zCL|_5jsvMiL}hD(53aTJh@v?*OH;i=W}nLuPzFWi^|HrLe0CWKS5j*2RwddS7(*Y43EuS3Fn5!-9RLdG*r zmm|XaDZ?o%iIsV}@rth=51zrN^j?h00pm2zFO{?|>J!~y7r&#Yo9mqYK43-fbCTOk zM)zBxGnjqNQ*j;G%BFJQlP5IYhjll?hs&8>b~jA-%wHrsC5Zn0*+7Lzz!>^>v-09u zi84>Rm*>VzafqG$FqeAuJ3n`>(I5>3q*WW^g>W=pMC3tc^~38Enu9K|-%<|NDf=x& z=+{Je!GFVO{>AQfxQnnIa1mv7bw@)-CkL>T1H|6J(A5D9u|3p5gv0qseECQNoW1HZ zo&qnYvbPbJm6O4`PGsUyp0W|*-*O(h25WxzWpsVi3A>7o5RE?}5o-Rn>`_L_DoWb|6RxWJSATp*+5~SgnwX0*qBBILBk+DJB?zZnMa#N z@g*NfXU~Fm5y0E1eC%|6@2%+KtmE?CvLL}+Qm;<&TcQA?Dv@hCxQ*ztc@txbK9ARf zeJ|5@VZ~5m^I^SoQ>N{K(K$0=E!9~S#KxvuPK6la=+oIT$A_oFvzI?f%aaU$pTG#v z()WnA#;oFOKRad<311K{9K6>YYK@zoZG4Ga8mLNv8Ler;^%|e2n}>YrLfWz`HIap# zr{lNU)Rf#hy|H=~#lnuvuUnt;($r?vWTk>mjKLUcfh4b*ykl%d!^j44Ux?20dqqiqGp?J&mL5lf6uqAQH-Bf47B0!e+RRQu5N`RO7S`VOWO!SudtnP4n9( zibFsetpEFB_17-q9EY;j?OB|*`S6De)qK;hLhK{#z{nb8FB1I+9NE;FC6#e`cq|R=Yj7QL1#OH)Z6c zPLD#R=a~M(DG;+G1}(&p{M92}Q|`nIlkDvSCgKm?@n2NDF<8>Qnma+KbEhBO*kEF* z_-m;P_`3;V*tcN1+7r@3Cp-jUz1K9K9slCm*2IM$0hz^qS5?hd7bgswRKXI8%(Vtf@mg|PiVoH=2%XpYSt zB`3VtynL{x?(4F`{6~)(#B)g){5ciYo2+gw-K8(LtHbPS9=hZXG>BDxNb8RWBq*W0 zdGA8Be^KL8(fh5F@+T_#_5H%Xji*$$^d`n^phKq1EsNS`h}<7FdHHTVgfHBB6|B92 z5e5#L|3Xc)QK|Z79&S2p&!T$+H=gcpp6>TARuu9#waC(oMw9Ncq%umxvj7S$XZ25a z2$xR28>E}LL@{C;JPUg|PUCTmx&zn!sr$)1Y|vSj?AWopnyQ?d_`|@-2dkGkWp5-GU2p~1ve~D6Q*sXL zB4%`48Sq{t4#vLO?Y*wWrXsanmAWr9Y-k{y((R{KXMeqbOq=_LBD>L)7ksqg{^vaN zV2N5d?aC&m-ldvx_2TL z5+83@+=?dYh1Q>?gPEfSI=OWDvpSgidL+B6-gHRwQp!@I$(_puwNj~_sqICd2gR=5 zT(*baD(@y#DdMZBPH{4bg#bUB4|AXA6$%v$_=+Bu!R$5lN$rh!zECdU_?FZ|GU0Dx}$9j=KyX`t*BP7c# zL|pPUqE5iW15$J=hh7*Iw~?pQ4witC{_s>BjFBaYS$Yxtps&rT4y0;YO;6wtdQm5M zBJi;Zb-Ay$0p(=4Yrk&RL?0SY|R5fCDCX7Vrpw5L9 zrTI{~DW>S_@7j)MjYD;%bQD=Q*(5J#0xl7}Dj5S(0^_IzvsNHA#oX_*A$5e(^|F&|8=}&B!)%1$%dACnSMoH_5c{^*%gx^z|26Rs7CZ|_^ zoDf~h?;?KK%Q6$mtEDUVX=#~I5$dug5J6-%G(j|@5?sWQ!R^_-R){SUiKn8XE5R>M zLcReKaG~}zE*&NTH{Nty$3zyhBCvoZ&Hr7caHQh?bB4)zpisTy+vnvi>JQz_6eUZ~8Y4{}$WgTS-HU2p zRi=#%F$}((%Rf?`ZrU{*Szyu~(H~3M`^duAdqB89UsIa9w8mTk-4e~m)1`!IvOeN& zs=W-W96!XO$eGdU>X)1z2^-GiME>0A(Td!opJ>yt2Q$%F*8%NKV#A?xp*+}tzJN35 zl0szy+@>lz3>e3O)v_nTl3>?mqHR^a;;o*>@k+gf5#hvzkJB{J~S?n6y z+STu|A-VDj3-7dPxIDYCN(-^LmmGpgls`_7U8npmhrWDrWvkuw>)W}4i*Gf5cuv2B z&2kOedVDOK(P1)cc)umSKyu#dxm#{*pT$sNK^LgWi9*Tf2UbzqO7FdqI}M}HN$#w6 z^=m_1IAS7Gi2xUSfdRJcp%>9NEqpo|rzNo8G`q0oSsC*{ewYGGot!&fC%R6OJr#5hvBkroEJKn(nI5O@Kybt)UI!8A({E zE?35V5SQ-GlsPW?T(r&LnAx>xIY~*$(E-Ii&p4Gn;F`GW81Onf{4Te799d51iADk6 zDeHvO8bq6Km-|94b43c9-G3+{Mc*7!nG3M%zJ1^JC2zNFxry=hGv8-Umv~jzhKHFd zjna81ZVkOj)YkU=K0?y!c1_Yc5)6w1N^aPcRj$uiZc5#EUh$1{e_rSNeq)@9*KWq| z19+5o%yT~972TWHbMSnoW9vxh(2Uf=lkbva@5{kn@4vqb)$;gOlAC%xhsMwQnWsJ& zUr3~_r|GNoH)+@#O^6bP%{9zfHQylLq8X*zVsb|GcSub(?KgT%;1TI&ieV8xt-==s zD5YZ(Id$vi2DyN(OIXo4Y1U6La;JP&#+mSAWLayKXR8Y5NIULxI%oy=0LD%buk9G|SiGIQnW> zu~7lm95OX;PenD?@F?7i?}HC0)N7}kKgCm9E(~G~Wq)z42~#afvOepDvO;ivoa#m_ z#(UPTC&GD*M(WEM+E`6#HvOUinp;**wG8&SrLL?3D~dO3@jJDcx^u|i%91e%NMv8c z9;KQKOszYSd_|$&1y|9S4>!l$?3CEP*Wo>^P50fM@*hKGf>g@tNk0yeI< zk>#*ledzZkHB&5p(rI|b2IBoTN?ny-y$e8>0NuKbo;@JVNhj8u)j!7<@hP{5SUB-b z8k(a^rJ}WoxzU-H3?7$vozx;4FX+-GDzEnSH(t8N^(GC6;;P&;OIf{A>=g4Lhbpb+ zyhc&P`)jrA1@~nT%gL8+hEfUwidJ`_{B@yuL41S~L6zn9=ObRsvB23rNTDNhYc;6g z(F~&4S7$gXk_a9ZNq*n8{`GNW|LFbLLAiOsf3!Kf@9eun_?`aIH zYK;VW9c(iD^K#kPIWuGxGv8(tFL#*C-w!B(#+1@T7dOYoi3o9f}({zDt z0=>)%B326b4JL_(79a6Q=v8hzD2GQDEjJ?7L$Hr}0yxA__w=%BsDJ$iz<1Ex_o(oK ze^0;vVmCN^{*PSH*qH|J$+{!yUXwSDV!oO!&B%T}V8Syw&BUh4lIYR;b1Dx%3$tix zUKW~Msxdvkkh0A!F^9)92^J{ZAS}u51`tWZ(X?s^67he=_YzSkiBB~?_|QS}Ok=8+$9>)&U9`rd)cU>=%4vH#k; zGvogw!%3h#LV|z93w193v*FyO`js$$O9ek0jug#r2=JHmQAd}YKN$`uV$p9WC+r5C z-QafkRQvagUl@f49C{V8Q20yl{ip0uXDI9_?B=0L^!r8HGu3}=Re~rS;L!Ugju7bY zGeefis7`q7CuUJ64=_U+hwPLVebaUZoAW<>U#vm-Ja~=EM@=Q|AYp$ z6GSZu5h?NX0gQK`+4I%i26ulUg4#-jPC{MIpJ@675hzR!OQ#*oZn34UHM`aY5vGMmFSs{{zrEGOqvt diff --git a/docs/plugins/interacting-with-virtual-assistants.md b/docs/plugins/interacting-with-virtual-assistants.md index 984a876f21c..59198820956 100644 --- a/docs/plugins/interacting-with-virtual-assistants.md +++ b/docs/plugins/interacting-with-virtual-assistants.md @@ -43,6 +43,7 @@ This list is not meant to be comprehensive, nor does it include every way you ca - "Alexa, ask Nightscout what is my basal" - "Alexa, ask Nightscout what is my current basal" - "Alexa, ask Nightscout what is my cob" +- "Alexa, ask Nightscout what is my delta" - "Alexa, ask Nightscout what is Charlie's carbs on board" - "Alexa, ask Nightscout what is Sophie's carbohydrates on board" - "Alexa, ask Nightscout what is Harper's loop forecast" From 161c496af1283439dedfcb512d83b4e8fe7879d9 Mon Sep 17 00:00:00 2001 From: inventor96 Date: Fri, 27 Dec 2019 03:25:20 -0700 Subject: [PATCH 014/104] Minor improvement in response when delta is 0 --- lib/api/alexa/index.js | 2 +- lib/api/googlehome/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/api/alexa/index.js b/lib/api/alexa/index.js index 6fdd42b7587..f2f42c1e7eb 100644 --- a/lib/api/alexa/index.js +++ b/lib/api/alexa/index.js @@ -120,7 +120,7 @@ function configure (app, wares, ctx, env) { translate('virtAsstTitleDelta'), translate('virtAsstDelta', { params: [ - sbx.properties.delta.display, + sbx.properties.delta.display == '+0' ? '0' : sbx.properties.delta.display, moment(records[0].date).from(moment(sbx.time)), moment(records[1].date).from(moment(sbx.time)) ] diff --git a/lib/api/googlehome/index.js b/lib/api/googlehome/index.js index ae7b1f95dee..f533f579389 100644 --- a/lib/api/googlehome/index.js +++ b/lib/api/googlehome/index.js @@ -111,7 +111,7 @@ function configure (app, wares, ctx, env) { translate('virtAsstTitleDelta'), translate('virtAsstDelta', { params: [ - sbx.properties.delta.display, + sbx.properties.delta.display == '+0' ? '0' : sbx.properties.delta.display, moment(records[0].date).from(moment(sbx.time)), moment(records[1].date).from(moment(sbx.time)) ] From 23d25a4e4e473f4e3fb0b3fc316f1c95b7c7b219 Mon Sep 17 00:00:00 2001 From: Tanja <7403966+tanja3981@users.noreply.github.com> Date: Wed, 1 Jan 2020 09:57:42 +0100 Subject: [PATCH 015/104] German translations improved and added (#5360) --- lib/language.js | 148 ++++++++++++++++++++++++------------------------ 1 file changed, 74 insertions(+), 74 deletions(-) diff --git a/lib/language.js b/lib/language.js index 87fb871e1d1..d9a023e5d72 100644 --- a/lib/language.js +++ b/lib/language.js @@ -894,7 +894,7 @@ function init() { } ,'Notes contain' : { cs: 'Poznámky obsahují' - ,de: 'Erläuterungen' + ,de: 'Notizen enthalten' ,he: 'ההערות מכילות' ,es: 'Contenido de las notas' ,fr: 'Notes contiennent' @@ -994,7 +994,7 @@ function init() { } ,'Display' : { cs: 'Zobraz' - ,de: 'Darstellen' + ,de: 'Anzeigen' ,es: 'Visualizar' ,fr: 'Afficher' ,el: 'Εμφάνιση' @@ -1219,7 +1219,7 @@ function init() { } ,'Portion' : { cs: 'Porce' - ,de: 'Portion' + ,de: 'Abschnitt' ,es: 'Porción' ,fr: 'Portion' ,el: 'Μερίδα' @@ -1396,7 +1396,7 @@ function init() { } ,'Week to week' : { cs: 'Week to week' - ,de: 'Week to week' + ,de: 'Woche zu Woche' ,es: 'Week to week' ,fr: 'Week to week' ,el: 'Week to week' @@ -1642,7 +1642,7 @@ function init() { } ,'Period' : { cs: 'Období' - ,de: 'Periode' + ,de: 'Zeitabschnitt' ,es: 'Periodo' ,fr: 'Période' ,el: 'Περίοδος' @@ -2017,7 +2017,7 @@ function init() { } ,'Total per day' : { cs: 'dní celkem' - ,de: 'Gesamttage' + ,de: 'Gesamt pro Tag' ,es: 'Total de días' ,fr: 'Total journalier' ,el: 'ημέρες συνολικά' @@ -3455,7 +3455,7 @@ function init() { } ,'Add from database' : { cs: 'Přidat z databáze' - ,de: 'Ergänzt aus Datenbank' + ,de: 'Ergänze aus Datenbank' ,es: 'Añadir desde la base de datos' ,fr: 'Ajouter à partir de la base de données' ,el: 'Επιλογή από τη Βάση Δεδομένων' @@ -3731,7 +3731,7 @@ function init() { ,'60 minutes earlier' : { cs: '60 min předem' ,he: 'שישים דקות מוקדם יותר' - ,de: '60 Min. früher' + ,de: '60 Minuten früher' ,es: '60 min antes' ,fr: '60 min plus tôt' ,el: '60 λεπτά πριν' @@ -3756,7 +3756,7 @@ function init() { ,'45 minutes earlier' : { cs: '45 min předem' ,he: 'ארבעים דקות מוקדם יותר' - ,de: '45 Min. früher' + ,de: '45 Minuten früher' ,es: '45 min antes' ,fr: '45 min plus tôt' ,el: '45 λεπτά πριν' @@ -3781,7 +3781,7 @@ function init() { ,'30 minutes earlier' : { cs: '30 min předem' ,he: 'שלושים דקות מוקדם יותר' - ,de: '30 Min früher' + ,de: '30 Minuten früher' ,es: '30 min antes' ,fr: '30 min plus tôt' ,el: '30 λεπτά πριν' @@ -3806,7 +3806,7 @@ function init() { ,'20 minutes earlier' : { cs: '20 min předem' ,he: 'עשרים דקות מוקדם יותר' - ,de: '20 Min. früher' + ,de: '20 Minuten früher' ,es: '20 min antes' ,fr: '20 min plus tôt' ,el: '20 λεπτά πριν' @@ -3831,7 +3831,7 @@ function init() { ,'15 minutes earlier' : { cs: '15 min předem' ,he: 'חמש עשרה דקות מוקדם יותר' - ,de: '15 Min. früher' + ,de: '15 Minuten früher' ,es: '15 min antes' ,fr: '15 min plus tôt' ,el: '15 λεπτά πριν' @@ -3879,7 +3879,7 @@ function init() { } ,'15 minutes later' : { cs: '15 min po' - ,de: '15 Min. später' + ,de: '15 Minuten später' ,es: '15 min más tarde' ,fr: '15 min plus tard' ,el: '15 λεπτά αργότερα' @@ -3904,7 +3904,7 @@ function init() { } ,'20 minutes later' : { cs: '20 min po' - ,de: '20 Min. später' + ,de: '20 Minuten später' ,es: '20 min más tarde' ,fr: '20 min plus tard' ,el: '20 λεπτά αργότερα' @@ -3929,7 +3929,7 @@ function init() { } ,'30 minutes later' : { cs: '30 min po' - ,de: '30 Min. später' + ,de: '30 Minuten später' ,es: '30 min más tarde' ,fr: '30 min plus tard' ,el: '30 λεπτά αργότερα' @@ -3954,7 +3954,7 @@ function init() { } ,'45 minutes later' : { cs: '45 min po' - ,de: '45 Min. später' + ,de: '45 Minuten später' ,es: '45 min más tarde' ,fr: '45 min plus tard' ,el: '45 λεπτά αργότερα' @@ -3979,7 +3979,7 @@ function init() { } ,'60 minutes later' : { cs: '60 min po' - ,de: '60 Min. später' + ,de: '60 Minuten später' ,es: '60 min más tarde' ,fr: '60 min plus tard' ,el: '60 λεπτά αργότερα' @@ -4030,7 +4030,7 @@ function init() { ,'RETRO MODE' : { cs: 'V MINULOSTI' ,he: 'מצב רטרו' - ,de: 'RETRO MODUS' + ,de: 'Retro Modus' ,es: 'Modo Retrospectivo' ,fr: 'MODE RETROSPECTIF' ,el: 'Αναδρομική Λειτουργία' @@ -5056,7 +5056,7 @@ function init() { } ,'BG Check' : { cs: 'Kontrola glykémie' - ,de: 'BG-Prüfung' + ,de: 'BG Messung' ,es: 'Control de glucemia' ,fr: 'Contrôle glycémie' ,el: 'Έλεγχος Γλυκόζης' @@ -5605,7 +5605,7 @@ function init() { } ,'View all treatments' : { cs: 'Zobraz všechny ošetření' - ,de: 'Zeige alle Eingaben' + ,de: 'Zeige alle Behandlungen' ,es: 'Visualizar todos los tratamientos' ,fr: 'Voir tous les traitements' ,el: 'Προβολή όλων των ενεργειών' @@ -5878,7 +5878,7 @@ function init() { ,'mins' : { cs: 'min' ,he: 'דקות' - ,de: 'min' + ,de: 'Minuten' ,es: 'min' ,fr: 'mins' ,el: 'λεπτά' @@ -6138,7 +6138,7 @@ function init() { ,'Theme' : { cs: 'Téma' ,he: 'נושא' - ,de: 'Thema' + ,de: 'Aussehen' ,es: 'Tema' ,fr: 'Thème' ,el: 'Θέμα απεικόνισης' @@ -6579,7 +6579,7 @@ function init() { ,'Clean' : { cs: 'Čistý' ,he: 'נקה' - ,de: 'Rein' + ,de: 'Löschen' ,es: 'Limpio' ,fr: 'Propre' ,el: 'Καθαρισμός' @@ -6682,7 +6682,7 @@ function init() { } ,'Treatment type' : { cs: 'Typ ošetření' - ,de: 'Eingabe-Typ' + ,de: 'Behandlungstyp' ,es: 'Tipo de tratamiento' ,fr: 'Type de traitement' ,el: 'Τύπος Ενέργειας' @@ -9136,7 +9136,7 @@ function init() { ,nb: 'IKH' ,fr: 'I:C' ,ro: 'ICR' - ,de: 'I:KH' + ,de: 'IE:KH' ,dk: 'I:C' ,es: 'I:C' ,sv: 'I:C' @@ -9836,7 +9836,7 @@ function init() { ,fr: 'Repas sous peu' ,sv: 'Snart matdags' ,nb: 'Snart tid for mat' - ,de: 'Bald essend' + ,de: 'Bald essen' ,dk: 'Spiser snart' ,es: 'Comer pronto' ,bg: 'Ядене скоро' @@ -10241,7 +10241,7 @@ function init() { ,sv: 'Är du säker att du vill ta bort:' ,nb: 'Er du sikker på at du vil slette:' ,fi: 'Oletko varmat että haluat tuhota: ' - ,de: ' Sind sie sicher, das Sie löschen wollen:' + ,de: 'Sind sie sicher, das Sie löschen wollen:' ,dk: 'Er du sikker på at du vil slette:' ,es: 'Seguro que quieres eliminarlo:' ,pt: 'Tem certeza de que deseja apagar:' @@ -10724,7 +10724,7 @@ function init() { ,sv: 'Tyst i %1 minuter' ,nb: 'Stille i %1 minutter' ,fi: 'Hiljennä %1 minuutiksi' - ,de: 'Inaktivität für %1 Minuten' + ,de: 'Ruhe für 1 Minuten' ,dk: 'Stilhed i %1 minutter' ,pt: 'Silencir por %1 minutos' ,es: 'Silenciado por %1 minutos' @@ -13202,7 +13202,7 @@ function init() { 'virtAsstUnknown': { bg: 'That value is unknown at the moment. Please see your Nightscout site for more details.' , cs: 'That value is unknown at the moment. Please see your Nightscout site for more details.' - , de: 'That value is unknown at the moment. Please see your Nightscout site for more details.' + , de: 'Dieser Wert ist momentan unbekannt. Prüfe deine Nightscout Webseite für Details!' , dk: 'That value is unknown at the moment. Please see your Nightscout site for more details.' , el: 'That value is unknown at the moment. Please see your Nightscout site for more details.' , en: 'That value is unknown at the moment. Please see your Nightscout site for more details.' @@ -13228,7 +13228,7 @@ function init() { 'virtAsstTitleAR2Forecast': { bg: 'AR2 Forecast' , cs: 'AR2 Forecast' - , de: 'AR2 Forecast' + , de: 'AR2 Vorhersage' , dk: 'AR2 Forecast' , el: 'AR2 Forecast' , en: 'AR2 Forecast' @@ -13254,7 +13254,7 @@ function init() { 'virtAsstTitleCurrentBasal': { bg: 'Current Basal' , cs: 'Current Basal' - , de: 'Current Basal' + , de: 'Aktuelles Basalinsulin' , dk: 'Current Basal' , el: 'Current Basal' , en: 'Current Basal' @@ -13280,7 +13280,7 @@ function init() { 'virtAsstTitleCurrentCOB': { bg: 'Current COB' , cs: 'Current COB' - , de: 'Current COB' + , de: 'Aktuelle Kohlenhydrate' , dk: 'Current COB' , el: 'Current COB' , en: 'Current COB' @@ -13306,7 +13306,7 @@ function init() { 'virtAsstTitleCurrentIOB': { bg: 'Current IOB' , cs: 'Current IOB' - , de: 'Current IOB' + , de: 'Aktuelles Restinsulin' , dk: 'Current IOB' , el: 'Current IOB' , en: 'Current IOB' @@ -13332,7 +13332,7 @@ function init() { 'virtAsstTitleLoopForecast': { bg: 'Loop Forecast' , cs: 'Loop Forecast' - , de: 'Loop Forecast' + , de: 'Loop Vorhersage' , dk: 'Loop Forecast' , el: 'Loop Forecast' , en: 'Loop Forecast' @@ -13358,7 +13358,7 @@ function init() { 'virtAsstTitleLastLoop': { bg: 'Last Loop' , cs: 'Last Loop' - , de: 'Last Loop' + , de: 'Letzter Loop' , dk: 'Last Loop' , el: 'Last Loop' , en: 'Last Loop' @@ -13384,7 +13384,7 @@ function init() { 'virtAsstTitleOpenAPSForecast': { bg: 'OpenAPS Forecast' , cs: 'OpenAPS Forecast' - , de: 'OpenAPS Forecast' + , de: 'OpenAPS Vorhersage' , dk: 'OpenAPS Forecast' , el: 'OpenAPS Forecast' , en: 'OpenAPS Forecast' @@ -13410,7 +13410,7 @@ function init() { 'virtAsstTitlePumpReservoir': { bg: 'Insulin Remaining' , cs: 'Insulin Remaining' - , de: 'Insulin Remaining' + , de: 'Verbleibendes Insulin' , dk: 'Insulin Remaining' , el: 'Insulin Remaining' , en: 'Insulin Remaining' @@ -13436,7 +13436,7 @@ function init() { 'virtAsstTitlePumpBattery': { bg: 'Pump Battery' , cs: 'Pump Battery' - , de: 'Pump Battery' + , de: 'Pumpenbatterie' , dk: 'Pump Battery' , el: 'Pump Battery' , en: 'Pump Battery' @@ -13462,7 +13462,7 @@ function init() { 'virtAsstTitleRawBG': { bg: 'Current Raw BG' , cs: 'Current Raw BG' - , de: 'Current Raw BG' + , de: 'Aktueller Blutzucker Rohwert' , dk: 'Current Raw BG' , el: 'Current Raw BG' , en: 'Current Raw BG' @@ -13488,7 +13488,7 @@ function init() { 'virtAsstTitleUploaderBattery': { bg: 'Uploader Battery' , cs: 'Uploader Battery' - , de: 'Uploader Battery' + , de: 'Uploader Batterie' , dk: 'Uploader Battery' , el: 'Uploader Battery' , en: 'Uploader Battery' @@ -13514,7 +13514,7 @@ function init() { 'virtAsstTitleCurrentBG': { bg: 'Current BG' , cs: 'Current BG' - , de: 'Current BG' + , de: 'Aktueller Blutzucker' , dk: 'Current BG' , el: 'Current BG' , en: 'Current BG' @@ -13540,7 +13540,7 @@ function init() { 'virtAsstTitleFullStatus': { bg: 'Full Status' , cs: 'Full Status' - , de: 'Full Status' + , de: 'Gesamtstatus' , dk: 'Full Status' , el: 'Full Status' , en: 'Full Status' @@ -13853,7 +13853,7 @@ function init() { , cs: 'Your uploader battery is at %1' , en: 'Your uploader battery is at %1' , hr: 'Your uploader battery is at %1' - , de: 'Your uploader battery is at %1' + , de: 'Der Akku deines Uploader Handys ist bei %1' , dk: 'Your uploader battery is at %1' , ko: 'Your uploader battery is at %1' , nl: 'Your uploader battery is at %1' @@ -13904,7 +13904,7 @@ function init() { , cs: 'Podle přepovědi smyčky je očekávána glykémie around %1 během následujících %2' , en: 'According to the loop forecast you are expected to be around %1 over the next %2' , hr: 'According to the loop forecast you are expected to be around %1 over the next %2' - , de: 'Entsprechend der Loop Vorhersage landest du bei around %1 während der nächsten %2' + , de: 'Entsprechend der Loop Vorhersage landest in den nächsten %2 bei %1' , dk: 'Ifølge Loops forudsigelse forventes du at blive around %1 i den næste %2' , ko: 'According to the loop forecast you are expected to be around %1 over the next %2' , nl: 'Volgens de Loop voorspelling is je waarde around %1 over de volgnede %2' @@ -13921,7 +13921,7 @@ function init() { , cs: 'Podle přepovědi smyčky je očekávána glykémie between %1 and %2 během následujících %3' , en: 'According to the loop forecast you are expected to be between %1 and %2 over the next %3' , hr: 'According to the loop forecast you are expected to be between %1 and %2 over the next %3' - , de: 'Entsprechend der Loop Vorhersage landest du bei between %1 and %2 während der nächsten %3' + , de: 'Entsprechend der Loop Vorhersage landest du zwischen %1 and %2 während der nächsten %3' , dk: 'Ifølge Loops forudsigelse forventes du at blive between %1 and %2 i den næste %3' , ko: 'According to the loop forecast you are expected to be between %1 and %2 over the next %3' , nl: 'Volgens de Loop voorspelling is je waarde between %1 and %2 over de volgnede %3' @@ -13938,7 +13938,7 @@ function init() { , cs: 'According to the AR2 forecast you are expected to be around %1 over the next %2' , en: 'According to the AR2 forecast you are expected to be around %1 over the next %2' , hr: 'According to the AR2 forecast you are expected to be around %1 over the next %2' - , de: 'According to the AR2 forecast you are expected to be around %1 over the next %2' + , de: 'Entsprechend der AR2 Vorhersage landest du in %2 bei %1' , dk: 'According to the AR2 forecast you are expected to be around %1 over the next %2' , ko: 'According to the AR2 forecast you are expected to be around %1 over the next %2' , nl: 'According to the AR2 forecast you are expected to be around %1 over the next %2' @@ -13955,7 +13955,7 @@ function init() { , cs: 'According to the AR2 forecast you are expected to be between %1 and %2 over the next %3' , en: 'According to the AR2 forecast you are expected to be between %1 and %2 over the next %3' , hr: 'According to the AR2 forecast you are expected to be between %1 and %2 over the next %3' - , de: 'According to the AR2 forecast you are expected to be between %1 and %2 over the next %3' + , de: 'Entsprechend der AR2 Vorhersage landest du in %3 zwischen %1 and %2' , dk: 'According to the AR2 forecast you are expected to be between %1 and %2 over the next %3' , ko: 'According to the AR2 forecast you are expected to be between %1 and %2 over the next %3' , nl: 'According to the AR2 forecast you are expected to be between %1 and %2 over the next %3' @@ -14019,35 +14019,35 @@ function init() { , tr: 'OpenAPS tarafından tahmin edilen kan şekeri %1' }, 'virtAsstCob3person': { - bg: '%1 has $2 carbohydrates on board' - , cs: '%1 has $2 carbohydrates on board' - , de: '%1 has $2 carbohydrates on board' - , dk: '%1 has $2 carbohydrates on board' - , el: '%1 has $2 carbohydrates on board' - , en: '%1 has $2 carbohydrates on board' - , es: '%1 has $2 carbohydrates on board' - , fi: '%1 has $2 carbohydrates on board' - , fr: '%1 has $2 carbohydrates on board' - , he: '%1 has $2 carbohydrates on board' - , hr: '%1 has $2 carbohydrates on board' - , it: '%1 has $2 carbohydrates on board' - , ko: '%1 has $2 carbohydrates on board' - , nb: '%1 has $2 carbohydrates on board' - , nl: '%1 has $2 carbohydrates on board' - , pl: '%1 has $2 carbohydrates on board' - , pt: '%1 has $2 carbohydrates on board' - , ro: '%1 has $2 carbohydrates on board' - , ru: '%1 has $2 carbohydrates on board' - , sk: '%1 has $2 carbohydrates on board' - , sv: '%1 has $2 carbohydrates on board' - , tr: '%1 has $2 carbohydrates on board' - , zh_cn: '%1 has $2 carbohydrates on board' - , zh_tw: '%1 has $2 carbohydrates on board' + bg: '%1 has %2 carbohydrates on board' + , cs: '%1 has %2 carbohydrates on board' + , de: '%1 hat %2 Kohlenhydrate wirkend' + , dk: '%1 has %2 carbohydrates on board' + , el: '%1 has %2 carbohydrates on board' + , en: '%1 has %2 carbohydrates on board' + , es: '%1 has %2 carbohydrates on board' + , fi: '%1 has %2 carbohydrates on board' + , fr: '%1 has %2 carbohydrates on board' + , he: '%1 has %2 carbohydrates on board' + , hr: '%1 has %2 carbohydrates on board' + , it: '%1 has %2 carbohydrates on board' + , ko: '%1 has %2 carbohydrates on board' + , nb: '%1 has %2 carbohydrates on board' + , nl: '%1 has %2 carbohydrates on board' + , pl: '%1 has %2 carbohydrates on board' + , pt: '%1 has %2 carbohydrates on board' + , ro: '%1 has %2 carbohydrates on board' + , ru: '%1 has %2 carbohydrates on board' + , sk: '%1 has %2 carbohydrates on board' + , sv: '%1 has %2 carbohydrates on board' + , tr: '%1 has %2 carbohydrates on board' + , zh_cn: '%1 has %2 carbohydrates on board' + , zh_tw: '%1 has %2 carbohydrates on board' }, 'virtAsstCob': { bg: 'You have %1 carbohydrates on board' , cs: 'You have %1 carbohydrates on board' - , de: 'You have %1 carbohydrates on board' + , de: 'Du hast noch %1 Kohlenhydrate wirkend.' , dk: 'You have %1 carbohydrates on board' , el: 'You have %1 carbohydrates on board' , en: 'You have %1 carbohydrates on board' @@ -14073,7 +14073,7 @@ function init() { 'virtAsstUnknownIntentTitle': { en: 'Unknown Intent' , cs: 'Unknown Intent' - , de: 'Unknown Intent' + , de: 'Unbekannter Satz' , dk: 'Unknown Intent' , ko: 'Unknown Intent' , nl: 'Unknown Intent' @@ -14090,7 +14090,7 @@ function init() { 'virtAsstUnknownIntentText': { en: 'I\'m sorry, I don\'t know what you\'re asking for.' , cs: 'I\'m sorry, I don\'t know what you\'re asking for.' - , de: 'I\'m sorry, I don\'t know what you\'re asking for.' + , de: 'Tut mir leid, ich hab deine Frage nicht verstanden.' , dk: 'I\'m sorry, I don\'t know what you\'re asking for.' , ko: 'I\'m sorry, I don\'t know what you\'re asking for.' , nl: 'I\'m sorry, I don\'t know what you\'re asking for.' From 9718bc748a441007f212675e5ff990726368b1b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Szubert?= Date: Wed, 1 Jan 2020 10:00:18 +0100 Subject: [PATCH 016/104] Fix Alexa Launch and SessionEnded Requests (#5377) * Fix Alexa Launch Request * Allow LaunchRequest to handle intent if set, change shouldEndSession from string to bool * Fix SessionEndedRequest --- lib/api/alexa/index.js | 38 +++++++++------- lib/language.js | 52 ++++++++++++++++++++++ tests/api.alexa.test.js | 96 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 171 insertions(+), 15 deletions(-) create mode 100644 tests/api.alexa.test.js diff --git a/lib/api/alexa/index.js b/lib/api/alexa/index.js index f2f42c1e7eb..37ae00ab244 100644 --- a/lib/api/alexa/index.js +++ b/lib/api/alexa/index.js @@ -53,21 +53,29 @@ function configure (app, wares, ctx, env) { } switch (req.body.request.type) { - case 'IntentRequest': - onIntent(req.body.request.intent, function (title, response) { - res.json(ctx.alexa.buildSpeechletResponse(title, response, '', 'true')); + case 'SessionEndedRequest': + onSessionEnded(function () { + res.json(''); next( ); }); break; case 'LaunchRequest': - onLaunch(req.body.request.intent, function (title, response) { - res.json(ctx.alexa.buildSpeechletResponse(title, response, '', 'true')); - next( ); - }); - break; - case 'SessionEndedRequest': - onSessionEnded(req.body.request.intent, function (alexaResponse) { - res.json(alexaResponse); + if (!req.body.request.intent) { + onLaunch(function () { + res.json(ctx.alexa.buildSpeechletResponse( + translate('virtAsstTitleLaunch'), + translate('virtAsstLaunch'), + translate('virtAsstLaunch'), + false + )); + next( ); + }); + break; + } + // if intent is set then fallback to IntentRequest + case 'IntentRequest': // eslint-disable-line no-fallthrough + onIntent(req.body.request.intent, function (title, response) { + res.json(ctx.alexa.buildSpeechletResponse(title, response, '', true)); next( ); }); break; @@ -139,10 +147,9 @@ function configure (app, wares, ctx, env) { }); - function onLaunch(intent, next) { + function onLaunch(next) { console.log('Session launched'); - console.log(JSON.stringify(intent)); - handleIntent(intent.name, intent.slots, next); + next( ); } function onIntent(intent, next) { @@ -151,8 +158,9 @@ function configure (app, wares, ctx, env) { handleIntent(intent.name, intent.slots, next); } - function onSessionEnded() { + function onSessionEnded(next) { console.log('Session ended'); + next( ); } function handleIntent(intentName, slots, next) { diff --git a/lib/language.js b/lib/language.js index f1bbaca7b4f..6a827e6b8ac 100644 --- a/lib/language.js +++ b/lib/language.js @@ -13329,6 +13329,32 @@ function init() { , zh_cn: 'Current IOB' , zh_tw: 'Current IOB' }, + 'virtAsstTitleLaunch': { + bg: 'Welcome to Nightscout' + , cs: 'Welcome to Nightscout' + , de: 'Welcome to Nightscout' + , dk: 'Welcome to Nightscout' + , el: 'Welcome to Nightscout' + , en: 'Welcome to Nightscout' + , es: 'Welcome to Nightscout' + , fi: 'Welcome to Nightscout' + , fr: 'Welcome to Nightscout' + , he: 'Welcome to Nightscout' + , hr: 'Welcome to Nightscout' + , it: 'Welcome to Nightscout' + , ko: 'Welcome to Nightscout' + , nb: 'Welcome to Nightscout' + , pl: 'Welcome to Nightscout' + , pt: 'Welcome to Nightscout' + , ro: 'Welcome to Nightscout' + , nl: 'Welcome to Nightscout' + , ru: 'Welcome to Nightscout' + , sk: 'Welcome to Nightscout' + , sv: 'Welcome to Nightscout' + , tr: 'Welcome to Nightscout' + , zh_cn: 'Welcome to Nightscout' + , zh_tw: 'Welcome to Nightscout' + }, 'virtAsstTitleLoopForecast': { bg: 'Loop Forecast' , cs: 'Loop Forecast' @@ -13745,6 +13771,32 @@ function init() { , zh_cn: '%1 单位' , zh_tw: '%1 units of' }, + 'virtAsstLaunch': { + bg: 'What would you like to check on Nightscout?' + , cs: 'What would you like to check on Nightscout?' + , de: 'What would you like to check on Nightscout?' + , dk: 'What would you like to check on Nightscout?' + , el: 'What would you like to check on Nightscout?' + , en: 'What would you like to check on Nightscout?' + , es: 'What would you like to check on Nightscout?' + , fi: 'What would you like to check on Nightscout?' + , fr: 'What would you like to check on Nightscout?' + , he: 'What would you like to check on Nightscout?' + , hr: 'What would you like to check on Nightscout?' + , it: 'What would you like to check on Nightscout?' + , ko: 'What would you like to check on Nightscout?' + , nb: 'What would you like to check on Nightscout?' + , pl: 'What would you like to check on Nightscout?' + , pt: 'What would you like to check on Nightscout?' + , ro: 'What would you like to check on Nightscout?' + , nl: 'What would you like to check on Nightscout?' + , ru: 'What would you like to check on Nightscout?' + , sk: 'What would you like to check on Nightscout?' + , sv: 'What would you like to check on Nightscout?' + , tr: 'What would you like to check on Nightscout?' + , zh_cn: 'What would you like to check on Nightscout?' + , zh_tw: 'What would you like to check on Nightscout?' + }, 'virtAsstPreamble': { bg: 'Your' , cs: 'Vaše' diff --git a/tests/api.alexa.test.js b/tests/api.alexa.test.js new file mode 100644 index 00000000000..8c2f5916cf6 --- /dev/null +++ b/tests/api.alexa.test.js @@ -0,0 +1,96 @@ +'use strict'; + +var request = require('supertest'); +var language = require('../lib/language')(); +const bodyParser = require('body-parser'); + +require('should'); + +describe('Alexa REST api', function ( ) { + const apiRoot = require('../lib/api/root'); + const api = require('../lib/api/'); + before(function (done) { + var env = require('../env')( ); + env.settings.enable = ['alexa']; + env.settings.authDefaultRoles = 'readable'; + env.api_secret = 'this is my long pass phrase'; + this.wares = require('../lib/middleware/')(env); + this.app = require('express')( ); + this.app.enable('api'); + var self = this; + require('../lib/server/bootevent')(env, language).boot(function booted (ctx) { + self.app.use('/api', bodyParser({ + limit: 1048576 * 50 + }), apiRoot(env, ctx)); + + self.app.use('/api/v1', bodyParser({ + limit: 1048576 * 50 + }), api(env, ctx)); + done( ); + }); + }); + + it('Launch Request', function (done) { + request(this.app) + .post('/api/v1/alexa') + .send({ + "request": { + "type": "LaunchRequest", + "locale": "en-US" + } + }) + .expect(200) + .end(function (err, res) { + if (err) return done(err); + + const launchText = 'What would you like to check on Nightscout?'; + + res.body.response.outputSpeech.text.should.equal(launchText); + res.body.response.reprompt.outputSpeech.text.should.equal(launchText); + res.body.response.shouldEndSession.should.equal(false); + done( ); + }); + }); + + it('Launch Request With Intent', function (done) { + request(this.app) + .post('/api/v1/alexa') + .send({ + "request": { + "type": "LaunchRequest", + "locale": "en-US", + "intent": { + "name": "UNKNOWN" + } + } + }) + .expect(200) + .end(function (err, res) { + if (err) return done(err); + + const unknownIntentText = 'I\'m sorry, I don\'t know what you\'re asking for.'; + + res.body.response.outputSpeech.text.should.equal(unknownIntentText); + res.body.response.shouldEndSession.should.equal(true); + done( ); + }); + }); + + it('Session Ended', function (done) { + request(this.app) + .post('/api/v1/alexa') + .send({ + "request": { + "type": "SessionEndedRequest", + "locale": "en-US" + } + }) + .expect(200) + .end(function (err) { + if (err) return done(err); + + done( ); + }); + }); +}); + From a4ce06ff688632585ce27c5ce0fffaf0ff6f6295 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Szubert?= Date: Wed, 1 Jan 2020 10:03:20 +0100 Subject: [PATCH 017/104] Fix #5149 - discord link on CONTRIBUTING.md doesn't do anything (#5380) --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c8e87e2db4a..335dfe7a7d1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -34,7 +34,7 @@ [coverage-img]: https://img.shields.io/coveralls/nightscout/cgm-remote-monitor/master.svg [coverage-url]: https://coveralls.io/r/nightscout/cgm-remote-monitor?branch=master [discord-img]: https://img.shields.io/discord/629952586895851530?label=discord%20chat -[discord-url]: https://discordapp.com/channels/629952586895851530/629952669967974410 +[discord-url]: https://discord.gg/rTKhrqz ## Installation for development From 8139cb950ebc23fc246ef1f3c8a6f3d8b0498afb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Szubert?= Date: Wed, 1 Jan 2020 10:14:12 +0100 Subject: [PATCH 018/104] Fix #5146 - display of treatment with only Fat and Protein set (#5381) --- lib/client/renderer.js | 13 ++++++++----- lib/profilefunctions.js | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/client/renderer.js b/lib/client/renderer.js index f058d3de860..7ea154ff0fd 100644 --- a/lib/client/renderer.js +++ b/lib/client/renderer.js @@ -542,23 +542,26 @@ function init (client, d3) { // these used to be semicircles from 1.5708 to 4.7124, but that made the tooltip target too big { 'element': '', 'color': 'transparent', 'start': 3.1400, 'end': 3.1432, 'inner': radius.R2, 'outer': radius.R3 } , { 'element': '', 'color': 'transparent', 'start': 3.1400, 'end': 3.1432, 'inner': radius.R2, 'outer': radius.R4 } - ]; + ] + , arc_data_1_elements = []; arc_data[0].outlineOnly = !treatment.carbs; arc_data[2].outlineOnly = !treatment.insulin; if (treatment.carbs > 0) { - arc_data[1].element = Math.round(treatment.carbs) + ' g'; + arc_data_1_elements.push(Math.round(treatment.carbs) + ' g'); } if (treatment.protein > 0) { - arc_data[1].element = arc_data[1].element + " / " + Math.round(treatment.protein) + ' g'; + arc_data_1_elements.push(Math.round(treatment.protein) + ' g'); } if (treatment.fat > 0) { - arc_data[1].element = arc_data[1].element + " / " + Math.round(treatment.fat) + ' g'; + arc_data_1_elements.push(Math.round(treatment.fat) + ' g'); } + arc_data[1].element = arc_data_1_elements.join(' / '); + if (treatment.foodType) { arc_data[1].element = arc_data[1].element + " " + treatment.foodType; } @@ -1007,7 +1010,7 @@ function init (client, d3) { }; renderer.drawTreatment = function drawTreatment (treatment, opts, carbratio) { - if (!treatment.carbs && !treatment.insulin) { + if (!treatment.carbs && !treatment.protein && !treatment.fat && !treatment.insulin) { return; } diff --git a/lib/profilefunctions.js b/lib/profilefunctions.js index 5826e6108b4..8bd251d4820 100644 --- a/lib/profilefunctions.js +++ b/lib/profilefunctions.js @@ -163,7 +163,7 @@ function init (profileData) { profile.getUnits = function getUnits (spec_profile) { var pu = profile.getCurrentProfile(null, spec_profile)['units'] + ' '; if (pu.toLowerCase().includes('mmol')) return 'mmol'; - return 'mgdl'; + return 'mg/dl'; }; profile.getTimezone = function getTimezone (spec_profile) { From f7922a6bd75fe34eabfb94a3ce0b446f34992a04 Mon Sep 17 00:00:00 2001 From: Andrew Dixon Date: Wed, 1 Jan 2020 09:20:48 +0000 Subject: [PATCH 019/104] Basic report page style fixes and updates (#5387) --- lib/report_plugins/daytoday.js | 30 +++--- static/css/report.css | 85 +++++++++++++++-- views/reportindex.html | 162 +++++++++++++++++---------------- 3 files changed, 175 insertions(+), 102 deletions(-) diff --git a/lib/report_plugins/daytoday.js b/lib/report_plugins/daytoday.js index f4b5da7cf45..cb66d4db0cc 100644 --- a/lib/report_plugins/daytoday.js +++ b/lib/report_plugins/daytoday.js @@ -23,17 +23,17 @@ daytoday.html = function html (client) { '

' + translate('Day to day') + '

' + '' + translate('To see this report, press SHOW while in this view') + '
' + translate('Display') + ': ' + - '' + translate('Insulin') + '' + - '' + translate('Carbs') + '' + - '' + translate('Basal rate') + '' + - '' + translate('Notes') + - '' + translate('Food') + - '' + translate('Raw') + '' + - '' + translate('IOB') + '' + - '' + translate('COB') + '' + - '' + translate('Predictions') + '' + - '' + translate('OpenAPS') + '' + - '' + translate('Insulin distribution') + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + ' ' + translate('Size') + ' ' + '
' + translate('Scale') + ': ' + - '' + - translate('Linear') + - '' + - translate('Logarithmic') + + '' + + '' + '' + '
' + '
' + @@ -363,7 +363,7 @@ daytoday.report = function report_daytoday (datastorage, sorteddaystoshow, optio var timestamp = treatmentsTimestamps[treatmentsIndex]; // TODO refactor code so this is set here - now set as global in file loaded by the browser // eslint-disable-next-line no-undef - var predictedIndex = findPredicted(predictions, timestamp, predictedOffset); // Find predictions offset before or after timestamp + var predictedIndex = findPredicted(predictions, timestamp, Nightscout.predictions.offset); // Find predictions offset before or after timestamp if (predictedIndex != null) { entry = predictions[predictedIndex]; // Start entry @@ -371,7 +371,7 @@ daytoday.report = function report_daytoday (datastorage, sorteddaystoshow, optio var end = moment().endOf('day'); if (options.predictedTruncate) { // eslint-disable-next-line no-undef - if (predictedOffset >= 0) { + if (Nightscout.predictions.offset >= 0) { // If we are looking forward we want to stop at the next treatment if (treatmentsIndex < treatmentsTimestamps.length - 1) { end = moment(treatmentsTimestamps[treatmentsIndex + 1]); diff --git a/static/report/js/predictions.js b/static/report/js/predictions.js deleted file mode 100644 index 336483a0dce..00000000000 --- a/static/report/js/predictions.js +++ /dev/null @@ -1,40 +0,0 @@ - -var predictedOffset = 0; - -function predictForward() { - predictedOffset += 5; - $("#rp_predictedOffset").html(predictedOffset); - $("#rp_show").click(); -} - -function predictMoreForward() { - predictedOffset += 30; - $("#rp_predictedOffset").html(predictedOffset); - $("#rp_show").click(); -} - -function predictBackward() { - predictedOffset -= 5; - $("#rp_predictedOffset").html(predictedOffset); - $("#rp_show").click(); -} - -function predictMoreBackward() { - predictedOffset -= 30; - $("#rp_predictedOffset").html(predictedOffset); - $("#rp_show").click(); -} - -function predictResetToZero() { - predictedOffset = 0; - $("#rp_predictedOffset").html(predictedOffset); - $("#rp_show").click(); -} - -$(document).on('change', '#rp_optionspredicted', function() { - if (this.checked) - $("#rp_predictedSettings").show(); - else - $("#rp_predictedSettings").hide(); - predictResetToZero(); -}); diff --git a/views/reportindex.html b/views/reportindex.html index aa16d0dc01a..dd1bcfd957b 100644 --- a/views/reportindex.html +++ b/views/reportindex.html @@ -128,7 +128,6 @@ - From ddbda15c0a25d8149fa60b76c8e3cbaa4ef7c22c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Szubert?= Date: Mon, 6 Jan 2020 18:31:03 +0100 Subject: [PATCH 029/104] Move toolbar and authentication status to partial and include on each page (#5393) * Move authentication status to partial and include on each page * Move toolbar to partial Co-authored-by: Sulka Haro --- app.js | 59 +++++++++++++++--------- static/css/drawer.css | 48 +++++++++++++------ static/css/main.css | 22 ++++++++- static/css/report.css | 26 ----------- static/css/translations.css | 13 +++++- views/adminindex.html | 19 ++------ views/foodindex.html | 23 ++------- views/index.html | 16 ++----- views/partials/authentication-status.ejs | 3 ++ views/partials/toolbar.ejs | 34 ++++++++++++++ views/profileindex.html | 20 ++------ views/reportindex.html | 30 ++++++------ views/translationsindex.html | 9 ++-- 13 files changed, 174 insertions(+), 148 deletions(-) create mode 100644 views/partials/authentication-status.ejs create mode 100644 views/partials/toolbar.ejs diff --git a/app.js b/app.js index 7c6f67b1ee1..b718e3772e9 100644 --- a/app.js +++ b/app.js @@ -131,36 +131,53 @@ function create (env, ctx) { } })); - const clockviews = require('./lib/server/clocks.js')(env, ctx); - clockviews.setLocals(app.locals); - - app.use("/clock", clockviews); - - app.get("/", (req, res) => { - res.render("index.html", { - locals: app.locals - }); - }); - var appPages = { - "/clock-color.html": "clock-color.html" - , "/admin": "adminindex.html" - , "/profile": "profileindex.html" - , "/food": "foodindex.html" - , "/bgclock.html": "bgclock.html" - , "/report": "reportindex.html" - , "/translations": "translationsindex.html" - , "/clock.html": "clock.html" + "/": { + file: "index.html" + , type: "index" + } + , "/admin": { + file: "adminindex.html" + , title: 'Admin Tools' + , type: 'admin' + } + , "/food": { + file: "foodindex.html" + , title: 'Food Editor' + , type: 'food' + } + , "/profile": { + file: "profileindex.html" + , title: 'Profile Editor' + , type: 'profile' + } + , "/report": { + file: "reportindex.html" + , title: 'Nightscout reporting' + , type: 'report' + } + , "/translations": { + file: "translationsindex.html" + , title: 'Nightscout translations' + , type: 'translations' + } }; Object.keys(appPages).forEach(function(page) { app.get(page, (req, res) => { - res.render(appPages[page], { - locals: app.locals + res.render(appPages[page].file, { + locals: app.locals, + title: appPages[page].title ? appPages[page].title : '', + type: appPages[page].type ? appPages[page].type : '', }); }); }); + const clockviews = require('./lib/server/clocks.js')(env, ctx); + clockviews.setLocals(app.locals); + + app.use("/clock", clockviews); + app.get("/appcache/*", (req, res) => { res.render("nightscout.appcache", { locals: app.locals diff --git a/static/css/drawer.css b/static/css/drawer.css index f9e6147fe2d..96e1375ebd8 100644 --- a/static/css/drawer.css +++ b/static/css/drawer.css @@ -207,34 +207,49 @@ h1, legend, } #toolbar { - background: url(/images/logo2.png) no-repeat 3px 3px #333; - border-bottom: 1px solid #999; - top: 0; - margin: 0; - height: 44px; text-shadow: 0 0 5px black; + display: flex; + height: 44px; + margin: 0 0 10px; + padding: 0 15px 0 40px; + position: relative; + align-items: center; + background: url(/images/logo2.png) no-repeat 3px center #333; + border-bottom: 1px solid #999; + justify-content: space-between; } #toolbar .customTitle { color: #ccc; font-size: 16px; - margin-top: 0; - margin-left: 42px; - padding-top: 10px; - padding-right: 150px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; } + +#toolbar .button-close { + color: #404040; + text-align: center; + text-shadow: none; + height: 20px; + width: 20px; + padding: 5px; + position: absolute; + top: 50%; + right: 10px; + transform: translateY(-50%); + background: grey; + border: 2px solid #404040; + border-radius: 5px; +} + +#toolbar .button-close + #buttonbar { + margin-right: 40px; +} + #buttonbar { - margin-right: 50px; - padding-right: 15px; - height: 44px; opacity: 0.75; vertical-align: middle; - position: absolute; - right: 0; - z-index: 500; } #buttonbar a, @@ -244,14 +259,17 @@ h1, legend, height: 44px; line-height: 44px; } + #buttonbar .selected { color: red; } + #buttonbar a { float: left; text-decoration: none; width: 34px; } + #buttonbar i { padding-left: 12px; } diff --git a/static/css/main.css b/static/css/main.css index 7c2e25837cb..8e6882dde9d 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -427,12 +427,30 @@ a, a:visited, a:link { display: none; } -#authorizationstatus a { +.toolbar-title { + text-align: left; + padding: 0 10px; +} + +.toolbar-title h2 { + margin: 0; +} + +.page-content { + padding: 10px; +} + +.authentication-status { + padding: 10px; + border-top: 1px solid #bdbdbd; +} + +.authentication-status a { color: #2196f3; text-decoration: underline; } -#authorizationstatus .small { +.authentication-status .small { font-size: 12px; } diff --git a/static/css/report.css b/static/css/report.css index 73f9571c81f..6994eb96b6c 100644 --- a/static/css/report.css +++ b/static/css/report.css @@ -14,32 +14,6 @@ body { color: black; } -header { - display: flex; - justify-content: flex-start; - align-items: center; - margin: 10px; -} - -.close { - color: #000; - display: block; - position: absolute; - background: #fff; - border-radius: 5px; - border: 1px solid #000; - padding: 5px 10px; - text-decoration: none; - top: 10px; - right: 10px; -} - -.pagetitle { - font-weight: bold; - font-size: 14pt; - text-transform: uppercase; -} - .centeraligned { text-align:center; } diff --git a/static/css/translations.css b/static/css/translations.css index 8408cf29f45..b1fae30e253 100644 --- a/static/css/translations.css +++ b/static/css/translations.css @@ -1,5 +1,16 @@ -td,th { +body { + color: black; + font-family: 'Open Sans', Helvetica, Arial, sans-serif; + background-color: white; +} + +th, +td { vertical-align: top; text-align: left; border: 1px solid } + +#translations { + padding: 15px; +} diff --git a/views/adminindex.html b/views/adminindex.html index 6108d1b3978..c9bb631cfeb 100644 --- a/views/adminindex.html +++ b/views/adminindex.html @@ -32,23 +32,12 @@ <% include preloadCSS %> - - -
X
- -
-

Nightscout

-
- -
-

Admin Tools

-
+ + <%- include('partials/toolbar') %>
- -
- Authentication status: - + + <%- include('partials/authentication-status') %> diff --git a/views/foodindex.html b/views/foodindex.html index d42eb16a50b..0cbb6a3ebf6 100644 --- a/views/foodindex.html +++ b/views/foodindex.html @@ -32,22 +32,9 @@ <% include preloadCSS %> - + + <%- include('partials/toolbar') %> -
X
- - -
-
- Status: Not loaded -
-

Nightscout

-
-

Food Editor

-
-
- -
Your database @@ -121,10 +108,10 @@

Food Editor

- Authentication status: -
- + + <%- include('partials/authentication-status') %> + diff --git a/views/index.html b/views/index.html index b80e11ddd42..153e187b7f7 100644 --- a/views/index.html +++ b/views/index.html @@ -117,17 +117,8 @@
-
- -
Nightscout
-
+ <%- include('partials/toolbar') %> +
@@ -277,8 +268,7 @@ Reset, and use defaults
-
- + <%- include('partials/authentication-status') %>
About diff --git a/views/partials/authentication-status.ejs b/views/partials/authentication-status.ejs new file mode 100644 index 00000000000..db969c906b1 --- /dev/null +++ b/views/partials/authentication-status.ejs @@ -0,0 +1,3 @@ +
+ Authentication status: +
\ No newline at end of file diff --git a/views/partials/toolbar.ejs b/views/partials/toolbar.ejs new file mode 100644 index 00000000000..d3c8a7613fb --- /dev/null +++ b/views/partials/toolbar.ejs @@ -0,0 +1,34 @@ +
+

Nightscout

+ + <%if (type !== 'index') { %> + X + <% } %> + + <%if (type === 'food') { %> +
+ Status: Not loaded +
+ <% } %> + <%if (type === 'profile') { %> +
+ Status: Not loaded +
+ <% } %> + <%if (type === 'index') { %> + + <% } %> +
+ +<%if (title) { %> +
+

<%= title %>

+
+<% } %> \ No newline at end of file diff --git a/views/profileindex.html b/views/profileindex.html index d64f1300917..55a98f19dc4 100644 --- a/views/profileindex.html +++ b/views/profileindex.html @@ -32,19 +32,8 @@ <% include preloadCSS %> - -
X
- -
-
- Status: Not loaded -
-

Nightscout

-
- -
-

Profile Editor

-
+ + <%- include('partials/toolbar') %>
@@ -168,15 +157,14 @@

Profile Editor

- Authentication status: - -

Status: Not loaded

+ <%- include('partials/authentication-status') %> + diff --git a/views/reportindex.html b/views/reportindex.html index dd1bcfd957b..164c08c0252 100644 --- a/views/reportindex.html +++ b/views/reportindex.html @@ -22,18 +22,16 @@ - + + <% include preloadCSS %> - - X -
- - Nightscout Reporting -
+ + <%- include('partials/toolbar') %> +
@@ -120,15 +118,15 @@

- -
- Authentication status: - - - - - -
+ + <%- include('partials/authentication-status') %> + + + + + + + \ No newline at end of file diff --git a/views/translationsindex.html b/views/translationsindex.html index 4145f3d1db0..6543bb06259 100644 --- a/views/translationsindex.html +++ b/views/translationsindex.html @@ -23,19 +23,18 @@ + <% include preloadCSS %> - -
X
+ + <%- include('partials/toolbar') %> -

Nightscout translations

-
- Authentication status: + <%- include('partials/authentication-status') %> From 40986896e17c0f3bdddd046eb97f3c46691ace65 Mon Sep 17 00:00:00 2001 From: Caleb Date: Sat, 11 Jan 2020 03:08:41 -0800 Subject: [PATCH 030/104] Virtual assistant code simplification (#5400) * Moved duplicate plugin code to single location, and small improvements * Defined _each() --- lib/api/alexa/index.js | 95 ++---------------------------- lib/api/googlehome/index.js | 95 ++---------------------------- lib/plugins/virtAsstBase.js | 111 ++++++++++++++++++++++++++++++++++++ lib/server/bootevent.js | 4 ++ 4 files changed, 125 insertions(+), 180 deletions(-) create mode 100644 lib/plugins/virtAsstBase.js diff --git a/lib/api/alexa/index.js b/lib/api/alexa/index.js index 37ae00ab244..560de7c495b 100644 --- a/lib/api/alexa/index.js +++ b/lib/api/alexa/index.js @@ -1,10 +1,8 @@ 'use strict'; var moment = require('moment'); -var _each = require('lodash/each'); function configure (app, wares, ctx, env) { - var entries = ctx.entries; var express = require('express') , api = express.Router( ); var translate = ctx.language.translate; @@ -16,30 +14,7 @@ function configure (app, wares, ctx, env) { // json body types get handled as parsed json api.use(wares.bodyParser.json()); - ctx.plugins.eachEnabledPlugin(function each(plugin){ - if (plugin.virtAsst) { - if (plugin.virtAsst.intentHandlers) { - console.log('Alexa: Plugin ' + plugin.name + ' supports Virtual Assistants'); - _each(plugin.virtAsst.intentHandlers, function (route) { - if (route) { - ctx.alexa.configureIntentHandler(route.intent, route.intentHandler, route.metrics); - } - }); - } - if (plugin.virtAsst.rollupHandlers) { - console.log('Alexa: Plugin ' + plugin.name + ' supports rollups for Virtual Assistants'); - _each(plugin.virtAsst.rollupHandlers, function (route) { - console.log('Route'); - console.log(route); - if (route) { - ctx.alexa.addToRollup(route.rollupGroup, route.rollupHandler, route.rollupName); - } - }); - } - } else { - console.log('Alexa: Plugin ' + plugin.name + ' does not support Virtual Assistants'); - } - }); + ctx.virtAsstBase.setupVirtAsstHandlers(ctx.alexa); api.post('/alexa', ctx.authorization.isPermitted('api:*:read'), function (req, res, next) { console.log('Incoming request from Alexa'); @@ -82,70 +57,7 @@ function configure (app, wares, ctx, env) { } }); - ctx.alexa.addToRollup('Status', function bgRollupHandler(slots, sbx, callback) { - entries.list({count: 1}, function (err, records) { - var direction; - if (translate(records[0].direction)) { - direction = translate(records[0].direction); - } else { - direction = records[0].direction; - } - var status = translate('virtAsstStatus', { - params: [ - sbx.scaleMgdl(records[0].sgv), - direction, - moment(records[0].date).from(moment(sbx.time)) - ] - }); - - callback(null, {results: status, priority: -1}); - }); - }, 'BG Status'); - - ctx.alexa.configureIntentHandler('MetricNow', function (callback, slots, sbx, locale) { - entries.list({count: 1}, function(err, records) { - var direction; - if(translate(records[0].direction)){ - direction = translate(records[0].direction); - } else { - direction = records[0].direction; - } - var status = translate('virtAsstStatus', { - params: [ - sbx.scaleMgdl(records[0].sgv), - direction, - moment(records[0].date).from(moment(sbx.time))] - }); - - callback(translate('virtAsstTitleCurrentBG'), status); - }); - }, ['bg', 'blood glucose', 'number']); - - ctx.alexa.configureIntentHandler('MetricNow', function (callback, slots, sbx, locale) { - if (sbx.properties.delta && sbx.properties.delta.display) { - entries.list({count: 2}, function(err, records) { - callback( - translate('virtAsstTitleDelta'), - translate('virtAsstDelta', { - params: [ - sbx.properties.delta.display == '+0' ? '0' : sbx.properties.delta.display, - moment(records[0].date).from(moment(sbx.time)), - moment(records[1].date).from(moment(sbx.time)) - ] - }) - ); - }); - } else { - callback(translate('virtAsstTitleDelta'), translate('virtAsstUnknown')); - } - }, ['delta']); - - ctx.alexa.configureIntentHandler('NSStatus', function (callback, slots, sbx, locale) { - ctx.alexa.getRollup('Status', sbx, slots, locale, function (status) { - callback(translate('virtAsstTitleFullStatus'), status); - }); - }); - + ctx.virtAsstBase.setupMutualIntents(ctx.alexa); function onLaunch(next) { console.log('Session launched'); @@ -181,6 +93,7 @@ function configure (app, wares, ctx, env) { metric = slots.metric.resolutions.resolutionsPerAuthority[0].values[0].value.name; } else { next(translate('virtAsstUnknownIntentTitle'), translate('virtAsstUnknownIntentText')); + return; } } @@ -188,8 +101,10 @@ function configure (app, wares, ctx, env) { if (handler){ var sbx = initializeSandbox(); handler(next, slots, sbx); + return; } else { next(translate('virtAsstUnknownIntentTitle'), translate('virtAsstUnknownIntentText')); + return; } } diff --git a/lib/api/googlehome/index.js b/lib/api/googlehome/index.js index f533f579389..8c99eea640e 100644 --- a/lib/api/googlehome/index.js +++ b/lib/api/googlehome/index.js @@ -1,10 +1,8 @@ 'use strict'; var moment = require('moment'); -var _each = require('lodash/each'); function configure (app, wares, ctx, env) { - var entries = ctx.entries; var express = require('express') , api = express.Router( ); var translate = ctx.language.translate; @@ -16,30 +14,7 @@ function configure (app, wares, ctx, env) { // json body types get handled as parsed json api.use(wares.bodyParser.json()); - ctx.plugins.eachEnabledPlugin(function each(plugin){ - if (plugin.virtAsst) { - if (plugin.virtAsst.intentHandlers) { - console.log('Google Home: Plugin ' + plugin.name + ' supports Virtual Assistants'); - _each(plugin.virtAsst.intentHandlers, function (route) { - if (route) { - ctx.googleHome.configureIntentHandler(route.intent, route.intentHandler, route.metrics); - } - }); - } - if (plugin.virtAsst.rollupHandlers) { - console.log('Google Home: Plugin ' + plugin.name + ' supports rollups for Virtual Assistants'); - _each(plugin.virtAsst.rollupHandlers, function (route) { - console.log('Route'); - console.log(route); - if (route) { - ctx.googleHome.addToRollup(route.rollupGroup, route.rollupHandler, route.rollupName); - } - }); - } - } else { - console.log('Google Home: Plugin ' + plugin.name + ' does not support Virtual Assistants'); - } - }); + ctx.virtAsstBase.setupVirtAsstHandlers(ctx.googleHome); api.post('/googlehome', ctx.authorization.isPermitted('api:*:read'), function (req, res, next) { console.log('Incoming request from Google Home'); @@ -58,76 +33,16 @@ function configure (app, wares, ctx, env) { handler(function (title, response) { res.json(ctx.googleHome.buildSpeechletResponse(response, false)); next( ); + return; }, req.body.queryResult.parameters, sbx); } else { - res.json(ctx.googleHome.buildSpeechletResponse('I\'m sorry. I don\'t know what you\'re asking for. Could you say that again?', true)); + res.json(ctx.googleHome.buildSpeechletResponse(translate('virtAsstUnknownIntentText'), true)); next( ); + return; } }); - ctx.googleHome.addToRollup('Status', function bgRollupHandler(slots, sbx, callback) { - entries.list({count: 1}, function (err, records) { - var direction; - if (translate(records[0].direction)) { - direction = translate(records[0].direction); - } else { - direction = records[0].direction; - } - var status = translate('virtAsstStatus', { - params: [ - sbx.scaleMgdl(records[0].sgv), - direction, - moment(records[0].date).from(moment(sbx.time)) - ] - }); - - callback(null, {results: status, priority: -1}); - }); - }, 'BG Status'); - - ctx.googleHome.configureIntentHandler('MetricNow', function (callback, slots, sbx, locale) { - entries.list({count: 1}, function(err, records) { - var direction; - if(translate(records[0].direction)){ - direction = translate(records[0].direction); - } else { - direction = records[0].direction; - } - var status = translate('virtAsstStatus', { - params: [ - sbx.scaleMgdl(records[0].sgv), - direction, - moment(records[0].date).from(moment(sbx.time))] - }); - - callback(translate('virtAsstTitleCurrentBG'), status); - }); - }, ['bg', 'blood glucose', 'number']); - - ctx.googleHome.configureIntentHandler('MetricNow', function (callback, slots, sbx, locale) { - if (sbx.properties.delta && sbx.properties.delta.display) { - entries.list({count: 2}, function(err, records) { - callback( - translate('virtAsstTitleDelta'), - translate('virtAsstDelta', { - params: [ - sbx.properties.delta.display == '+0' ? '0' : sbx.properties.delta.display, - moment(records[0].date).from(moment(sbx.time)), - moment(records[1].date).from(moment(sbx.time)) - ] - }) - ); - }); - } else { - callback(translate('virtAsstTitleDelta'), translate('virtAsstUnknown')); - } - }, ['delta']); - - ctx.googleHome.configureIntentHandler('NSStatus', function (callback, slots, sbx, locale) { - ctx.googleHome.getRollup('Status', sbx, slots, locale, function (status) { - callback(translate('virtAsstTitleFullStatus'), status); - }); - }); + ctx.virtAsstBase.setupMutualIntents(ctx.googleHome); function initializeSandbox() { var sbx = require('../../sandbox')(); diff --git a/lib/plugins/virtAsstBase.js b/lib/plugins/virtAsstBase.js new file mode 100644 index 00000000000..79864f5bf95 --- /dev/null +++ b/lib/plugins/virtAsstBase.js @@ -0,0 +1,111 @@ +'use strict'; + +var moment = require('moment'); +var _each = require('lodash/each'); + +function init(env, ctx) { + function virtAsstBase() { + return virtAsstBase; + } + + var entries = ctx.entries; + var translate = ctx.language.translate; + + virtAsstBase.setupMutualIntents = function (configuredPlugin) { + // full status + configuredPlugin.addToRollup('Status', function (slots, sbx, callback) { + entries.list({count: 1}, function (err, records) { + var direction; + if (translate(records[0].direction)) { + direction = translate(records[0].direction); + } else { + direction = records[0].direction; + } + var status = translate('virtAsstStatus', { + params: [ + sbx.scaleMgdl(records[0].sgv), + direction, + moment(records[0].date).from(moment(sbx.time)) + ] + }); + + callback(null, {results: status, priority: -1}); + }); + }, 'BG Status'); + + configuredPlugin.configureIntentHandler('NSStatus', function (callback, slots, sbx, locale) { + configuredPlugin.getRollup('Status', sbx, slots, locale, function (status) { + callback(translate('virtAsstTitleFullStatus'), status); + }); + }); + + // blood sugar and direction + configuredPlugin.configureIntentHandler('MetricNow', function (callback, slots, sbx, locale) { + entries.list({count: 1}, function(err, records) { + var direction; + if(translate(records[0].direction)){ + direction = translate(records[0].direction); + } else { + direction = records[0].direction; + } + var status = translate('virtAsstStatus', { + params: [ + sbx.scaleMgdl(records[0].sgv), + direction, + moment(records[0].date).from(moment(sbx.time))] + }); + + callback(translate('virtAsstTitleCurrentBG'), status); + }); + }, ['bg', 'blood glucose', 'number']); + + // blood sugar delta + configuredPlugin.configureIntentHandler('MetricNow', function (callback, slots, sbx, locale) { + if (sbx.properties.delta && sbx.properties.delta.display) { + entries.list({count: 2}, function(err, records) { + callback( + translate('virtAsstTitleDelta'), + translate('virtAsstDelta', { + params: [ + sbx.properties.delta.display == '+0' ? '0' : sbx.properties.delta.display, + moment(records[0].date).from(moment(sbx.time)), + moment(records[1].date).from(moment(sbx.time)) + ] + }) + ); + }); + } else { + callback(translate('virtAsstTitleDelta'), translate('virtAsstUnknown')); + } + }, ['delta']); + }; + + virtAsstBase.setupVirtAsstHandlers = function (configuredPlugin) { + ctx.plugins.eachEnabledPlugin(function (plugin){ + if (plugin.virtAsst) { + if (plugin.virtAsst.intentHandlers) { + console.log('Plugin "' + plugin.name + '" supports Virtual Assistants'); + _each(plugin.virtAsst.intentHandlers, function (route) { + if (route) { + configuredPlugin.configureIntentHandler(route.intent, route.intentHandler, route.metrics); + } + }); + } + if (plugin.virtAsst.rollupHandlers) { + console.log('Plugin "' + plugin.name + '" supports rollups for Virtual Assistants'); + _each(plugin.virtAsst.rollupHandlers, function (route) { + if (route) { + configuredPlugin.addToRollup(route.rollupGroup, route.rollupHandler, route.rollupName); + } + }); + } + } else { + console.log('Plugin "' + plugin.name + '" does not support Virtual Assistants'); + } + }); + }; + + return virtAsstBase; +} + +module.exports = init; \ No newline at end of file diff --git a/lib/server/bootevent.js b/lib/server/bootevent.js index b5e61fad85f..f5702efc4fe 100644 --- a/lib/server/bootevent.js +++ b/lib/server/bootevent.js @@ -179,6 +179,10 @@ function boot (env, language) { ctx.dataloader = require('../data/dataloader')(env, ctx); ctx.notifications = require('../notifications')(env, ctx); + if (env.settings.isEnabled('alexa') || env.settings.isEnabled('googlehome')) { + ctx.virtAsstBase = require('../plugins/virtAsstBase')(env, ctx); + } + if (env.settings.isEnabled('alexa')) { ctx.alexa = require('../plugins/alexa')(env, ctx); } From 4f9f73516409f5174e0eada6e4b47ce7dcf68094 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sat, 11 Jan 2020 12:40:33 +0000 Subject: [PATCH 031/104] Pr/5379 (#5441) * Release 13.0.1 (#5329) * Release ref update v2 (#5301) * Updated release name and number * Added missing version number * Added missing version number * Fix auth dialog sizing error (#5315) * Fix auth dialog sizing error (#5314) * Fix auth dialog sizing error * Fix Client Init After Auth (cherry picked from commit 1bf416c3bb964ed555850c3fc55977d69980b8e5) * update NS minor version * Added handlers and translations for CGM info * Defined translate() * Fixed sensor state reference * Improved wording for tx age response * Improved wording for session duration response * Updated documentation and templates * Updated README.md TOC and a reference to it * Added CGM battery info * Added unit reference to CGM battery levels * Added handlers and translations for CGM info * Defined translate() * Fixed sensor state reference * Improved wording for tx age response * Improved wording for session duration response * Updated documentation and templates * Updated README.md TOC and a reference to it * Added CGM battery info * Added unit reference to CGM battery levels * Updated API reference in Google Home template Co-authored-by: Caleb --- README.md | 10 +- docs/plugins/alexa-templates/en-us.json | 61 +++ docs/plugins/google-home-templates/en-us.zip | Bin 6246 -> 6367 bytes .../interacting-with-virtual-assistants.md | 9 + lib/language.js | 390 ++++++++++++++++++ lib/plugins/xdripjs.js | 110 +++++ 6 files changed, 576 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 76f95442705..8586e0b5a3b 100644 --- a/README.md +++ b/README.md @@ -49,23 +49,24 @@ Community maintained fork of the - [Install](#install) - [Supported configurations:](#supported-configurations) - - [Minimum browser requirements for viewing the site:](#minimum-browser-requirements-for-viewing-the-site) + - [Recommended minimum browser versions for using Nightscout:](#recommended-minimum-browser-versions-for-using-nightscout) - [Windows installation software requirements:](#windows-installation-software-requirements) - [Installation notes for users with nginx or Apache reverse proxy for SSL/TLS offloading:](#installation-notes-for-users-with-nginx-or-apache-reverse-proxy-for-ssltls-offloading) - [Installation notes for Microsoft Azure, Windows:](#installation-notes-for-microsoft-azure-windows) +- [Development](#development) - [Usage](#usage) - [Updating my version?](#updating-my-version) - - [What is my mongo string?](#what-is-my-mongo-string) - [Configure my uploader to match](#configure-my-uploader-to-match) - [Nightscout API](#nightscout-api) - [Example Queries](#example-queries) - [Environment](#environment) - [Required](#required) - - [Features/Labs](#featureslabs) + - [Features](#features) - [Alarms](#alarms) - [Core](#core) - [Predefined values for your browser settings (optional)](#predefined-values-for-your-browser-settings-optional) - [Predefined values for your server settings (optional)](#predefined-values-for-your-server-settings-optional) + - [Views](#views) - [Plugins](#plugins) - [Default Plugins](#default-plugins) - [`delta` (BG Delta)](#delta-bg-delta) @@ -97,7 +98,7 @@ Community maintained fork of the - [`openaps` (OpenAPS)](#openaps-openaps) - [`loop` (Loop)](#loop-loop) - [`override` (Override Mode)](#override-override-mode) - - [`xdripjs` (xDrip-js)](#xdripjs-xdripjs) + - [`xdripjs` (xDrip-js)](#xdripjs-xdrip-js) - [`alexa` (Amazon Alexa)](#alexa-amazon-alexa) - [`googlehome` (Google Home/DialogFLow)](#googlehome-google-homedialogflow) - [`speech` (Speech)](#speech-speech) @@ -109,6 +110,7 @@ Community maintained fork of the - [Setting environment variables](#setting-environment-variables) - [Vagrant install](#vagrant-install) - [More questions?](#more-questions) + - [Browser testing suite provided by](#browser-testing-suite-provided-by) - [License](#license) diff --git a/docs/plugins/alexa-templates/en-us.json b/docs/plugins/alexa-templates/en-us.json index d9e13174f78..79cc1baa977 100644 --- a/docs/plugins/alexa-templates/en-us.json +++ b/docs/plugins/alexa-templates/en-us.json @@ -172,6 +172,67 @@ "raw blood glucose" ] } + }, + { + "name": { + "value": "cgm noise" + } + }, + { + "name": { + "value": "cgm tx age", + "synonyms": [ + "tx age", + "transmitter age", + "cgm transmitter age" + ] + } + }, + { + "name": { + "value": "cgm tx status", + "synonyms": [ + "tx status", + "transmitter status", + "cgm transmitter status" + ] + } + }, + { + "name": { + "value": "cgm battery", + "synonyms": [ + "cgm battery level", + "cgm battery levels", + "cgm batteries", + "cgm transmitter battery", + "cgm transmitter battery level", + "cgm transmitter battery levels", + "cgm transmitter batteries", + "transmitter battery", + "transmitter battery level", + "transmitter battery levels", + "transmitter batteries" + ] + } + }, + { + "name": { + "value": "cgm session age", + "synonyms": [ + "session age" + ] + } + }, + { + "name": { + "value": "cgm status" + } + }, + { + "name": { + "value": "cgm mode" + } } ] } diff --git a/docs/plugins/google-home-templates/en-us.zip b/docs/plugins/google-home-templates/en-us.zip index b68d7a09b02997f375628281e317983a995dde24..d8ada2a834a29cb73802fb364902d86034ca3421 100644 GIT binary patch delta 4789 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 b_ZEVu|D>~tJ{iG(0014~?jg|Ymm&WH$w}zg delta 4824 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 AS^xk5 diff --git a/docs/plugins/interacting-with-virtual-assistants.md b/docs/plugins/interacting-with-virtual-assistants.md index 59198820956..3fe67bee2fb 100644 --- a/docs/plugins/interacting-with-virtual-assistants.md +++ b/docs/plugins/interacting-with-virtual-assistants.md @@ -52,6 +52,15 @@ This list is not meant to be comprehensive, nor does it include every way you ca - "Alexa, ask Nightscout what is Arden's raw bg" - "Alexa, ask Nightscout what is Dana's raw blood glucose" +*CGM Info:* (when using the [`xdripjs` plugin](/README.md#xdripjs-xdrip-js)) + +- "Alexa, ask Nightscout what's my CGM status" +- "Alexa, ask Nightscout what's my CGM session age" +- "Alexa, ask Nightscout what's my CGM transmitter age" +- "Alexa, ask Nightscout what's my CGM mode" +- "Alexa, ask Nightscout what's my CGM noise" +- "Alexa, ask Nightscout what's my CGM battery" + *Insulin Remaining:* - "Alexa, ask Nightscout how much insulin do I have left" diff --git a/lib/language.js b/lib/language.js index 962593c2b48..c105c5239d7 100644 --- a/lib/language.js +++ b/lib/language.js @@ -13623,6 +13623,162 @@ function init() { , zh_cn: 'Full Status' , zh_tw: 'Full Status' }, + 'virtAsstTitleCGMMode': { + bg: 'CGM Mode' + , cs: 'CGM Mode' + , de: 'CGM Mode' + , dk: 'CGM Mode' + , el: 'CGM Mode' + , en: 'CGM Mode' + , es: 'CGM Mode' + , fi: 'CGM Mode' + , fr: 'CGM Mode' + , he: 'CGM Mode' + , hr: 'CGM Mode' + , it: 'CGM Mode' + , ko: 'CGM Mode' + , nb: 'CGM Mode' + , pl: 'CGM Mode' + , pt: 'CGM Mode' + , ro: 'CGM Mode' + , nl: 'CGM Mode' + , ru: 'CGM Mode' + , sk: 'CGM Mode' + , sv: 'CGM Mode' + , tr: 'CGM Mode' + , zh_cn: 'CGM Mode' + , zh_tw: 'CGM Mode' + }, + 'virtAsstTitleCGMStatus': { + bg: 'CGM Status' + , cs: 'CGM Status' + , de: 'CGM Status' + , dk: 'CGM Status' + , el: 'CGM Status' + , en: 'CGM Status' + , es: 'CGM Status' + , fi: 'CGM Status' + , fr: 'CGM Status' + , he: 'CGM Status' + , hr: 'CGM Status' + , it: 'CGM Status' + , ko: 'CGM Status' + , nb: 'CGM Status' + , pl: 'CGM Status' + , pt: 'CGM Status' + , ro: 'CGM Status' + , nl: 'CGM Status' + , ru: 'CGM Status' + , sk: 'CGM Status' + , sv: 'CGM Status' + , tr: 'CGM Status' + , zh_cn: 'CGM Status' + , zh_tw: 'CGM Status' + }, + 'virtAsstTitleCGMSessionAge': { + bg: 'CGM Session Age' + , cs: 'CGM Session Age' + , de: 'CGM Session Age' + , dk: 'CGM Session Age' + , el: 'CGM Session Age' + , en: 'CGM Session Age' + , es: 'CGM Session Age' + , fi: 'CGM Session Age' + , fr: 'CGM Session Age' + , he: 'CGM Session Age' + , hr: 'CGM Session Age' + , it: 'CGM Session Age' + , ko: 'CGM Session Age' + , nb: 'CGM Session Age' + , pl: 'CGM Session Age' + , pt: 'CGM Session Age' + , ro: 'CGM Session Age' + , nl: 'CGM Session Age' + , ru: 'CGM Session Age' + , sk: 'CGM Session Age' + , sv: 'CGM Session Age' + , tr: 'CGM Session Age' + , zh_cn: 'CGM Session Age' + , zh_tw: 'CGM Session Age' + }, + 'virtAsstTitleCGMTxStatus': { + bg: 'CGM Transmitter Status' + , cs: 'CGM Transmitter Status' + , de: 'CGM Transmitter Status' + , dk: 'CGM Transmitter Status' + , el: 'CGM Transmitter Status' + , en: 'CGM Transmitter Status' + , es: 'CGM Transmitter Status' + , fi: 'CGM Transmitter Status' + , fr: 'CGM Transmitter Status' + , he: 'CGM Transmitter Status' + , hr: 'CGM Transmitter Status' + , it: 'CGM Transmitter Status' + , ko: 'CGM Transmitter Status' + , nb: 'CGM Transmitter Status' + , pl: 'CGM Transmitter Status' + , pt: 'CGM Transmitter Status' + , ro: 'CGM Transmitter Status' + , nl: 'CGM Transmitter Status' + , ru: 'CGM Transmitter Status' + , sk: 'CGM Transmitter Status' + , sv: 'CGM Transmitter Status' + , tr: 'CGM Transmitter Status' + , zh_cn: 'CGM Transmitter Status' + , zh_tw: 'CGM Transmitter Status' + }, + 'virtAsstTitleCGMTxAge': { + bg: 'CGM Transmitter Age' + , cs: 'CGM Transmitter Age' + , de: 'CGM Transmitter Age' + , dk: 'CGM Transmitter Age' + , el: 'CGM Transmitter Age' + , en: 'CGM Transmitter Age' + , es: 'CGM Transmitter Age' + , fi: 'CGM Transmitter Age' + , fr: 'CGM Transmitter Age' + , he: 'CGM Transmitter Age' + , hr: 'CGM Transmitter Age' + , it: 'CGM Transmitter Age' + , ko: 'CGM Transmitter Age' + , nb: 'CGM Transmitter Age' + , pl: 'CGM Transmitter Age' + , pt: 'CGM Transmitter Age' + , ro: 'CGM Transmitter Age' + , nl: 'CGM Transmitter Age' + , ru: 'CGM Transmitter Age' + , sk: 'CGM Transmitter Age' + , sv: 'CGM Transmitter Age' + , tr: 'CGM Transmitter Age' + , zh_cn: 'CGM Transmitter Age' + , zh_tw: 'CGM Transmitter Age' + }, + 'virtAsstTitleCGMNoise': { + bg: 'CGM Noise' + , cs: 'CGM Noise' + , de: 'CGM Noise' + , dk: 'CGM Noise' + , el: 'CGM Noise' + , en: 'CGM Noise' + , es: 'CGM Noise' + , fi: 'CGM Noise' + , fr: 'CGM Noise' + , he: 'CGM Noise' + , hr: 'CGM Noise' + , it: 'CGM Noise' + , ko: 'CGM Noise' + , nb: 'CGM Noise' + , pl: 'CGM Noise' + , pt: 'CGM Noise' + , ro: 'CGM Noise' + , nl: 'CGM Noise' + , ru: 'CGM Noise' + , sk: 'CGM Noise' + , sv: 'CGM Noise' + , tr: 'CGM Noise' + , zh_cn: 'CGM Noise' + , zh_tw: 'CGM Noise' + }, 'virtAsstTitleDelta': { bg: 'Blood Glucose Delta' , cs: 'Blood Glucose Delta' @@ -14182,6 +14338,240 @@ function init() { , zh_cn: 'You have %1 carbohydrates on board' , zh_tw: 'You have %1 carbohydrates on board' }, + 'virtAsstCGMMode': { + bg: 'Your CGM mode was %1 as of %2.' + , cs: 'Your CGM mode was %1 as of %2.' + , de: 'Your CGM mode was %1 as of %2.' + , dk: 'Your CGM mode was %1 as of %2.' + , el: 'Your CGM mode was %1 as of %2.' + , en: 'Your CGM mode was %1 as of %2.' + , es: 'Your CGM mode was %1 as of %2.' + , fi: 'Your CGM mode was %1 as of %2.' + , fr: 'Your CGM mode was %1 as of %2.' + , he: 'Your CGM mode was %1 as of %2.' + , hr: 'Your CGM mode was %1 as of %2.' + , it: 'Your CGM mode was %1 as of %2.' + , ko: 'Your CGM mode was %1 as of %2.' + , nb: 'Your CGM mode was %1 as of %2.' + , nl: 'Your CGM mode was %1 as of %2.' + , pl: 'Your CGM mode was %1 as of %2.' + , pt: 'Your CGM mode was %1 as of %2.' + , ro: 'Your CGM mode was %1 as of %2.' + , ru: 'Your CGM mode was %1 as of %2.' + , sk: 'Your CGM mode was %1 as of %2.' + , sv: 'Your CGM mode was %1 as of %2.' + , tr: 'Your CGM mode was %1 as of %2.' + , zh_cn: 'Your CGM mode was %1 as of %2.' + , zh_tw: 'Your CGM mode was %1 as of %2.' + }, + 'virtAsstCGMStatus': { + bg: 'Your CGM status was %1 as of %2.' + , cs: 'Your CGM status was %1 as of %2.' + , de: 'Your CGM status was %1 as of %2.' + , dk: 'Your CGM status was %1 as of %2.' + , el: 'Your CGM status was %1 as of %2.' + , en: 'Your CGM status was %1 as of %2.' + , es: 'Your CGM status was %1 as of %2.' + , fi: 'Your CGM status was %1 as of %2.' + , fr: 'Your CGM status was %1 as of %2.' + , he: 'Your CGM status was %1 as of %2.' + , hr: 'Your CGM status was %1 as of %2.' + , it: 'Your CGM status was %1 as of %2.' + , ko: 'Your CGM status was %1 as of %2.' + , nb: 'Your CGM status was %1 as of %2.' + , nl: 'Your CGM status was %1 as of %2.' + , pl: 'Your CGM status was %1 as of %2.' + , pt: 'Your CGM status was %1 as of %2.' + , ro: 'Your CGM status was %1 as of %2.' + , ru: 'Your CGM status was %1 as of %2.' + , sk: 'Your CGM status was %1 as of %2.' + , sv: 'Your CGM status was %1 as of %2.' + , tr: 'Your CGM status was %1 as of %2.' + , zh_cn: 'Your CGM status was %1 as of %2.' + , zh_tw: 'Your CGM status was %1 as of %2.' + }, + 'virtAsstCGMSessAge': { + bg: 'Your CGM session has been active for %1 days and %2 hours.' + , cs: 'Your CGM session has been active for %1 days and %2 hours.' + , de: 'Your CGM session has been active for %1 days and %2 hours.' + , dk: 'Your CGM session has been active for %1 days and %2 hours.' + , el: 'Your CGM session has been active for %1 days and %2 hours.' + , en: 'Your CGM session has been active for %1 days and %2 hours.' + , es: 'Your CGM session has been active for %1 days and %2 hours.' + , fi: 'Your CGM session has been active for %1 days and %2 hours.' + , fr: 'Your CGM session has been active for %1 days and %2 hours.' + , he: 'Your CGM session has been active for %1 days and %2 hours.' + , hr: 'Your CGM session has been active for %1 days and %2 hours.' + , it: 'Your CGM session has been active for %1 days and %2 hours.' + , ko: 'Your CGM session has been active for %1 days and %2 hours.' + , nb: 'Your CGM session has been active for %1 days and %2 hours.' + , nl: 'Your CGM session has been active for %1 days and %2 hours.' + , pl: 'Your CGM session has been active for %1 days and %2 hours.' + , pt: 'Your CGM session has been active for %1 days and %2 hours.' + , ro: 'Your CGM session has been active for %1 days and %2 hours.' + , ru: 'Your CGM session has been active for %1 days and %2 hours.' + , sk: 'Your CGM session has been active for %1 days and %2 hours.' + , sv: 'Your CGM session has been active for %1 days and %2 hours.' + , tr: 'Your CGM session has been active for %1 days and %2 hours.' + , zh_cn: 'Your CGM session has been active for %1 days and %2 hours.' + , zh_tw: 'Your CGM session has been active for %1 days and %2 hours.' + }, + 'virtAsstCGMSessNotStarted': { + bg: 'There is no active CGM session at the moment.' + , cs: 'There is no active CGM session at the moment.' + , de: 'There is no active CGM session at the moment.' + , dk: 'There is no active CGM session at the moment.' + , el: 'There is no active CGM session at the moment.' + , en: 'There is no active CGM session at the moment.' + , es: 'There is no active CGM session at the moment.' + , fi: 'There is no active CGM session at the moment.' + , fr: 'There is no active CGM session at the moment.' + , he: 'There is no active CGM session at the moment.' + , hr: 'There is no active CGM session at the moment.' + , it: 'There is no active CGM session at the moment.' + , ko: 'There is no active CGM session at the moment.' + , nb: 'There is no active CGM session at the moment.' + , nl: 'There is no active CGM session at the moment.' + , pl: 'There is no active CGM session at the moment.' + , pt: 'There is no active CGM session at the moment.' + , ro: 'There is no active CGM session at the moment.' + , ru: 'There is no active CGM session at the moment.' + , sk: 'There is no active CGM session at the moment.' + , sv: 'There is no active CGM session at the moment.' + , tr: 'There is no active CGM session at the moment.' + , zh_cn: 'There is no active CGM session at the moment.' + , zh_tw: 'There is no active CGM session at the moment.' + }, + 'virtAsstCGMTxStatus': { + bg: 'Your CGM transmitter status was %1 as of %2.' + , cs: 'Your CGM transmitter status was %1 as of %2.' + , de: 'Your CGM transmitter status was %1 as of %2.' + , dk: 'Your CGM transmitter status was %1 as of %2.' + , el: 'Your CGM transmitter status was %1 as of %2.' + , en: 'Your CGM transmitter status was %1 as of %2.' + , es: 'Your CGM transmitter status was %1 as of %2.' + , fi: 'Your CGM transmitter status was %1 as of %2.' + , fr: 'Your CGM transmitter status was %1 as of %2.' + , he: 'Your CGM transmitter status was %1 as of %2.' + , hr: 'Your CGM transmitter status was %1 as of %2.' + , it: 'Your CGM transmitter status was %1 as of %2.' + , ko: 'Your CGM transmitter status was %1 as of %2.' + , nb: 'Your CGM transmitter status was %1 as of %2.' + , nl: 'Your CGM transmitter status was %1 as of %2.' + , pl: 'Your CGM transmitter status was %1 as of %2.' + , pt: 'Your CGM transmitter status was %1 as of %2.' + , ro: 'Your CGM transmitter status was %1 as of %2.' + , ru: 'Your CGM transmitter status was %1 as of %2.' + , sk: 'Your CGM transmitter status was %1 as of %2.' + , sv: 'Your CGM transmitter status was %1 as of %2.' + , tr: 'Your CGM transmitter status was %1 as of %2.' + , zh_cn: 'Your CGM transmitter status was %1 as of %2.' + , zh_tw: 'Your CGM transmitter status was %1 as of %2.' + }, + 'virtAsstCGMTxAge': { + bg: 'Your CGM transmitter is %1 days old.' + , cs: 'Your CGM transmitter is %1 days old.' + , de: 'Your CGM transmitter is %1 days old.' + , dk: 'Your CGM transmitter is %1 days old.' + , el: 'Your CGM transmitter is %1 days old.' + , en: 'Your CGM transmitter is %1 days old.' + , es: 'Your CGM transmitter is %1 days old.' + , fi: 'Your CGM transmitter is %1 days old.' + , fr: 'Your CGM transmitter is %1 days old.' + , he: 'Your CGM transmitter is %1 days old.' + , hr: 'Your CGM transmitter is %1 days old.' + , it: 'Your CGM transmitter is %1 days old.' + , ko: 'Your CGM transmitter is %1 days old.' + , nb: 'Your CGM transmitter is %1 days old.' + , nl: 'Your CGM transmitter is %1 days old.' + , pl: 'Your CGM transmitter is %1 days old.' + , pt: 'Your CGM transmitter is %1 days old.' + , ro: 'Your CGM transmitter is %1 days old.' + , ru: 'Your CGM transmitter is %1 days old.' + , sk: 'Your CGM transmitter is %1 days old.' + , sv: 'Your CGM transmitter is %1 days old.' + , tr: 'Your CGM transmitter is %1 days old.' + , zh_cn: 'Your CGM transmitter is %1 days old.' + , zh_tw: 'Your CGM transmitter is %1 days old.' + }, + 'virtAsstCGMNoise': { + bg: 'Your CGM noise was %1 as of %2.' + , cs: 'Your CGM noise was %1 as of %2.' + , de: 'Your CGM noise was %1 as of %2.' + , dk: 'Your CGM noise was %1 as of %2.' + , el: 'Your CGM noise was %1 as of %2.' + , en: 'Your CGM noise was %1 as of %2.' + , es: 'Your CGM noise was %1 as of %2.' + , fi: 'Your CGM noise was %1 as of %2.' + , fr: 'Your CGM noise was %1 as of %2.' + , he: 'Your CGM noise was %1 as of %2.' + , hr: 'Your CGM noise was %1 as of %2.' + , it: 'Your CGM noise was %1 as of %2.' + , ko: 'Your CGM noise was %1 as of %2.' + , nb: 'Your CGM noise was %1 as of %2.' + , nl: 'Your CGM noise was %1 as of %2.' + , pl: 'Your CGM noise was %1 as of %2.' + , pt: 'Your CGM noise was %1 as of %2.' + , ro: 'Your CGM noise was %1 as of %2.' + , ru: 'Your CGM noise was %1 as of %2.' + , sk: 'Your CGM noise was %1 as of %2.' + , sv: 'Your CGM noise was %1 as of %2.' + , tr: 'Your CGM noise was %1 as of %2.' + , zh_cn: 'Your CGM noise was %1 as of %2.' + , zh_tw: 'Your CGM noise was %1 as of %2.' + }, + 'virtAsstCGMBattOne': { + bg: 'Your CGM battery was %1 volts as of %2.' + , cs: 'Your CGM battery was %1 volts as of %2.' + , de: 'Your CGM battery was %1 volts as of %2.' + , dk: 'Your CGM battery was %1 volts as of %2.' + , el: 'Your CGM battery was %1 volts as of %2.' + , en: 'Your CGM battery was %1 volts as of %2.' + , es: 'Your CGM battery was %1 volts as of %2.' + , fi: 'Your CGM battery was %1 volts as of %2.' + , fr: 'Your CGM battery was %1 volts as of %2.' + , he: 'Your CGM battery was %1 volts as of %2.' + , hr: 'Your CGM battery was %1 volts as of %2.' + , it: 'Your CGM battery was %1 volts as of %2.' + , ko: 'Your CGM battery was %1 volts as of %2.' + , nb: 'Your CGM battery was %1 volts as of %2.' + , nl: 'Your CGM battery was %1 volts as of %2.' + , pl: 'Your CGM battery was %1 volts as of %2.' + , pt: 'Your CGM battery was %1 volts as of %2.' + , ro: 'Your CGM battery was %1 volts as of %2.' + , ru: 'Your CGM battery was %1 volts as of %2.' + , sk: 'Your CGM battery was %1 volts as of %2.' + , sv: 'Your CGM battery was %1 volts as of %2.' + , tr: 'Your CGM battery was %1 volts as of %2.' + , zh_cn: 'Your CGM battery was %1 volts as of %2.' + , zh_tw: 'Your CGM battery was %1 volts as of %2.' + }, + 'virtAsstCGMBattTwo': { + bg: 'Your CGM battery levels were %1 volts and %2 volts as of %3.' + , cs: 'Your CGM battery levels were %1 volts and %2 volts as of %3.' + , de: 'Your CGM battery levels were %1 volts and %2 volts as of %3.' + , dk: 'Your CGM battery levels were %1 volts and %2 volts as of %3.' + , el: 'Your CGM battery levels were %1 volts and %2 volts as of %3.' + , en: 'Your CGM battery levels were %1 volts and %2 volts as of %3.' + , es: 'Your CGM battery levels were %1 volts and %2 volts as of %3.' + , fi: 'Your CGM battery levels were %1 volts and %2 volts as of %3.' + , fr: 'Your CGM battery levels were %1 volts and %2 volts as of %3.' + , he: 'Your CGM battery levels were %1 volts and %2 volts as of %3.' + , hr: 'Your CGM battery levels were %1 volts and %2 volts as of %3.' + , it: 'Your CGM battery levels were %1 volts and %2 volts as of %3.' + , ko: 'Your CGM battery levels were %1 volts and %2 volts as of %3.' + , nb: 'Your CGM battery levels were %1 volts and %2 volts as of %3.' + , nl: 'Your CGM battery levels were %1 volts and %2 volts as of %3.' + , pl: 'Your CGM battery levels were %1 volts and %2 volts as of %3.' + , pt: 'Your CGM battery levels were %1 volts and %2 volts as of %3.' + , ro: 'Your CGM battery levels were %1 volts and %2 volts as of %3.' + , ru: 'Your CGM battery levels were %1 volts and %2 volts as of %3.' + , sk: 'Your CGM battery levels were %1 volts and %2 volts as of %3.' + , sv: 'Your CGM battery levels were %1 volts and %2 volts as of %3.' + , tr: 'Your CGM battery levels were %1 volts and %2 volts as of %3.' + , zh_cn: 'Your CGM battery levels were %1 volts and %2 volts as of %3.' + , zh_tw: 'Your CGM battery levels were %1 volts and %2 volts as of %3.' + }, 'virtAsstDelta': { bg: 'Your delta was %1 between %2 and %3.' , cs: 'Your delta was %1 between %2 and %3.' diff --git a/lib/plugins/xdripjs.js b/lib/plugins/xdripjs.js index a36a42de2e0..dc44aad2988 100644 --- a/lib/plugins/xdripjs.js +++ b/lib/plugins/xdripjs.js @@ -9,6 +9,7 @@ function init(ctx) { var utils = require('../utils')(ctx); var firstPrefs = true; var lastStateNotification = null; + var translate = ctx.language.translate; var sensorState = { name: 'xdripjs' @@ -321,6 +322,115 @@ function init(ctx) { } }; + function virtAsstGenericCGMHandler(translateItem, field, next, sbx) { + var response; + if (sbx.properties.sensorState && sbx.properties.sensorState[field]) { + response = translate('virtAsstCGM'+translateItem, { + params:[ + sbx.properties.sensorState[field] + , moment(sbx.properties.sensorState.lastStateTime).from(moment(sbx.time)) + ] + }); + } else { + response = translate('virtAsstUnknown'); + } + + next(translate('virtAsstTitleCGM'+translateItem), response); + } + + sensorState.virtAsst = { + intentHandlers: [ + { + intent: 'MetricNow' + , metrics: ['cgm mode'] + , intentHandler: function(next, slots, sbx){virtAsstGenericCGMHandler('Mode', 'lastMode', next, sbx);} + } + , { + intent: 'MetricNow' + , metrics: ['cgm status'] + , intentHandler: function(next, slots, sbx){virtAsstGenericCGMHandler('Status', 'lastStateString', next, sbx);} + } + , { + intent: 'MetricNow' + , metrics: ['cgm session age'] + , intentHandler: function(next, slots, sbx){ + var response; + // session start is only valid if in a session + if (sbx.properties.sensorState && sbx.properties.sensorState.lastSessionStart) { + if (sbx.properties.sensorState.lastState != 0x1) { + var duration = moment.duration(moment().diff(moment(sbx.properties.sensorState.lastSessionStart))); + response = translate('virtAsstCGMSessAge', { + params: [ + duration.days(), + duration.hours() + ] + }); + } else { + response = translate('virtAsstCGMSessNotStarted'); + } + } else { + response = translate('virtAsstUnknown'); + } + + next(translate('virtAsstTitleCGMSessAge'), response); + } + } + , { + intent: 'MetricNow' + , metrics: ['cgm tx status'] + , intentHandler: function(next, slots, sbx){virtAsstGenericCGMHandler('TxStatus', 'lastTxStatusString', next, sbx);} + } + , { + intent: 'MetricNow' + , metrics: ['cgm tx age'] + , intentHandler: function(next, slots, sbx){ + next( + translate('virtAsstTitleCGMTxAge'), + (sbx.properties.sensorState && sbx.properties.sensorState.lastTxActivation) + ? translate('virtAsstCGMTxAge', {params:[moment().diff(moment(sbx.properties.sensorState.lastTxActivation), 'days')]}) + : translate('virtAsstUnknown') + ); + } + } + , { + intent: 'MetricNow' + , metrics: ['cgm noise'] + , intentHandler: function(next, slots, sbx){virtAsstGenericCGMHandler('Noise', 'lastNoiseString', next, sbx);} + } + , { + intent: 'MetricNow' + , metrics: ['cgm battery'] + , intentHandler: function(next, slots, sbx){ + var response; + var sensor = sbx.properties.sensorState; + if (sensor && (sensor.lastVoltageA || sensor.lastVoltageB)) { + if (sensor.lastVoltageA && sensor.lastVoltageB) { + response = translate('virtAsstCGMBattTwo', { + params:[ + (sensor.lastVoltageA / 100) + , (sensor.lastVoltageB / 100) + , moment(sensor.lastBatteryTimestamp).from(moment(sbx.time)) + ] + }); + } else { + var finalValue = sensor.lastVoltageA ? sensor.lastVoltageA : sensor.lastVoltageB; + response = translate('virtAsstCGMBattOne', { + params:[ + (finalValue / 100) + , moment(sensor.lastBatteryTimestamp).from(moment(sbx.time)) + ] + }); + } + } else { + response = translate('virtAsstUnknown'); + } + + next(translate('virtAsstTitleCGMBatt'), response); + } + } + ] + }; + return sensorState; } From c49ea37db595c6801e71f8ccded52a222754a329 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Szubert?= Date: Sat, 11 Jan 2020 13:41:18 +0100 Subject: [PATCH 032/104] Volunteer for Polish translations (#5396) --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index aaa33bd5377..7beea2d51a3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -181,6 +181,7 @@ Also if you can't code, it's possible to contribute by improving the documentati [@unsoluble]: https://github.com/unsoluble [@viderehh]: https://github.com/viderehh [@OpossumGit]: https://github.com/OpossumGit +[@Bartlomiejsz]: https://github.com/Bartlomiejsz | Contribution area | List of contributors | | ------------------------------------- | ---------------------------------- | From 2f60d8d6a186a25b5fd2c8f5764b8eb4b99f7cd7 Mon Sep 17 00:00:00 2001 From: Petr Ondrusek <34578008+PetrOndrusek@users.noreply.github.com> Date: Sun, 12 Jan 2020 16:16:52 +0100 Subject: [PATCH 033/104] Api3 output renderers (#5425) * APIv3: adding framework for various output renderers * APIv3: adding xml output renderer * APIv3: adding csv output renderer * APIv3: documenting supported output renderers * APIv3: testing output renderers --- lib/api3/const.json | 2 + lib/api3/doc/formats.md | 88 +++++++++ lib/api3/generic/create/insert.js | 3 +- lib/api3/generic/history/operation.js | 4 +- lib/api3/generic/read/operation.js | 4 +- lib/api3/generic/search/operation.js | 4 +- lib/api3/generic/update/replace.js | 3 +- lib/api3/index.js | 5 +- lib/api3/shared/renderer.js | 99 ++++++++++ lib/api3/swagger.js | 2 +- lib/api3/swagger.yaml | 35 +++- npm-shrinkwrap.json | 56 ++++++ package.json | 6 +- tests/api3.generic.workflow.test.js | 2 - tests/api3.renderer.test.js | 268 ++++++++++++++++++++++++++ 15 files changed, 570 insertions(+), 11 deletions(-) create mode 100644 lib/api3/doc/formats.md create mode 100644 lib/api3/shared/renderer.js create mode 100644 tests/api3.renderer.test.js diff --git a/lib/api3/const.json b/lib/api3/const.json index 1c3dfd873ee..fd874a1175a 100644 --- a/lib/api3/const.json +++ b/lib/api3/const.json @@ -15,6 +15,7 @@ "UNAUTHORIZED": 401, "FORBIDDEN": 403, "NOT_FOUND": 404, + "NOT_ACCEPTABLE": 406, "GONE": 410, "PRECONDITION_FAILED": 412, "UNPROCESSABLE_ENTITY": 422, @@ -40,6 +41,7 @@ "HTTP_401_MISSING_OR_BAD_TOKEN": "Missing or bad access token or JWT", "HTTP_403_MISSING_PERMISSION": "Missing permission {0}", "HTTP_403_NOT_USING_HTTPS": "Not using SSL/TLS", + "HTTP_406_UNSUPPORTED_FORMAT": "Unsupported output format requested", "HTTP_422_READONLY_MODIFICATION": "Trying to modify read-only document", "HTTP_500_INTERNAL_ERROR": "Internal Server Error", "STORAGE_ERROR": "Database error", diff --git a/lib/api3/doc/formats.md b/lib/api3/doc/formats.md new file mode 100644 index 00000000000..5a01a802f3e --- /dev/null +++ b/lib/api3/doc/formats.md @@ -0,0 +1,88 @@ +# APIv3: Output formats + +### Choosing output format +In APIv3, the standard content type is JSON for both HTTP request and HTTP response. +However, in HTTP response, the response content type can be changed to XML or CSV +for READ, SEARCH, and HISTORY operations. + +The response content type can be requested in one of the following ways: +- add a file type extension to the URL, eg. + `/api/v3/entries.csv?...` + or `/api/v3/treatments/95e1a6e3-1146-5d6a-a3f1-41567cae0895.xml?...` +- set `Accept` HTTP request header to `text/csv` or `application/xml` + +The server replies with `406 Not Acceptable` HTTP status in case of not supported content type. + + +### JSON + +Default content type is JSON, output can look like this: + +``` +[ + { + "type":"sgv", + "sgv":"171", + "dateString":"2014-07-19T02:44:15.000-07:00", + "date":1405763055000, + "device":"dexcom", + "direction":"Flat", + "identifier":"5c5a2404e0196f4d3d9a718a", + "srvModified":1405763055000, + "srvCreated":1405763055000 + }, + { + "type":"sgv", + "sgv":"176", + "dateString":"2014-07-19T03:09:15.000-07:00", + "date":1405764555000, + "device":"dexcom", + "direction":"Flat", + "identifier":"5c5a2404e0196f4d3d9a7187", + "srvModified":1405764555000, + "srvCreated":1405764555000 + } +] +``` + +### XML + +Sample output: + +``` + + + + sgv + 171 + 2014-07-19T02:44:15.000-07:00 + 1405763055000 + dexcom + Flat + 5c5a2404e0196f4d3d9a718a + 1405763055000 + 1405763055000 + + + sgv + 176 + 2014-07-19T03:09:15.000-07:00 + 1405764555000 + dexcom + Flat + 5c5a2404e0196f4d3d9a7187 + 1405764555000 + 1405764555000 + + +``` + +### CSV + +Sample output: + +``` +type,sgv,dateString,date,device,direction,identifier,srvModified,srvCreated +sgv,171,2014-07-19T02:44:15.000-07:00,1405763055000,dexcom,Flat,5c5a2404e0196f4d3d9a718a,1405763055000,1405763055000 +sgv,176,2014-07-19T03:09:15.000-07:00,1405764555000,dexcom,Flat,5c5a2404e0196f4d3d9a7187,1405764555000,1405764555000 +``` diff --git a/lib/api3/generic/create/insert.js b/lib/api3/generic/create/insert.js index 4ac80a37e94..b643818569a 100644 --- a/lib/api3/generic/create/insert.js +++ b/lib/api3/generic/create/insert.js @@ -3,6 +3,7 @@ const apiConst = require('../../const.json') , security = require('../../security') , validate = require('./validate.js') + , path = require('path') ; /** @@ -33,7 +34,7 @@ async function insert (opCtx, doc) { throw new Error('empty identifier'); res.setHeader('Last-Modified', now.toUTCString()); - res.setHeader('Location', `${req.baseUrl}${req.path}/${identifier}`); + res.setHeader('Location', path.posix.join(req.baseUrl, req.path, identifier)); res.status(apiConst.HTTP.CREATED).send({ }); ctx.bus.emit('storage-socket-create', { colName: col.colName, doc }); diff --git a/lib/api3/generic/history/operation.js b/lib/api3/generic/history/operation.js index 0929a09cc4f..5151c8b2749 100644 --- a/lib/api3/generic/history/operation.js +++ b/lib/api3/generic/history/operation.js @@ -1,6 +1,7 @@ 'use strict'; const dateTools = require('../../shared/dateTools') + , renderer = require('../../shared/renderer') , apiConst = require('../../const.json') , security = require('../../security') , opTools = require('../../shared/operationTools') @@ -53,7 +54,8 @@ async function history (opCtx, fieldsProjector) { _.each(result, fieldsProjector.applyProjection); - res.status(apiConst.HTTP.OK).send(result); + res.status(apiConst.HTTP.OK); + renderer.render(res, result); } } diff --git a/lib/api3/generic/read/operation.js b/lib/api3/generic/read/operation.js index 04d6f03bc70..c2e65a4afcc 100644 --- a/lib/api3/generic/read/operation.js +++ b/lib/api3/generic/read/operation.js @@ -4,6 +4,7 @@ const apiConst = require('../../const.json') , security = require('../../security') , opTools = require('../../shared/operationTools') , dateTools = require('../../shared/dateTools') + , renderer = require('../../shared/renderer') , FieldsProjector = require('../../shared/fieldsProjector') ; @@ -48,7 +49,8 @@ async function read (opCtx) { fieldsProjector.applyProjection(doc); - res.status(apiConst.HTTP.OK).send(doc); + res.status(apiConst.HTTP.OK); + renderer.render(res, doc); } diff --git a/lib/api3/generic/search/operation.js b/lib/api3/generic/search/operation.js index 074f864d58a..c24f978dc27 100644 --- a/lib/api3/generic/search/operation.js +++ b/lib/api3/generic/search/operation.js @@ -3,6 +3,7 @@ const apiConst = require('../../const.json') , security = require('../../security') , opTools = require('../../shared/operationTools') + , renderer = require('../../shared/renderer') , input = require('./input') , _each = require('lodash/each') , FieldsProjector = require('../../shared/fieldsProjector') @@ -49,7 +50,8 @@ async function search (opCtx) { _each(result, fieldsProjector.applyProjection); - res.status(apiConst.HTTP.OK).send(result); + res.status(apiConst.HTTP.OK); + renderer.render(res, result); } } diff --git a/lib/api3/generic/update/replace.js b/lib/api3/generic/update/replace.js index ca490b31136..fdf803ed16f 100644 --- a/lib/api3/generic/update/replace.js +++ b/lib/api3/generic/update/replace.js @@ -3,6 +3,7 @@ const apiConst = require('../../const.json') , security = require('../../security') , validate = require('./validate.js') + , path = require('path') ; /** @@ -38,7 +39,7 @@ async function replace (opCtx, doc, storageDoc, options) { res.setHeader('Last-Modified', now.toUTCString()); if (storageDoc.identifier !== doc.identifier || isDeduplication) { - res.setHeader('Location', `${req.baseUrl}${req.path}/${doc.identifier}`); + res.setHeader('Location', path.posix.join(req.baseUrl, req.path, doc.identifier)); } res.status(apiConst.HTTP.NO_CONTENT).send({ }); diff --git a/lib/api3/index.js b/lib/api3/index.js index 5b1799c78fd..4bfe07a35fe 100644 --- a/lib/api3/index.js +++ b/lib/api3/index.js @@ -2,6 +2,7 @@ const express = require('express') , bodyParser = require('body-parser') + , renderer = require('./shared/renderer') , StorageSocket = require('./storageSocket') , apiConst = require('./const.json') , security = require('./security') @@ -47,6 +48,8 @@ function configure (env, ctx) { } }); + app.use(renderer.extension2accept); + // we don't need these here app.set('etag', false); app.set('x-powered-by', false); // this seems to be unreliable @@ -74,7 +77,7 @@ function configure (env, ctx) { app.get('/version', require('./specific/version')(app, ctx, env)); - if (app.get('env') === 'development' || app.get('ci')) { // for development and testing purposes only + if (app.get('env') === 'development' || app.get('ci')) { // for development and testing purposes only app.get('/test', async function test (req, res) { try { diff --git a/lib/api3/shared/renderer.js b/lib/api3/shared/renderer.js new file mode 100644 index 00000000000..a3588819a72 --- /dev/null +++ b/lib/api3/shared/renderer.js @@ -0,0 +1,99 @@ +'use strict'; + +const apiConst = require('../const.json') + , mime = require('mime') + , url = require('url') + , opTools = require('./operationTools') + , EasyXml = require('easyxml') + , csvStringify = require('csv-stringify') + ; + + +/** + * Middleware that converts url's extension to Accept HTTP request header + * @param {Object} req + * @param {Object} res + * @param {Function} next + */ +function extension2accept (req, res, next) { + + const pathSplit = req.path.split('.'); + + if (pathSplit.length < 2) + return next(); + + const pathBase = pathSplit[0] + , extension = pathSplit.slice(1).join('.'); + + if (!extension) + return next(); + + const mimeType = mime.getType(extension); + if (!mimeType) + return opTools.sendJSONStatus(res, apiConst.HTTP.NOT_ACCEPTABLE, apiConst.MSG.HTTP_406_UNSUPPORTED_FORMAT); + + req.extToAccept = { + url: req.url, + accept: req.headers.accept + }; + + req.headers.accept = mimeType; + const parsed = url.parse(req.url); + parsed.pathname = pathBase; + req.url = url.format(parsed); + + next(); +} + + +/** + * Sends data to output using the client's desired format + * @param {Object} res + * @param {any} data + */ +function render (res, data) { + res.format({ + 'json': () => res.send(data), + 'csv': () => renderCsv(res, data), + 'xml': () => renderXml(res, data), + 'default': () => + opTools.sendJSONStatus(res, apiConst.HTTP.NOT_ACCEPTABLE, apiConst.MSG.HTTP_406_UNSUPPORTED_FORMAT) + }); +} + + +/** + * Format data to output as .csv + * @param {Object} res + * @param {any} data + */ +function renderCsv (res, data) { + const csvSource = Array.isArray(data) ? data : [data]; + csvStringify(csvSource, { + header: true + }, + function csvStringified (err, output) { + res.send(output); + }); +} + + +/** + * Format data to output as .xml + * @param {Object} res + * @param {any} data + */ +function renderXml (res, data) { + const serializer = new EasyXml({ + rootElement: 'item', + dateFormat: 'ISO', + manifest: true + }); + res.send(serializer.render(data)); +} + + +module.exports = { + extension2accept, + render +}; \ No newline at end of file diff --git a/lib/api3/swagger.js b/lib/api3/swagger.js index 2d434e97f53..ff965061c87 100644 --- a/lib/api3/swagger.js +++ b/lib/api3/swagger.js @@ -10,7 +10,7 @@ function setupSwaggerUI (app) { const serveSwaggerDef = function serveSwaggerDef (req, res) { res.sendFile(__dirname + '/swagger.yaml'); }; - app.get('/swagger.yaml', serveSwaggerDef); + app.get('/swagger', serveSwaggerDef); const swaggerUiAssetPath = require('swagger-ui-dist').getAbsoluteFSPath(); const swaggerFiles = express.static(swaggerUiAssetPath); diff --git a/lib/api3/swagger.yaml b/lib/api3/swagger.yaml index 17db893e0ef..5cbb2a05544 100644 --- a/lib/api3/swagger.yaml +++ b/lib/api3/swagger.yaml @@ -2,7 +2,7 @@ openapi: 3.0.0 servers: - url: '/api/v3' info: - version: '3.0.1' + version: "3.0.1" title: Nightscout API contact: name: NS development discussion channel @@ -135,6 +135,8 @@ paths: $ref: '#/components/responses/403Forbidden' 404: $ref: '#/components/responses/404NotFound' + 406: + $ref: '#/components/responses/406NotAcceptable' ###################################################################################### @@ -240,6 +242,8 @@ paths: $ref: '#/components/responses/403Forbidden' 404: $ref: '#/components/responses/404NotFound' + 406: + $ref: '#/components/responses/406NotAcceptable' 410: $ref: '#/components/responses/410Gone' @@ -459,6 +463,8 @@ paths: $ref: '#/components/responses/403Forbidden' 404: $ref: '#/components/responses/404NotFound' + 406: + $ref: '#/components/responses/406NotAcceptable' ###################################################################################### @@ -518,6 +524,8 @@ paths: $ref: '#/components/responses/403Forbidden' 404: $ref: '#/components/responses/404NotFound' + 406: + $ref: '#/components/responses/406NotAcceptable' ###################################################################################### @@ -888,6 +896,9 @@ components: 404NotFound: description: The collection or document specified was not found. + 406NotAcceptable: + description: The requested content type (in `Accept` header) is not supported. + 412PreconditionFailed: description: The document has already been modified on the server since specified timestamp (in If-Unmodified-Since header). @@ -903,6 +914,12 @@ components: application/json: schema: $ref: '#/components/schemas/DocumentArray' + text/csv: + schema: + $ref: '#/components/schemas/DocumentArray' + application/xml: + schema: + $ref: '#/components/schemas/DocumentArray' search204: description: Successful operation - no documents matching the filtering criteria @@ -913,6 +930,12 @@ components: application/json: schema: $ref: '#/components/schemas/Document' + text/csv: + schema: + $ref: '#/components/schemas/Document' + application/xml: + schema: + $ref: '#/components/schemas/Document' headers: 'Last-Modified': $ref: '#/components/schemas/headerLastModified' @@ -924,6 +947,12 @@ components: application/json: schema: $ref: '#/components/schemas/DocumentArray' + text/csv: + schema: + $ref: '#/components/schemas/DocumentArray' + application/xml: + schema: + $ref: '#/components/schemas/DocumentArray' headers: 'Last-Modified': $ref: '#/components/schemas/headerLastModifiedMaximum' @@ -1417,6 +1446,8 @@ components: Document: description: Single document + xml: + name: 'item' type: object oneOf: - $ref: '#/components/schemas/DeviceStatus' @@ -1483,6 +1514,8 @@ components: DocumentArray: type: object + xml: + name: 'items' oneOf: - $ref: '#/components/schemas/DeviceStatusArray' - $ref: '#/components/schemas/EntryArray' diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index d80078c1143..5b4118aae7c 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -3179,6 +3179,17 @@ "cssom": "0.3.x" } }, + "csv-parse": { + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-4.8.3.tgz", + "integrity": "sha512-0GPxubzYzSn08lhNTWDCkcDKn8krmw0WuscqB2RrW6sugGGskbwaaEz7PCFFwbQ0phNGTTieiyfzzu3S/jZZ7Q==", + "dev": true + }, + "csv-stringify": { + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-5.3.5.tgz", + "integrity": "sha512-bFbaJqz7LcwnTzdryyJuhR6Pys2deU8+z7O8N0JBnNGm7vnJVr3K0n68bhb+rlMpwCmDbUtinr8yq5I2RlPMqw==" + }, "cyclist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", @@ -3684,6 +3695,15 @@ "stream-shift": "^1.0.0" } }, + "easyxml": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/easyxml/-/easyxml-2.0.1.tgz", + "integrity": "sha1-7qCShCyREwCox4GRPL5b04pQcRw=", + "requires": { + "elementtree": "^0.1.6", + "inflect": "^0.3.0" + } + }, "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -3716,6 +3736,21 @@ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.275.tgz", "integrity": "sha512-/YWtW/VapMnuYA1lNOaa1F4GhR1LBf+CUTp60lzDPEEh0XOzyOAyULyYZVF9vziZ3qSbTqCwmKwsyRXp66STbw==" }, + "elementtree": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/elementtree/-/elementtree-0.1.7.tgz", + "integrity": "sha1-mskb5uUvtuYkTE5UpKw+2K6OKcA=", + "requires": { + "sax": "1.1.4" + }, + "dependencies": { + "sax": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.1.4.tgz", + "integrity": "sha1-dLbTPJrh4AFRDxeakRaFiPGu2qk=" + } + } + }, "elliptic": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.1.tgz", @@ -5699,6 +5734,11 @@ "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==" }, + "inflect": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/inflect/-/inflect-0.3.0.tgz", + "integrity": "sha1-gdDqqja1CmAjC3UQBIs5xBQv5So=" + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -12117,6 +12157,22 @@ "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==" }, + "xml2js": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "dev": true, + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + } + }, + "xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "dev": true + }, "xmlhttprequest-ssl": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", diff --git a/package.json b/package.json index 4a9b6ed7496..36d527c657e 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,9 @@ "compression": "^1.7.4", "css-loader": "^1.0.1", "cssmin": "^0.4.3", + "csv-stringify": "^5.3.5", "d3": "^5.12.0", + "easyxml": "^2.0.1", "ejs": "^2.6.2", "errorhandler": "^1.5.1", "event-stream": "3.3.4", @@ -121,6 +123,7 @@ "devDependencies": { "babel-eslint": "^10.0.3", "benv": "^3.3.0", + "csv-parse": "^4.8.3", "env-cmd": "^8.0.2", "eslint": "^6.2.1", "eslint-loader": "^2.2.1", @@ -133,7 +136,8 @@ "terser-webpack-plugin": "^1.4.1", "webpack-bundle-analyzer": "^3.4.1", "webpack-dev-middleware": "^3.7.2", - "webpack-hot-middleware": "^2.25.0" + "webpack-hot-middleware": "^2.25.0", + "xml2js": "^0.4.23" }, "browserslist": "> 0.25%, not dead" } diff --git a/tests/api3.generic.workflow.test.js b/tests/api3.generic.workflow.test.js index cd4b4a46b4b..f94238d6747 100644 --- a/tests/api3.generic.workflow.test.js +++ b/tests/api3.generic.workflow.test.js @@ -9,10 +9,8 @@ describe('Generic REST API3', function() { , instance = require('./fixtures/api3/instance') , authSubject = require('./fixtures/api3/authSubject') , opTools = require('../lib/api3/shared/operationTools') - , utils = require('./fixtures/api3/utils') ; - utils.randomString('32', 'aA#'); // let's have a brand new identifier for your testing document self.urlLastModified = '/api/v3/lastModified'; self.historyTimestamp = 0; diff --git a/tests/api3.renderer.test.js b/tests/api3.renderer.test.js new file mode 100644 index 00000000000..70401897025 --- /dev/null +++ b/tests/api3.renderer.test.js @@ -0,0 +1,268 @@ +/* eslint require-atomic-updates: 0 */ +'use strict'; + +require('should'); + +describe('API3 output renderers', function() { + const self = this + , testConst = require('./fixtures/api3/const.json') + , instance = require('./fixtures/api3/instance') + , authSubject = require('./fixtures/api3/authSubject') + , opTools = require('../lib/api3/shared/operationTools') + , _ = require('lodash') + , xml2js = require('xml2js') + , csvParse = require('csv-parse/lib/sync') + ; + + self.historyFrom = (new Date()).getTime() - 1000; // starting timestamp for HISTORY operations + + self.doc1 = testConst.SAMPLE_ENTRIES[0]; + self.doc1.date = (new Date()).getTime() - (5 * 60 * 1000); + self.doc1.identifier = opTools.calculateIdentifier(self.doc1); + + self.doc2 = testConst.SAMPLE_ENTRIES[1]; + self.doc2.date = (new Date()).getTime(); + self.doc2.identifier = opTools.calculateIdentifier(self.doc2); + + self.xmlParser = new xml2js.Parser({ + explicitArray: false + }); + + self.csvParserOptions = { + columns: true, + skip_empty_lines: true + }; + + self.timeout(15000); + + + before(async () => { + self.instance = await instance.create({}); + + self.app = self.instance.app; + self.env = self.instance.env; + self.url = '/api/v3/entries'; + + let authResult = await authSubject(self.instance.ctx.authorization.storage); + + self.subject = authResult.subject; + self.token = authResult.token; + }); + + + after(() => { + self.instance.server.close(); + }); + + + /** + * Checks if all properties from obj1 are string identical in obj2 + * (comparison of properties is made using toString()) + * @param {Object} obj1 + * @param {Object} obj2 + */ + self.checkProps = function checkProps (obj1, obj2) { + for (let propName in obj1) { + obj1[propName].toString().should.eql(obj2[propName].toString()); + } + }; + + + /** + * Checks if all objects from arrModel exist in arr + * (with string identical properties) + * @param arrModel + * @param arr + */ + self.checkItems = function checkItems (arrModel, arr) { + for (let itemModel of arrModel) { + const item = _.find(arr, (doc) => doc.identifier === itemModel.identifier); + item.should.not.be.empty(); + self.checkProps(itemModel, item); + } + }; + + + /** + * Checks if given text is valid XML. + * Next checks if all objects from arrModel exist in parsed array + * (with string identical properties) + * @param arrModel + * @param xmlText + * @returns {Promise} + */ + self.checkXmlItems = async function checkXmlItems (arrModel, xmlText) { + xmlText.should.startWith(''); + + const xml = await self.xmlParser.parseStringPromise(xmlText); + xml.items.should.not.be.empty(); + let items = xml.items.item; + items.should.be.Array(); + items.length.should.be.aboveOrEqual(arrModel.length); + + self.checkItems(arrModel, items); + }; + + + /** + * Checks if given text is valid CSV. + * Next checks if all objects from arrModel exist in parsed array + * (with string identical properties) + * @param arrModel + * @param csvText + * @returns {Promise} + */ + self.checkCsvItems = async function checkXmlItems (arrModel, csvText) { + csvText.should.not.be.empty(); + + const items = csvParse(csvText, self.csvParserOptions); + items.should.be.Array(); + items.length.should.be.aboveOrEqual(arrModel.length); + + self.checkItems(arrModel, items); + }; + + + it('should create 2 mock documents', async () => { + + async function createDoc (doc) { + + let res = await self.instance.post(`${self.url}?token=${self.token.create}`) + .send(doc) + .expect(201); + + res.body.should.be.empty(); + + res = await self.instance.get(`${self.url}/${doc.identifier}?token=${self.token.read}`) + .expect(200); + return res.body; + } + + self.doc1json = await createDoc(self.doc1); + self.doc2json = await createDoc(self.doc2); + }); + + + it('READ/SEARCH/HISTORY should not accept unsupported content type', async () => { + + async function check406 (request) { + const res = await request + .expect(406); + res.body.message.should.eql('Unsupported output format requested'); + } + + await check406(self.instance.get(`${self.url}/${self.doc1.identifier}.ttf?fields=_all&token=${self.token.read}`)); + await check406(self.instance.get(`${self.url}/${self.doc1.identifier}?fields=_all&token=${self.token.read}`) + .set('Accept', 'font/ttf')); + + await check406(self.instance.get(`${self.url}.ttf?fields=_all&token=${self.token.read}`)); + await check406(self.instance.get(`${self.url}?fields=_all&token=${self.token.read}`) + .set('Accept', 'font/ttf')); + + await check406(self.instance.get(`${self.url}/history/${self.doc1.date}.ttf?token=${self.token.read}`)); + await check406(self.instance.get(`${self.url}/history/${self.doc1.date}?token=${self.token.read}`) + .set('Accept', 'font/ttf')); + }); + + + it('READ should accept xml content type', async () => { + let res = await self.instance.get(`${self.url}/${self.doc1.identifier}.xml?fields=_all&token=${self.token.read}`) + .expect(200); + + res.text.should.startWith(''); + + const xml = await self.xmlParser.parseStringPromise(res.text); + xml.item.should.not.be.empty(); + self.checkProps(self.doc1, xml.item); + + let res2 = await self.instance.get(`${self.url}/${self.doc1.identifier}?fields=_all&token=${self.token.read}`) + .set('Accept', 'application/xml') + .expect(200); + + res.text.should.eql(res2.text); + }); + + + it('READ should accept csv content type', async () => { + let res = await self.instance.get(`${self.url}/${self.doc1.identifier}.csv?fields=_all&token=${self.token.read}`) + .expect(200); + + await self.checkCsvItems([self.doc1], res.text); + + let res2 = await self.instance.get(`${self.url}/${self.doc1.identifier}?fields=_all&token=${self.token.read}`) + .set('Accept', 'text/csv') + .expect(200); + + res.text.should.eql(res2.text); + }); + + + it('SEARCH should accept xml content type', async () => { + let res = await self.instance.get(`${self.url}.xml?token=${self.token.read}&date$gte=${self.doc1.date}`) + .expect(200); + + await self.checkXmlItems([self.doc1, self.doc2], res.text); + + let res2 = await self.instance.get(`${self.url}?token=${self.token.read}&date$gte=${self.doc1.date}`) + .set('Accept', 'application/xml') + .expect(200); + + res.text.should.be.eql(res2.text); + }); + + + it('SEARCH should accept csv content type', async () => { + let res = await self.instance.get(`${self.url}.csv?token=${self.token.read}&date$gte=${self.doc1.date}`) + .expect(200); + + await self.checkCsvItems([self.doc1, self.doc2], res.text); + + let res2 = await self.instance.get(`${self.url}?token=${self.token.read}&date$gte=${self.doc1.date}`) + .set('Accept', 'text/csv') + .expect(200); + + res.text.should.be.eql(res2.text); + }); + + + it('HISTORY should accept xml content type', async () => { + let res = await self.instance.get(`${self.url}/history/${self.historyFrom}.xml?token=${self.token.read}`) + .expect(200); + + await self.checkXmlItems([self.doc1, self.doc2], res.text); + + let res2 = await self.instance.get(`${self.url}/history/${self.historyFrom}?token=${self.token.read}`) + .set('Accept', 'application/xml') + .expect(200); + + res.text.should.be.eql(res2.text); + }); + + + it('HISTORY should accept csv content type', async () => { + let res = await self.instance.get(`${self.url}/history/${self.historyFrom}.csv?token=${self.token.read}`) + .expect(200); + + await self.checkCsvItems([self.doc1, self.doc2], res.text); + + let res2 = await self.instance.get(`${self.url}/history/${self.historyFrom}?token=${self.token.read}`) + .set('Accept', 'text/csv') + .expect(200); + + res.text.should.be.eql(res2.text); + }); + + + it('should remove mock documents', async () => { + + async function deleteDoc (identifier) { + await self.instance.delete(`${self.url}/${identifier}?token=${self.token.delete}`) + .query({ 'permanent': 'true' }) + .expect(204); + } + + await deleteDoc(self.doc1.identifier); + await deleteDoc(self.doc2.identifier); + }); +}); + From 5c152438e0723f0e8fc6fd3efe1908768301c4c3 Mon Sep 17 00:00:00 2001 From: Jakob Date: Sun, 12 Jan 2020 10:39:36 -0800 Subject: [PATCH 034/104] Restore glucose distribution test (#5434) --- tests/reports.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/reports.test.js b/tests/reports.test.js index 7d5a0eb7009..2546bbbf5e8 100644 --- a/tests/reports.test.js +++ b/tests/reports.test.js @@ -268,7 +268,7 @@ describe('reports', function ( ) { result.indexOf('50 g').should.be.greaterThan(-1); // daytoday result.indexOf('TDD average: 2.9U').should.be.greaterThan(-1); // daytoday result.indexOf('0%100%0%2').should.be.greaterThan(-1); //dailystats - //TODO FIXME result.indexOf('td class="tdborder" style="background-color:#8f8">Normal: 64.7%6').should.be.greaterThan(-1); // distribution + result.indexOf('In Range: 47.6%10').should.be.greaterThan(-1); // distribution result.indexOf('16 (100%)').should.be.greaterThan(-1); // hourlystats result.indexOf('
').should.be.greaterThan(-1); //success result.indexOf('CAL: Scale: 1.10 Intercept: 31102 Slope: 776.91').should.be.greaterThan(-1); //calibrations From 81d926a989f12e0c954f9fcbc4c654263f7ab333 Mon Sep 17 00:00:00 2001 From: Jakob Date: Sun, 12 Jan 2020 10:46:08 -0800 Subject: [PATCH 035/104] Increase coverage of tests on units utility and clean up timeago test (#5435) * Increase coverage of tests on units utility * clean up timeago test --- tests/timeago.test.js | 3 --- tests/units.test.js | 16 ++++++++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/tests/timeago.test.js b/tests/timeago.test.js index 6c44981bfde..66306a3d154 100644 --- a/tests/timeago.test.js +++ b/tests/timeago.test.js @@ -53,9 +53,6 @@ describe('timeago', function() { var currentTime = new Date().getTime(); - // eslint-disable-next-line no-empty - while (currentTime + 500 >= new Date().getTime()) {} - var highest = ctx.notifications.findHighestAlarm('Time Ago'); highest.level.should.equal(levels.WARN); highest.message.should.equal('Last received: 16 mins ago\nBG Now: 100 mg/dl'); diff --git a/tests/units.test.js b/tests/units.test.js index b6e8a9faa8f..2fbef0c4d3e 100644 --- a/tests/units.test.js +++ b/tests/units.test.js @@ -13,4 +13,20 @@ describe('units', function ( ) { units.mgdlToMMOL(180).should.equal('10.0'); }); + it('should convert 5.5 to 99', function () { + units.mmolToMgdl(5.5).should.equal(99); + }); + + it('should convert 10.0 to 180', function () { + units.mmolToMgdl(10.0).should.equal(180); + }); + + it('should convert 5.5 mmol and then convert back to 5.5 mmol', function () { + units.mgdlToMMOL(units.mmolToMgdl(5.5)).should.equal('5.5'); + }); + + it('should convert 99 mgdl and then convert back to 99 mgdl', function () { + units.mmolToMgdl(units.mgdlToMMOL(99)).should.equal(99); + }); + }); From 937aa474aa5cdd54eab4a68793e056b36e64685e Mon Sep 17 00:00:00 2001 From: Jakob Date: Sun, 12 Jan 2020 10:49:08 -0800 Subject: [PATCH 036/104] Refactor to encapsulate duplicated settings logic (#5426) * Encapsulate duplicate settings checks inside functions * Simplify settings::isAlarmEventEnabled() --- lib/settings.js | 42 ++++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/lib/settings.js b/lib/settings.js index c2497ed66d5..d1f18b0e9c5 100644 --- a/lib/settings.js +++ b/lib/settings.js @@ -290,34 +290,40 @@ function init () { return enabled; } - function isAlarmEventEnabled (notify) { - var enabled = false; + function isUrgentHighAlarmEnabled(notify) { + return notify.eventName === 'high' && notify.level === levels.URGENT && settings.alarmUrgentHigh; + } - if ('high' !== notify.eventName && 'low' !== notify.eventName) { - enabled = true; - } else if (notify.eventName === 'high' && notify.level === levels.URGENT && settings.alarmUrgentHigh) { - enabled = true; - } else if (notify.eventName === 'high' && settings.alarmHigh) { - enabled = true; - } else if (notify.eventName === 'low' && notify.level === levels.URGENT && settings.alarmUrgentLow) { - enabled = true; - } else if (notify.eventName === 'low' && settings.alarmLow) { - enabled = true; - } + function isHighAlarmEnabled(notify) { + return notify.eventName === 'high' && settings.alarmHigh; + } - return enabled; + function isUrgentLowAlarmEnabled(notify) { + return notify.eventName === 'low' && notify.level === levels.URGENT && settings.alarmUrgentLow; + } + + function isLowAlarmEnabled(notify) { + return notify.eventName === 'low' && settings.alarmLow; + } + + function isAlarmEventEnabled (notify) { + return ('high' !== notify.eventName && 'low' !== notify.eventName) + || isUrgentHighAlarmEnabled(notify) + || isHighAlarmEnabled(notify) + || isUrgentLowAlarmEnabled(notify) + || isLowAlarmEnabled(notify); } function snoozeMinsForAlarmEvent (notify) { var snoozeTime; - if (notify.eventName === 'high' && notify.level === levels.URGENT && settings.alarmUrgentHigh) { + if (isUrgentHighAlarmEnabled(notify)) { snoozeTime = settings.alarmUrgentHighMins; - } else if (notify.eventName === 'high' && settings.alarmHigh) { + } else if (isHighAlarmEnabled(notify)) { snoozeTime = settings.alarmHighMins; - } else if (notify.eventName === 'low' && notify.level === levels.URGENT && settings.alarmUrgentLow) { + } else if (isUrgentLowAlarmEnabled(notify)) { snoozeTime = settings.alarmUrgentLowMins; - } else if (notify.eventName === 'low' && settings.alarmLow) { + } else if (isLowAlarmEnabled(notify)) { snoozeTime = settings.alarmLowMins; } else if (notify.level === levels.URGENT) { snoozeTime = settings.alarmUrgentMins; From 64663d2bfc8390abb4211d5fac91eae2706e8e64 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 12 Jan 2020 18:49:19 +0000 Subject: [PATCH 037/104] Send coverage reports to Codacy (#5444) * Send coverage reports to Codacy * Action secrets may not be read from forks :( * Let's see if this fixes the path & key passing * Ok one more try for the Actions env * Run tests and coverage separately * Try increasing the admintools test runtime for Node 10 * Run tests only once --- .github/workflows/main.yml | 4 +- ci.test.env | 3 +- npm-shrinkwrap.json | 105 +++++++++++++++++++++++++++++++++++++ package.json | 5 +- tests/admintools.test.js | 2 +- 5 files changed, 114 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 23cc61e5ef7..6e7d00d747c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -28,5 +28,7 @@ jobs: sudo apt-get install -y --allow-downgrades mongodb-org=3.6.14 mongodb-org-server=3.6.14 mongodb-org-shell=3.6.14 mongodb-org-mongos=3.6.14 mongodb-org-tools=3.6.14 - name: Start MongoDB run: sudo systemctl start mongod - - name: Run tests + - name: Run Tests run: npm run-script test-ci + - name: Send Coverage + run: npm run-script coverage diff --git a/ci.test.env b/ci.test.env index c57e5eeb0c4..f5e240f8381 100644 --- a/ci.test.env +++ b/ci.test.env @@ -4,4 +4,5 @@ HOSTNAME=localhost INSECURE_USE_HTTP=true PORT=1337 NODE_ENV=production -CI=true \ No newline at end of file +CI=true +CODACY_PROJECT_TOKEN=cff7ab3377d6434a9355fd051dbb4595 \ No newline at end of file diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 5b4118aae7c..fefff4919b9 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -2805,6 +2805,23 @@ "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=" }, + "codacy-coverage": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/codacy-coverage/-/codacy-coverage-3.4.0.tgz", + "integrity": "sha512-A0ats3/gZtOw76muu++HZ6QrInztWjjLefkLJmmBpjPfyn6nNwNLoApmGmj3F3dfgl2+o6u5GwPnUBkKdfKXTQ==", + "dev": true, + "requires": { + "bluebird": "^3.5.x", + "commander": "^2.x", + "jacoco-parse": "^2.x", + "joi": "^13.x", + "lcov-parse": "^1.x", + "lodash": "^4.17.4", + "log-driver": "^1.x", + "request": "^2.88.0", + "request-promise": "^4.x" + } + }, "collection-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", @@ -5543,6 +5560,12 @@ "minimalistic-crypto-utils": "^1.0.1" } }, + "hoek": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-5.0.4.tgz", + "integrity": "sha512-Alr4ZQgoMlnere5FZJsIyfIjORBqZll5POhDsF4q64dPuJR6rNxXdDxtHSQq8OXRurhmx+PWYEE8bXRROY8h0w==", + "dev": true + }, "homedir-polyfill": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", @@ -6040,6 +6063,15 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" }, + "isemail": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/isemail/-/isemail-3.2.0.tgz", + "integrity": "sha512-zKqkK+O+dGqevc93KNsbZ/TqTUFd46MwWjYOoMrjIMZ51eU7DtQG3Wmd9SQQT7i7RVnuTPEiYEWHU3MSbxC1Tg==", + "dev": true, + "requires": { + "punycode": "2.x.x" + } + }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -6289,6 +6321,27 @@ "handlebars": "^4.1.2" } }, + "jacoco-parse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jacoco-parse/-/jacoco-parse-2.0.1.tgz", + "integrity": "sha512-YGhIb2iXuQ4/zNh2zgHd6Z6dqlYwLYH1wfsxtTNQ+jnHH9PhhuMwqOFihXymSI41trxok48LdKkSeDIWs28tYg==", + "dev": true, + "requires": { + "mocha": "^5.2.0", + "xml2js": "^0.4.9" + } + }, + "joi": { + "version": "13.7.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-13.7.0.tgz", + "integrity": "sha512-xuY5VkHfeOYK3Hdi91ulocfuFopwgbSORmIwzcwHKESQhC7w1kD5jaVSPnqDxS2I8t3RZ9omCKAxNwXN5zG1/Q==", + "dev": true, + "requires": { + "hoek": "5.x.x", + "isemail": "3.x.x", + "topo": "3.x.x" + } + }, "jquery": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.4.1.tgz", @@ -6495,6 +6548,12 @@ "invert-kv": "^2.0.0" } }, + "lcov-parse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-1.0.0.tgz", + "integrity": "sha1-6w1GtUER68VhrLTECO+TY73I9+A=", + "dev": true + }, "left-pad": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", @@ -6618,6 +6677,12 @@ "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=" }, + "log-driver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", + "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", + "dev": true + }, "loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -9634,6 +9699,29 @@ } } }, + "request-promise": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.5.tgz", + "integrity": "sha512-ZgnepCykFdmpq86fKGwqntyTiUrHycALuGggpyCZwMvGaZWgxW6yagT0FHkgo5LzYvOaCNvxYwWYIjevSH1EDg==", + "dev": true, + "requires": { + "bluebird": "^3.5.0", + "request-promise-core": "1.1.3", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + }, + "dependencies": { + "request-promise-core": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz", + "integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==", + "dev": true, + "requires": { + "lodash": "^4.17.15" + } + } + } + }, "request-promise-core": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz", @@ -11242,6 +11330,23 @@ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" }, + "topo": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/topo/-/topo-3.0.3.tgz", + "integrity": "sha512-IgpPtvD4kjrJ7CRA3ov2FhWQADwv+Tdqbsf1ZnPUSAtCJ9e1Z44MmoSGDXGk4IppoZA7jd/QRkNddlLJWlUZsQ==", + "dev": true, + "requires": { + "hoek": "6.x.x" + }, + "dependencies": { + "hoek": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-6.1.3.tgz", + "integrity": "sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ==", + "dev": true + } + } + }, "touch": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", diff --git a/package.json b/package.json index 36d527c657e..8018bc64255 100644 --- a/package.json +++ b/package.json @@ -28,14 +28,14 @@ "scripts": { "start": "node server.js", "test": "env-cmd ./my.test.env mocha --exit tests/*.test.js", - "test-ci": "env-cmd ./ci.test.env mocha --exit tests/*.test.js", + "test-ci": "env-cmd ./ci.test.env nyc --reporter=lcov --reporter=text-summary mocha --exit tests/*.test.js", "env": "env", "postinstall": "webpack --mode production --config webpack.config.js && npm run-script update-buster", "bundle": "webpack --mode production --config webpack.config.js && npm run-script update-buster", "bundle-dev": "webpack --mode development --config webpack.config.js && npm run-script update-buster", "bundle-analyzer": "webpack --mode development --config webpack.config.js --profile --json > stats.json && webpack-bundle-analyzer stats.json", "update-buster": "node bin/generateCacheBuster.js >tmp/cacheBusterToken", - "coverage": "env-cmd ./test.env nyc mocha --exit tests/*.test.js", + "coverage": "cat ./coverage/lcov.info | env-cmd ./ci.test.env codacy-coverage", "dev": "env-cmd ./my.env nodemon server.js 0.0.0.0", "prod": "env-cmd ./my.prod.env node server.js 0.0.0.0" }, @@ -123,6 +123,7 @@ "devDependencies": { "babel-eslint": "^10.0.3", "benv": "^3.3.0", + "codacy-coverage": "^3.4.0", "csv-parse": "^4.8.3", "env-cmd": "^8.0.2", "eslint": "^6.2.1", diff --git a/tests/admintools.test.js b/tests/admintools.test.js index 0b95acdf6c1..2fe6169f59a 100644 --- a/tests/admintools.test.js +++ b/tests/admintools.test.js @@ -66,7 +66,7 @@ var someData = { describe('admintools', function ( ) { var self = this; - this.timeout(30000); // TODO: see why this test takes longer on Travis to complete + this.timeout(45000); // TODO: see why this test takes longer on CI to complete before(function (done) { benv.setup(function() { From 7dc292e05656d4060f1e03c1e32ec6390aa541ff Mon Sep 17 00:00:00 2001 From: Jakob Date: Sun, 12 Jan 2020 12:13:29 -0800 Subject: [PATCH 038/104] Resolve unnecessary uses of eslint-ignore (#5436) * Resolve unnecessary disabling of no-undef eslint rule * Resolve unnecessary disabling of no-prototype-builtins eslint rule * Resolve unnecessary disabling of no-unused-vars eslint rule * Resolve unnecessary disabling of no-empty eslint rule --- bundle/bundle.reports.source.js | 2 +- bundle/bundle.source.js | 2 +- lib/api/devicestatus/index.js | 3 +-- lib/api/entries/index.js | 3 +-- lib/api/treatments/index.js | 3 +-- lib/client/clock-client.js | 3 +-- lib/client/index.js | 5 ++--- lib/data/dataloader.js | 3 +-- lib/notifications.js | 3 +-- lib/plugins/ar2.js | 1 - lib/plugins/careportal.js | 3 +-- lib/report_plugins/daytoday.js | 2 -- lib/report_plugins/loopalyzer.js | 13 +++++++------ lib/report_plugins/profiles.js | 3 +-- lib/server/clocks.js | 3 +-- lib/server/treatments.js | 3 +-- lib/server/websocket.js | 3 +-- 17 files changed, 22 insertions(+), 36 deletions(-) diff --git a/bundle/bundle.reports.source.js b/bundle/bundle.reports.source.js index a7d7272f2e9..27d67e9fb82 100644 --- a/bundle/bundle.reports.source.js +++ b/bundle/bundle.reports.source.js @@ -7,5 +7,5 @@ console.info('Nightscout report bundle ready'); // Needed for Hot Module Replacement if(typeof(module.hot) !== 'undefined') { - module.hot.accept() // eslint-disable-line no-undef + module.hot.accept() } diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index d554744e6e4..db61af947bf 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -32,5 +32,5 @@ console.info('Nightscout bundle ready'); // Needed for Hot Module Replacement if(typeof(module.hot) !== 'undefined') { - module.hot.accept() // eslint-disable-line no-undef + module.hot.accept() } diff --git a/lib/api/devicestatus/index.js b/lib/api/devicestatus/index.js index 91702902fa3..97489646176 100644 --- a/lib/api/devicestatus/index.js +++ b/lib/api/devicestatus/index.js @@ -30,8 +30,7 @@ function configure (app, wares, ctx, env) { // Support date de-normalization for older clients if (env.settings.deNormalizeDates) { results.forEach(function(e) { - // eslint-disable-next-line no-prototype-builtins - if (e.created_at && e.hasOwnProperty('utcOffset')) { + if (e.created_at && Object.prototype.hasOwnProperty.call(e, 'utcOffset')) { const d = moment(e.created_at).utcOffset(e.utcOffset); e.created_at = d.toISOString(true); delete e.utcOffset; diff --git a/lib/api/entries/index.js b/lib/api/entries/index.js index faf922ff0e8..9e5f977322a 100644 --- a/lib/api/entries/index.js +++ b/lib/api/entries/index.js @@ -74,8 +74,7 @@ function configure (app, wares, ctx, env) { // Support date de-normalization for older clients if (env.settings.deNormalizeDates) { - // eslint-disable-next-line no-prototype-builtins - if (data.dateString && data.hasOwnProperty('utcOffset')) { + if (data.dateString && Object.prototype.hasOwnProperty.call(data, 'utcOffset')) { const d = moment(data.dateString).utcOffset(data.utcOffset); data.dateString = d.toISOString(true); delete data.utcOffset; diff --git a/lib/api/treatments/index.js b/lib/api/treatments/index.js index 5d527fce6ac..e62e6a35faa 100644 --- a/lib/api/treatments/index.js +++ b/lib/api/treatments/index.js @@ -45,8 +45,7 @@ function configure (app, wares, ctx, env) { t.carbs = Number(t.carbs); t.insulin = Number(t.insulin); - // eslint-disable-next-line no-prototype-builtins - if (deNormalizeDates && t.hasOwnProperty('utcOffset')) { + if (deNormalizeDates && Object.prototype.hasOwnProperty.call(t, 'utcOffset')) { const d = moment(t.created_at).utcOffset(t.utcOffset); t.created_at = d.toISOString(true); delete t.utcOffset; diff --git a/lib/client/clock-client.js b/lib/client/clock-client.js index 324a5168693..e6eec7190c6 100644 --- a/lib/client/clock-client.js +++ b/lib/client/clock-client.js @@ -107,8 +107,7 @@ client.render = function render (xhr) { if (m < 10) m = "0" + m; $('#clock').text(h + ":" + m); - // defined in the template this is loaded into - // eslint-disable-next-line no-undef + /* global clockFace */ if (clockFace === 'clock-color') { var bgHigh = window.serverSettings.settings.thresholds.bgHigh; diff --git a/lib/client/index.js b/lib/client/index.js index b4c428ff2b5..5c611306d9f 100644 --- a/lib/client/index.js +++ b/lib/client/index.js @@ -64,8 +64,7 @@ client.init = function init (callback) { console.log('Application appears to be online'); $('#centerMessagePanel').hide(); client.load(serverSettings, callback); - // eslint-disable-next-line no-unused-vars - }).fail(function fail (jqXHR, textStatus, errorThrown) { + }).fail(function fail (jqXHR) { // check if we couldn't reach the server at all, show offline message if (!jqXHR.readyState) { @@ -961,7 +960,7 @@ client.load = function load (serverSettings, callback) { //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Client-side code to connect to server and handle incoming data //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // eslint-disable-next-line no-undef + /* global io */ client.socket = socket = io.connect(); socket.on('dataUpdate', dataUpdate); diff --git a/lib/data/dataloader.js b/lib/data/dataloader.js index cc4426aa6ab..31121c2c124 100644 --- a/lib/data/dataloader.js +++ b/lib/data/dataloader.js @@ -11,8 +11,7 @@ var ONE_DAY = 86400000, function uniq(a) { var seen = {}; return a.filter(function(item) { - // eslint-disable-next-line no-prototype-builtins - return seen.hasOwnProperty(item.mills) ? false : (seen[item.mills] = true); + return Object.prototype.hasOwnProperty.call(seen, item.mills) ? false : (seen[item.mills] = true); }); } diff --git a/lib/notifications.js b/lib/notifications.js index 1a03ab87244..b4a8767afbe 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -108,8 +108,7 @@ function init (env, ctx) { }; notifications.requestNotify = function requestNotify (notify) { - // eslint-disable-next-line no-prototype-builtins - if (!notify.hasOwnProperty('level') || !notify.title || !notify.message || !notify.plugin) { + if (!Object.prototype.hasOwnProperty.call(notify, 'level') || !notify.title || !notify.message || !notify.plugin) { console.error(new Error('Unable to request notification, since the notify isn\'t complete: ' + JSON.stringify(notify))); return; } diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 52aec073759..133c5b8d3c9 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -15,7 +15,6 @@ var AR = [-0.723, 1.716]; //TODO: move this to css var AR2_COLOR = 'cyan'; -// eslint-disable-next-line no-unused-vars function init (ctx) { var translate = ctx.language.translate; diff --git a/lib/plugins/careportal.js b/lib/plugins/careportal.js index 2d65e1c9e9c..90f9bbd992a 100644 --- a/lib/plugins/careportal.js +++ b/lib/plugins/careportal.js @@ -8,8 +8,7 @@ function init() { , pluginType: 'drawer' }; - // eslint-disable-next-line no-unused-vars - careportal.getEventTypes = function getEventTypes (sbx) { + careportal.getEventTypes = function getEventTypes () { //TODO: use sbx and new CAREPORTAL_EVENTTYPE_GROUPS="core temps combo dad sensor site etc" diff --git a/lib/report_plugins/daytoday.js b/lib/report_plugins/daytoday.js index 3dd7ba624cb..9348bd76e8f 100644 --- a/lib/report_plugins/daytoday.js +++ b/lib/report_plugins/daytoday.js @@ -362,7 +362,6 @@ daytoday.report = function report_daytoday (datastorage, sorteddaystoshow, optio for (var treatmentsIndex = 0; treatmentsIndex < treatmentsTimestamps.length; treatmentsIndex++) { var timestamp = treatmentsTimestamps[treatmentsIndex]; // TODO refactor code so this is set here - now set as global in file loaded by the browser - // eslint-disable-next-line no-undef var predictedIndex = findPredicted(predictions, timestamp, Nightscout.predictions.offset); // Find predictions offset before or after timestamp if (predictedIndex != null) { @@ -370,7 +369,6 @@ daytoday.report = function report_daytoday (datastorage, sorteddaystoshow, optio var d = moment(entry.startDate); var end = moment().endOf('day'); if (options.predictedTruncate) { - // eslint-disable-next-line no-undef if (Nightscout.predictions.offset >= 0) { // If we are looking forward we want to stop at the next treatment if (treatmentsIndex < treatmentsTimestamps.length - 1) { diff --git a/lib/report_plugins/loopalyzer.js b/lib/report_plugins/loopalyzer.js index 2d53fa251a3..c37c58f635f 100644 --- a/lib/report_plugins/loopalyzer.js +++ b/lib/report_plugins/loopalyzer.js @@ -318,10 +318,12 @@ loopalyzer.fillNanWithTreatments = function(array, treatments) { var stop = index; // Now move left and right until we find real numbers, so not NaN - // eslint-disable-next-line no-empty - while (start-- >= 0 && isNaN(array[start])) {} - // eslint-disable-next-line no-empty - while (stop++ < array.length && isNaN(array[stop])) {} + while (start >= 0 && isNaN(array[start])) { + start--; + } + while (stop < array.length && isNaN(array[stop])) { + stop++; + } // var gap = stop - start; // if (isNaN(array[start]) || isNaN(array[stop]) || gap > interpolationGap || (gap < interpolationGap && array[start]= interpolationGap || array[start]==0)) ) { @@ -732,8 +734,7 @@ loopalyzer.renderProfilesTable = function(datastoreProfiles, daysToShow, client) if (store) { for (var key in store) { if (laDebug) console.log('profile ' + key); - // eslint-disable-next-line no-prototype-builtins - if (store.hasOwnProperty(key)) { + if (Object.prototype.hasOwnProperty.call(store, key)) { var defaultProfile = store[key]; newEntry.profileName = key; if (defaultProfile.basal) newEntry.basal = defaultProfile.basal; diff --git a/lib/report_plugins/profiles.js b/lib/report_plugins/profiles.js index 580367891de..4aea2d040a8 100644 --- a/lib/report_plugins/profiles.js +++ b/lib/report_plugins/profiles.js @@ -30,8 +30,7 @@ profiles.css = ' height: 100%;' + '}'; -// eslint-disable-next-line no-unused-vars -profiles.report = function report_profiles (datastorage, sorteddaystoshow, options) { +profiles.report = function report_profiles (datastorage) { var Nightscout = window.Nightscout; var client = Nightscout.client; var translate = client.translate; diff --git a/lib/server/clocks.js b/lib/server/clocks.js index 282acd01951..56bce7b6f13 100644 --- a/lib/server/clocks.js +++ b/lib/server/clocks.js @@ -3,8 +3,7 @@ const express = require('express'); const path = require('path'); -// eslint-disable-next-line no-unused-vars -function clockviews(env, ctx) { +function clockviews() { const app = new express(); let locals = {}; diff --git a/lib/server/treatments.js b/lib/server/treatments.js index c02bd50f317..580e5cf0c45 100644 --- a/lib/server/treatments.js +++ b/lib/server/treatments.js @@ -25,8 +25,7 @@ function storage (env, ctx) { errs.push(err); callback(err, docs) }); - // eslint-disable-next-line no-unused-vars - }, function (err, docs) { + }, function () { errs = _.compact(errs); done(errs.length > 0 ? errs : null, allDocs); }); diff --git a/lib/server/websocket.js b/lib/server/websocket.js index 9a3181b1774..0288da5f197 100644 --- a/lib/server/websocket.js +++ b/lib/server/websocket.js @@ -34,8 +34,7 @@ function init (env, ctx, server) { }; // This is little ugly copy but I was unable to pass testa after making module from status and share with /api/v1/status - // eslint-disable-next-line no-unused-vars - function status (profile) { + function status () { var versionNum = 0; var verParse = /(\d+)\.(\d+)\.(\d+)*/.exec(env.version); if (verParse) { From 3a1d9be89d81036a7bee26d32d2407aaaab13601 Mon Sep 17 00:00:00 2001 From: Jakob Date: Tue, 14 Jan 2020 07:33:57 -0800 Subject: [PATCH 039/104] Fix eslint errors and add npm script for eslint (#5427) * re-enable auth check for device status routes * Resolve eslint errors * Add npm script for eslint * Correct regex for express extension middleware and add tests for expected behaviour * Resolve lint error in virtual assistant base * Update index.js * Update index.js --- lib/api/activity/index.js | 8 ++--- lib/api/alexa/index.js | 2 +- lib/api/devicestatus/index.js | 2 +- lib/api/googlehome/index.js | 2 +- lib/api/status.js | 2 +- lib/api/treatments/index.js | 6 ++-- lib/authorization/index.js | 2 +- lib/authorization/storage.js | 6 ++-- lib/client/chart.js | 2 -- lib/data/calcdelta.js | 12 +++---- lib/data/dataloader.js | 1 - lib/middleware/express-extension-to-accept.js | 2 +- lib/middleware/index.js | 2 +- lib/plugins/alexa.js | 2 +- lib/plugins/googlehome.js | 2 +- lib/plugins/virtAsstBase.js | 12 +++---- lib/server/websocket.js | 6 ---- package.json | 3 +- tests/expressextensions.test.js | 33 +++++++++++++++++++ 19 files changed, 66 insertions(+), 41 deletions(-) create mode 100644 tests/expressextensions.test.js diff --git a/lib/api/activity/index.js b/lib/api/activity/index.js index d88019ab936..c42e73570c6 100644 --- a/lib/api/activity/index.js +++ b/lib/api/activity/index.js @@ -43,17 +43,17 @@ function configure(app, wares, ctx) { var d2 = null; - if (t.hasOwnProperty('created_at')) { + if (Object.prototype.hasOwnProperty.call(t, 'created_at')) { d2 = new Date(t.created_at); } else { - if (t.hasOwnProperty('timestamp')) { + if (Object.prototype.hasOwnProperty.call(t, 'timestamp')) { d2 = new Date(t.timestamp); } } if (d2 == null) { return; } - if (d1 == null || d2.getTime() > d1.getTime()) { + if (d1 == null || d2.getTime() > d1.getTime()) { d1 = d2; } }); @@ -80,7 +80,7 @@ function configure(app, wares, ctx) { if (!_isArray(activity)) { activity = [activity]; - }; + } ctx.activity.create(activity, function(err, created) { if (err) { diff --git a/lib/api/alexa/index.js b/lib/api/alexa/index.js index 560de7c495b..2a5fd4ef6cd 100644 --- a/lib/api/alexa/index.js +++ b/lib/api/alexa/index.js @@ -118,4 +118,4 @@ function configure (app, wares, ctx, env) { return api; } -module.exports = configure; \ No newline at end of file +module.exports = configure; diff --git a/lib/api/devicestatus/index.js b/lib/api/devicestatus/index.js index 97489646176..25e226efef6 100644 --- a/lib/api/devicestatus/index.js +++ b/lib/api/devicestatus/index.js @@ -105,7 +105,7 @@ function configure (app, wares, ctx, env) { api.delete('/devicestatus/', ctx.authorization.isPermitted('api:devicestatus:delete'), delete_records); } - if (app.enabled('api') || true /*TODO: auth disabled for quick UI testing...*/ ) { + if (app.enabled('api')) { config_authed(app, api, wares, ctx); } diff --git a/lib/api/googlehome/index.js b/lib/api/googlehome/index.js index 8c99eea640e..b44715b25eb 100644 --- a/lib/api/googlehome/index.js +++ b/lib/api/googlehome/index.js @@ -54,4 +54,4 @@ function configure (app, wares, ctx, env) { return api; } -module.exports = configure; \ No newline at end of file +module.exports = configure; diff --git a/lib/api/status.js b/lib/api/status.js index a6ab2e82750..dc8d97bcdd3 100644 --- a/lib/api/status.js +++ b/lib/api/status.js @@ -15,7 +15,7 @@ function configure (app, wares, env, ctx) { // Status badge/text/json api.get('/status', function (req, res) { - var authToken = req.query.token || req.query.secret || ''; + var authToken = req.query.token || req.query.secret || ''; var date = new Date(); var info = { status: 'ok' diff --git a/lib/api/treatments/index.js b/lib/api/treatments/index.js index e62e6a35faa..b30c297143b 100644 --- a/lib/api/treatments/index.js +++ b/lib/api/treatments/index.js @@ -53,10 +53,10 @@ function configure (app, wares, ctx, env) { var d2 = null; - if (t.hasOwnProperty('created_at')) { + if (Object.prototype.hasOwnProperty.call(t, 'created_at')) { d2 = new Date(t.created_at); } else { - if (t.hasOwnProperty('timestamp')) { + if (Object.prototype.hasOwnProperty.call(t, 'timestamp')) { d2 = new Date(t.timestamp); } } @@ -90,7 +90,7 @@ function configure (app, wares, ctx, env) { if (!_isArray(treatments)) { treatments = [treatments]; - }; + } ctx.treatments.create(treatments, function(err, created) { if (err) { diff --git a/lib/authorization/index.js b/lib/authorization/index.js index feaed739b42..b81578e0dca 100644 --- a/lib/authorization/index.js +++ b/lib/authorization/index.js @@ -186,7 +186,7 @@ function init (env, ctx) { authorization.isPermitted = function isPermitted (permission, opts) { - opts = mkopts(opts); + mkopts(opts); authorization.seenPermissions = _.chain(authorization.seenPermissions) .push(permission) .sort() diff --git a/lib/authorization/storage.js b/lib/authorization/storage.js index 3a4c4490876..c032018d170 100644 --- a/lib/authorization/storage.js +++ b/lib/authorization/storage.js @@ -24,7 +24,7 @@ function init (env, ctx) { function create (collection) { function doCreate(obj, fn) { - if (!obj.hasOwnProperty('created_at')) { + if (!Object.prototype.hasOwnProperty.call(obj, 'created_at')) { obj.created_at = (new Date()).toISOString(); } collection.insert(obj, function (err, doc) { @@ -211,14 +211,14 @@ function init (env, ctx) { if (!accessToken) return null; var split_token = accessToken.split('-'); - var prefix = split_token ? _.last(split_token) : ''; + var prefix = split_token ? _.last(split_token) : ''; if (prefix.length < 16) { return null; } return _.find(storage.subjects, function matches (subject) { - return subject.accessTokenDigest.indexOf(accessToken) === 0 || subject.digest.indexOf(prefix) === 0; + return subject.accessTokenDigest.indexOf(accessToken) === 0 || subject.digest.indexOf(prefix) === 0; }); }; diff --git a/lib/client/chart.js b/lib/client/chart.js index ffd513cd650..71d68bbc761 100644 --- a/lib/client/chart.js +++ b/lib/client/chart.js @@ -730,8 +730,6 @@ function init (client, d3, $) { // on the number of hours the user has selected to show var forecastMills = Math.min(availForecastMills, maxForecastMills); - var lastSGVMills = client.sbx.lastSGVMills(); - // Don't allow the forecast time to go below the minimum forecast time client.forecastTime = Math.max(forecastMills, minForecastMills); } diff --git a/lib/data/calcdelta.js b/lib/data/calcdelta.js index e3e0fde7052..c991bd8d602 100644 --- a/lib/data/calcdelta.js +++ b/lib/data/calcdelta.js @@ -75,7 +75,7 @@ module.exports = function calcDelta (oldData, newData) { var result = []; l = newArray.length; for (var j = 0; j < l; j++) { - if (!seen.hasOwnProperty(newArray[j].mills)) { + if (!Object.prototype.hasOwnProperty.call(seen, newArray[j].mills)) { result.push(newArray[j]); } } @@ -94,12 +94,12 @@ module.exports = function calcDelta (oldData, newData) { var changesFound = false; for (var array in compressibleArrays) { - if (compressibleArrays.hasOwnProperty(array)) { + if (Object.prototype.hasOwnProperty.call(compressibleArrays, array)) { var a = compressibleArrays[array]; - if (newData.hasOwnProperty(a)) { + if (Object.prototype.hasOwnProperty.call(newData, a)) { // if previous data doesn't have the property (first time delta?), just assign data over - if (!oldData.hasOwnProperty(a)) { + if (!Object.prototype.hasOwnProperty.call(oldData, a)) { delta[a] = newData[a]; changesFound = true; continue; @@ -125,9 +125,9 @@ module.exports = function calcDelta (oldData, newData) { var changesFound = false; for (var object in skippableObjects) { - if (skippableObjects.hasOwnProperty(object)) { + if (Object.prototype.hasOwnProperty.call(skippableObjects, object)) { var o = skippableObjects[object]; - if (newData.hasOwnProperty(o)) { + if (Object.prototype.hasOwnProperty.call(newData, o)) { if (JSON.stringify(newData[o]) !== JSON.stringify(oldData[o])) { //console.log('delta changes found on', o); changesFound = true; diff --git a/lib/data/dataloader.js b/lib/data/dataloader.js index 31121c2c124..1c00f998d73 100644 --- a/lib/data/dataloader.js +++ b/lib/data/dataloader.js @@ -192,7 +192,6 @@ function loadActivity(ddata, ctx, callback) { } }; - var activity = []; ctx.activity.list(q, function(err, results) { if (err) { diff --git a/lib/middleware/express-extension-to-accept.js b/lib/middleware/express-extension-to-accept.js index 8f357e869a1..cdfe9a269fe 100644 --- a/lib/middleware/express-extension-to-accept.js +++ b/lib/middleware/express-extension-to-accept.js @@ -16,7 +16,7 @@ module.exports = function (formats) { throw new Error('Invalid format.') }) - var regexp = new RegExp('\.(' + formats.join('|') + ')$', 'i') + var regexp = new RegExp('\\.(' + formats.join('|') + ')$', 'i') return function (req, res, next) { var match = req.path.match(regexp) diff --git a/lib/middleware/index.js b/lib/middleware/index.js index c20eede6351..deb4fd5fb41 100644 --- a/lib/middleware/index.js +++ b/lib/middleware/index.js @@ -10,7 +10,7 @@ function extensions (list) { return require('./express-extension-to-accept')(list); } -function configure (env) { +function configure () { return { sendJSONStatus: wares.sendJSONStatus( ), bodyParser: wares.bodyParser, diff --git a/lib/plugins/alexa.js b/lib/plugins/alexa.js index da7bbdca2f1..25b13d0596d 100644 --- a/lib/plugins/alexa.js +++ b/lib/plugins/alexa.js @@ -1,7 +1,7 @@ var _ = require('lodash'); var async = require('async'); -function init (env, ctx) { +function init () { console.log('Configuring Alexa...'); function alexa() { return alexa; diff --git a/lib/plugins/googlehome.js b/lib/plugins/googlehome.js index 8e8181512c8..6b6d7b098f2 100644 --- a/lib/plugins/googlehome.js +++ b/lib/plugins/googlehome.js @@ -1,7 +1,7 @@ var _ = require('lodash'); var async = require('async'); -function init (env, ctx) { +function init () { console.log('Configuring Google Home...'); function googleHome() { return googleHome; diff --git a/lib/plugins/virtAsstBase.js b/lib/plugins/virtAsstBase.js index 79864f5bf95..e0d103672a2 100644 --- a/lib/plugins/virtAsstBase.js +++ b/lib/plugins/virtAsstBase.js @@ -28,7 +28,7 @@ function init(env, ctx) { moment(records[0].date).from(moment(sbx.time)) ] }); - + callback(null, {results: status, priority: -1}); }); }, 'BG Status'); @@ -40,7 +40,7 @@ function init(env, ctx) { }); // blood sugar and direction - configuredPlugin.configureIntentHandler('MetricNow', function (callback, slots, sbx, locale) { + configuredPlugin.configureIntentHandler('MetricNow', function (callback, slots, sbx) { entries.list({count: 1}, function(err, records) { var direction; if(translate(records[0].direction)){ @@ -54,13 +54,13 @@ function init(env, ctx) { direction, moment(records[0].date).from(moment(sbx.time))] }); - + callback(translate('virtAsstTitleCurrentBG'), status); }); }, ['bg', 'blood glucose', 'number']); - + // blood sugar delta - configuredPlugin.configureIntentHandler('MetricNow', function (callback, slots, sbx, locale) { + configuredPlugin.configureIntentHandler('MetricNow', function (callback, slots, sbx) { if (sbx.properties.delta && sbx.properties.delta.display) { entries.list({count: 2}, function(err, records) { callback( @@ -108,4 +108,4 @@ function init(env, ctx) { return virtAsstBase; } -module.exports = init; \ No newline at end of file +module.exports = init; diff --git a/lib/server/websocket.js b/lib/server/websocket.js index 0288da5f197..899d9326cc2 100644 --- a/lib/server/websocket.js +++ b/lib/server/websocket.js @@ -443,15 +443,9 @@ function init (env, ctx, server) { socketAuthorization = authorization; clientType = message.client; history = message.history || 48; //default history is 48 hours - var from = message.from; if (socketAuthorization.read) { socket.join('DataReceivers'); - var msecHistory = times.hours(history).msecs; - // if `from` is received, it's a reconnection and full data is not needed - if (from && from > 0) { - msecHistory = Math.min(new Date().getTime() - from, msecHistory); - } if (lastData && lastData.dataWithRecentStatuses) { let data = lastData.dataWithRecentStatuses(); diff --git a/package.json b/package.json index 8018bc64255..0abbae897b5 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,8 @@ "update-buster": "node bin/generateCacheBuster.js >tmp/cacheBusterToken", "coverage": "cat ./coverage/lcov.info | env-cmd ./ci.test.env codacy-coverage", "dev": "env-cmd ./my.env nodemon server.js 0.0.0.0", - "prod": "env-cmd ./my.prod.env node server.js 0.0.0.0" + "prod": "env-cmd ./my.prod.env node server.js 0.0.0.0", + "lint": "eslint lib" }, "main": "server.js", "config": { diff --git a/tests/expressextensions.test.js b/tests/expressextensions.test.js new file mode 100644 index 00000000000..b65ebd8952b --- /dev/null +++ b/tests/expressextensions.test.js @@ -0,0 +1,33 @@ +'use strict'; + +require('should'); + +var extensionsMiddleware = require('../lib/middleware/express-extension-to-accept.js'); + +var acceptJsonRequests = extensionsMiddleware(['json']); + +describe('Express extension middleware', function ( ) { + + it('Valid json request should be given accept header for application/json', function () { + var entriesRequest = { + path: '/api/v1/entries.json', + url: '/api/v1/entries.json', + headers: {} + }; + + acceptJsonRequests(entriesRequest, {}, () => {}); + entriesRequest.headers.accept.should.equal('application/json'); + }); + + it('Invalid json request should NOT be given accept header', function () { + var invalidEntriesRequest = { + path: '/api/v1/entriesXjson', + url: '/api/v1/entriesXjson', + headers: {} + }; + + acceptJsonRequests(invalidEntriesRequest, {}, () => {}); + should(invalidEntriesRequest.headers.accept).not.be.ok; + }); + +}); From 04edbec7d75ebe0301c7d45da561dcfeb42dad7d Mon Sep 17 00:00:00 2001 From: Jakob Date: Tue, 14 Jan 2020 07:53:13 -0800 Subject: [PATCH 040/104] Remove redundant checks on entry ID (#5440) --- lib/api/entries/index.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/api/entries/index.js b/lib/api/entries/index.js index 9e5f977322a..5dd05b419fb 100644 --- a/lib/api/entries/index.js +++ b/lib/api/entries/index.js @@ -14,8 +14,7 @@ const expand = braces.expand; const ID_PATTERN = /^[a-f\d]{24}$/; function isId (value) { - //TODO: why did we need tht length check? - return value && ID_PATTERN.test(value) && value.length === 24; + return ID_PATTERN.test(value); } /** From d095482c2c46fa0185d9862d07596fd8ea353205 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Tue, 14 Jan 2020 08:05:51 -0800 Subject: [PATCH 041/104] Run CI Action for Pull Requests --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6e7d00d747c..3c9fc778c1b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,6 +1,6 @@ name: CI test -on: [push] +on: [push, pull_request] jobs: build: From 3386ac685c3c5012042559ecbafa2b120dcca7ec Mon Sep 17 00:00:00 2001 From: jonfawcett <38429455+jonfawcett@users.noreply.github.com> Date: Sat, 18 Jan 2020 06:36:31 -0500 Subject: [PATCH 042/104] Update DayToDay report for Loop Overrides (#5452) Add Loop override name/reason as text to the grey bar at the top of the graph. --- lib/report_plugins/daytoday.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/lib/report_plugins/daytoday.js b/lib/report_plugins/daytoday.js index 9348bd76e8f..6728a5e1860 100644 --- a/lib/report_plugins/daytoday.js +++ b/lib/report_plugins/daytoday.js @@ -848,6 +848,26 @@ daytoday.report = function report_daytoday (datastorage, sorteddaystoshow, optio .attr('y', yScale2(client.utils.scaleMgdl(306)) + padding.top) .attr('x', xScale2(treatment.mills + times.mins(treatment.duration).msecs / 2) + padding.left) .text(treatment.notes); + } else if (treatment.eventType === 'Temporary Override' && treatment.duration ) { + // Loop Overrides with duration + context.append('rect') + .attr('x', xScale2(treatment.mills) + padding.left) + .attr('y', yScale2(client.utils.scaleMgdl(432)) + padding.top) + .attr('width', xScale2(treatment.mills + times.mins(treatment.duration).msecs) - xScale2(treatment.mills)) + .attr('height', yScale2(client.utils.scaleMgdl(396)) - yScale2(client.utils.scaleMgdl(432))) + .attr('stroke-width', 1) + .attr('opacity', .2) + .attr('stroke', 'white') + .attr('fill', 'black'); + context.append('text') + .style('font-size', '12px') + .style('font-weight', 'bold') + .attr('fill', 'black') + .attr('text-anchor', 'middle') + .attr('dy', '.35em') + .attr('y', yScale2(client.utils.scaleMgdl(414)) + padding.top) + .attr('x', xScale2(treatment.mills + times.mins(treatment.duration).msecs / 2) + padding.left) + .text(treatment.reason); } else if (!treatment.duration) { // other treatments without duration context.append('circle') From baad198bd5a5b85be11c9dfcb05ce61089d38cf8 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sat, 18 Jan 2020 13:37:53 +0200 Subject: [PATCH 043/104] Fix earlier merge error with predictions having moved to the report bundle --- views/reportindex.html | 1 - 1 file changed, 1 deletion(-) diff --git a/views/reportindex.html b/views/reportindex.html index 164c08c0252..546e89daf75 100644 --- a/views/reportindex.html +++ b/views/reportindex.html @@ -126,7 +126,6 @@ - \ No newline at end of file From 6045547ea5dc6545f4ecbed649687e71846ef7e7 Mon Sep 17 00:00:00 2001 From: Jeremy Cunningham <34543464+jpcunningh@users.noreply.github.com> Date: Sat, 1 Feb 2020 01:18:41 -0600 Subject: [PATCH 044/104] fix brushing loop (#5499) --- lib/client/chart.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/client/chart.js b/lib/client/chart.js index 71d68bbc761..7a9b57bdd5d 100644 --- a/lib/client/chart.js +++ b/lib/client/chart.js @@ -602,8 +602,6 @@ function init (client, d3, $) { }; function scrollUpdate () { - scrolling = false; - var nowDate = scrollNow; var currentBrushExtent = scrollBrushExtent; @@ -669,6 +667,8 @@ function init (client, d3, $) { // console.log('Redrawing brush due to update: ', currentBrushExtent); chart.theBrush.call(chart.brush.move, currentBrushExtent.map(chart.xScale2)); + + scrolling = false; } chart.scroll = function scroll (nowDate) { From c86b8901a286815bda6532fd053bef0a7e273b39 Mon Sep 17 00:00:00 2001 From: Jonas Hummelstrand Date: Mon, 3 Feb 2020 08:06:25 +0100 Subject: [PATCH 045/104] Update README.md (#5480) Clarified that the "bridge" plugin is for Dexcom Share ("Dexcom" didn't appear at all in the read me), fixed a few typos, and added line 444 about the BRIDGE_SERVER variable. --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8586e0b5a3b..62bd048f059 100644 --- a/README.md +++ b/README.md @@ -433,14 +433,15 @@ To learn more about the Nightscout API, visit https://YOUR-SITE.com/api-docs/ or * `BASAL_RENDER` (`none`) - Possible values are `none`, `default`, or `icicle` (inverted) ##### `bridge` (Share2Nightscout bridge) - Glucose reading directly from the Share service, uses these extended settings: - * `BRIDGE_USER_NAME` - Your user name for the Share service. + Glucose reading directly from the Dexcom Share service, uses these extended settings: + * `BRIDGE_USER_NAME` - Your username for the Share service. * `BRIDGE_PASSWORD` - Your password for the Share service. - * `BRIDGE_INTERVAL` (`150000` *2.5 minutes*) - The time to wait between each update. + * `BRIDGE_INTERVAL` (`150000` *2.5 minutes*) - The time (in milliseconds) to wait between each update. * `BRIDGE_MAX_COUNT` (`1`) - The number of records to attempt to fetch per update. * `BRIDGE_FIRST_FETCH_COUNT` (`3`) - Changes max count during the very first update only. * `BRIDGE_MAX_FAILURES` (`3`) - How many failures before giving up. - * `BRIDGE_MINUTES` (`1400`) - The time window to search for new data per update (default is one day in minutes). + * `BRIDGE_MINUTES` (`1400`) - The time window to search for new data per update (the default value is one day in minutes). + * `BRIDGE_SERVER` (``) - The default blank value is used to fetch data from Dexcom servers in the US. Set to (`EU`) to fetch from European servers instead. ##### `mmconnect` (MiniMed Connect bridge) Transfer real-time MiniMed Connect data from the Medtronic CareLink server into Nightscout ([read more](https://github.com/mddub/minimed-connect-to-nightscout)) From c0f6b2245cfd18d715905876df693d100e8c37c5 Mon Sep 17 00:00:00 2001 From: peterleimbach Date: Mon, 3 Feb 2020 08:09:43 +0100 Subject: [PATCH 046/104] =?UTF-8?q?Added=20period=20of=20days=20into=20hea?= =?UTF-8?q?dline=20of=20glucose=20distribution=20and=20percen=E2=80=A6=20(?= =?UTF-8?q?#5428)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added period of days into headline of glucose distribution and percentil chart report I make screencopies of the glucose distribution and percential chart report for my diabtes consultant and had to manually add the period of days to the report everytime because it was not shown in the report itself. I added the period of days this with a small number of lines of code and think this is helpful for other too. * removed comments as requested removed comments as requested * Camelcase for new variables reportPlugins, firstDay, lastDay, countDays * forget to save the change of reportPlugins in percentile.js --- lib/report_plugins/glucosedistribution.js | 12 ++++++--- lib/report_plugins/percentile.js | 31 ++++++++++++++++------- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/lib/report_plugins/glucosedistribution.js b/lib/report_plugins/glucosedistribution.js index f97bd034765..21d5213e9c9 100644 --- a/lib/report_plugins/glucosedistribution.js +++ b/lib/report_plugins/glucosedistribution.js @@ -19,9 +19,9 @@ glucosedistribution.html = function html (client) { var ret = '

' + translate('Glucose distribution') + - ' (' + - ' ' + - ' )' + + ' (' + + '' + + ')' + '

' + '' + '' + @@ -122,7 +122,11 @@ glucosedistribution.report = function report_glucosedistribution (datastorage, s var data = datastorage.allstatsrecords; var days = datastorage.alldays; - $('#glucosedistribution-days').text(days + ' ' + translate('days total')); + var reportPlugins = Nightscout.report_plugins; + var firstDay = reportPlugins.utils.localeDate(sorteddaystoshow[sorteddaystoshow.length - 1]); + var lastDay = reportPlugins.utils.localeDate(sorteddaystoshow[0]); + + $('#glucosedistribution-days').text(days + ' ' + translate('days total') + ', ' + firstDay + ' - ' + lastDay); for (var i = 0; i < 24; i++) { $('#glucosedistribution-' + i).unbind('click').click(onClick); diff --git a/lib/report_plugins/percentile.js b/lib/report_plugins/percentile.js index 803860d5e47..9694fd51faf 100644 --- a/lib/report_plugins/percentile.js +++ b/lib/report_plugins/percentile.js @@ -15,11 +15,17 @@ module.exports = init; percentile.html = function html(client) { var translate = client.translate; var ret = - '

' + translate('Glucose Percentile report') + '

' - + '
' - + '
' - + '
' - ; + '

' + + translate('Glucose Percentile report') + + ' (' + + '' + + ')' + + '

' + + '
' + + '
' + + '
' + ; + return ret; }; @@ -36,16 +42,23 @@ percentile.report = function report_percentile(datastorage, sorteddaystoshow, op var translate = client.translate; var ss = require('simple-statistics'); - var minutewindow = 30; //minute-window should be a divisor of 60 - + var minutewindow = 30; //minute-window should be a divisor of 60 + var data = datastorage.allstatsrecords; - + var bins = []; var filterFunc = function withinWindow(record) { var recdate = new Date(record.displayTime); return recdate.getHours() === hour && recdate.getMinutes() >= minute && recdate.getMinutes() < minute + minutewindow; }; - + + var reportPlugins = Nightscout.report_plugins; + var firstDay = reportPlugins.utils.localeDate(sorteddaystoshow[sorteddaystoshow.length - 1]); + var lastDay = reportPlugins.utils.localeDate(sorteddaystoshow[0]); + var countDays = sorteddaystoshow.length; + + $('#percentile-days').text(countDays + ' ' + translate('days total') + ', ' + firstDay + ' - ' + lastDay); + for (var hour = 0; hour < 24; hour++) { for (var minute = 0; minute < 60; minute = minute + minutewindow) { var date = new Date(); From 82f00763d65dd9d6d1726b10831d54a5e96b7d87 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Wed, 5 Feb 2020 08:16:26 +0200 Subject: [PATCH 047/104] Move app caching to a service worker (#5504) * Move app caching to a webworker * Code cleanup * Code cleanup * Make Codacy happy * More parentheses --- app.js | 16 +- views/adminindex.html | 10 +- views/clockviews/shared.html | 2 +- views/foodindex.html | 10 +- views/index.html | 1386 +++++++++++++++++----------------- views/nightscout.appcache | 37 - views/profileindex.html | 10 +- views/reportindex.html | 16 +- views/service-worker.js | 107 +++ 9 files changed, 844 insertions(+), 750 deletions(-) delete mode 100644 views/nightscout.appcache create mode 100644 views/service-worker.js diff --git a/app.js b/app.js index b718e3772e9..16ee906007c 100644 --- a/app.js +++ b/app.js @@ -7,6 +7,7 @@ const bodyParser = require('body-parser'); const path = require('path'); const fs = require('fs'); +const ejs = require('ejs'); function create (env, ctx) { var app = express(); @@ -92,6 +93,15 @@ function create (env, ctx) { } app.locals.cachebuster = cacheBuster; + app.get("/sw.js", (req, res) => { + res.setHeader('Content-Type', 'application/javascript'); + res.send(ejs.render(fs.readFileSync( + require.resolve(`${__dirname}/views/service-worker.js`), + { encoding: 'utf-8' }), + { locals: app.locals} + )); + }); + if (ctx.bootErrors && ctx.bootErrors.length > 0) { app.get('*', require('./lib/server/booterror')(ctx)); return app; @@ -178,12 +188,6 @@ function create (env, ctx) { app.use("/clock", clockviews); - app.get("/appcache/*", (req, res) => { - res.render("nightscout.appcache", { - locals: app.locals - }); - }); - app.use('/api', bodyParser({ limit: 1048576 * 50 }), apiRoot); diff --git a/views/adminindex.html b/views/adminindex.html index c9bb631cfeb..9b4ff750d2e 100644 --- a/views/adminindex.html +++ b/views/adminindex.html @@ -26,10 +26,10 @@ - + - + <% include preloadCSS %> @@ -39,8 +39,8 @@ <%- include('partials/authentication-status') %> - - - + + + diff --git a/views/clockviews/shared.html b/views/clockviews/shared.html index cb27b137dfb..beac7dc0f2e 100644 --- a/views/clockviews/shared.html +++ b/views/clockviews/shared.html @@ -40,7 +40,7 @@
- + - - + + + diff --git a/views/index.html b/views/index.html index 153e187b7f7..156aa331325 100644 --- a/views/index.html +++ b/views/index.html @@ -1,696 +1,716 @@ - - manifest="appcache/nightscout-<%= locals.cachebuster %>.appcache" - <% } %>> - - - - - - - Nightscout - - - - - - - - - - - - - - - - - - - - - - - - - -<% include preloadCSS %> - - -
-
-

-

Loading the client

-
-
-
-
-
-
-
-
- <%- include('partials/toolbar') %> - -
- -
-
-
- - -
-
-
- -
-
-
---
-
-
-
-
-
- -
    -
  • Hours:
  • -
  • 2
  • -
  • 3
  • -
  • 4
  • -
  • 6
  • -
  • 12
  • -
  • 24
  • -
  • ...
  • -
-
- -
-
-
-
+ + <% include preloadCSS %> + + + +
+
+

+

Loading the client

+
+
+
+
+
+
+
+
+ <%- include('partials/toolbar') %> + +
+ +
+
+
+ - -
-
- -
- Settings -
-
Units
-
-
-
-
-
Date format
-
-
-
-
-
Language
-
- -
-
-
-
Scale
-
- -
-
-
-
Render Basal
-
- -
-
-
-
Enable Alarms
-
-
-
-
-
- - - mins -
-
- - - mins -
-
-
-
-
Night Mode
-
-
-
-
Edit Mode
-
-
-
-
Show Raw BG Data
-
-
-
-
-
-
Custom Title
-
-
-
-
Theme
-
-
-
-
-
-
Show Plugins
-
-
- - - - <%- include('partials/authentication-status') %> - -
- About -
-
version
-
head
-

-

License: AGPL
-
Copyright © 2017 Nightscout contributors
-

- -
- + +
+
+
+ +
+
+
---
+
+
- -
-
-
- Log a Treatment - - - -
- Targets - - -
- -
- Glucose Reading - - - - -
-
- - - -
- - - - - - - - - - -
- - - -
- Event Time - - - - -
- - -
- +
+
+ +
    +
  • Hours:
  • +
  • 2
  • +
  • 3
  • +
  • 4
  • +
  • 6
  • +
  • 12
  • +
  • 24
  • +
  • ...
  • +
+
+ +
+
+
+
+
+ +
+
+ +
+ Settings +
+
Units
+
+
+
+
+
Date format
+
+
+
+
+
Language
+
+ +
+
+
+
Scale
+
+ +
+
+
+
Render Basal
+
+ +
+
+
+
Enable Alarms
+
+
+
+
+
+ + + mins +
+
+ + + mins +
+
+
+
+
Night Mode
+
+
+
+
Edit Mode
+
+
+
+
Show Raw BG Data
+
+
+
+
+
+
Custom Title
+
+
+
+
Theme
+
+
+
+
+
+
Show Plugins
+
+
+ + + + <%- include('partials/authentication-status') %> + +
+ About +
+
version
+
head
+

+

License: AGPL
+
Copyright © 2017 Nightscout contributors
+

+ +
+ +
+ +
+
+
+ Log a Treatment + + + +
+ Targets + + +
+ +
+ Glucose Reading + + + + +
+
+ + + +
+ + + + + + + + + + +
+ + + +
+ Event Time + + + +
-
- -
- - Bolus Wizard - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - BG: - - - - -
- - - - - - - - - - 0.00 -
- Quickpick: -
- -
- - or - - Add from database - - -
-
-
- - - Carbs: - - g - - - -
- - - COB: - - g - - - -
- - - IOB: - - 0.00 -
- - Other correction: - - -
- - Rounding: - - 0.00 -
- - Calculation is in target range. -
- - - Insulin needed: - - 0.00 -
- - Carbs needed: - - - -
- - Basal rate: - - -
-
- -
- + +
+ + + +
+
+
+ + Bolus Wizard + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + BG: + + + + +
+ + + + + + + + + + 0.00 +
+ Quickpick:
- - - - +
- - - - -
- Event Time: - - - - - - + + or + + Add from database + + +
- - - - - - -
- - -
- - +
+ + + Carbs: + + g + + + +
+ + + COB: + + g + + + +
+ + + IOB: + + 0.00 +
+ + Other correction: + + +
+ + Rounding: + + 0.00 +
+ + Calculation is in target range. +
+ + + Insulin needed: + + 0.00 +
+ + Carbs needed: + + + +
+ + Basal rate: + + +
+
+ +
+ +
+ + + + +
+ + + + +
+ Event Time: + + + + + +
+ +
+ + + + +
+ +
+ + +
+ + + + + + + + - - - - diff --git a/views/nightscout.appcache b/views/nightscout.appcache deleted file mode 100644 index 3823f894e5f..00000000000 --- a/views/nightscout.appcache +++ /dev/null @@ -1,37 +0,0 @@ -CACHE MANIFEST - -/images/launch.png -/images/apple-touch-icon-57x57.png -/images/apple-touch-icon-60x60.png -/images/apple-touch-icon-72x72.png -/images/apple-touch-icon-76x76.png -/images/apple-touch-icon-114x114.png -/images/apple-touch-icon-120x120.png -/images/apple-touch-icon-144x144.png -/images/apple-touch-icon-152x152.png -/images/apple-touch-icon-180x180.png -/images/favicon-32x32.png -/images/android-chrome-192x192.png -/images/favicon-96x96.png -/images/favicon-16x16.png -/manifest.json -/images/favicon.ico -/images/mstile-144x144.png -/css/ui-darkness/jquery-ui.min.css?v=<%= locals.cachebuster %> -/css/jquery.tooltips.css?v=<%= locals.cachebuster %> -/audio/alarm.mp3 -/audio/alarm2.mp3 -/css/ui-darkness/images/ui-icons_ffffff_256x240.png -/css/ui-darkness/images/ui-icons_cccccc_256x240.png -/css/ui-darkness/images/ui-bg_inset-soft_25_000000_1x100.png -/css/ui-darkness/images/ui-bg_gloss-wave_25_333333_500x100.png -/css/main.css?v=<%= locals.cachebuster %> -/bundle/js/bundle.app.js?v=<%= locals.cachebuster %> -/bundle/js/bundle.clock.js?v=<%= locals.cachebuster %> -/bundle/js/bundle.report.js?v=<%= locals.cachebuster %> -/socket.io/socket.io.js?v=<%= locals.cachebuster %> -/js/client.js?v=<%= locals.cachebuster %> -/images/logo2.png - -NETWORK: -* diff --git a/views/profileindex.html b/views/profileindex.html index 55a98f19dc4..d915d0c8cc8 100644 --- a/views/profileindex.html +++ b/views/profileindex.html @@ -25,10 +25,10 @@ - + - + <% include preloadCSS %> @@ -166,8 +166,8 @@ <%- include('partials/authentication-status') %> - - - + + + diff --git a/views/reportindex.html b/views/reportindex.html index 546e89daf75..8d02bf08f58 100644 --- a/views/reportindex.html +++ b/views/reportindex.html @@ -23,9 +23,9 @@ - - - + + + <% include preloadCSS %> @@ -122,10 +122,10 @@ <%- include('partials/authentication-status') %> - - - - - + + + + + \ No newline at end of file diff --git a/views/service-worker.js b/views/service-worker.js new file mode 100644 index 00000000000..50e720b1aba --- /dev/null +++ b/views/service-worker.js @@ -0,0 +1,107 @@ +'use strict'; + +var CACHE = '<%= locals.cachebuster %>'; + +const CACHE_LIST = [ + '/', + '/images/launch.png', + '/images/apple-touch-icon-57x57.png', + '/images/apple-touch-icon-60x60.png', + '/images/apple-touch-icon-72x72.png', + '/images/apple-touch-icon-76x76.png', + '/images/apple-touch-icon-114x114.png', + '/images/apple-touch-icon-120x120.png', + '/images/apple-touch-icon-144x144.png', + '/images/apple-touch-icon-152x152.png', + '/images/apple-touch-icon-180x180.png', + '/images/favicon-32x32.png', + '/images/android-chrome-192x192.png', + '/images/favicon-96x96.png', + '/images/favicon-16x16.png', + '/manifest.json', + '/images/favicon.ico', + '/images/mstile-144x144.png', + '/css/ui-darkness/jquery-ui.min.css', + '/css/jquery.tooltips.css', + '/audio/alarm.mp3', + '/audio/alarm2.mp3', + '/css/ui-darkness/images/ui-icons_ffffff_256x240.png', + '/css/ui-darkness/images/ui-icons_cccccc_256x240.png', + '/css/ui-darkness/images/ui-bg_inset-soft_25_000000_1x100.png', + '/css/ui-darkness/images/ui-bg_gloss-wave_25_333333_500x100.png', + '/css/main.css', + '/bundle/js/bundle.app.js', + '/bundle/js/bundle.clock.js', + '/bundle/js/bundle.report.js', + '/socket.io/socket.io.js', + '/js/client.js', + '/images/logo2.png' +]; + +// Open a cache and use `addAll()` with an array of assets to add all of them +// to the cache. Return a promise resolving when all the assets are added. +function precache() { + return caches.open(CACHE).then(function (cache) { + return cache.addAll(CACHE_LIST); + }); +} + +// Open the cache where the assets were stored and search for the requested +// resource. Notice that in case of no matching, the promise still resolves +// but it does with `undefined` as value. +function fromCache(request) { + return caches.open(CACHE).then(function (cache) { + return cache.match(request).then(function (matching) { + return matching || Promise.reject('no-match'); + }); + }); +} + +// Update consists in opening the cache, performing a network request and +// storing the new response data. +function update(request) { + return caches.open(CACHE).then(function (cache) { + return fetch(request).then(function (response) { + return cache.put(request, response); + }); + }); +} + +// On install, cache some resources. +self.addEventListener('install', function(evt) { + //console.log('The service worker is being installed.'); + evt.waitUntil(precache()); +}); + +function inCache(request) { + let found = false; + CACHE_LIST.forEach( function (e) { + if (request.url.endsWith(e)) { + found = true; + } + }); + return found; +} + +self.addEventListener('fetch', function(evt) { + if (!evt.request.url.startsWith(self.location.origin) || CACHE === 'developmentMode' || !inCache(evt.request) || evt.request.method !== 'GET') { + //console.log('Skipping cache for ', evt.request.url); + return void evt.respondWith(fetch(evt.request)); + } + //console.log('Returning cached for ', evt.request.url); + evt.respondWith(fromCache(evt.request)); + evt.waitUntil(update(evt.request)); +}); + +self.addEventListener('activate', (event) => { + event.waitUntil( + caches.keys().then((cacheNames) => { + return cacheNames.filter((cacheName) => CACHE !== cacheName); + }).then((unusedCaches) => { + //console.log('DESTROYING CACHE', unusedCaches.join(',')); + return Promise.all(unusedCaches.map((unusedCache) => { + return caches.delete(unusedCache); + })); + }).then(() => self.clients.claim()) + ); +}); \ No newline at end of file From b1ec21cd3fa48f2fc558f09611013c790472d7ff Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Tue, 4 Feb 2020 22:27:56 -0800 Subject: [PATCH 048/104] Added indexes to 'entries' and 'treatments' along with other updates (#5463) * Added compound indexes for treatments and entries collections. Updated ensureIndex to createIndex in mongo-strage.js as ensureIndex has been deprecated. Finally, updated testing/populate.js to be compatible with more recent versions of the node driver, as well as fixing a path issue. * Fixed missing end quote in lib/server/treatments.js. Changed all newly added double quotes to single quote to match style guide. * Removed indexes that referenced key600. --- lib/server/entries.js | 11 +++++++++-- lib/server/treatments.js | 1 + lib/storage/mongo-storage.js | 6 +++--- testing/populate.js | 7 +++++-- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/lib/server/entries.js b/lib/server/entries.js index d7258f13b79..02dd115bb95 100644 --- a/lib/server/entries.js +++ b/lib/server/entries.js @@ -140,7 +140,15 @@ function storage(env, ctx) { api.query_for = query_for; api.getEntry = getEntry; api.aggregate = require('./aggregate')({ }, api); - api.indexedFields = [ 'date', 'type', 'sgv', 'mbg', 'sysTime', 'dateString' ]; + api.indexedFields = [ + 'date' + , 'type' + , 'sgv' + , 'mbg' + , 'sysTime' + , 'dateString' + , { 'type' : 1, 'date' : -1, 'dateString' : 1 } + ]; return api; } @@ -160,4 +168,3 @@ storage.queryOpts = { // expose module storage.storage = storage; module.exports = storage; - diff --git a/lib/server/treatments.js b/lib/server/treatments.js index 580e5cf0c45..3edd00a155e 100644 --- a/lib/server/treatments.js +++ b/lib/server/treatments.js @@ -133,6 +133,7 @@ function storage (env, ctx) { , 'percent' , 'absolute' , 'duration' + , { 'eventType' : 1, 'duration' : 1, 'created_at' : 1 } ]; api.remove = remove; diff --git a/lib/storage/mongo-storage.js b/lib/storage/mongo-storage.js index 275cde6eb8a..fbbc328d0e2 100644 --- a/lib/storage/mongo-storage.js +++ b/lib/storage/mongo-storage.js @@ -37,11 +37,11 @@ function init (env, cb, forceNewConnection) { console.log('Error connecting to MongoDB: %j - retrying in ' + timeout/1000 + ' sec', err); setTimeout(connect_with_retry, timeout, i+1); } else if (err.message) { - throw new Error('MongoDB connection string '+env.storageURI+' seems invalid: '+err.message) ; + throw new Error('MongoDB connection string '+env.storageURI+' seems invalid: '+err.message) ; } } else { console.log('Successfully established a connected to MongoDB'); - + var dbName = env.storageURI.split('/').pop().split('?'); dbName=dbName[0]; // drop Connection Options mongo.db = client.db(dbName); @@ -66,7 +66,7 @@ function init (env, cb, forceNewConnection) { mongo.ensureIndexes = function ensureIndexes (collection, fields) { fields.forEach(function (field) { console.info('ensuring index for: ' + field); - collection.ensureIndex(field, function (err) { + collection.createIndex(field, { 'background': true }, function (err) { if (err) { console.error('unable to ensureIndex for: ' + field + ' - ' + err); } diff --git a/testing/populate.js b/testing/populate.js index 34c307b7759..c4d9e1422a1 100644 --- a/testing/populate.js +++ b/testing/populate.js @@ -3,19 +3,22 @@ var mongodb = require('mongodb'); var env = require('./../env')(); -var util = require('./helpers/util'); +var util = require('./util'); main(); function main() { var MongoClient = mongodb.MongoClient; - MongoClient.connect(env.storageURI, function connected(err, db) { + MongoClient.connect(env.storageURI, { "useUnifiedTopology" : true, "useNewUrlParser" : true }, function connected(err, client) { console.log('Connecting to mongo...'); if (err) { console.log('Error occurred: ', err); throw err; } + + var db = client.db(); + populate_collection(db); }); } From 765d7f8ea56e617504eb66f8da69b659b919630c Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sat, 8 Feb 2020 11:22:12 +0200 Subject: [PATCH 049/104] Fix: Round interporlated mg/dL value to an integer --- lib/data/treatmenttocurve.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/data/treatmenttocurve.js b/lib/data/treatmenttocurve.js index afa17b397ec..714fb9049e6 100644 --- a/lib/data/treatmenttocurve.js +++ b/lib/data/treatmenttocurve.js @@ -41,7 +41,7 @@ module.exports = function fitTreatmentsToBGCurve (ddata, env, ctx) { calcedBG = mgdlAfter; } - return calcedBG || 180; + return Math.round(calcedBG) || 180; } function mgdlValue (entry) { From 44ab2a79dcc509d89c94e96aef07c284fa08f57a Mon Sep 17 00:00:00 2001 From: Dominik Dzienia Date: Mon, 10 Feb 2020 07:56:38 +0100 Subject: [PATCH 050/104] Plugin to show database size (% of available space or in MiB) (#5496) * Database size plugin - pill that displays current mongoDB database size * Enabled dbsize by default * Fixed bug with dbsize not shownig when size is (rounded) 0% but real bytes > 0 * Cleanup & update to iconfont generation manual * Changed how warning/urgent levels are configured - from absolute MiB to percentage of DBSIZE_MAX --- README.md | 19 ++ assets/fonts/Nightscout Plugin Icons.json | 87 ++++++ assets/fonts/README.md | 29 ++ lib/client/receiveddata.js | 3 + lib/data/dataloader.js | 18 ++ lib/data/ddata.js | 2 + lib/language.js | 28 +- lib/plugins/dbsize.js | 163 +++++++++++ lib/plugins/index.js | 1 + lib/settings.js | 4 +- static/css/main.css | 23 +- tests/dbsize.test.js | 320 ++++++++++++++++++++++ 12 files changed, 693 insertions(+), 4 deletions(-) create mode 100644 assets/fonts/Nightscout Plugin Icons.json create mode 100644 assets/fonts/README.md create mode 100644 lib/plugins/dbsize.js create mode 100644 tests/dbsize.test.js diff --git a/README.md b/README.md index 62bd048f059..ba921dc2b98 100644 --- a/README.md +++ b/README.md @@ -525,6 +525,25 @@ For remote overrides, the following extended settings must be configured: Enabled [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) so other websites can make request to your Nightscout site, uses these extended settings: * `CORS_ALLOW_ORIGIN` (`*`) - The list of sites that are allow to make requests +##### `dbsize` (Database Size) + Show size of Nightscout Database, as a percentage of declared available space or in MiB. + + Many deployments of Nightscout use free tier of MongoDB on Heroku, which is limited in size. After some time, as volume of stored data grows, it may happen that this limit is reached and system is unable to store new data. This plugin provides pill that indicates size of Database and shows (when configured) alarms regarding reaching space limit. + + **IMPORTANT:** This plugin can only check how much space database already takes, _but cannot infer_ max size available on server for it. To have correct alarms and realistic percentage, `DBSIZE_MAX` need to be properly set - according to your mongoDB hosting configuration. + + **NOTE:** It may happen that new data cannot be saved to database (due to size limits) even when this plugin reports that storage is not 100% full. MongoDB pre-allocate data in advance, and database file is always made bigger than total real data size. To avoid premature alarms, this plugin refers to data size instead of file size, but sets warning thresholds low. It may happen, that file size of database will take 100% of allowed space but there will be plenty of place inside to store the data. But it may also happen, with file size is at its max, that data size will be ~70% of file size, and there will be no place left. That may happen because data inside file is fragmented and free space _holes_ are too small for new entries. In such case calling `db.repairDatabase()` on mongoDB is recommended to compact and repack data (currently only doable with third-party mongoDB tools, but action is planned to be added in _Admin Tools_ section in future releases). + + All sizes are expressed as integers, in _Mebibytes_ `1 MiB == 1024 KiB == 1024*1024 B` + + * `DBSIZE_MAX` (`496`) - Maximal allowed size of database on your mongoDB server, in MiB. You need to adjust that value to match your database hosting limits - default value is for standard Heroku mongoDB free tier. + * `DBSIZE_WARN_PERCENTAGE` (`60`) - Threshold to show first warning about database size. When database reach this percentage of `DBSIZE_MAX` size - pill will show size in yellow. + * `DBSIZE_URGENT_PERCENTAGE` (`75`) - Threshold to show urgent warning about database size. When database reach this percentage of `DBSIZE_MAX` size, it is urgent to do backup and clean up old data. At this percentage info pill turns red. + * `DBSIZE_ENABLE_ALERTS` (`false`) - Set to `true` to enable notifications about database size. + * `DBSIZE_IN_MIB` (`false`) - Set to `true` to display size of database in MiB-s instead of default percentage. + + This plugin should be enabled by default, if needed can be diasabled by adding `dbsize` to the list of disabled plugins, for example: `DISABLE="dbsize"`. + #### Extended Settings Some plugins support additional configuration using extra environment variables. These are prefixed with the name of the plugin and a `_`. For example setting `MYPLUGIN_EXAMPLE_VALUE=1234` would make `extendedSettings.exampleValue` available to the `MYPLUGIN` plugin. diff --git a/assets/fonts/Nightscout Plugin Icons.json b/assets/fonts/Nightscout Plugin Icons.json new file mode 100644 index 00000000000..65874c15679 --- /dev/null +++ b/assets/fonts/Nightscout Plugin Icons.json @@ -0,0 +1,87 @@ +{ + "metadata": { + "name": "Nightscout Plugin Icons", + "lastOpened": 0, + "created": 1580075608590 + }, + "iconSets": [ + { + "selection": [ + { + "order": 2, + "id": 0, + "name": "database", + "prevSize": 32, + "code": 59649, + "tempChar": "" + } + ], + "id": 2, + "metadata": { + "name": "Plugin Icons", + "importSize": { + "width": 16, + "height": 18 + } + }, + "height": 1024, + "prevSize": 32, + "icons": [ + { + "id": 0, + "paths": [ + "M455.111 0c-251.449 0-455.111 101.831-455.111 227.556s203.662 227.556 455.111 227.556 455.111-101.831 455.111-227.556-203.662-227.556-455.111-227.556zM0 341.333v170.667c0 125.724 203.662 227.556 455.111 227.556s455.111-101.831 455.111-227.556v-170.667c0 125.724-203.662 227.556-455.111 227.556s-455.111-101.831-455.111-227.556zM0 625.778v170.667c0 125.724 203.662 227.556 455.111 227.556s455.111-101.831 455.111-227.556v-170.667c0 125.724-203.662 227.556-455.111 227.556s-455.111-101.831-455.111-227.556z" + ], + "attrs": [ + {} + ], + "width": 910, + "isMulticolor": false, + "isMulticolor2": false, + "grid": 0, + "tags": [ + "plugins" + ] + } + ], + "invisible": false, + "colorThemes": [] + } + ], + "preferences": { + "showGlyphs": true, + "showQuickUse": true, + "showQuickUse2": true, + "showSVGs": true, + "fontPref": { + "prefix": "plugicon-", + "metadata": { + "fontFamily": "pluginicons", + "majorVersion": 1, + "minorVersion": 0 + }, + "metrics": { + "emSize": 1024, + "baseline": 6.25, + "whitespace": 50 + }, + "embed": false, + "showSelector": false, + "showMetrics": false, + "showMetadata": false, + "showVersion": false + }, + "imagePref": { + "prefix": "icon-", + "png": true, + "useClassSelector": true, + "color": 0, + "bgColor": 16777215, + "classSelector": ".icon" + }, + "historySize": 50, + "showCodes": true, + "gridSize": 16 + }, + "uid": -1 +} \ No newline at end of file diff --git a/assets/fonts/README.md b/assets/fonts/README.md new file mode 100644 index 00000000000..b6b98ea4322 --- /dev/null +++ b/assets/fonts/README.md @@ -0,0 +1,29 @@ +How to upgrade icons in icon-fonts on Nightscout +================================================ + +This guide is fol developers regarding how to add new icon to Nightscout. + +Nightscout use icon fonts to render icons. Each icon is glyph (like - letter, or more like emoji character) inside custom made font file. +That way we have nice, vector icons, that are small, scalable, looks good on each platform, and are easy to embed inside CSS. + +To extend existing icon set.: + +1. Prepare minimalist, black & white icon in SVG tool of choice, and optimize it (you can use Inkscape) to be small in size and render good at small sizes. +2. Use https://icomoon.io/app and import accompanied JSON project file (`Nightscout Plugin Icons.json`) +3. Add SVG as new glyph. Remember to take care to set proper character code and CSS name +4. Save new version of JSON project file and store in this folder +5. Generate font, download zip file and unpack it to get `fonts/pluginicons.svg` and `fonts/pluginicons.woff` +6. Update `statc/css/main.css` file + * In section of `@font-face` with `font-family: 'pluginicons'` + * update part after `data:application/font-woff;charset=utf-8;base64,` with Base64-encoded content of just generated `pluginicons.woff` font + * update part after `data:application/font-svg;charset=utf-8;base64,` with Base64-encoded content of just generated `pluginicons.svg` font + * copy/update all entries `.plugicon-****:before { content: "****"; }` from generated font `style.css` into `statc/css/main.css` +7. Do not forget to update `Nightscout Plugin Icons.json` in this repo (´download updated project from icomoon.io) + +Hints +----- + +* You can find many useful online tools to encode file into Base64, like: https://base64.guru/converter/encode/file +* Do not split Base64 output - it should be one LONG line +* Since update process is **manual** and generated fonts & updated CSS sections are **binary** - try to avoid **git merge conflicts** by speaking with other developers if you plan to add new icon +* When in doubt - check `git log` and reach last contributor for guidelines :) diff --git a/lib/client/receiveddata.js b/lib/client/receiveddata.js index e0adf13fb3b..c167d9463a3 100644 --- a/lib/client/receiveddata.js +++ b/lib/client/receiveddata.js @@ -128,6 +128,9 @@ function receiveDData (received, ddata, settings) { } } + if (received.dbstats && received.dbstats.fileSize) { + ddata.dbstats = received.dbstats; + } } //expose for tests diff --git a/lib/data/dataloader.js b/lib/data/dataloader.js index 1c00f998d73..b0eafdd4fe0 100644 --- a/lib/data/dataloader.js +++ b/lib/data/dataloader.js @@ -69,6 +69,7 @@ function init(env, ctx) { // clear treatments, we're going to merge from more queries ddata.treatments = []; + ddata.dbstats = {}; async.parallel([ loadEntries.bind(null, ddata, ctx) @@ -79,6 +80,7 @@ function init(env, ctx) { , loadFood.bind(null, ddata, ctx) , loadDeviceStatus.bind(null, ddata, env, ctx) , loadActivity.bind(null, ddata, ctx) + , loadDatabaseStats.bind(null, ddata, ctx) ], loadComplete); }; @@ -399,5 +401,21 @@ function loadDeviceStatus(ddata, env, ctx, callback) { }); } +function loadDatabaseStats(ddata, ctx, callback) { + ctx.store.db.stats(function mongoDone (err, result) { + if (err) { + console.log("Problem loading database stats"); + } + if (!err && result) { + ddata.dbstats = { + dataSize: result.dataSize + , indexSize: result.indexSize + , fileSize: result.fileSize + }; + } + callback(); + }); +} + module.exports = init; diff --git a/lib/data/ddata.js b/lib/data/ddata.js index 9fa470e3f16..b5226124ccf 100644 --- a/lib/data/ddata.js +++ b/lib/data/ddata.js @@ -17,6 +17,7 @@ function init () { , devicestatus: [] , food: [] , activity: [] + , dbstats: {} , lastUpdated: 0 }; @@ -51,6 +52,7 @@ function init () { results.mbgs = ddata.mbgs; results.food = ddata.food; results.treatments = ddata.treatments; + results.dbstats = ddata.dbstats; return results; diff --git a/lib/language.js b/lib/language.js index c105c5239d7..ede0a734add 100644 --- a/lib/language.js +++ b/lib/language.js @@ -15100,7 +15100,33 @@ function init() { , pl: 'Tłuszcz ogółem' ,ru: 'Всего жиров' ,he: 'כל שומנים' - } + }, + 'Database Size': { + pl: 'Rozmiar Bazy Danych' + }, + 'Database Size near its limits!': { + pl: 'Rozmiar bazy danych zbliża się do limitu!' + }, + 'Database size is %1 MiB out of %2 MiB. Please backup and clean up database!': { + pl: 'Baza danych zajmuje %1 MiB z dozwolonych %2 MiB. Proszę zrób kopię zapasową i oczyść bazę danych!' + }, + 'Database file size': { + pl: 'Rozmiar pliku bazy danych' + }, + '%1 MiB of %2 MiB (%3%)': { + pl: '%1 MiB z %2 MiB (%3%)' + }, + 'Data size': { + pl: 'Rozmiar danych' + }, + 'virtAsstDatabaseSize': { + en: '%1 MiB that is %2% of available database space' + ,pl: '%1 MiB co stanowi %2% przestrzeni dostępnej dla bazy danych' + }, + 'virtAsstTitleDatabaseSize': { + en: 'Database file size' + ,pl: 'Rozmiar pliku bazy danych' + } }; language.translations = translations; diff --git a/lib/plugins/dbsize.js b/lib/plugins/dbsize.js new file mode 100644 index 00000000000..b2b85b83e7a --- /dev/null +++ b/lib/plugins/dbsize.js @@ -0,0 +1,163 @@ +'use strict'; + +var _ = require('lodash'); +var levels = require('../levels'); + +function init (ctx) { + var translate = ctx.language.translate; + + var dbsize = { + name: 'dbsize' + , label: translate('Database Size') + , pluginType: 'pill-status' + , pillFlip: true + }; + + dbsize.getPrefs = function getPrefs (sbx) { + return { + warnPercentage: sbx.extendedSettings.warnPercentage ? sbx.extendedSettings.warnPercentage : 60 + , urgentPercentage: sbx.extendedSettings.urgentPercentage ? sbx.extendedSettings.urgentPercentage : 75 + , max: sbx.extendedSettings.max ? sbx.extendedSettings.max : 496 + , enableAlerts: sbx.extendedSettings.enableAlerts + , inMib: sbx.extendedSettings.inMib + }; + }; + + dbsize.setProperties = function setProperties (sbx) { + sbx.offerProperty('dbsize', function setDbsize () { + return dbsize.analyzeData(sbx); + }); + }; + + dbsize.analyzeData = function analyzeData (sbx) { + + var prefs = dbsize.getPrefs(sbx); + + var recentData = sbx.data.dbstats; + + var result = { + level: undefined + , display: prefs.inMib ? '?MiB' : '?%' + , status: undefined + }; + + var maxSize = (prefs.max > 0) ? prefs.max : 100 * 1024; + var currentSize = (recentData && recentData.fileSize ? recentData.fileSize : 0) / (1024 * 1024); + var totalDataSize = (recentData && recentData.dataSize) ? recentData.dataSize : 0; + totalDataSize += (recentData && recentData.indexSize) ? recentData.indexSize : 0; + totalDataSize /= 1024 * 1024; + + var sizePercentage = Math.floor((currentSize * 100.0) / maxSize); + var dataPercentage = Math.floor((totalDataSize * 100.0) / maxSize); + + result.totalDataSize = totalDataSize; + result.dataPercentage = dataPercentage; + result.notificationLevel = levels.INFO; + result.details = { + fileSize: parseFloat(currentSize.toFixed(2)) + , maxSize: parseFloat(maxSize.toFixed(2)) + , dataSize: parseFloat(totalDataSize.toFixed(2)) + , sizePercentage: sizePercentage + }; + + // failsafe to have percentage in 0..100 range + var boundWarnPercentage = Math.max(0, Math.min(100, parseInt(prefs.warnPercentage))); + var boundUrgentPercentage = Math.max(0, Math.min(100, parseInt(prefs.urgentPercentage))); + + var warnSize = Math.floor((boundWarnPercentage/100) * maxSize); + var urgentSize = Math.floor((boundUrgentPercentage/100) * maxSize); + + if ((totalDataSize >= urgentSize)&&(boundUrgentPercentage > 0)) { + result.notificationLevel = levels.URGENT; + } else if ((totalDataSize >= warnSize)&&(boundWarnPercentage > 0)) { + result.notificationLevel = levels.WARN; + } + + result.display = prefs.inMib ? parseFloat(totalDataSize.toFixed(0)) + 'MiB' : dataPercentage + '%'; + result.status = levels.toStatusClass(result.notificationLevel); + + return result; + }; + + dbsize.checkNotifications = function checkNotifications (sbx) { + var prefs = dbsize.getPrefs(sbx); + + if (!prefs.enableAlerts) { return; } + + var prop = sbx.properties.dbsize; + + if (prop.dataPercentage && prop.notificationLevel && prop.notificationLevel >= levels.WARN) { + sbx.notifications.requestNotify({ + level: prop.notificationLevel + , title: levels.toDisplay(prop.notificationLevel) + ' ' + translate('Database Size near its limits!') + , message: translate('Database size is %1 MiB out of %2 MiB. Please backup and clean up database!', { + params: [prop.details.dataSize, prop.details.maxSize] + }) + , pushoverSound: 'echo' + , group: 'Database Size' + , plugin: dbsize + , debug: prop + }); + } + }; + + dbsize.updateVisualisation = function updateVisualisation (sbx) { + var prop = sbx.properties.dbsize; + + var infos = [{ + label: translate('Data size') + , value: translate('%1 MiB of %2 MiB (%3%)', { + params: [prop.details.dataSize, prop.details.maxSize, prop.dataPercentage] + }) + } + , { + label: translate('Database file size') + , value: translate('%1 MiB of %2 MiB (%3%)', { + params: [prop.details.fileSize, prop.details.maxSize, prop.details.sizePercentage] + }) + } + ]; + + sbx.pluginBase.updatePillText(dbsize, { + value: prop && prop.display + , labelClass: 'plugicon-database' + , pillClass: prop && prop.status + , info: infos + , hide: !(prop && prop.totalDataSize && prop.totalDataSize >= 0) + }); + }; + + function virtAsstDatabaseSizeHandler (next, slots, sbx) { + if (sbx.properties.dbsize.display) { + var response = translate('virtAsstDatabaseSize', { + params: [ + sbx.properties.dbsize.details.dataSize + , sbx.properties.dbsize.dataPercentage + ] + }); + next(translate('virtAsstTitleDatabaseSize'), response); + } else { + next(translate('virtAsstTitleDatabaseSize'), translate('virtAsstUnknown')); + } + } + + dbsize.virtAsst = { + intentHandlers: [ + { + // for backwards compatibility + intent: 'DatabaseSize' + , intentHandler: virtAsstDatabaseSizeHandler + } + , { + intent: 'MetricNow' + , metrics: ['database size', 'file size', 'db size', 'data size'] + , intentHandler: virtAsstDatabaseSizeHandler + } + ] + }; + + return dbsize; + +} + +module.exports = init; diff --git a/lib/plugins/index.js b/lib/plugins/index.js index 2568b634afd..5a6cb2822da 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -49,6 +49,7 @@ function init (ctx) { , require('./boluscalc')(ctx) // fake plugin to show/hide , require('./profile')(ctx) // fake plugin to hold extended settings , require('./speech')(ctx) + , require('./dbsize')(ctx) ]; var serverDefaultPlugins = [ diff --git a/lib/settings.js b/lib/settings.js index d1f18b0e9c5..8ac1c5ed768 100644 --- a/lib/settings.js +++ b/lib/settings.js @@ -119,7 +119,7 @@ function init () { } //TODO: getting sent in status.json, shouldn't be - settings.DEFAULT_FEATURES = ['bgnow', 'delta', 'direction', 'timeago', 'devicestatus', 'upbat', 'errorcodes', 'profile']; + settings.DEFAULT_FEATURES = ['bgnow', 'delta', 'direction', 'timeago', 'devicestatus', 'upbat', 'errorcodes', 'profile', 'dbsize']; var wasSet = []; @@ -260,7 +260,7 @@ function init () { function adjustShownPlugins () { var showPluginsUnset = settings.showPlugins && 0 === settings.showPlugins.length; - settings.showPlugins += ' delta direction upbat'; + settings.showPlugins += ' delta direction upbat dbsize'; if (settings.showRawbg === 'always' || settings.showRawbg === 'noise') { settings.showPlugins += ' rawbg'; } diff --git a/static/css/main.css b/static/css/main.css index 8e6882dde9d..25c6bae0f0e 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -9,7 +9,21 @@ font-style: normal; } - [class^="icon-"]:before, [class*=" icon-"]:before { +/* + Icon font for additional plugin icons. + Please read assets/fonts/README.md about update process +*/ +@font-face { + font-family: 'pluginicons'; + /* Plugin Icons font files content (from WOFF and SVG icon files, base64 encoded) */ + src: url(data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAAWAAAsAAAAABTQAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABPUy8yAAABCAAAAGAAAABgDxIE8mNtYXAAAAFoAAAAVAAAAFQXVdKJZ2FzcAAAAbwAAAAIAAAACAAAABBnbHlmAAABxAAAAUgAAAFIFA4eR2hlYWQAAAMMAAAANgAAADYXVLrVaGhlYQAAA0QAAAAkAAAAJAdQA8ZobXR4AAADaAAAABQAAAAUCY4AAGxvY2EAAAN8AAAADAAAAAwAKAC4bWF4cAAAA4gAAAAgAAAAIAAJAFxuYW1lAAADqAAAAbYAAAG2DBt7mXBvc3QAAAVgAAAAIAAAACAAAwAAAAMCxwGQAAUAAAKZAswAAACPApkCzAAAAesAMwEJAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAQAAA6QEDwP/AAEADwABAAAAAAQAAAAAAAAAAAAAAIAAAAAAAAwAAAAMAAAAcAAEAAwAAABwAAwABAAAAHAAEADgAAAAKAAgAAgACAAEAIOkB//3//wAAAAAAIOkB//3//wAB/+MXAwADAAEAAAAAAAAAAAAAAAEAAf//AA8AAQAAAAAAAAAAAAIAADc5AQAAAAABAAAAAAAAAAAAAgAANzkBAAAAAAEAAAAAAAAAAAACAAA3OQEAAAAAAwAA/8ADjgPAABsAOgBZAAABIgcOAQcGFRQXHgEXFjMyNz4BNzY1NCcuAScmARUUFx4BFxYzMjc+ATc2PQEUBw4BBwYjIicuAScmNREVFBceARcWMzI3PgE3Nj0BFAcOAQcGIyInLgEnJjUBx15TU3skJCQke1NTXl5TU3wjJCQjfFNT/dskJHtTU15eU1N8IyQkI3xTU15eU1N7JCQkJHtTU15eU1N8IyQkI3xTU15eU1N7JCQDwBISPikpMC8pKj0SEhISPSopLzApKT4SEv6rqy8qKT4SEhISPikqL6svKik+EhISEj4pKi/+46owKSk+EhISEj4pKTCqLykqPhESEhE+KikvAAAAAAEAAAABAABgRbaTXw889QALBAAAAAAA2lO7LAAAAADaU7ssAAD/wAOOA8AAAAAIAAIAAAAAAAAAAQAAA8D/wAAABAAAAAAAA44AAQAAAAAAAAAAAAAAAAAAAAUEAAAAAAAAAAAAAAACAAAAA44AAAAAAAAACgAUAB4ApAABAAAABQBaAAMAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAADgCuAAEAAAAAAAEACwAAAAEAAAAAAAIABwCEAAEAAAAAAAMACwBCAAEAAAAAAAQACwCZAAEAAAAAAAUACwAhAAEAAAAAAAYACwBjAAEAAAAAAAoAGgC6AAMAAQQJAAEAFgALAAMAAQQJAAIADgCLAAMAAQQJAAMAFgBNAAMAAQQJAAQAFgCkAAMAAQQJAAUAFgAsAAMAAQQJAAYAFgBuAAMAAQQJAAoANADUcGx1Z2luaWNvbnMAcABsAHUAZwBpAG4AaQBjAG8AbgBzVmVyc2lvbiAxLjAAVgBlAHIAcwBpAG8AbgAgADEALgAwcGx1Z2luaWNvbnMAcABsAHUAZwBpAG4AaQBjAG8AbgBzcGx1Z2luaWNvbnMAcABsAHUAZwBpAG4AaQBjAG8AbgBzUmVndWxhcgBSAGUAZwB1AGwAYQBycGx1Z2luaWNvbnMAcABsAHUAZwBpAG4AaQBjAG8AbgBzRm9udCBnZW5lcmF0ZWQgYnkgSWNvTW9vbi4ARgBvAG4AdAAgAGcAZQBuAGUAcgBhAHQAZQBkACAAYgB5ACAASQBjAG8ATQBvAG8AbgAuAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==) format('woff'), + url(data:application/font-svg;charset=utf-8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pg0KPCFET0NUWVBFIHN2ZyBQVUJMSUMgIi0vL1czQy8vRFREIFNWRyAxLjEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkIiA+DQo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+DQo8bWV0YWRhdGE+R2VuZXJhdGVkIGJ5IEljb01vb248L21ldGFkYXRhPg0KPGRlZnM+DQo8Zm9udCBpZD0icGx1Z2luaWNvbnMiIGhvcml6LWFkdi14PSIxMDI0Ij4NCjxmb250LWZhY2UgdW5pdHMtcGVyLWVtPSIxMDI0IiBhc2NlbnQ9Ijk2MCIgZGVzY2VudD0iLTY0IiAvPg0KPG1pc3NpbmctZ2x5cGggaG9yaXotYWR2LXg9IjEwMjQiIC8+DQo8Z2x5cGggdW5pY29kZT0iJiN4MjA7IiBob3Jpei1hZHYteD0iNTEyIiBkPSIiIC8+DQo8Z2x5cGggdW5pY29kZT0iJiN4ZTkwMTsiIGdseXBoLW5hbWU9ImRhdGFiYXNlIiBob3Jpei1hZHYteD0iOTEwIiBkPSJNNDU1LjExMSA5NjBjLTI1MS40NDkgMC00NTUuMTExLTEwMS44MzEtNDU1LjExMS0yMjcuNTU2czIwMy42NjItMjI3LjU1NiA0NTUuMTExLTIyNy41NTYgNDU1LjExMSAxMDEuODMxIDQ1NS4xMTEgMjI3LjU1Ni0yMDMuNjYyIDIyNy41NTYtNDU1LjExMSAyMjcuNTU2ek0wIDYxOC42Njd2LTE3MC42NjdjMC0xMjUuNzI0IDIwMy42NjItMjI3LjU1NiA0NTUuMTExLTIyNy41NTZzNDU1LjExMSAxMDEuODMxIDQ1NS4xMTEgMjI3LjU1NnYxNzAuNjY3YzAtMTI1LjcyNC0yMDMuNjYyLTIyNy41NTYtNDU1LjExMS0yMjcuNTU2cy00NTUuMTExIDEwMS44MzEtNDU1LjExMSAyMjcuNTU2ek0wIDMzNC4yMjJ2LTE3MC42NjdjMC0xMjUuNzI0IDIwMy42NjItMjI3LjU1NiA0NTUuMTExLTIyNy41NTZzNDU1LjExMSAxMDEuODMxIDQ1NS4xMTEgMjI3LjU1NnYxNzAuNjY3YzAtMTI1LjcyNC0yMDMuNjYyLTIyNy41NTYtNDU1LjExMS0yMjcuNTU2cy00NTUuMTExIDEwMS44MzEtNDU1LjExMSAyMjcuNTU2eiIgLz4NCjwvZm9udD48L2RlZnM+PC9zdmc+) format('svg'); + font-weight: normal; + font-style: normal; +} + + [class^="icon-"]:before, [class*=" icon-"]:before, + [class^="plugicon-"]:before, [class*=" plugicon-"]:before { font-family: "nsicons"; font-style: normal; font-weight: normal; @@ -43,6 +57,10 @@ /* Uncomment for 3D effect */ /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */ } + +[class^="plugicon-"]:before, [class*=" plugicon-"]:before { + font-family: "pluginicons"; +} .icon-volume:before { content: '\e800'; } .icon-plus:before { content: '\e801'; } @@ -65,6 +83,9 @@ .icon-chart-line:before { content: '\f201'; } .icon-hourglass:before { content: '\f254'; } +/* Plugin Icons id-s (copy from generated icon style.css) */ +.plugicon-database:before { content: "\e901"; } + html, body { margin: 0; padding: 0; diff --git a/tests/dbsize.test.js b/tests/dbsize.test.js new file mode 100644 index 00000000000..0a66bb3ad6d --- /dev/null +++ b/tests/dbsize.test.js @@ -0,0 +1,320 @@ +'use strict'; + +require('should'); + +describe('Database Size', function() { + + var dataInRange = { dbstats: { dataSize: 1024 * 1024 * 137, indexSize: 1024 * 1024 * 48, fileSize: 1024 * 1024 * 256 } }; + var dataWarn = { dbstats: { dataSize: 1024 * 1024 * 250, indexSize: 1024 * 1024 * 100, fileSize: 1024 * 1024 * 360 } }; + var dataUrgent = { dbstats: { dataSize: 1024 * 1024 * 300, indexSize: 1024 * 1024 * 150, fileSize: 1024 * 1024 * 496 } }; + + var env = require('../env')(); + + it('display database size in range', function(done) { + var sandbox = require('../lib/sandbox')(); + var ctx = { + settings: {} + , language: require('../lib/language')() + }; + ctx.language.set('en'); + ctx.levels = require('../lib/levels'); + + var sbx = sandbox.clientInit(ctx, Date.now(), dataInRange); + + sbx.offerProperty = function mockedOfferProperty (name, setter) { + name.should.equal('dbsize'); + var result = setter(); + result.display.should.equal('37%'); + result.status.should.equal('current'); + done(); + }; + + var dbsize = require('../lib/plugins/dbsize')(ctx); + dbsize.setProperties(sbx); + + }); + + // ~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~. + + it('display database size warning', function(done) { + var sandbox = require('../lib/sandbox')(); + var ctx = { + settings: {} + , language: require('../lib/language')() + }; + ctx.language.set('en'); + ctx.levels = require('../lib/levels'); + + var sbx = sandbox.clientInit(ctx, Date.now(), dataWarn); + + sbx.offerProperty = function mockedOfferProperty (name, setter) { + name.should.equal('dbsize'); + var result = setter(); + result.display.should.equal('70%'); + result.status.should.equal('warn'); + done(); + }; + + var dbsize = require('../lib/plugins/dbsize')(ctx); + + dbsize.setProperties(sbx); + + }); + + // ~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~. + + it('display database size urgent', function(done) { + var sandbox = require('../lib/sandbox')(); + var ctx = { + settings: {} + , language: require('../lib/language')() + }; + ctx.language.set('en'); + ctx.levels = require('../lib/levels'); + + var sbx = sandbox.clientInit(ctx, Date.now(), dataUrgent); + + sbx.offerProperty = function mockedOfferProperty (name, setter) { + name.should.equal('dbsize'); + var result = setter(); + result.display.should.equal('90%'); + result.status.should.equal('urgent'); + done(); + }; + + var dbsize = require('../lib/plugins/dbsize')(ctx); + dbsize.setProperties(sbx); + + }); + + // ~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~. + + it('display database size warning notiffication', function(done) { + var sandbox = require('../lib/sandbox')(); + var ctx = { + settings: {} + , language: require('../lib/language')() + , notifications: require('../lib/notifications')(env, ctx) + }; + ctx.notifications.initRequests(); + ctx.language.set('en'); + ctx.levels = require('../lib/levels'); + + var sbx = sandbox.clientInit(ctx, Date.now(), dataWarn); + sbx.extendedSettings = { 'enableAlerts': 'TRUE' }; + + var dbsize = require('../lib/plugins/dbsize')(ctx); + + dbsize.setProperties(sbx); + dbsize.checkNotifications(sbx); + + var notif = ctx.notifications.findHighestAlarm('Database Size'); + notif.level.should.equal(ctx.levels.WARN); + notif.title.should.equal('Warning Database Size near its limits!'); + notif.message.should.equal('Database size is 350 MiB out of 496 MiB. Please backup and clean up database!'); + done(); + }); + + // ~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~. + + it('display database size urgent notiffication', function(done) { + var sandbox = require('../lib/sandbox')(); + var ctx = { + settings: {} + , language: require('../lib/language')() + , notifications: require('../lib/notifications')(env, ctx) + }; + ctx.notifications.initRequests(); + ctx.language.set('en'); + ctx.levels = require('../lib/levels'); + + var sbx = sandbox.clientInit(ctx, Date.now(), dataUrgent); + sbx.extendedSettings = { 'enableAlerts': 'TRUE' }; + + var dbsize = require('../lib/plugins/dbsize')(ctx); + + dbsize.setProperties(sbx); + dbsize.checkNotifications(sbx); + + var notif = ctx.notifications.findHighestAlarm('Database Size'); + notif.level.should.equal(ctx.levels.URGENT); + notif.title.should.equal('Urgent Database Size near its limits!'); + notif.message.should.equal('Database size is 450 MiB out of 496 MiB. Please backup and clean up database!'); + done(); + }); + + // ~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~. + + it('set a pill to the database size in percent', function(done) { + var ctx = { + settings: {} + , pluginBase: { + updatePillText: function mockedUpdatePillText (plugin, options) { + options.value.should.equal('90%'); + options.labelClass.should.equal('plugicon-database'); + options.pillClass.should.equal('urgent'); + done(); + } + } + , language: require('../lib/language')() + }; + ctx.language.set('en'); + + var sandbox = require('../lib/sandbox')(); + var sbx = sandbox.clientInit(ctx, Date.now(), dataUrgent); + var dbsize = require('../lib/plugins/dbsize')(ctx); + dbsize.setProperties(sbx); + dbsize.updateVisualisation(sbx); + + }); + + // ~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~. + + it('set a pill to the database size in MiB', function(done) { + var ctx = { + settings: { + extendedSettings: { + empty: false + , dbsize: { + inMib: true + } + } + } + , pluginBase: { + updatePillText: function mockedUpdatePillText (plugin, options) { + options.value.should.equal('450MiB'); + options.labelClass.should.equal('plugicon-database'); + options.pillClass.should.equal('urgent'); + done(); + } + } + , language: require('../lib/language')() + }; + ctx.language.set('en'); + + var sandbox = require('../lib/sandbox')(); + var sbx = sandbox.clientInit(ctx, Date.now(), dataUrgent); + var dbsize = require('../lib/plugins/dbsize')(ctx); + dbsize.setProperties(sbx.withExtendedSettings(dbsize)); + dbsize.updateVisualisation(sbx); + + }); + + // ~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~. + + it('configure warn level percentage', function(done) { + + var ctx = { + settings: { + extendedSettings: { + empty: false + , dbsize: { + warnPercentage: 30 + } + } + } + , pluginBase: { + updatePillText: function mockedUpdatePillText (plugin, options) { + options.value.should.equal('37%'); + options.pillClass.should.equal('warn'); + done(); + } + } + , language: require('../lib/language')() + }; + ctx.language.set('en'); + + var sandbox = require('../lib/sandbox')(); + var sbx = sandbox.clientInit(ctx, Date.now(), dataInRange); + var dbsize = require('../lib/plugins/dbsize')(ctx); + dbsize.setProperties(sbx.withExtendedSettings(dbsize)); + dbsize.updateVisualisation(sbx); + }); + + // ~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~. + + it('configure urgent level percentage', function(done) { + + var ctx = { + settings: { + extendedSettings: { + empty: false + , dbsize: { + warnPercentage: 30 + , urgentPercentage: 36 + } + } + } + , pluginBase: { + updatePillText: function mockedUpdatePillText (plugin, options) { + options.value.should.equal('37%'); + options.pillClass.should.equal('urgent'); + done(); + } + } + , language: require('../lib/language')() + }; + ctx.language.set('en'); + + var sandbox = require('../lib/sandbox')(); + var sbx = sandbox.clientInit(ctx, Date.now(), dataInRange); + var dbsize = require('../lib/plugins/dbsize')(ctx); + dbsize.setProperties(sbx.withExtendedSettings(dbsize)); + dbsize.updateVisualisation(sbx); + }); + + // ~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~. + + it('hide the pill if there is no info regarding database size', function(done) { + var ctx = { + settings: {} + , pluginBase: { + updatePillText: function mockedUpdatePillText (plugin, options) { + options.hide.should.equal(true); + done(); + } + } + , language: require('../lib/language')() + }; + ctx.language.set('en'); + + var sandbox = require('../lib/sandbox')(); + var sbx = sandbox.clientInit(ctx, Date.now(), {}); + var dbsize = require('../lib/plugins/dbsize')(ctx); + dbsize.setProperties(sbx); + dbsize.updateVisualisation(sbx); + }); + + // ~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~. + + it('should handle virtAsst requests', function(done) { + + var ctx = { + settings: {} + , language: require('../lib/language')() + }; + ctx.language.set('en'); + + var sandbox = require('../lib/sandbox')(); + var sbx = sandbox.clientInit(ctx, Date.now(), dataUrgent); + var dbsize = require('../lib/plugins/dbsize')(ctx); + dbsize.setProperties(sbx); + + dbsize.virtAsst.intentHandlers.length.should.equal(2); + + dbsize.virtAsst.intentHandlers[0].intentHandler(function next (title, response) { + title.should.equal('Database file size'); + response.should.equal('450 MiB that is 90% of available database space'); + + dbsize.virtAsst.intentHandlers[1].intentHandler(function next (title, response) { + title.should.equal('Database file size'); + response.should.equal('450 MiB that is 90% of available database space'); + + done(); + }, [], sbx); + + }, [], sbx); + + }); + +}); From 65b9f38f88e5d72bceeebb9da3d897723fa390c3 Mon Sep 17 00:00:00 2001 From: Petr Ondrusek <34578008+PetrOndrusek@users.noreply.github.com> Date: Tue, 11 Feb 2020 08:32:22 +0100 Subject: [PATCH 051/104] Trying to fix random fail of APIv3 tests (#5519) * APIv3: isolating documents from tests (not allowing clashes of calculated identifiers) * removing unused async keyword --- tests/api.alexa.test.js | 1 + tests/api3.create.test.js | 2 +- tests/api3.generic.workflow.test.js | 2 +- tests/api3.patch.test.js | 2 +- tests/api3.read.test.js | 2 +- tests/api3.update.test.js | 1 + 6 files changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/api.alexa.test.js b/tests/api.alexa.test.js index 8c2f5916cf6..440050eb2cc 100644 --- a/tests/api.alexa.test.js +++ b/tests/api.alexa.test.js @@ -7,6 +7,7 @@ const bodyParser = require('body-parser'); require('should'); describe('Alexa REST api', function ( ) { + this.timeout(10000); const apiRoot = require('../lib/api/root'); const api = require('../lib/api/'); before(function (done) { diff --git a/tests/api3.create.test.js b/tests/api3.create.test.js index ba0cce83ba3..5ea3ab1a869 100644 --- a/tests/api3.create.test.js +++ b/tests/api3.create.test.js @@ -16,7 +16,7 @@ describe('API3 CREATE', function() { self.validDoc = { date: (new Date()).getTime(), app: testConst.TEST_APP, - device: testConst.TEST_DEVICE, + device: testConst.TEST_DEVICE + ' API3 CREATE', eventType: 'Correction Bolus', insulin: 0.3 }; diff --git a/tests/api3.generic.workflow.test.js b/tests/api3.generic.workflow.test.js index f94238d6747..7cfbc53f618 100644 --- a/tests/api3.generic.workflow.test.js +++ b/tests/api3.generic.workflow.test.js @@ -19,7 +19,7 @@ describe('Generic REST API3', function() { insulin: 1, date: (new Date()).getTime(), app: testConst.TEST_APP, - device: testConst.TEST_DEVICE + device: testConst.TEST_DEVICE + ' Generic REST API3' }; self.identifier = opTools.calculateIdentifier(self.docOriginal); self.docOriginal.identifier = self.identifier; diff --git a/tests/api3.patch.test.js b/tests/api3.patch.test.js index 3967e53cf12..36dccc94bfa 100644 --- a/tests/api3.patch.test.js +++ b/tests/api3.patch.test.js @@ -15,7 +15,7 @@ describe('API3 PATCH', function() { date: (new Date()).getTime(), utcOffset: -180, app: testConst.TEST_APP, - device: testConst.TEST_DEVICE, + device: testConst.TEST_DEVICE + ' API3 PATCH', eventType: 'Correction Bolus', insulin: 0.3 }; diff --git a/tests/api3.read.test.js b/tests/api3.read.test.js index de29b26d000..d9f73ebf13a 100644 --- a/tests/api3.read.test.js +++ b/tests/api3.read.test.js @@ -15,7 +15,7 @@ describe('API3 READ', function() { self.validDoc = { date: (new Date()).getTime(), app: testConst.TEST_APP, - device: testConst.TEST_DEVICE, + device: testConst.TEST_DEVICE + ' API3 READ', uploaderBattery: 58 }; self.validDoc.identifier = opTools.calculateIdentifier(self.validDoc); diff --git a/tests/api3.update.test.js b/tests/api3.update.test.js index 35995d27236..481827b05d6 100644 --- a/tests/api3.update.test.js +++ b/tests/api3.update.test.js @@ -17,6 +17,7 @@ describe('API3 UPDATE', function() { date: (new Date()).getTime(), utcOffset: -180, app: testConst.TEST_APP, + device: testConst.TEST_DEVICE + ' API3 UPDATE', eventType: 'Correction Bolus', insulin: 0.3 }; From cd2ad5e1aea6b9d47d4fec36321b1624a8506362 Mon Sep 17 00:00:00 2001 From: stephencmorton <17858976+stephencmorton@users.noreply.github.com> Date: Sun, 16 Feb 2020 07:14:45 -0500 Subject: [PATCH 052/104] client - mobile - Axis label font is scaled on small screens (#5512) On phones or other small screens, the axis labels (especially x axis) were previously an unreadable jumble of too-close letters --- static/css/main.css | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/static/css/main.css b/static/css/main.css index 25c6bae0f0e..354a53b6bef 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -537,6 +537,11 @@ a, a:visited, a:link { } @media (max-width: 750px) { + .x.axis { + font-size: 2.5vmin !important; + } + + .bgStatus { width: 50%; padding: 0 0 20px 0; @@ -678,6 +683,10 @@ a, a:visited, a:link { #chartContainer { font-size: 14px; } + .y.axis { + font-size: 2.5vmin !important; + } + } @media (max-height: 600px) { From 77795cae5a5af3c26bdc46dec94416f92e9f894a Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 16 Feb 2020 14:15:27 +0200 Subject: [PATCH 053/104] Split view (#5518) * Adds a 2, 3, 4 and 8 way split view option * Updated description * Generate the table on demand, so any number of sites from 1 to 8 generates a sensible layout * Update readme & don't crash if a name is missing --- README.md | 6 ++++++ app.js | 6 ++++++ lib/settings.js | 16 ++++++++++++++ views/frame.html | 54 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 82 insertions(+) create mode 100644 views/frame.html diff --git a/README.md b/README.md index ba921dc2b98..4378a63708d 100644 --- a/README.md +++ b/README.md @@ -310,6 +310,12 @@ To learn more about the Nightscout API, visit https://YOUR-SITE.com/api-docs/ or * `Color` - Shows current BG and trend arrow. White text on a background that changes color to indicate current BG threshold (green = in range; blue = below range; yellow = above range; red = urgent below/above). Set `SHOW_CLOCK_DELTA` to `true` to show BG change in the last 5 minutes, set `SHOW_CLOCK_LAST_TIME` to `true` to always show BG age. * `Simple` - Shows current BG. Grey text on a black background. +### Split View + + Some users will need easy access to multiple Nightscout views at the same time. We have a special view for this case, accessed on /split path on your Nightscout URL. The view supports any number of sites between 1 to 8 way split, where the content for the screen can be loaded from multiple Nightscout instances. Note you still need to host separate instances for each Nightscout being monitored including the one that hosts the split view page - these variables only add the ability to load multiple views into one browser page. To set the URLs from which the content is loaded, set: + * `FRAME_URL_1` - URL where content is loaded, for the first view (increment the number up to 8 to get more views) + * `FRAME_NAME_1` - Name for the first split view portion of the screen (increment the number to name more views) + ### Plugins Plugins are used extend the way information is displayed, how notifications are sent, alarms are triggered, and more. diff --git a/app.js b/app.js index 16ee906007c..b3fa861f362 100644 --- a/app.js +++ b/app.js @@ -171,6 +171,11 @@ function create (env, ctx) { , title: 'Nightscout translations' , type: 'translations' } + , "/split": { + file: "frame.html" + , title: '8-user view' + , type: 'index' + } }; Object.keys(appPages).forEach(function(page) { @@ -179,6 +184,7 @@ function create (env, ctx) { locals: app.locals, title: appPages[page].title ? appPages[page].title : '', type: appPages[page].type ? appPages[page].type : '', + settings: env.settings }); }); }); diff --git a/lib/settings.js b/lib/settings.js index 8ac1c5ed768..309da22babf 100644 --- a/lib/settings.js +++ b/lib/settings.js @@ -51,6 +51,22 @@ function init () { , deNormalizeDates: false , showClockDelta: false , showClockLastTime: false + , frameUrl1: '' + , frameUrl2: '' + , frameUrl3: '' + , frameUrl4: '' + , frameUrl5: '' + , frameUrl6: '' + , frameUrl7: '' + , frameUrl8: '' + , frameName1: '' + , frameName2: '' + , frameName3: '' + , frameName4: '' + , frameName5: '' + , frameName6: '' + , frameName7: '' + , frameName8: '' }; var valueMappers = { diff --git a/views/frame.html b/views/frame.html new file mode 100644 index 00000000000..aff6ea0d6ce --- /dev/null +++ b/views/frame.html @@ -0,0 +1,54 @@ +<% + +let urlArray = []; +let nameArray = []; + +for (let i = 0; i <= 8; i++) { + let u = settings['frameUrl' + i]; + let n = settings['frameName' + i] || " "; + if (u) { + urlArray.push(u); + nameArray.push(n); + } +} + +const sitesPerRow = urlArray.length > 3 ? Math.round(urlArray.length / 2) : urlArray.length; +const rows = urlArray.length > 3 ? 2 : 1; + +%> + + Nightscout multiframe view + + + + + + <% let s = 0; + for (let r = 1; r <= rows; r++) { + %> + <% for (let sp = 0; sp < sitesPerRow; sp++) { + let pointer = sp + s; + %> + <% } %> + + + <% for (let sp = 0; sp < sitesPerRow; sp++) { + let pointer = sp + s; + %> + <% } %> + + <% s += sitesPerRow; } %> +
<%= nameArray[pointer] %>
+ + + \ No newline at end of file From 729747a2f813e314ea3823debebc68042ba58892 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 16 Feb 2020 14:30:15 +0200 Subject: [PATCH 054/104] Allow flagging specific settings to be not exposed the /properties and /status APIs (#5525) --- lib/api/properties.js | 2 ++ lib/api/status.js | 7 +++++-- lib/settings.js | 31 +++++++++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/lib/api/properties.js b/lib/api/properties.js index 981f3c31328..7e9fd88ebab 100644 --- a/lib/api/properties.js +++ b/lib/api/properties.js @@ -42,6 +42,8 @@ function create (env, ctx) { result = _pick(sbx.properties, selected); } + result = env.settings.filteredSettings(result); + if (req.query && req.query.pretty) { res.setHeader('Content-Type', 'application/json'); res.send(JSON.stringify(result, null, 2)); diff --git a/lib/api/status.js b/lib/api/status.js index dc8d97bcdd3..b630d629593 100644 --- a/lib/api/status.js +++ b/lib/api/status.js @@ -14,6 +14,9 @@ function configure (app, wares, env, ctx) { // Status badge/text/json api.get('/status', function (req, res) { + + let extended = env.settings.filteredSettings(app.extendedClientSettings); + let settings = env.settings.filteredSettings(env.settings); var authToken = req.query.token || req.query.secret || ''; @@ -26,8 +29,8 @@ function configure (app, wares, env, ctx) { , apiEnabled: app.enabled('api') , careportalEnabled: app.enabled('api') && env.settings.enable.indexOf('careportal') > -1 , boluscalcEnabled: app.enabled('api') && env.settings.enable.indexOf('boluscalc') > -1 - , settings: env.settings - , extendedSettings: app.extendedClientSettings + , settings: settings + , extendedSettings: extended , authorized: ctx.authorization.authorize(authToken) }; diff --git a/lib/settings.js b/lib/settings.js index 309da22babf..fac18986aae 100644 --- a/lib/settings.js +++ b/lib/settings.js @@ -69,6 +69,12 @@ function init () { , frameName8: '' }; + var secureSettings = [ + 'apnsKey' + , 'apnsKeyId' + , 'developerTeamId' + ]; + var valueMappers = { nightMode: mapTruthy , alarmUrgentHigh: mapTruthy @@ -96,6 +102,30 @@ function init () { , bgTargetBottom: mapNumber }; + function filterObj(obj, secureKeys) { + if (obj && typeof obj === 'object') { + var allKeys = Object.keys(obj); + for (var i = 0 ; i < allKeys.length ; i++) { + var k = allKeys[i]; + if (secureKeys.includes(k)) { + console.log('Deleting key', k); + delete obj[k]; + } else { + var value = obj[k]; + if ( typeof value === 'object') { + filterObj(value, secureKeys); + } + } + } + } + return obj; + } + + function filteredSettings(settingsObject) { + let so = _.cloneDeep(settingsObject); + return filterObj(so, secureSettings); + } + function mapNumberArray (value) { if (!value || _.isArray(value)) { return value; @@ -360,6 +390,7 @@ function init () { settings.isAlarmEventEnabled = isAlarmEventEnabled; settings.snoozeMinsForAlarmEvent = snoozeMinsForAlarmEvent; settings.snoozeFirstMinsForAlarmEvent = snoozeFirstMinsForAlarmEvent; + settings.filteredSettings = filteredSettings; return settings; From dcf8248a1b52531a4d980c22ec33a705824bd210 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 16 Feb 2020 22:46:40 +0200 Subject: [PATCH 055/104] Add more variables to the special list --- lib/settings.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/settings.js b/lib/settings.js index fac18986aae..9cf2aac9b82 100644 --- a/lib/settings.js +++ b/lib/settings.js @@ -73,6 +73,8 @@ function init () { 'apnsKey' , 'apnsKeyId' , 'developerTeamId' + , 'userName' + , 'password' ]; var valueMappers = { From 43165a0ccad9cff7142b60e5f00038fa24a0d0db Mon Sep 17 00:00:00 2001 From: Dominik Dzienia Date: Sat, 22 Feb 2020 16:00:21 +0100 Subject: [PATCH 056/104] [DEV][FIX] Fix dbsize plugin to make it hideable again (#5529) * [FIX] Making dbsize plugin default but hideable - removed its forced show state * Removed unused lodash dependency --- app.json | 4 ++-- azuredeploy.json | 4 ++-- lib/plugins/dbsize.js | 1 - lib/settings.js | 4 ++-- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/app.json b/app.json index 3d86d59bfa2..814262b6802 100644 --- a/app.json +++ b/app.json @@ -99,7 +99,7 @@ }, "ENABLE": { "description": "Plugins to enable for your site. Must be a space-delimited, lower-case list. Include the word 'bridge' here if you are receiving data from the Dexcom Share service. Include 'mmconnect' if you are bridging from the MiniMed CareLink service.", - "value": "careportal basal", + "value": "careportal basal dbsize", "required": false }, "MMCONNECT_USER_NAME": { @@ -129,7 +129,7 @@ }, "SHOW_PLUGINS": { "description": "Default setting for whether or not these plugins are checked (active) by default, not merely enabled. Include plugins here as in the ENABLE line; space-separated and lower-case.", - "value": "careportal", + "value": "careportal dbsize", "required": false }, "SHOW_RAWBG": { diff --git a/azuredeploy.json b/azuredeploy.json index dc89c0ad956..bff5e3c41f7 100644 --- a/azuredeploy.json +++ b/azuredeploy.json @@ -173,7 +173,7 @@ }, "enable": { "type": "string", - "defaultValue": "basal bwp cage careportal iob cob rawbg sage iage treatmentnotify boluscalc profile food" + "defaultValue": "basal bwp cage careportal iob cob rawbg sage iage treatmentnotify boluscalc profile food dbsize" }, "night_mode": { "type": "string", @@ -185,7 +185,7 @@ }, "show_plugins": { "type": "string", - "defaultValue": "careportal" + "defaultValue": "careportal dbsize" }, "show_rawbg": { "type": "string", diff --git a/lib/plugins/dbsize.js b/lib/plugins/dbsize.js index b2b85b83e7a..fa4f69c8fcf 100644 --- a/lib/plugins/dbsize.js +++ b/lib/plugins/dbsize.js @@ -1,6 +1,5 @@ 'use strict'; -var _ = require('lodash'); var levels = require('../levels'); function init (ctx) { diff --git a/lib/settings.js b/lib/settings.js index 9cf2aac9b82..dbe9e893e30 100644 --- a/lib/settings.js +++ b/lib/settings.js @@ -31,7 +31,7 @@ function init () { , alarmPumpBatteryLow: false , language: 'en' , scaleY: 'log' - , showPlugins: '' + , showPlugins: 'dbsize' , showForecast: 'ar2' , focusHours: 3 , heartbeat: 60 @@ -308,7 +308,7 @@ function init () { function adjustShownPlugins () { var showPluginsUnset = settings.showPlugins && 0 === settings.showPlugins.length; - settings.showPlugins += ' delta direction upbat dbsize'; + settings.showPlugins += ' delta direction upbat'; if (settings.showRawbg === 'always' || settings.showRawbg === 'noise') { settings.showPlugins += ' rawbg'; } From be678c04e86a4c9515d5c532e3c97fc766ef388f Mon Sep 17 00:00:00 2001 From: Simon Persson Date: Sat, 22 Feb 2020 16:02:16 +0100 Subject: [PATCH 057/104] Removed duplicate if statement (#5531) Removed the additional if statement checking for the loop specific developer team id. This since the exact same if statement existed just above it. --- lib/server/loop.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/server/loop.js b/lib/server/loop.js index 6a04fcd73aa..a5e3aec5d8e 100644 --- a/lib/server/loop.js +++ b/lib/server/loop.js @@ -24,11 +24,6 @@ function init (env, ctx) { return; } - if (env.extendedSettings.loop.developerTeamId === undefined || env.extendedSettings.loop.developerTeamId.length != 10) { - completion("Loop notification failed: LOOP_DEVELOPER_TEAM_ID not set."); - return; - } - if (ctx.ddata.profiles === undefined || ctx.ddata.profiles.length < 1 || ctx.ddata.profiles[0].loopSettings === undefined) { completion("Loop notification failed: Could not find loopSettings in profile."); return; From 68fb745cadac8c21900e01ccf83ed2bc32320080 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cas=20Eli=C3=ABns?= Date: Sat, 22 Feb 2020 16:02:40 +0100 Subject: [PATCH 058/104] Update Dutch translations (#5532) --- lib/language.js | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/lib/language.js b/lib/language.js index ede0a734add..ac9b0a02218 100644 --- a/lib/language.js +++ b/lib/language.js @@ -687,7 +687,7 @@ function init() { ,pl: 'between' ,ru: 'между' ,sk: 'between' - ,nl: 'between' + ,nl: 'tussen' ,ko: 'between' ,tr: 'between' ,zh_cn: 'between' @@ -712,7 +712,7 @@ function init() { ,pl: 'around' ,ru: 'около' ,sk: 'around' - ,nl: 'around' + ,nl: 'rond' ,ko: 'around' ,tr: 'around' ,zh_cn: 'around' @@ -737,7 +737,7 @@ function init() { ,pl: 'and' ,ru: 'и' ,sk: 'and' - ,nl: 'and' + ,nl: 'en' ,ko: 'and' ,tr: 'and' ,zh_cn: 'and' @@ -6472,7 +6472,7 @@ function init() { ,pl: 'minuta temu' ,ru: 'мин назад' ,sk: 'min. pred' - ,nl: 'm geleden' + ,nl: 'minuut geleden' ,ko: '분 전' ,tr: 'dk. önce' ,zh_cn: '分钟前' @@ -6498,7 +6498,7 @@ function init() { ,pl: 'minut temu' ,ru: 'минут назад' ,sk: 'min. pred' - ,nl: 'm geleden' + ,nl: 'minuten geleden' ,ko: '분 전' ,tr: 'dakika önce' ,zh_cn: '分钟前' @@ -15046,6 +15046,7 @@ function init() { , pl: 'Białko' ,ru: 'Белки' ,he: 'חלבון' + ,nl: 'Eiwit' }, 'Fat': { fi: 'Rasva' @@ -15055,6 +15056,7 @@ function init() { , pl: 'Tłuszcz' ,ru: 'Жиры' ,he: 'שמן' + ,nl: 'Vet' }, 'Protein average': { fi: 'Proteiini keskiarvo' @@ -15064,6 +15066,7 @@ function init() { , pl: 'Średnia białka' ,ru: 'Средний белок' ,he: 'חלבון ממוצע' + ,nl: 'eiwitgemiddelde' }, 'Fat average': { fi: 'Rasva keskiarvo' @@ -15073,6 +15076,7 @@ function init() { , pl: 'Średnia tłuszczu' ,ru: 'Средний жир' ,he: 'שמן ממוצע' + ,nl: 'Vetgemiddelde' }, 'Total carbs': { fi: 'Hiilihydraatit yhteensä' @@ -15082,6 +15086,7 @@ function init() { , pl: 'Węglowodany ogółem' ,ru: 'Всего углеводов' ,he: 'כל פחמימות' + ,nl: 'Totaal koolhydraten' }, 'Total protein': { fi: 'Proteiini yhteensä' @@ -15091,6 +15096,7 @@ function init() { , pl: 'Białko ogółem' ,ru: 'Всего белков' ,he: 'כל חלבונים' + ,nl: 'Totaal eiwitten' }, 'Total fat': { fi: 'Rasva yhteensä' @@ -15100,32 +15106,41 @@ function init() { , pl: 'Tłuszcz ogółem' ,ru: 'Всего жиров' ,he: 'כל שומנים' + ,nl: 'Totaal vetten' }, 'Database Size': { pl: 'Rozmiar Bazy Danych' + ,nl: 'Grootte database' }, 'Database Size near its limits!': { pl: 'Rozmiar bazy danych zbliża się do limitu!' + ,nl: 'Database grootte nadert limiet!' }, 'Database size is %1 MiB out of %2 MiB. Please backup and clean up database!': { pl: 'Baza danych zajmuje %1 MiB z dozwolonych %2 MiB. Proszę zrób kopię zapasową i oczyść bazę danych!' + ,nl: 'Database grootte is %1 MiB van de %2 MiB. Maak een backup en verwijder oude data' }, 'Database file size': { pl: 'Rozmiar pliku bazy danych' + ,nl: 'Database bestandsgrootte' }, '%1 MiB of %2 MiB (%3%)': { pl: '%1 MiB z %2 MiB (%3%)' + ,nl: '%1 MiB van de %2 MiB (%3%)' }, 'Data size': { pl: 'Rozmiar danych' + ,nl: 'Datagrootte' }, 'virtAsstDatabaseSize': { en: '%1 MiB that is %2% of available database space' ,pl: '%1 MiB co stanowi %2% przestrzeni dostępnej dla bazy danych' + ,nl: '%1 MiB dat is %2% van de beschikbaare database ruimte' }, 'virtAsstTitleDatabaseSize': { en: 'Database file size' ,pl: 'Rozmiar pliku bazy danych' + ,nl: 'Database bestandsgrootte' } }; From 7d95b06c2081baa3c3beea69bab126e7c10d5e1c Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sat, 22 Feb 2020 17:07:21 +0200 Subject: [PATCH 059/104] Fix settings test --- tests/settings.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/settings.test.js b/tests/settings.test.js index 3e591cfac27..9c12b5bf483 100644 --- a/tests/settings.test.js +++ b/tests/settings.test.js @@ -28,7 +28,7 @@ describe('settings', function ( ) { settings.alarmTimeagoUrgent.should.equal(true); settings.alarmTimeagoUrgentMins.should.equal(30); settings.language.should.equal('en'); - settings.showPlugins.should.equal(''); + settings.showPlugins.should.equal('dbsize'); settings.insecureUseHttp.should.equal(false); settings.secureHstsHeader.should.equal(true); settings.secureCsp.should.equal(false); From 32f4791ef12ec663de76231c80bd2cdddced4eed Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sat, 22 Feb 2020 17:17:18 +0200 Subject: [PATCH 060/104] Update version to 13.1.0 --- package.json | 2 +- swagger.json | 2 +- swagger.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 0abbae897b5..ad221334050 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nightscout", - "version": "13.0.2-dev", + "version": "13.1.0", "description": "Nightscout acts as a web-based CGM (Continuous Glucose Montinor) to allow multiple caregivers to remotely view a patients glucose data in realtime.", "license": "AGPL-3.0", "author": "Nightscout Team", diff --git a/swagger.json b/swagger.json index 5ff1e1c251e..1cff91411fe 100755 --- a/swagger.json +++ b/swagger.json @@ -8,7 +8,7 @@ "info": { "title": "Nightscout API", "description": "Own your DData with the Nightscout API", - "version": "13.0.2-dev", + "version": "13.1.0", "license": { "name": "AGPL 3", "url": "https://www.gnu.org/licenses/agpl.txt" diff --git a/swagger.yaml b/swagger.yaml index 4b2b13a8276..b69dee73c24 100755 --- a/swagger.yaml +++ b/swagger.yaml @@ -4,7 +4,7 @@ servers: info: title: Nightscout API description: Own your DData with the Nightscout API - version: 13.0.2-dev + version: 13.1.0 license: name: AGPL 3 url: 'https://www.gnu.org/licenses/agpl.txt' From 4cdd00db400ae74d7f978a4d92e8194e3fae16b2 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Mon, 9 Mar 2020 08:19:08 +0200 Subject: [PATCH 061/104] Fix topbar collapse on small screens (#5562) * Fix top bar collapsing on small screens * Fix wide button array overflowing the button bar --- static/css/drawer.css | 2 +- static/css/main.css | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/static/css/drawer.css b/static/css/drawer.css index 96e1375ebd8..3e5e72542a4 100644 --- a/static/css/drawer.css +++ b/static/css/drawer.css @@ -265,7 +265,7 @@ h1, legend, } #buttonbar a { - float: left; + float: right; text-decoration: none; width: 34px; } diff --git a/static/css/main.css b/static/css/main.css index 354a53b6bef..d6bee072d16 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -699,6 +699,22 @@ a, a:visited, a:link { #container #toolbar { float: right; height: auto; + background: none; + border-bottom: none; + margin: 0px; + } + + #buttonbar { + margin-right: 0px; + padding-right: 15px; + margin-top: 15px; + height: 44px; + opacity: 0.75; + vertical-align: middle; + position: absolute; + right: 0; + z-index: 500; + width: 400px; } #toolbar .customTitle { From 17eb4ae7bc8289c33dcaaa678df99018bf58ea19 Mon Sep 17 00:00:00 2001 From: Andrew Dixon Date: Tue, 14 Apr 2020 08:20:35 +0100 Subject: [PATCH 062/104] Option to switch off bolus amount outputs (#5522) * Option to switch off bolus amount outputs (#5514) * Fixing issue with carb value not be output when set to "none" (#5514) * Adding additional output options (#5514) * Adding environment variable option for 'x U and Over' option. This option is BOLUS_RENDER_OVER with a default value of 1 and the value can be an integer or a float, e.g. 0.3, 1.5, 2, etc... * Adding change to change the font size depending on the bolus value. * Merge two "all" options to create an option that displays as SMB had. --- README.md | 1 + app.json | 5 +++++ lib/client/browser-settings.js | 15 ++++++++++++-- lib/client/index.js | 7 +++++++ lib/client/renderer.js | 36 +++++++++++++++++++--------------- lib/settings.js | 1 + views/index.html | 12 ++++++++++++ 7 files changed, 59 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 4378a63708d..bd55ebf8094 100644 --- a/README.md +++ b/README.md @@ -294,6 +294,7 @@ To learn more about the Nightscout API, visit https://YOUR-SITE.com/api-docs/ or * The `linear` option has equidistant tick marks; the range used is dynamic so that space at the top of chart isn't wasted. * The `log-dynamic` is similar to the default `log` options, but uses the same dynamic range and the `linear` scale. * `EDIT_MODE` (`on`) - possible values `on` or `off`. Enables the icon allowing for editing of treatments in the main view. + * `BOLUS_RENDER_OVER` (1) - U value over which the bolus values are rendered on the chart if the 'x U and Over' option is selected. This value can be an integer or a float, e.g. 0.3, 1.5, 2, etc... ### Predefined values for your server settings (optional) * `INSECURE_USE_HTTP` (`false`) - Redirect unsafe http traffic to https. Possible values `false`, or `true`. Your site redirects to `https` by default. If you don't want that from Nightscout, but want to implement that with a Nginx or Apache proxy, set `INSECURE_USE_HTTP` to `true`. Note: This will allow (unsafe) http traffic to your Nightscout instance and is not recommended. diff --git a/app.json b/app.json index 814262b6802..46c2fe0ae46 100644 --- a/app.json +++ b/app.json @@ -72,6 +72,11 @@ "value": "180", "required": false }, + "BOLUS_RENDER_OVER": { + "description": "U value over which the bolus values are rendered on the chart if the 'x U and Over' option is selected.", + "value": "1", + "required": false + }, "BRIDGE_PASSWORD": { "description": "Your Dexcom account password, to receive CGM data from the Dexcom Share service. Also make sure to include 'bridge' in your ENABLE line.", "value": "", diff --git a/lib/client/browser-settings.js b/lib/client/browser-settings.js index 2119b39da40..f45631f4b6f 100644 --- a/lib/client/browser-settings.js +++ b/lib/client/browser-settings.js @@ -71,6 +71,8 @@ function init (client, serverSettings, $) { $('#basalrender').val(settings.extendedSettings.basal ? settings.extendedSettings.basal.render : 'none'); + $('#bolusrender').val(settings.extendedSettings.bolus ? settings.extendedSettings.bolus.render : 'all'); + if (settings.timeFormat === 24) { $('#24-browser').prop('checked', true); } else { @@ -161,6 +163,7 @@ function init (client, serverSettings, $) { storage.remove(name); }); storage.remove('basalrender'); + storage.remove('bolusrender'); event.preventDefault(); client.browserUtils.reload(); }); @@ -213,6 +216,7 @@ function init (client, serverSettings, $) { , language: $('#language').val() , scaleY: $('#scaleY').val() , basalrender: $('#basalrender').val() + , bolusrender: $('#bolusrender').val() , showPlugins: checkedPluginNames() , storageVersion: STORAGE_VERSION }); @@ -268,8 +272,15 @@ function init (client, serverSettings, $) { settings.extendedSettings.basal = {}; } - var stored = storage.get('basalrender'); - settings.extendedSettings.basal.render = stored !== null ? stored : settings.extendedSettings.basal.render; + var basalStored = storage.get('basalrender'); + settings.extendedSettings.basal.render = basalStored !== null ? basalStored : settings.extendedSettings.basal.render; + + if (!settings.extendedSettings.bolus) { + settings.extendedSettings.bolus = {}; + } + + var bolusStored = storage.get('bolusrender'); + settings.extendedSettings.bolus.render = bolusStored !== null ? bolusStored : settings.extendedSettings.bolus.render; } catch (err) { console.error(err); diff --git a/lib/client/index.js b/lib/client/index.js index 5c611306d9f..29dc9593ae8 100644 --- a/lib/client/index.js +++ b/lib/client/index.js @@ -399,6 +399,12 @@ client.load = function load (serverSettings, callback) { } } + function updateBolusRenderOver () { + var bolusRenderOver = (client.settings.bolusRenderOver || 1) + ' U and Over'; + $('#bolusRenderOver').text(bolusRenderOver); + console.log('here'); + } + function alarmingNow () { return container.hasClass('alarming'); } @@ -1240,6 +1246,7 @@ client.load = function load (serverSettings, callback) { prepareEntries(); updateTitle(); + updateBolusRenderOver(); // Don't invoke D3 in headless mode diff --git a/lib/client/renderer.js b/lib/client/renderer.js index 7ea154ff0fd..818ee6f41d0 100644 --- a/lib/client/renderer.js +++ b/lib/client/renderer.js @@ -531,7 +531,7 @@ function init (client, d3) { }; } - function prepareArc (treatment, radius) { + function prepareArc (treatment, radius, renderBasal) { var arc_data = [ // white carb half-circle on top { 'element': '', 'color': 'white', 'start': -1.5708, 'end': 1.5708, 'inner': 0, 'outer': radius.R1 } @@ -568,16 +568,13 @@ function init (client, d3) { if (treatment.insulin > 0) { var dosage_units = '' + Math.round(treatment.insulin * 100) / 100; - - var unit_of_measurement = ' U'; // One international unit of insulin (1 IU) is shown as '1 U' - var enteredBy = '' + treatment.enteredBy; - - if ((treatment.insulin < 1 && !treatment.carbs && enteredBy.indexOf('openaps') > -1) || treatment.isSMB) { // don't show the unit of measurement for insulin boluses < 1 without carbs (e.g. oref0 SMB's). Otherwise lot's of small insulin only dosages are often unreadable - unit_of_measurement = ''; - // remove leading zeros to avoid overlap with adjacent boluses + + if (renderBasal === 'all-remove-zero-u') { dosage_units = (dosage_units + "").replace(/^0/, ""); } + var unit_of_measurement = (renderBasal === 'all-remove-zero-u' ? '' : ' U'); // One international unit of insulin (1 IU) is shown as '1 U' + arc_data[3].element = dosage_units + unit_of_measurement; } @@ -971,11 +968,15 @@ function init (client, d3) { .attr('id', 'label') .style('fill', 'white'); - // reduce the treatment label font size to make it readable with SMB - var fontBaseSize = (opts.treatments >= 30) ? 40 : 50 - Math.floor((25 - opts.treatments) / 30 * 10); - label.append('text') - .style('font-size', fontBaseSize / opts.scale) + .style('font-size', function(d) { + var fontSize = ( (opts.treatments >= 30) ? 40 : 50 - Math.floor((25 - opts.treatments) / 30 * 10) ) / opts.scale; + var elementValue = parseFloat(d.element); + if (!isNaN(elementValue) && elementValue < 1) { + fontSize = (25 + Math.floor(elementValue * 10)) / opts.scale; + } + return fontSize; + }) .style('text-shadow', '0px 0px 10px rgba(0, 0, 0, 1)') .attr('text-anchor', 'middle') .attr('dy', '.35em') @@ -993,6 +994,7 @@ function init (client, d3) { renderer.drawTreatments = function drawTreatments (client) { var treatmentCount = 0; + var renderBasal = client.settings.extendedSettings.bolus.render; chart().focus.selectAll('.draggable-treatment').remove(); _.forEach(client.ddata.treatments, function eachTreatment (d) { @@ -1001,15 +1003,17 @@ function init (client, d3) { // add treatment bubbles _.forEach(client.ddata.treatments, function eachTreatment (d) { + var showLabels = ( !d.carbs && ( ( renderBasal == 'none') || ( renderBasal === 'over' && d.insulin < client.settings.bolusRenderOver) ) ) ? false : true; renderer.drawTreatment(d, { scale: renderer.bubbleScale() - , showLabels: true + , showLabels: showLabels , treatments: treatmentCount - }, client.sbx.data.profile.getCarbRatio(new Date())); + }, client.sbx.data.profile.getCarbRatio(new Date()), + renderBasal); }); }; - renderer.drawTreatment = function drawTreatment (treatment, opts, carbratio) { + renderer.drawTreatment = function drawTreatment (treatment, opts, carbratio, renderBasal) { if (!treatment.carbs && !treatment.protein && !treatment.fat && !treatment.insulin) { return; } @@ -1027,7 +1031,7 @@ function init (client, d3) { return; } - var arc = prepareArc(treatment, radius); + var arc = prepareArc(treatment, radius, renderBasal); var treatmentDots = appendTreatments(treatment, arc); appendLabels(treatmentDots, arc, opts); }; diff --git a/lib/settings.js b/lib/settings.js index dbe9e893e30..5a19ba6309c 100644 --- a/lib/settings.js +++ b/lib/settings.js @@ -51,6 +51,7 @@ function init () { , deNormalizeDates: false , showClockDelta: false , showClockLastTime: false + , bolusRenderOver: 1 , frameUrl1: '' , frameUrl2: '' , frameUrl3: '' diff --git a/views/index.html b/views/index.html index 156aa331325..6ef0dc2cce6 100644 --- a/views/index.html +++ b/views/index.html @@ -215,6 +215,18 @@ +
+
Render Bolus Amount
+
+ +
+
+
Enable Alarms
From 7c5a69dd9a1cf4f2236dbef12331a1b5401b20b6 Mon Sep 17 00:00:00 2001 From: Jeremy Cunningham <34543464+jpcunningh@users.noreply.github.com> Date: Tue, 5 May 2020 14:13:21 -0500 Subject: [PATCH 063/104] Fix d3 portrait to landscape brush failure (#5638) * fix d3 portrait to landscape brush failure * fix client.renderer.test for highlighBrushPoints function prototype change * fix highlightBrush * move brush reset inside check for valid brush --- lib/client/chart.js | 19 ++++-- lib/client/index.js | 14 +++-- lib/client/renderer.js | 6 +- npm-shrinkwrap.json | 114 +++++++++++++++++----------------- package.json | 2 +- tests/client.renderer.test.js | 5 +- 6 files changed, 85 insertions(+), 75 deletions(-) diff --git a/lib/client/chart.js b/lib/client/chart.js index 7a9b57bdd5d..d84dd0998b5 100644 --- a/lib/client/chart.js +++ b/lib/client/chart.js @@ -84,9 +84,13 @@ function init (client, d3, $) { function brushEnded () { // update the opacity of the context data points to brush extent + var selectedRange = chart.createAdjustedRange(); + var from = selectedRange[0].getTime(); + var to = selectedRange[1].getTime(); + chart.context.selectAll('circle') .data(client.entries) - .style('opacity', function(d) { return renderer.highlightBrushPoints(d) }); + .style('opacity', function(d) { return renderer.highlightBrushPoints(d, from, to) }); } var extent = client.dataExtent(); @@ -251,6 +255,7 @@ function init (client, d3, $) { chart.createBrushedRange = function() { var brushedRange = chart.theBrush && d3.brushSelection(chart.theBrush.node()) || null; + var range = brushedRange && brushedRange.map(chart.xScale2.invert); var dataExtent = client.dataExtent(); @@ -267,6 +272,8 @@ function init (client, d3, $) { range[1] = new Date(end); range[0] = new Date(end - client.focusRangeMS); + // console.log('createBrushedRange: ', brushedRange, range); + return range; } @@ -375,7 +382,8 @@ function init (client, d3, $) { chart.theBrush.selectAll('rect') .attr('y', 0) - .attr('height', contextHeight); + .attr('height', contextHeight) + .attr('width', '100%'); // disable resizing of brush chart.context.select('.x.brush').select('.overlay').style('cursor', 'move'); @@ -507,7 +515,7 @@ function init (client, d3, $) { .attr('y', 0) .attr('height', contextHeight); - // console.log('Redrawing old brush with new dimensions: ', currentBrushExtent); + // console.log('chart.update(): Redrawing old brush with new dimensions: ', currentBrushExtent); // redraw old brush with new dimensions chart.theBrush.call(chart.brush.move, currentBrushExtent.map(chart.xScale2)); @@ -578,7 +586,7 @@ function init (client, d3, $) { chart.xScaleBasals.domain(dataRange); - // console.log('Redrawing brush due to update: ', currentBrushExtent); + // console.log('chart.update(): Redrawing brush due to update: ', currentBrushExtent); chart.theBrush.call(chart.brush.move, currentBrushExtent.map(chart.xScale2)); }; @@ -663,8 +671,7 @@ function init (client, d3, $) { renderer.addTreatmentProfiles(client); renderer.drawTreatments(client); - - // console.log('Redrawing brush due to update: ', currentBrushExtent); + // console.log('scrollUpdate(): Redrawing brush due to update: ', currentBrushExtent); chart.theBrush.call(chart.brush.move, currentBrushExtent.map(chart.xScale2)); diff --git a/lib/client/index.js b/lib/client/index.js index 29dc9593ae8..c7487c6b410 100644 --- a/lib/client/index.js +++ b/lib/client/index.js @@ -390,9 +390,12 @@ client.load = function load (serverSettings, callback) { brushExtent[0] = new Date(brushExtent[1].getTime() - client.focusRangeMS); - // console.log('Resetting brush in updateBrushToNow: ', brushExtent); + // console.log('updateBrushToNow(): Resetting brush: ', brushExtent); - chart.theBrush && chart.theBrush.call(chart.brush.move, brushExtent.map(chart.xScale2)); + if (chart.theBrush) { + chart.theBrush.call(chart.brush) + chart.theBrush.call(chart.brush.move, brushExtent.map(chart.xScale2)); + } if (!skipBrushing) { brushed(); @@ -415,7 +418,6 @@ client.load = function load (serverSettings, callback) { function brushed () { // Brush not initialized - console.log("brushed"); if (!chart.theBrush) { return; } @@ -426,11 +428,13 @@ client.load = function load (serverSettings, callback) { var brushedRange = d3.brushSelection(chart.theBrush.node()); + // console.log("brushed(): coordinates: ", brushedRange); + if (brushedRange) { brushExtent = brushedRange.map(chart.xScale2.invert); } - // console.log('Brushed to: ', brushExtent); + console.log('brushed(): Brushed to: ', brushExtent); if (!brushedRange || (brushExtent[1].getTime() - brushExtent[0].getTime() !== client.focusRangeMS)) { // ensure that brush updating is with the time range @@ -440,7 +444,7 @@ client.load = function load (serverSettings, callback) { brushExtent[1] = new Date(brushExtent[0].getTime() + client.focusRangeMS); } - // console.log('Updating brushed to: ', brushExtent); + // console.log('brushed(): updating to: ', brushExtent); chart.theBrush.call(chart.brush.move, brushExtent.map(chart.xScale2)); } diff --git a/lib/client/renderer.js b/lib/client/renderer.js index 818ee6f41d0..a0caed8083a 100644 --- a/lib/client/renderer.js +++ b/lib/client/renderer.js @@ -58,11 +58,7 @@ function init (client, d3) { } // get the desired opacity for context chart based on the brush extent - renderer.highlightBrushPoints = function highlightBrushPoints (data) { - var selectedRange = chart().createAdjustedRange(); - var from = selectedRange[0].getTime(); - var to = selectedRange[1].getTime(); - + renderer.highlightBrushPoints = function highlightBrushPoints (data, from, to) { if (client.latestSGV && data.mills >= from && data.mills <= to) { return chart().futureOpacity(data.mills - client.latestSGV.mills); } else { diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index c28d19cac5b..46f925b9aba 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -3213,9 +3213,9 @@ "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=" }, "d3": { - "version": "5.12.0", - "resolved": "https://registry.npmjs.org/d3/-/d3-5.12.0.tgz", - "integrity": "sha512-flYVMoVuhPFHd9zVCe2BxIszUWqBcd5fvQGMNRmSiBrgdnh6Vlruh60RJQTouAK9xPbOB0plxMvBm4MoyODXNg==", + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-5.16.0.tgz", + "integrity": "sha512-4PL5hHaHwX4m7Zr1UapXW23apo6pexCgdetdJ5kTmADpG/7T9Gkxw0M0tf/pjoB63ezCCm0u5UaFYy2aMt0Mcw==", "requires": { "d3-array": "1", "d3-axis": "1", @@ -3261,9 +3261,9 @@ "integrity": "sha512-ejINPfPSNdGFKEOAtnBtdkpr24c4d4jsei6Lg98mxf424ivoDP2956/5HDpIAtmHo85lqT4pruy+zEgvRUBqaQ==" }, "d3-brush": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-1.1.3.tgz", - "integrity": "sha512-v8bbYyCFKjyCzFk/tdWqXwDykY8YWqhXYjcYxfILIit085VZOpj4XJKOMccTsvWxgzSLMJQg5SiqHjslsipEDg==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-1.1.5.tgz", + "integrity": "sha512-rEaJ5gHlgLxXugWjIkolTA0OyMvw8UWU1imYXy1v642XyyswmI1ybKOv05Ft+ewq+TFmdliD3VuK0pRp1VT/5A==", "requires": { "d3-dispatch": "1", "d3-drag": "1", @@ -3287,9 +3287,9 @@ "integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==" }, "d3-color": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.4.0.tgz", - "integrity": "sha512-TzNPeJy2+iEepfiL92LAAB7fvnp/dV2YwANPVHdDWmYMm23qIJBYww3qT8I8C1wXrmrg4UWs7BKc2tKIgyjzHg==" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.4.1.tgz", + "integrity": "sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q==" }, "d3-contour": { "version": "1.3.2", @@ -3300,23 +3300,23 @@ } }, "d3-dispatch": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.5.tgz", - "integrity": "sha512-vwKx+lAqB1UuCeklr6Jh1bvC4SZgbSqbkGBLClItFBIYH4vqDJCA7qfoy14lXmJdnBOdxndAMxjCbImJYW7e6g==" + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.6.tgz", + "integrity": "sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA==" }, "d3-drag": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-1.2.4.tgz", - "integrity": "sha512-ICPurDETFAelF1CTHdIyiUM4PsyZLaM+7oIBhmyP+cuVjze5vDZ8V//LdOFjg0jGnFIZD/Sfmk0r95PSiu78rw==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-1.2.5.tgz", + "integrity": "sha512-rD1ohlkKQwMZYkQlYVCrSFxsWPzI97+W+PaEIBNTMxRuxz9RF0Hi5nJWHGVJ3Om9d2fRTe1yOBINJyy/ahV95w==", "requires": { "d3-dispatch": "1", "d3-selection": "1" } }, "d3-dsv": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.1.1.tgz", - "integrity": "sha512-1EH1oRGSkeDUlDRbhsFytAXU6cAmXFzc52YUe6MRlPClmWb85MP1J5x+YJRzya4ynZWnbELdSAvATFW/MbxaXw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.2.0.tgz", + "integrity": "sha512-9yVlqvZcSOMhCYzniHE7EVUws7Fa1zgw+/EAV2BxJoG3ME19V6BQFBwI855XQDsxyOuG7NibqRMTtiF/Qup46g==", "requires": { "commander": "2", "iconv-lite": "0.4", @@ -3324,9 +3324,9 @@ } }, "d3-ease": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.5.tgz", - "integrity": "sha512-Ct1O//ly5y5lFM9YTdu+ygq7LleSgSE4oj7vUt9tPLHUi8VCV7QoizGpdWRWAwCO9LdYzIrQDg97+hGVdsSGPQ==" + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.6.tgz", + "integrity": "sha512-SZ/lVU7LRXafqp7XtIcBdxnWl8yyLpgOmzAk0mWBI9gXNzLDx5ybZgnRbH9dN/yY5tzVBqCQ9avltSnqVwessQ==" }, "d3-fetch": { "version": "1.1.2", @@ -3348,45 +3348,45 @@ } }, "d3-format": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.1.tgz", - "integrity": "sha512-TUswGe6hfguUX1CtKxyG2nymO+1lyThbkS1ifLX0Sr+dOQtAD5gkrffpHnx+yHNKUZ0Bmg5T4AjUQwugPDrm0g==" + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.4.tgz", + "integrity": "sha512-TWks25e7t8/cqctxCmxpUuzZN11QxIA7YrMbram94zMQ0PXjE4LVIMe/f6a4+xxL8HQ3OsAFULOINQi1pE62Aw==" }, "d3-geo": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.11.6.tgz", - "integrity": "sha512-z0J8InXR9e9wcgNtmVnPTj0TU8nhYT6lD/ak9may2PdKqXIeHUr8UbFLoCtrPYNsjv6YaLvSDQVl578k6nm7GA==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.12.0.tgz", + "integrity": "sha512-NalZVW+6/SpbKcnl+BCO67m8gX+nGeJdo6oGL9H6BRUGUL1e+AtPcP4vE4TwCQ/gl8y5KE7QvBzrLn+HsKIl+w==", "requires": { "d3-array": "1" } }, "d3-hierarchy": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.8.tgz", - "integrity": "sha512-L+GHMSZNwTpiq4rt9GEsNcpLa4M96lXMR8M/nMG9p5hBE0jy6C+3hWtyZMenPQdwla249iJy7Nx0uKt3n+u9+w==" + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.9.tgz", + "integrity": "sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ==" }, "d3-interpolate": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.3.2.tgz", - "integrity": "sha512-NlNKGopqaz9qM1PXh9gBF1KSCVh+jSFErrSlD/4hybwoNX/gt1d8CDbDW+3i+5UOHhjC6s6nMvRxcuoMVNgL2w==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.4.0.tgz", + "integrity": "sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA==", "requires": { "d3-color": "1" } }, "d3-path": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.8.tgz", - "integrity": "sha512-J6EfUNwcMQ+aM5YPOB8ZbgAZu6wc82f/0WFxrxwV6Ll8wBwLaHLKCqQ5Imub02JriCVVdPjgI+6P3a4EWJCxAg==" + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==" }, "d3-polygon": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-1.0.5.tgz", - "integrity": "sha512-RHhh1ZUJZfhgoqzWWuRhzQJvO7LavchhitSTHGu9oj6uuLFzYZVeBzaWTQ2qSO6bz2w55RMoOCf0MsLCDB6e0w==" + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-1.0.6.tgz", + "integrity": "sha512-k+RF7WvI08PC8reEoXa/w2nSg5AUMTi+peBD9cmFc+0ixHfbs4QmxxkarVal1IkVkgxVuk9JSHhJURHiyHKAuQ==" }, "d3-quadtree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.6.tgz", - "integrity": "sha512-NUgeo9G+ENQCQ1LsRr2qJg3MQ4DJvxcDNCiohdJGHt5gRhBW6orIB5m5FJ9kK3HNL8g9F4ERVoBzcEwQBfXWVA==" + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.7.tgz", + "integrity": "sha512-RKPAeXnkC59IDGD0Wu5mANy0Q2V28L+fNe65pOCXVdVuTJS3WPKaJlFHer32Rbh9gIo9qMuJXio8ra4+YmIymA==" }, "d3-random": { "version": "1.1.2", @@ -3416,14 +3416,14 @@ } }, "d3-selection": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.0.tgz", - "integrity": "sha512-EYVwBxQGEjLCKF2pJ4+yrErskDnz5v403qvAid96cNdCMr8rmCYfY5RGzWz24mdIbxmDf6/4EAH+K9xperD5jg==" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.1.tgz", + "integrity": "sha512-BTIbRjv/m5rcVTfBs4AMBLKs4x8XaaLkwm28KWu9S2vKNqXkXt2AH2Qf0sdPZHjFxcWg/YL53zcqAz+3g4/7PA==" }, "d3-shape": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.5.tgz", - "integrity": "sha512-VKazVR3phgD+MUCldapHD7P9kcrvPcexeX/PkMJmkUov4JM8IxsSg1DvbYoYich9AtdTsa5nNk2++ImPiDiSxg==", + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", "requires": { "d3-path": "1" } @@ -3434,22 +3434,22 @@ "integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==" }, "d3-time-format": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.1.3.tgz", - "integrity": "sha512-6k0a2rZryzGm5Ihx+aFMuO1GgelgIz+7HhB4PH4OEndD5q2zGn1mDfRdNrulspOfR6JXkb2sThhDK41CSK85QA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.2.3.tgz", + "integrity": "sha512-RAHNnD8+XvC4Zc4d2A56Uw0yJoM7bsvOlJR33bclxq399Rak/b9bhvu/InjxdWhPtkgU53JJcleJTGkNRnN6IA==", "requires": { "d3-time": "1" } }, "d3-timer": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.9.tgz", - "integrity": "sha512-rT34J5HnQUHhcLvhSB9GjCkN0Ddd5Y8nCwDBG2u6wQEeYxT/Lf51fTFFkldeib/sE/J0clIe0pnCfs6g/lRbyg==" + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.10.tgz", + "integrity": "sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw==" }, "d3-transition": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.2.0.tgz", - "integrity": "sha512-VJ7cmX/FPIPJYuaL2r1o1EMHLttvoIuZhhuAlRoOxDzogV8iQS6jYulDm3xEU3TqL80IZIhI551/ebmCMrkvhw==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.3.2.tgz", + "integrity": "sha512-sc0gRU4PFqZ47lPVHloMn9tlPcv8jxgOQg+0zjhfZXMQuvppjG6YuwdMBE0TuqCZjeJkLecku/l9R0JPcRhaDA==", "requires": { "d3-color": "1", "d3-dispatch": "1", diff --git a/package.json b/package.json index ad221334050..920c5a48849 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "css-loader": "^1.0.1", "cssmin": "^0.4.3", "csv-stringify": "^5.3.5", - "d3": "^5.12.0", + "d3": "^5.16.0", "easyxml": "^2.0.1", "ejs": "^2.6.2", "errorhandler": "^1.5.1", diff --git a/tests/client.renderer.test.js b/tests/client.renderer.test.js index ca81e7d99e8..5dc707ab2b1 100644 --- a/tests/client.renderer.test.js +++ b/tests/client.renderer.test.js @@ -64,7 +64,10 @@ describe('renderer', () => { describe(`data.mills ${extent.mills} and chart().brush.extent() times ${extent.times}`, () => { it(extent.expectation, () => { - renderer(mockClient, {}).highlightBrushPoints(mockData).should.equal(extent.expectedOpacity); + var selectedRange = mockClient.chart.createAdjustedRange(); + var from = selectedRange[0].getTime(); + var to = selectedRange[1].getTime(); + renderer(mockClient, {}).highlightBrushPoints(mockData, from, to).should.equal(extent.expectedOpacity); }); }); }); From 9236771b2906759892c9a195bd5adc2542279be9 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Tue, 5 May 2020 22:18:42 +0300 Subject: [PATCH 064/104] Sanitize data from manual careportal entries so only fields with actual data are sent to the server (#5619) --- lib/client/careportal.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/client/careportal.js b/lib/client/careportal.js index a5c86232d8e..632ab24986f 100644 --- a/lib/client/careportal.js +++ b/lib/client/careportal.js @@ -226,11 +226,17 @@ function init (client, $) { , duration: times.msecs(parse_duration($('#duration').val())).mins < 1 ? $('#duration').val() : times.msecs(parse_duration($('#duration').val())).mins , percent: $('#percent').val() , profile: $('#profile').val() - , preBolus: parseInt($('#preBolus').val()) + , preBolus: $('#preBolus').val() , notes: $('#notes').val() , units: client.settings.units }; + data.preBolus = parseInt(data.preBolus); + + if (isNaN(data.preBolus)) { + delete data.preBolus; + } + var reasons = inputMatrix[eventType]['reasons']; var reason = _.find(reasons, function matches (r) { return r.name === selectedReason; @@ -273,7 +279,11 @@ function init (client, $) { data.splitExt = parseInt($('#insulinSplitExt').val()) || 0; } - return data; + let d = {}; + Object.keys(data).forEach(function(key) { + if (data[key] != "" && data[key] != null) d[key] = data[key]; + }); + return d; } careportal.save = function save (event) { From db0e08c522dbfad3f55c649c17d0c9bf6dccb981 Mon Sep 17 00:00:00 2001 From: Ben West Date: Fri, 12 Jun 2020 07:17:38 -0700 Subject: [PATCH 065/104] do not redirect to profile editor by default (#5671) In a default configuration, there is no treatment data. The code to redirect the UI to the profile editor is buried deep within the chart rendering code for basals. This plugin is only supposed to go into action when enabled via ENABLE=basal. This commit fixes first-use experience for the default configuration intended to draw real-time CGM traces and no basal information is expected. Since no basal information is expected unless plugin is enabled via ENABLE=basal, this allows skipping instead of redirecting to the profile editor. --- lib/client/renderer.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/client/renderer.js b/lib/client/renderer.js index a0caed8083a..a3841695060 100644 --- a/lib/client/renderer.js +++ b/lib/client/renderer.js @@ -1034,6 +1034,9 @@ function init (client, d3) { renderer.addBasals = function addBasals (client) { + if (!client.settings.isEnabled('basal')) { + return; + } var mode = client.settings.extendedSettings.basal.render; var profile = client.sbx.data.profile; var linedata = []; From f3fab567c5e2d25da3d5a2f452c28b99cb016b0f Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 12 Jun 2020 10:17:52 -0400 Subject: [PATCH 066/104] Fix Issue #5486 - Device Status Days Feature (#5651) * Device Status Days Feature * Edits per review from @sulkaharo --- README.md | 1 + env.js | 2 ++ lib/data/dataloader.js | 4 +++- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index bd55ebf8094..e8aeecdbcc5 100644 --- a/README.md +++ b/README.md @@ -557,6 +557,7 @@ For remote overrides, the following extended settings must be configured: Plugins only have access to their own extended settings, all the extended settings of client plugins will be sent to the browser. * `DEVICESTATUS_ADVANCED` (`true`) - Defaults to true. Users who only have a single device uploading data to Nightscout can set this to false to reduce the data use of the site. + * `DEVICESTATUS_DAYS` (`1`) - Defaults to 1, can optionally be set to 2. Users can use this to show 48 hours of device status data for in retro mode, rather than the default 24 hours. Setting this value to 2 will roughly double the bandwidth usage of nightscout, so users with a data cap may not want to update this setting. #### Pushover In addition to the normal web based alarms, there is also support for [Pushover](https://pushover.net/) based alarms and notifications. diff --git a/env.js b/env.js index 0d8d41409b0..9875435ff02 100644 --- a/env.js +++ b/env.js @@ -170,6 +170,8 @@ function findExtendedSettings (envs) { extended.devicestatus = {}; extended.devicestatus.advanced = true; + extended.devicestatus.days = 1; + if(process.env['DEVICESTATUS_DAYS'] && process.env['DEVICESTATUS_DAYS'] == '2') extended.devicestatus.days = 1; function normalizeEnv (key) { return key.toUpperCase().replace('CUSTOMCONNSTR_', ''); diff --git a/lib/data/dataloader.js b/lib/data/dataloader.js index b0eafdd4fe0..3c2d8c6502d 100644 --- a/lib/data/dataloader.js +++ b/lib/data/dataloader.js @@ -361,8 +361,10 @@ function loadFood(ddata, ctx, callback) { } function loadDeviceStatus(ddata, env, ctx, callback) { + var retroDays = ONE_DAY; + if(env.extendedSettings.devicestatus && env.extendedSettings.devicestatus.days && env.extendedSettings.devicestatus.days == 2) retroDays = TWO_DAYS; var dateRange = { - $gte: new Date(ddata.lastUpdated - ONE_DAY).toISOString() + $gte: new Date( ddata.lastUpdated - (retroDays) ).toISOString() }; if (ddata.page && ddata.page.frame) { dateRange['$lte'] = new Date(ddata.lastUpdated).toISOString(); From 3d6f488edab52eff2d8357997efcda1d6b5b28d8 Mon Sep 17 00:00:00 2001 From: ireneusz-ptak <31506973+ireneusz-ptak@users.noreply.github.com> Date: Fri, 12 Jun 2020 16:29:15 +0200 Subject: [PATCH 067/104] Configurable clock views (#5625) * Configurable clock views * Configurable clock views * Configurable clock views * Configurable clock views * Configurable clock views * Configurable clock views * Update README.md * Update README.md * Configurable clock views --- README.md | 22 ++- lib/client/clock-client.js | 180 +++++++++++-------- lib/server/clocks.js | 2 +- views/clockviews/bgclock.css | 28 --- views/clockviews/clock-color.css | 32 ---- views/clockviews/clock-config.css | 39 ++++ views/clockviews/clock-config.html | 65 +++++++ views/clockviews/clock-shared.css | 53 +++--- views/clockviews/clock.css | 5 - views/clockviews/{shared.html => clock.html} | 24 ++- views/index.html | 1 + 11 files changed, 263 insertions(+), 188 deletions(-) delete mode 100644 views/clockviews/bgclock.css delete mode 100644 views/clockviews/clock-color.css create mode 100644 views/clockviews/clock-config.css create mode 100644 views/clockviews/clock-config.html delete mode 100644 views/clockviews/clock.css rename views/clockviews/{shared.html => clock.html} (88%) diff --git a/README.md b/README.md index e8aeecdbcc5..d9e4a57262c 100644 --- a/README.md +++ b/README.md @@ -306,11 +306,29 @@ To learn more about the Nightscout API, visit https://YOUR-SITE.com/api-docs/ or ### Views - There are a few alternate web views available from the main menu that display a simplified BG stream. (If you launch one of these in a fullscreen view in iOS, you can use a left-to-right swipe gesture to exit the view.) + Nightscout allows to create custom, simplified views using a predefined set of elements. This option is available under `[+]` link in the main menu. + + List of available items: + * `SGV` - Sensor Glucose Value + * `SGV age` - time since the last SGV read + * `SGV delta` - change of SGV in the last 5 minutes + * `Trend arrow` - icon of the SG trend + * `Time` - current time + * `Line break` - invisible item that will move following items to the next line (by default all are showing on the same level) + + All visible items have `Size` property which allows to customize the view even more. Also, all items may appear multiple times on the view. + + Apart from adding items, it is possible to customize other aspects of the views, like selecting `Color` or `Black` background. The first one will indicate current BG threshold (green = in range; blue = below range; yellow = above range; red = urgent below/above). + `Show SGV age` option will make `SGV age` item appear `Always` or only if the predefined threshold is reached: `Only after threshold`. Breaching `SGV age threshold` will also make `Color` background turn grey and strike through `SGV`. + `Clock view configurator` will generate an URL (available under `Open my clock view!` link) that could be bookmarked. + + There are a few default views available from the main menu: * `Clock` - Shows current BG, trend arrow, and time of day. Grey text on a black background. - * `Color` - Shows current BG and trend arrow. White text on a background that changes color to indicate current BG threshold (green = in range; blue = below range; yellow = above range; red = urgent below/above). Set `SHOW_CLOCK_DELTA` to `true` to show BG change in the last 5 minutes, set `SHOW_CLOCK_LAST_TIME` to `true` to always show BG age. + * `Color` - Shows current BG and trend arrow. White text on a color background. * `Simple` - Shows current BG. Grey text on a black background. + If you launch one of these views in a fullscreen view in iOS, you can use a left-to-right swipe gesture to exit the view. + ### Split View Some users will need easy access to multiple Nightscout views at the same time. We have a special view for this case, accessed on /split path on your Nightscout URL. The view supports any number of sites between 1 to 8 way split, where the content for the screen can be loaded from multiple Nightscout instances. Note you still need to host separate instances for each Nightscout being monitored including the one that hosts the split view page - these variables only add the ability to load multiple views into one browser page. To set the URLs from which the content is loaded, set: diff --git a/lib/client/clock-client.js b/lib/client/clock-client.js index e6eec7190c6..891f10d57d5 100644 --- a/lib/client/clock-client.js +++ b/lib/client/clock-client.js @@ -6,7 +6,7 @@ var client = {}; client.settings = browserSettings(client, window.serverSettings, $); -// console.log('settings', client.settings); +//console.log('settings', client.settings); // client.settings now contains all settings client.query = function query () { @@ -20,7 +20,7 @@ client.query = function query () { }); var secret = localStorage.getItem('apisecrethash'); - var src = '/api/v1/entries.json?count=3&t=' + new Date().getTime(); + var src = '/api/v1/entries.json?find[type]=sgv&count=3&t=' + new Date().getTime(); if (secret) { src += '&secret=' + secret; @@ -39,6 +39,7 @@ client.render = function render (xhr) { let rec; let delta; + // Get SGV, calculate DELTA xhr.forEach(element => { if (element.sgv && !rec) { rec = element; @@ -49,28 +50,65 @@ client.render = function render (xhr) { }); let $errorMessage = $('#errorMessage'); + let $inner = $('#inner'); // If no one measured value found => show "-?-" if (!rec) { if (!$errorMessage.length) { - $('#arrowDiv').append('
-?-
'); - $('#arrow').hide(); + $inner.after('
-?-
') } else { $errorMessage.show(); } + $inner.hide(); return; } else { $errorMessage.length && $errorMessage.hide(); - $('#arrow').show(); + $inner.show(); + } + + //Parse face parameters + let face = $inner.data('face').toLowerCase(); + + // Backward compatible + if (face === 'clock-color') { + face = 'c' + (window.serverSettings.settings.showClockLastTime ? 'y' : 'n') + '13-sg40-' + (window.serverSettings.settings.showClockDelta ? 'dt14-' : '') + 'nl-ar30-nl-ag6'; + } + else if (face === 'clock') { + face = 'bn0-sg40'; + } + else if (face === 'bgclock') { + face = 'bn0-sg30-ar18-nl-nl-tm26'; + } + else if (face === 'config') { + face = $inner.attr('data-face-config'); + $inner.empty(); + } + + let faceParams = face.split('-'); + let bgColor = false; + let staleMinutes = 13; + let alwaysShowTime = false; + + let clockCreated = ($inner.children().length > 0); + + for (let param in faceParams) { + if (param === '0') { + bgColor = (faceParams[param].substr(0, 1) === 'c'); // do we want colorful background? + alwaysShowTime = (faceParams[param].substr(1, 1) === 'y'); // always show "stale time" text? + staleMinutes = (faceParams[param].substr(2,2) - 0 >= 0) ? faceParams[param].substr(2,2) : 13; // threshold value (0=never) + } else if (!clockCreated){ + let div = '
0) ? ' style="' + ((faceParams[param].substr(0,2) === 'ar') ? 'height' : 'font-size') + ':' + faceParams[param].substr(2,2) + 'vmin"' : '') + '>
'; + $inner.append(div); + } } - - let last = new Date(rec.date); - let now = new Date(); // Convert BG to mmol/L if necessary. + let displayValue; + let deltaDisplayValue; + if (window.serverSettings.settings.units === 'mmol') { - var displayValue = window.Nightscout.units.mgdlToMMOL(rec.sgv); - var deltaDisplayValue = window.Nightscout.units.mgdlToMMOL(delta); + displayValue = window.Nightscout.units.mgdlToMMOL(rec.sgv); + deltaDisplayValue = window.Nightscout.units.mgdlToMMOL(delta); } else { displayValue = rec.sgv; deltaDisplayValue = Math.round(delta); @@ -80,18 +118,8 @@ client.render = function render (xhr) { deltaDisplayValue = '+' + deltaDisplayValue; } - // Insert the BG value text. - $('#bgnow').html(displayValue); - - // Insert the trend arrow. - $('#arrow').attr('src', '/images/' + (!rec.direction || rec.direction === 'NOT COMPUTABLE' ? 'NONE' : rec.direction) + '.svg'); - - // Time before data considered stale. - let staleMinutes = 13; - let threshold = 1000 * 60 * staleMinutes; - - // Toggle stale if necessary. - $('#bgnow').toggleClass('stale', (now - last > threshold)); + // Insert the delta value text. + $('.dt').html(deltaDisplayValue); // Generate and insert the clock. let timeDivisor = parseInt(client.settings.timeFormat ? client.settings.timeFormat : 12, 10); @@ -105,51 +133,24 @@ client.render = function render (xhr) { } let m = today.getMinutes(); if (m < 10) m = "0" + m; - $('#clock').text(h + ":" + m); - - /* global clockFace */ - if (clockFace === 'clock-color') { - - var bgHigh = window.serverSettings.settings.thresholds.bgHigh; - var bgLow = window.serverSettings.settings.thresholds.bgLow; - var bgTargetBottom = window.serverSettings.settings.thresholds.bgTargetBottom; - var bgTargetTop = window.serverSettings.settings.thresholds.bgTargetTop; + $('.tm').html(h + ":" + m); - var bgNum = parseFloat(rec.sgv); + // Color background + if (bgColor) { // These are the particular shades of red, yellow, green, and blue. - var red = 'rgba(213,9,21,1)'; - var yellow = 'rgba(234,168,0,1)'; - var green = 'rgba(134,207,70,1)'; - var blue = 'rgba(78,143,207,1)'; - - var elapsedMins = Math.round(((now - last) / 1000) / 60); - - // Insert the BG stale time text. - let staleTimeText; - if (elapsedMins == 0) { - staleTimeText = 'Just now'; - } - else if (elapsedMins == 1) { - staleTimeText = '1 minute ago'; - } - else { - staleTimeText = elapsedMins + ' minutes ago'; - } - $('#staleTime').text(staleTimeText); - - // Force NS to always show 'x minutes ago' - if (window.serverSettings.settings.showClockLastTime) { - $('#staleTime').css('display', 'block'); - } + let red = 'rgba(213,9,21,1)'; + let yellow = 'rgba(234,168,0,1)'; + let green = 'rgba(134,207,70,1)'; + let blue = 'rgba(78,143,207,1)'; - // Insert the delta value text. - $('#delta').html(deltaDisplayValue); + // Threshold values + let bgHigh = client.settings.thresholds.bgHigh; + let bgLow = client.settings.thresholds.bgLow; + let bgTargetBottom = client.settings.thresholds.bgTargetBottom; + let bgTargetTop = client.settings.thresholds.bgTargetTop; - // Show delta - if (window.serverSettings.settings.showClockDelta) { - $('#delta').css('display', 'inline-block'); - } + let bgNum = parseFloat(rec.sgv); // Threshold background coloring. if (bgNum < bgLow) { @@ -168,25 +169,52 @@ client.render = function render (xhr) { $('body').css('background-color', red); } - // Restyle body bg, and make the "x minutes ago" visible too. - if (now - last > threshold) { - $('body').css('background-color', 'grey'); - $('body').css('color', 'black'); - $('#arrow').css('filter', 'brightness(0%)'); + } + else { + $('body').css('background-color', 'black'); + } - if (!window.serverSettings.settings.showClockLastTime) { - $('#staleTime').css('display', 'block'); - } + // Time before data considered stale. + let threshold = 1000 * 60 * staleMinutes; - } else { - $('body').css('color', 'white'); - $('#arrow').css('filter', 'brightness(100%)'); + let last = new Date(rec.date); + let now = new Date(); + + let elapsedMins = Math.round(((now - last) / 1000) / 60); + + let thresholdReached = (now - last > threshold) && threshold > 0; - if (!window.serverSettings.settings.showClockLastTime) { - $('#staleTime').css('display', 'none'); - } + // Insert the BG value text, toggle stale if necessary. + $('.sg').toggleClass('stale', thresholdReached).html(displayValue); + if (thresholdReached || alwaysShowTime) { + let staleTimeText; + if (elapsedMins === 0) { + staleTimeText = 'Just now'; + } + else if (elapsedMins === 1) { + staleTimeText = '1 minute ago'; + } + else { + staleTimeText = elapsedMins + ' minutes ago'; } + + $('.ag').html(staleTimeText); + } + else { + $('.ag').html(''); + } + + // Insert the trend arrow. + let arrow = $('arrow').attr('src', '/images/' + (!rec.direction || rec.direction === 'NOT COMPUTABLE' ? 'NONE' : rec.direction) + '.svg'); + + // Restyle body bg + if (thresholdReached) { + $('body').css('background-color', 'grey').css('color', 'black'); + $('.ar').css('filter', 'brightness(0%)').html(arrow); + } else { + $('body').css('color', bgColor ? 'white' : 'grey'); + $('.ar').css('filter', bgColor ? 'brightness(100%)' : 'brightness(50%)').html(arrow); } }; diff --git a/lib/server/clocks.js b/lib/server/clocks.js index 56bce7b6f13..9926eefcf82 100644 --- a/lib/server/clocks.js +++ b/lib/server/clocks.js @@ -17,7 +17,7 @@ function clockviews() { const face = req.params.face; console.log('Clockface requested:', face); - res.render('shared.html', { + res.render('clock.html', { face, locals }); diff --git a/views/clockviews/bgclock.css b/views/clockviews/bgclock.css deleted file mode 100644 index 3e2cbef246f..00000000000 --- a/views/clockviews/bgclock.css +++ /dev/null @@ -1,28 +0,0 @@ -.inner { - -webkit-transform: translateY(-2%); -} - -#bgnow, #arrowDiv { - display: flex; - flex-grow: 0; - font-weight: 700; - font-size: 30vmin; - padding: 0 20px; - margin: 0; -} - -img#arrow { - height: 18vmin; - filter: brightness(50%); - -webkit-transform: translateY(5%); -} - -#clock { - font-weight: 700; - font-size: 25vmin; - display: inline; -} - -.stale { - text-decoration: line-through; -} \ No newline at end of file diff --git a/views/clockviews/clock-color.css b/views/clockviews/clock-color.css deleted file mode 100644 index 6a6796ef823..00000000000 --- a/views/clockviews/clock-color.css +++ /dev/null @@ -1,32 +0,0 @@ -body { - color: white; -} - -#trend { - -webkit-transform: translateX(1%); - -webkit-flex-direction: column; - flex-direction: column; -} - -#bgnow { - display: inline-block; - vertical-align: middle; -} - -#delta { - font-size: 16vmin; - vertical-align: middle; -} - -#innerTrend { - word-spacing: 2em; -} - -#arrowDiv { - flex-grow: 1; - text-align: center; -} - -img#arrow { - height: 30vmin; -} \ No newline at end of file diff --git a/views/clockviews/clock-config.css b/views/clockviews/clock-config.css new file mode 100644 index 00000000000..0d5f2b8b912 --- /dev/null +++ b/views/clockviews/clock-config.css @@ -0,0 +1,39 @@ +#config-form { + position: fixed; + top: 10px; + left: 10px; + width: 250px; + min-width: 220px; + background: white; + color: black; + opacity: 0.8; + padding: 1%; + font-size: 10px; +} +#config-form p { + margin: 15px; + text-align: left; +} +input.elmt { + width: 120px; +} +select { + width: 100%; +} +#facename { + font-size: 7px; +} +#clocklink { + font-size: 18px; +} +#clocklink:link, #clocklink:visited { + background-color: #f44336; + color: white; + padding: 14px 25px; + text-align: center; + text-decoration: none; + display: inline-block; +} +#clocklink:hover, #clocklink:active { + background-color: red; +} \ No newline at end of file diff --git a/views/clockviews/clock-config.html b/views/clockviews/clock-config.html new file mode 100644 index 00000000000..fff92bb65cb --- /dev/null +++ b/views/clockviews/clock-config.html @@ -0,0 +1,65 @@ +
+

Clock view configurator

+
+

+ + +

+

+ + +

+

SGV age threshold: minutes

+

+

Size:

+

Size:

+

Size:

+

Size:

+

Size:

+

+ Open my clock view! +
cy10
+
+
+ \ No newline at end of file diff --git a/views/clockviews/clock-shared.css b/views/clockviews/clock-shared.css index 83328fe4114..fc624a52c00 100644 --- a/views/clockviews/clock-shared.css +++ b/views/clockviews/clock-shared.css @@ -3,57 +3,50 @@ body { margin: 0 0; padding: 0; overflow: hidden; - font-family: 'Open Sans'; + font-family: 'Open Sans', Arial, Helvetica, sans-serif; color: grey; background-color: black; } main { + height: 100vh; +} + +#inner { display: -webkit-box; display: -ms-flexbox; display: -webkit-flex; display: flex; - -webkit-box-align: center; - -ms-flex-align: center; - -webkit-align-items: center; align-items: center; - height: 100vh; -} - -.inner { + justify-content: center; + align-content: center; + flex-flow: wrap; + height: 100%; width: 100%; - -webkit-transform: translateY(-5%); } -#bgnow { - font-weight: 700; - font-size: 40vmin; +#inner div { + margin-right: 2vmin; + margin-left: 2vmin; + line-height: 1em; } -#trend { - display: -ms-flexbox; - display: -webkit-flex; - display: flex; - -ms-flex-align: center; - -webkit-align-items: center; - align-items: center; - justify-content: center; - -webkit-flex-direction: row; - flex-direction: row; +#inner div img { + height: 100%; } -#staleTime { - flex-grow: 1; - font-size: 6vmin; - display: none; +#inner div.nl { + width: 100%; + margin: 0; + height: 3vmin; } -#clock { - display: none; +#errorMessage { + font-size: 25em; } -#delta { - display: none; +.stale { + text-decoration: line-through; } .close { diff --git a/views/clockviews/clock.css b/views/clockviews/clock.css deleted file mode 100644 index 96ffe68b84a..00000000000 --- a/views/clockviews/clock.css +++ /dev/null @@ -1,5 +0,0 @@ -#trend { - -webkit-transform: translateX(1%); - -webkit-flex-direction: column; - flex-direction: column; -} \ No newline at end of file diff --git a/views/clockviews/shared.html b/views/clockviews/clock.html similarity index 88% rename from views/clockviews/shared.html rename to views/clockviews/clock.html index beac7dc0f2e..2893aec3612 100644 --- a/views/clockviews/shared.html +++ b/views/clockviews/clock.html @@ -1,5 +1,5 @@ - + @@ -21,23 +21,16 @@
-
-
-
- - -
-
arrow
-
-
-
+
>
@@ -69,7 +62,7 @@ script.src = src; document.head.appendChild(script); //or something of the likes - + <%if (face !== 'config') { %> var buttonVisible = true; function hideClose () { @@ -99,8 +92,11 @@ window.addEventListener('click', function() { showClose(); }); - + <% } %> + <%if (face == 'config') { %> + <%- include('clock-config.html', {}); %> + <% } %> diff --git a/views/index.html b/views/index.html index 6ef0dc2cce6..617d7dd8e67 100644 --- a/views/index.html +++ b/views/index.html @@ -174,6 +174,7 @@ Clock Color Simple + [+]
From 5698ae28c131c1562c62f194f084dfa7b41b643b Mon Sep 17 00:00:00 2001 From: josep1972 Date: Fri, 12 Jun 2020 09:33:17 -0500 Subject: [PATCH 068/104] Add remote bolus/carbs + otp entry for loop (#5598) Add a remote bolus entry field for users on Loop, along with support for an OTP field --- lib/client/careportal.js | 31 +++++++++++++++++++++++++++++- lib/plugins/loop.js | 21 ++++++++++++++++++++ lib/server/loop.js | 41 +++++++++++++++++++++++++++++++++++----- test | 0 views/index.html | 20 +++++++++++++++++++- 5 files changed, 106 insertions(+), 7 deletions(-) create mode 100644 test diff --git a/lib/client/careportal.js b/lib/client/careportal.js index 632ab24986f..bff59a821c0 100644 --- a/lib/client/careportal.js +++ b/lib/client/careportal.js @@ -52,7 +52,7 @@ function init (client, $) { submitHooks = {}; _.forEach(careportal.allEventTypes, function each (event) { - inputMatrix[event.val] = _.pick(event, ['bg', 'insulin', 'carbs', 'protein', 'fat', 'prebolus', 'duration', 'percent', 'absolute', 'profile', 'split', 'reasons', 'targets']); + inputMatrix[event.val] = _.pick(event, ['otp','remoteCarbs', 'remoteAbsorption', 'remoteBolus', 'bg', 'insulin', 'carbs', 'protein', 'fat', 'prebolus', 'duration', 'percent', 'absolute', 'profile', 'split', 'reasons', 'targets']); submitHooks[event.val] = event.submitHook; }); } @@ -80,11 +80,18 @@ function init (client, $) { $('#reasonLabel').css('display', displayType(reasons && reasons.length > 0)); $('#targets').css('display', displayType(inputMatrix[eventType]['targets'])); + $('#otpLabel').css('display', displayType(inputMatrix[eventType]['otp'])); + $('#remoteCarbsLabel').css('display', displayType(inputMatrix[eventType]['remoteCarbs'])); + $('#remoteAbsorptionLabel').css('display', displayType(inputMatrix[eventType]['remoteAbsorption'])); + $('#remoteBolusLabel').css('display', displayType(inputMatrix[eventType]['remoteBolus'])); + $('#bg').css('display', displayType(inputMatrix[eventType]['bg'])); $('#insulinGivenLabel').css('display', displayType(inputMatrix[eventType]['insulin'])); + $('#carbsGivenLabel').css('display', displayType(inputMatrix[eventType]['carbs'])); $('#proteinGivenLabel').css('display', displayType(inputMatrix[eventType]['protein'])); $('#fatGivenLabel').css('display', displayType(inputMatrix[eventType]['fat'])); + $('#durationLabel').css('display', displayType(inputMatrix[eventType]['duration'])); $('#percentLabel').css('display', displayType(inputMatrix[eventType]['percent'] && $('#absolute').val() === '')); $('#absoluteLabel').css('display', displayType(inputMatrix[eventType]['absolute'] && $('#percent').val() === '')); @@ -99,6 +106,11 @@ function init (client, $) { careportal.reasonable(); + resetIfHidden(inputMatrix[eventType]['otp'], '#otp'); + resetIfHidden(inputMatrix[eventType]['remoteCarbs'], '#remoteCarbs'); + resetIfHidden(inputMatrix[eventType]['remoteAbsorption'], '#remoteAbsorption'); + resetIfHidden(inputMatrix[eventType]['remoteBolus'], '#remoteBolus'); + resetIfHidden(inputMatrix[eventType]['insulin'], '#insulinGiven'); resetIfHidden(inputMatrix[eventType]['carbs'], '#carbsGiven'); resetIfHidden(inputMatrix[eventType]['protein'], '#proteinGiven'); @@ -192,6 +204,12 @@ function init (client, $) { $('#eventType').val(''); $('#glucoseValue').val('').attr('placeholder', translate('Value in') + ' ' + client.settings.units); $('#meter').prop('checked', true); + + $('#otp').val(''); + $('#remoteCarbs').val(''); + $('#remoteAbsorption').val(''); + $('#remoteBolus').val(''); + $('#carbsGiven').val(''); $('#proteinGiven').val(''); $('#fatGiven').val(''); @@ -214,6 +232,10 @@ function init (client, $) { var data = { enteredBy: $('#enteredBy').val() , eventType: eventType + , otp: $('#otp').val() + , remoteCarbs: $('#remoteCarbs').val() + , remoteAbsorption: $('#remoteAbsorption').val() + , remoteBolus: $('#remoteBolus').val() , glucose: $('#glucoseValue').val().replace(',', '.') , reason: selectedReason , targetTop: $('#targetTop').val().replace(',', '.') @@ -337,6 +359,8 @@ function init (client, $) { } } + // TODO: add check for remote (Bolus, Carbs, Absorption) + return { allOk , messages @@ -360,6 +384,11 @@ function init (client, $) { text[text.length - 1] += ' ' + translate('Cancel'); } + pushIf(data.remoteCarbs, translate('Remote Carbs') + ': ' + data.remoteCarbs); + pushIf(data.remoteAbsorption, translate('Remote Absorption') + ': ' + data.remoteAbsorption); + pushIf(data.remoteBolus, translate('Remote Bolus') + ': ' + data.remoteBolus); + pushIf(data.otp, translate('One Time Pascode') + ': ' + data.otp); + pushIf(data.glucose, translate('Blood Glucose') + ': ' + data.glucose); pushIf(data.glucose, translate('Measurement Method') + ': ' + translate(data.glucoseType)); diff --git a/lib/plugins/loop.js b/lib/plugins/loop.js index 9b099dd188c..46d16738787 100644 --- a/lib/plugins/loop.js +++ b/lib/plugins/loop.js @@ -214,6 +214,8 @@ function init (ctx) { }); } + // TODO: add OTP entry + return [ { val: 'Temporary Override' @@ -229,6 +231,7 @@ function init (ctx) { , split: false , targets: false , reasons: reasonconf + , otp: true , submitHook: postLoopNotification }, { @@ -245,10 +248,28 @@ function init (ctx) { , split: false , targets: false , submitHook: postLoopNotification + }, + { + val: 'Remote Carbs Entry' + , name: 'Remote Carbs Entry' + , remoteCarbs: true + , remoteAbsorption: true + , otp: true + , submitHook: postLoopNotification + }, + { + val: 'Remote Bolus Entry' + , name: 'Remote Bolus Entry' + , remoteBolus: true + , otp: true + , submitHook: postLoopNotification } ]; }; + // TODO: Add event listener to customize labels + + loop.updateVisualisation = function updateVisualisation (sbx) { var prop = sbx.properties.loop; diff --git a/lib/server/loop.js b/lib/server/loop.js index a5e3aec5d8e..ca87892bba6 100644 --- a/lib/server/loop.js +++ b/lib/server/loop.js @@ -1,4 +1,4 @@ -'use strict'; +//'use strict'; const apn = require('apn'); @@ -9,6 +9,10 @@ function init (env, ctx) { } loop.sendNotification = function sendNotification (data, remoteAddress, completion) { + + // console.info("JAP"); + // console.info(data); + if (env.extendedSettings.loop.apnsKey === undefined || env.extendedSettings.loop.apnsKey.length == 0) { completion("Loop notification failed: LOOP_APNS_KEY not set."); return; @@ -63,7 +67,38 @@ function init (env, ctx) { alert = "Cancel Temporary Override"; } else if (data.eventType === 'Temporary Override') { payload["override-name"] = data.reason; + if (data.duration !== undefined && parseInt(data.duration) > 0) { + payload["override-duration-minutes"] = parseInt(data.duration); + } alert = data.reasonDisplay + " Temporary Override"; + } else if (data.eventType === 'Remote Carbs Entry') { + payload["carbs-entry"] = parseFloat(data.remoteCarbs); + if(payload["carbs-entry"] > 0.0 ) { + payload["absorption-time"] = 3.0; + if (data.remoteAbsorption !== undefined && parseFloat(data.remoteAbsorption) > 0.0) { + payload["absorption-time"] = parseFloat(data.remoteAbsorption); + } + if (data.otp !== undefined && data.otp.length > 0) { + payload["otp"] = ""+data.otp + } + alert = "Remote Carbs Entry: "+payload["carbs-entry"]+" grams\n"; + alert += "Absorption Time: "+payload["absorption-time"]+" hours"; + } else { + completion("Loop remote carbs failed. Incorrect carbs entry: ", data.remoteCarbs); + return; + } + + } else if (data.eventType === 'Remote Bolus Entry') { + payload["bolus-entry"] = parseFloat(data.remoteBolus); + if(payload["bolus-entry"] > 0.0 ) { + alert = "Remote Bolus Entry: "+payload["bolus-entry"]+" U\n"; + if (data.otp !== undefined && data.otp.length > 0) { + payload["otp"] = ""+data.otp + } + } else { + completion("Loop remote bolus failed. Incorrect bolus entry: ", data.remoteBolus); + return; + } } else { completion("Loop notification failed: Unhandled event type:", data.eventType); return; @@ -84,10 +119,6 @@ function init (env, ctx) { notification.expiry = Math.round((Date.now() / 1000)) + 60 * 5; // Allow this to enact within 5 minutes. notification.payload = payload; - if (data.duration && parseInt(data.duration) > 0) { - notification.payload["override-duration-minutes"] = parseInt(data.duration); - } - provider.send(notification, [loopSettings.deviceToken]).then( (response) => { if (response.sent && response.sent.length > 0) { completion(); diff --git a/test b/test new file mode 100644 index 00000000000..e69de29bb2d diff --git a/views/index.html b/views/index.html index 617d7dd8e67..c0f6b31613e 100644 --- a/views/index.html +++ b/views/index.html @@ -340,7 +340,24 @@ Sensor
-
+ + + + + +
+