From 8c98c1d782dc326ad0bc36b049c91160a35ac6e0 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 13 Nov 2014 23:45:34 -0800 Subject: [PATCH 001/937] simple pushover based alarms, ported from old pebble code --- lib/entries.js | 156 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 132 insertions(+), 24 deletions(-) diff --git a/lib/entries.js b/lib/entries.js index c8f8eb7a64b..e864ac98619 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -3,7 +3,11 @@ var es = require('event-stream'); var sgvdata = require('sgvdata'); -var TEN_MINS = 10 * 60 * 1000; +// declare local constants for time differences +var TIME_10_MINS = 10 * 60 * 1000, + TIME_15_MINS = 15 * 60 * 1000, + TIME_30_MINS = TIME_15_MINS * 2; + /**********\ * Entries @@ -102,6 +106,7 @@ function storage(name, storage, pushover) { var firstErr = null, totalCreated = 0; + console.info("docs: " + JSON.stringify(docs)); docs.forEach(function(doc) { collection.update(doc, doc, {upsert: true}, function (err, created) { firstErr = firstErr || err; @@ -113,31 +118,134 @@ function storage(name, storage, pushover) { }); } - //currently the Android upload will send the last MBG over and over, make sure we get a single notification - var lastMBG = 0; - function sendPushover(doc) { - if (doc.type && doc.mbg && doc.type == 'mbg' && doc.date && doc.date != lastMBG && pushover) { - var offset = new Date().getTime() - doc.date; - if (offset > TEN_MINS) { - console.info('No MBG Pushover, offset: ' + offset + ' too big, doc.date: ' + doc.date + ', now: ' + new Date().getTime()); - } else { - var msg = { - expire: 14400, // 4 hours - message: '\nMeter BG: ' + doc.mbg, - title: 'Calibration', - sound: 'magic', - timestamp: new Date(doc.date), - priority: 0, - retry: 30 - }; - - pushover.send(msg, function (err, result) { - console.log(result); - }); - } - lastMBG = doc.date; + if (doc.type && doc.date && pushover) { + if (doc.type == 'mbg') { + sendMBGPushover(doc); + } else if (doc.type == 'sgv') { + sendSGVPushover(doc); + } else { + console.info("Not an MBG or SGV: " + JSON.stringify(doc)); } + } else { + console.info("What are you: " + JSON.stringify(doc)); + } + } + + //currently the Android upload will send the last MBG over and over, make sure we get a single notification + var lastMBGDate = 0; + + function sendMBGPushover(doc) { + + if (doc.mbg && doc.type == 'mbg' && doc.date != lastMBGDate) { + var offset = new Date().getTime() - doc.date; + if (offset > TIME_10_MINS) { + console.info('No MBG Pushover, offset: ' + offset + ' too big, doc.date: ' + doc.date + ', now: ' + new Date().getTime()); + } else { + var msg = { + expire: 14400, // 4 hours + message: '\nMeter BG: ' + doc.mbg, + title: 'Calibration', + sound: 'magic', + timestamp: new Date(doc.date), + priority: 0, + retry: 30 + }; + + pushover.send(msg, function (err, result) { + console.log(result); + }); + } + lastMBGDate = doc.date; + } + } + + // global variable for last alert time + var lastAlert = 0; + var lastSGVDate = 0; + + function sendSGVPushover(doc) { + + if (!doc.sgv || doc.type != 'sgv') { + console.info("Not an SGV: " + JSON.stringify(doc)); + return; + } + + var now = new Date().getTime(), + offset = new Date().getTime() - doc.date; + + if (offset > TIME_10_MINS || doc.date == lastSGVDate) { + console.info('No SVG Pushover, offset: ' + offset + ' too big, doc.date: ' + doc.date + ', now: ' + new Date().getTime() + ', lastSGVDate: ' + lastSGVDate); + return; + } + + // initialize message data + var sinceLastAlert = now - lastAlert, + priority = 0, + sound = "bike", + readingtime = doc.date, + readago = now - readingtime; + + console.info("now: " + now); + console.info("doc.sgv: " + doc.sgv); + console.info("doc.direction: " + doc.direction); + console.info("doc.date: " + doc.date); + console.info("readingtime: " + readingtime); + console.info("readago: " + readago); + + // set vibration pattern; alert value; 0 nothing, 1 normal, 2 low, 3 high + if (doc.sgv < 39) { + if (sinceLastAlert > TIME_10_MINS) { + priority = 2; + sound = "siren"; + } + } else if (doc.sgv < 55) { + priority = 2; + sound = "spacealarm"; + } else if (doc.sgv < 70 && sinceLastAlert > TIME_15_MINS) { + priority = 1; + sound = "falling"; + } else if (doc.sgv < 120 && doc.direction == 'DoubleDown') { + priority = 1; + sound = "falling"; + } else if (doc.sgv == 100 && doc.direction == 'Flat' && sinceLastAlert > TIME_15_MINS) { //Perfect Score - a good time to take a picture :) + priority = 0; + sound = "cashregister"; + } else if (doc.sgv > 120 && doc.direction == 'DoubleUp' && sinceLastAlert > TIME_15_MINS) { + priority = 1; + sound = "intermission"; + } else if (doc.sgv > 250 && sinceLastAlert > TIME_30_MINS) { + priority = 1; + sound = "tugboat"; + } else if (doc.sgv > 300 && sinceLastAlert > TIME_15_MINS) { + priority = 2; + sound = "climb"; + } + + if (priority === 0 && readago > TIME_15_MINS && sinceLastAlert > TIME_30_MINS) { + priority = 1; + sound = "cosmic"; + } + + if (priority > 0) { + lastAlert = now; + } + + var msg = { + expire: 14400, // 4 hours + message: '\BG NOW: ' + doc.sgv, + title: 'CGM Alert', + sound: sound, + timestamp: new Date(doc.date), + priority: priority, + retry: 30 + }; + + pushover.send(msg, function (err, result) { + console.log(result); + }); + + lastSGVDate = doc.date; } function getEntry(fn, id) { From e89714f8a48c5b5e047f3ddf338b3a96aff48a22 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 13 Nov 2014 23:49:39 -0800 Subject: [PATCH 002/937] removed some extra info's --- lib/entries.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/entries.js b/lib/entries.js index e864ac98619..f05a3e0f886 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -106,7 +106,6 @@ function storage(name, storage, pushover) { var firstErr = null, totalCreated = 0; - console.info("docs: " + JSON.stringify(docs)); docs.forEach(function(doc) { collection.update(doc, doc, {upsert: true}, function (err, created) { firstErr = firstErr || err; @@ -124,11 +123,7 @@ function storage(name, storage, pushover) { sendMBGPushover(doc); } else if (doc.type == 'sgv') { sendSGVPushover(doc); - } else { - console.info("Not an MBG or SGV: " + JSON.stringify(doc)); } - } else { - console.info("What are you: " + JSON.stringify(doc)); } } @@ -167,7 +162,6 @@ function storage(name, storage, pushover) { function sendSGVPushover(doc) { if (!doc.sgv || doc.type != 'sgv') { - console.info("Not an SGV: " + JSON.stringify(doc)); return; } From 6682d274b01920aceaa00cd12e26281638847bb7 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 14 Nov 2014 00:06:21 -0800 Subject: [PATCH 003/937] fixed bug that cause notification to be sent too often --- lib/entries.js | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/lib/entries.js b/lib/entries.js index f05a3e0f886..79d6f36ee39 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -176,7 +176,7 @@ function storage(name, storage, pushover) { // initialize message data var sinceLastAlert = now - lastAlert, priority = 0, - sound = "bike", + sound = null, readingtime = doc.date, readago = now - readingtime; @@ -216,28 +216,29 @@ function storage(name, storage, pushover) { sound = "climb"; } - if (priority === 0 && readago > TIME_15_MINS && sinceLastAlert > TIME_30_MINS) { + if (sound == null && readago > TIME_15_MINS && sinceLastAlert > TIME_30_MINS) { priority = 1; sound = "cosmic"; } - if (priority > 0) { + if (sound != null) { lastAlert = now; + + var msg = { + expire: 14400, // 4 hours + message: '\BG NOW: ' + doc.sgv, + title: 'CGM Alert', + sound: sound, + timestamp: new Date(doc.date), + priority: priority, + retry: 30 + }; + + pushover.send(msg, function (err, result) { + console.log(result); + }); } - var msg = { - expire: 14400, // 4 hours - message: '\BG NOW: ' + doc.sgv, - title: 'CGM Alert', - sound: sound, - timestamp: new Date(doc.date), - priority: priority, - retry: 30 - }; - - pushover.send(msg, function (err, result) { - console.log(result); - }); lastSGVDate = doc.date; } From 82fb83b23077bb1cd74011b317e17baffd2affb2 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 16 Nov 2014 18:12:55 -0800 Subject: [PATCH 004/937] really basic template to post sgvs --- bin/post-sgv.sh | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100755 bin/post-sgv.sh diff --git a/bin/post-sgv.sh b/bin/post-sgv.sh new file mode 100755 index 00000000000..9b48e5dfcc9 --- /dev/null +++ b/bin/post-sgv.sh @@ -0,0 +1,9 @@ +#!/bin/sh +# "date": "1413782506964" + +curl -H "Content-Type: application/json" -H "api-secret: $API_SECRET" -XPOST 'http://localhost:1337/api/v1/entries/' -d '{ + "sgv": 100, + "type": "sgv", + "direction": "Flat", + "date": "1415950912800" +}' From 79c1e5ebd87949ed0aa68f872e6a5ff45d6aafa9 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 22 Nov 2014 07:13:58 -0700 Subject: [PATCH 005/937] minor push notification changes --- lib/entries.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/entries.js b/lib/entries.js index 79d6f36ee39..acd5d43a3dd 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -189,11 +189,11 @@ function storage(name, storage, pushover) { // set vibration pattern; alert value; 0 nothing, 1 normal, 2 low, 3 high if (doc.sgv < 39) { - if (sinceLastAlert > TIME_10_MINS) { - priority = 2; + if (sinceLastAlert > TIME_15_MINS) { + priority = 1; sound = "siren"; } - } else if (doc.sgv < 55) { + } else if (doc.sgv < 55 && sinceLastAlert > TIME_15_MINS) { priority = 2; sound = "spacealarm"; } else if (doc.sgv < 70 && sinceLastAlert > TIME_15_MINS) { @@ -211,8 +211,8 @@ function storage(name, storage, pushover) { } else if (doc.sgv > 250 && sinceLastAlert > TIME_30_MINS) { priority = 1; sound = "tugboat"; - } else if (doc.sgv > 300 && sinceLastAlert > TIME_15_MINS) { - priority = 2; + } else if (doc.sgv > 300 && sinceLastAlert > TIME_30_MINS) { + priority = 1; sound = "climb"; } From c3ce249c7da36a0ab5b29dfdf415e3e6926b5c92 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 1 Dec 2014 17:34:52 -0800 Subject: [PATCH 006/937] try some new sounds --- lib/entries.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/entries.js b/lib/entries.js index acd5d43a3dd..7256720eb22 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -195,7 +195,7 @@ function storage(name, storage, pushover) { } } else if (doc.sgv < 55 && sinceLastAlert > TIME_15_MINS) { priority = 2; - sound = "spacealarm"; + sound = "persistent"; } else if (doc.sgv < 70 && sinceLastAlert > TIME_15_MINS) { priority = 1; sound = "falling"; @@ -210,10 +210,10 @@ function storage(name, storage, pushover) { sound = "intermission"; } else if (doc.sgv > 250 && sinceLastAlert > TIME_30_MINS) { priority = 1; - sound = "tugboat"; + sound = "climb"; } else if (doc.sgv > 300 && sinceLastAlert > TIME_30_MINS) { priority = 1; - sound = "climb"; + sound = "updown"; } if (sound == null && readago > TIME_15_MINS && sinceLastAlert > TIME_30_MINS) { From be30be173aebef97fca7b28b7dc34ad3a7f03669 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 13 Dec 2014 11:48:08 -0800 Subject: [PATCH 007/937] adjust push message title for extra information --- lib/entries.js | 48 ++++++++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/lib/entries.js b/lib/entries.js index 7256720eb22..60cb15db393 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -175,50 +175,54 @@ function storage(name, storage, pushover) { // initialize message data var sinceLastAlert = now - lastAlert, + title = 'CGM Alert', priority = 0, sound = null, readingtime = doc.date, readago = now - readingtime; - console.info("now: " + now); - console.info("doc.sgv: " + doc.sgv); - console.info("doc.direction: " + doc.direction); - console.info("doc.date: " + doc.date); - console.info("readingtime: " + readingtime); - console.info("readago: " + readago); + console.info('now: ' + now); + console.info('doc.sgv: ' + doc.sgv); + console.info('doc.direction: ' + doc.direction); + console.info('doc.date: ' + doc.date); + console.info('readingtime: ' + readingtime); + console.info('readago: ' + readago); // set vibration pattern; alert value; 0 nothing, 1 normal, 2 low, 3 high if (doc.sgv < 39) { - if (sinceLastAlert > TIME_15_MINS) { + if (sinceLastAlert > TIME_30_MINS) { + title = 'CGM Error'; priority = 1; - sound = "siren"; + sound = 'persistent'; } } else if (doc.sgv < 55 && sinceLastAlert > TIME_15_MINS) { + title = 'Low'; priority = 2; - sound = "persistent"; + sound = 'persistent'; } else if (doc.sgv < 70 && sinceLastAlert > TIME_15_MINS) { + title = 'Low'; priority = 1; - sound = "falling"; + sound = 'falling'; } else if (doc.sgv < 120 && doc.direction == 'DoubleDown') { + title = 'Falling'; priority = 1; - sound = "falling"; + sound = 'falling'; } else if (doc.sgv == 100 && doc.direction == 'Flat' && sinceLastAlert > TIME_15_MINS) { //Perfect Score - a good time to take a picture :) + title = 'Perfect'; priority = 0; - sound = "cashregister"; + sound = 'cashregister'; } else if (doc.sgv > 120 && doc.direction == 'DoubleUp' && sinceLastAlert > TIME_15_MINS) { + title = 'Rising'; priority = 1; - sound = "intermission"; + sound = 'intermission'; } else if (doc.sgv > 250 && sinceLastAlert > TIME_30_MINS) { + title = 'High'; priority = 1; - sound = "climb"; + sound = 'climb'; } else if (doc.sgv > 300 && sinceLastAlert > TIME_30_MINS) { + title = 'High'; priority = 1; - sound = "updown"; - } - - if (sound == null && readago > TIME_15_MINS && sinceLastAlert > TIME_30_MINS) { - priority = 1; - sound = "cosmic"; + sound = 'updown'; } if (sound != null) { @@ -226,8 +230,8 @@ function storage(name, storage, pushover) { var msg = { expire: 14400, // 4 hours - message: '\BG NOW: ' + doc.sgv, - title: 'CGM Alert', + message: 'BG NOW: ' + doc.sgv, + title: title, sound: sound, timestamp: new Date(doc.date), priority: priority, From fcfc0176cd3d39b342ac46352cac2a7c8e16bf57 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 20 Dec 2014 09:01:19 -0800 Subject: [PATCH 008/937] use new BG thresholds instead of hardcoded BGs --- lib/entries.js | 18 +++++++++--------- server.js | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/entries.js b/lib/entries.js index 60cb15db393..80bddbae1e5 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -14,7 +14,7 @@ var TIME_10_MINS = 10 * 60 * 1000, * Encapsulate persistent storage of sgv entries. \**********/ -function storage(name, storage, pushover) { +function storage(name, storage, pushover, env) { // TODO: Code is a little redundant. @@ -195,16 +195,16 @@ function storage(name, storage, pushover) { priority = 1; sound = 'persistent'; } - } else if (doc.sgv < 55 && sinceLastAlert > TIME_15_MINS) { - title = 'Low'; + } else if (doc.sgv < env.thresholds.bg_low && sinceLastAlert > TIME_15_MINS) { + title = 'Urgent Low'; priority = 2; sound = 'persistent'; - } else if (doc.sgv < 70 && sinceLastAlert > TIME_15_MINS) { + } else if (doc.sgv < env.thresholds.bg_target_bottom && sinceLastAlert > TIME_15_MINS) { title = 'Low'; priority = 1; sound = 'falling'; } else if (doc.sgv < 120 && doc.direction == 'DoubleDown') { - title = 'Falling'; + title = 'Double Down'; priority = 1; sound = 'falling'; } else if (doc.sgv == 100 && doc.direction == 'Flat' && sinceLastAlert > TIME_15_MINS) { //Perfect Score - a good time to take a picture :) @@ -212,15 +212,15 @@ function storage(name, storage, pushover) { priority = 0; sound = 'cashregister'; } else if (doc.sgv > 120 && doc.direction == 'DoubleUp' && sinceLastAlert > TIME_15_MINS) { - title = 'Rising'; + title = 'Double Up'; priority = 1; sound = 'intermission'; - } else if (doc.sgv > 250 && sinceLastAlert > TIME_30_MINS) { + } else if (doc.sgv > env.thresholds.bg_target_top && sinceLastAlert > TIME_30_MINS) { title = 'High'; priority = 1; sound = 'climb'; - } else if (doc.sgv > 300 && sinceLastAlert > TIME_30_MINS) { - title = 'High'; + } else if (doc.sgv > env.thresholds.bg_high && sinceLastAlert > TIME_30_MINS) { + title = 'Urgent High'; priority = 1; sound = 'updown'; } diff --git a/server.js b/server.js index 660540c3747..ad9d559611f 100644 --- a/server.js +++ b/server.js @@ -47,7 +47,7 @@ var express = require('express'); /////////////////////////////////////////////////// // api and json object variables /////////////////////////////////////////////////// -var entriesStorage = entries.storage(env.mongo_collection, store, pushover); +var entriesStorage = entries.storage(env.mongo_collection, store, pushover, env); var settings = require('./lib/settings')(env.settings_collection, store); var treatmentsStorage = treatments.storage(env.treatments_collection, store, pushover); var devicestatusStorage = devicestatus.storage(env.devicestatus_collection, store); From 0d44f0a81e197856d9eb820310077ec5951eaa80 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 12 Jan 2015 10:17:33 -0800 Subject: [PATCH 009/937] adjusted alarms sounds for double up and urgent high --- lib/entries.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/entries.js b/lib/entries.js index 80bddbae1e5..e899a526b98 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -214,7 +214,7 @@ function storage(name, storage, pushover, env) { } else if (doc.sgv > 120 && doc.direction == 'DoubleUp' && sinceLastAlert > TIME_15_MINS) { title = 'Double Up'; priority = 1; - sound = 'intermission'; + sound = 'climb'; } else if (doc.sgv > env.thresholds.bg_target_top && sinceLastAlert > TIME_30_MINS) { title = 'High'; priority = 1; @@ -222,7 +222,7 @@ function storage(name, storage, pushover, env) { } else if (doc.sgv > env.thresholds.bg_high && sinceLastAlert > TIME_30_MINS) { title = 'Urgent High'; priority = 1; - sound = 'updown'; + sound = 'persistent'; } if (sound != null) { From 4a0fc8ffef13ae957fb0570f5d58328a801addd5 Mon Sep 17 00:00:00 2001 From: Ben West Date: Tue, 24 Mar 2015 18:15:05 -0700 Subject: [PATCH 010/937] update API to support searching for all types Should enable searching for all data types. --- app.js | 2 +- lib/api/devicestatus/index.js | 6 ++- lib/api/entries/index.js | 87 ++++++++++++++++++++++++++++++----- lib/api/index.js | 12 ++--- lib/devicestatus.js | 5 +- lib/storage.js | 6 +++ lib/treatments.js | 2 +- tests/api.entries.test.js | 9 ++-- 8 files changed, 103 insertions(+), 26 deletions(-) diff --git a/app.js b/app.js index 272735bcb77..cab4dbe1655 100644 --- a/app.js +++ b/app.js @@ -5,7 +5,7 @@ function create (env, ctx) { /////////////////////////////////////////////////// // api and json object variables /////////////////////////////////////////////////// - var api = require('./lib/api/')(env, ctx.entries, ctx.settings, ctx.treatments, ctx.profiles, ctx.devicestatus); + var api = require('./lib/api/')(env, ctx); var pebble = ctx.pebble; var app = express(); diff --git a/lib/api/devicestatus/index.js b/lib/api/devicestatus/index.js index af67dc7b64e..40d3a3246d3 100644 --- a/lib/api/devicestatus/index.js +++ b/lib/api/devicestatus/index.js @@ -17,7 +17,11 @@ function configure (app, wares, devicestatus) { // List settings available api.get('/devicestatus/', function(req, res) { - devicestatus.list(function (err, profiles) { + var q = req.query; + if (!q.count) { + q.count = 10; + } + devicestatus.list(q, function (err, profiles) { return res.json(profiles); }); }); diff --git a/lib/api/entries/index.js b/lib/api/entries/index.js index 79642cb0f77..6c779fa115c 100644 --- a/lib/api/entries/index.js +++ b/lib/api/entries/index.js @@ -7,7 +7,8 @@ var sgvdata = require('sgvdata'); /**********\ * Entries \**********/ -function configure (app, wares, entries) { +function configure (app, wares, core) { + var entries = core.entries; var express = require('express'), api = express.Router( ) ; @@ -26,8 +27,24 @@ function configure (app, wares, entries) { // also support url-encoded content-type api.use(wares.bodyParser.urlencoded({ extended: true })); + function force_typed_data (opts) { + function sync (data, next) { + if (data.type != opts.type) { + console.warn('BAD DATA TYPE, setting', data.type, 'to', opts.type); + data.type = opts.type; + } + next(null, data); + } + return es.map(sync); + } + // Middleware to format any response involving entries. function format_entries (req, res, next) { + var type_params = { + type: (req.query && req.query.find && req.query.find.type + && req.query.find.type != req.params.model) + ? req.query.find.type : req.params.model + }; var output = es.readArray(res.entries || [ ]); if (res.entries_err) { return res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', res.entries_err); @@ -37,7 +54,7 @@ function configure (app, wares, entries) { es.pipeline(output, sgvdata.format( ), res); }, json: function ( ) { - es.pipeline(output, entries.map( ), es.writeArray(function (err, out) { + es.pipeline(output, force_typed_data(type_params), entries.map( ), es.writeArray(function (err, out) { res.json(out); })); } @@ -106,24 +123,70 @@ function configure (app, wares, entries) { es.pipeline(inputs( ), persist(done)); } - api.get('/entries', function(req, res, next) { - // If "?count=" is present, use that number to decided how many to return. - var query = req.query; - if (!query.count) { query.count = 10 }; - entries.list(query, function(err, entries) { - res.entries = entries; + api.param('model', function (req, res, next, model) { + var find = { }; + switch (model) { + case 'treatments': + case 'devicestatus': + case 'settings': + case 'profile': + req.model = core[model]; + // find.type = model; + break; + + case 'meter': + case 'mbg': + find.type = 'mbg'; + req.model = core.entries; + break; + case 'cal': + find.type = 'cal'; + req.model = core.entries; + break; + case 'sensor': + find.type = 'sensor'; + req.model = core.entries; + break; + case 'sgv': + find.type = 'sgv'; + req.model = core.entries; + break; + default: + req.model = core.entries; + break; + } + if (!req.query.find) { + req.query.find = find; + } else { + req.query.find.type = find.type; + } + next( ); + }); + api.get('/entries/current', function(req, res, next) { + entries.list({count: 1}, function(err, records) { + res.entries = records; res.entries_err = err; return next( ); }); }, format_entries); - api.get('/entries/current', function(req, res, next) { - entries.list({count: 1}, function(err, records) { - res.entries = records; + api.get('/entries/:model', query_models, format_entries); + api.get('/entries', query_models, format_entries); + + + function query_models (req, res, next) { + // If "?count=" is present, use that number to decided how many to return. + if (!req.model) { + req.model = core.entries; + } + var query = req.query; + if (!query.count) { query.count = 10 }; + req.model.list(query, function(err, entries) { + res.entries = entries; res.entries_err = err; return next( ); }); - }, format_entries); + } // Allow previewing your post content, just echos everything you diff --git a/lib/api/index.js b/lib/api/index.js index e666a71b6e0..04280c607ea 100644 --- a/lib/api/index.js +++ b/lib/api/index.js @@ -1,6 +1,6 @@ 'use strict'; -function create (env, entries, settings, treatments, profile, devicestatus) { +function create (env, core) { var express = require('express'), app = express( ) ; @@ -46,11 +46,11 @@ function create (env, entries, settings, treatments, profile, devicestatus) { } // Entries and settings - app.use('/', require('./entries/')(app, wares, entries)); - app.use('/', require('./settings/')(app, wares, settings)); - app.use('/', require('./treatments/')(app, wares, treatments)); - app.use('/', require('./profile/')(app, wares, profile)); - app.use('/', require('./devicestatus/')(app, wares, devicestatus)); + app.use('/', require('./entries/')(app, wares, core)); + app.use('/', require('./settings/')(app, wares, core.settings)); + app.use('/', require('./treatments/')(app, wares, core.treatments)); + app.use('/', require('./profile/')(app, wares, core.profile)); + app.use('/', require('./devicestatus/')(app, wares, core.devicestatus)); // Status app.use('/', require('./status')(app, wares)); diff --git a/lib/devicestatus.js b/lib/devicestatus.js index 2c1ec79d87e..00d9ce2d9f1 100644 --- a/lib/devicestatus.js +++ b/lib/devicestatus.js @@ -27,8 +27,9 @@ function storage (collection, storage) { }); } - function list(fn) { - return api().find({}).sort({created_at: -1}).toArray(fn); + function list(opts, fn) { + var q = opts && opts.find ? opts.find : { }; + return storage.limit.call(api().find(q).sort({created_at: -1}), opts).toArray(fn); } function api() { diff --git a/lib/storage.js b/lib/storage.js index d5b128a0592..0f8c50ba3fa 100644 --- a/lib/storage.js +++ b/lib/storage.js @@ -49,6 +49,12 @@ function init (env, cb) { }; }; + mongo.limit = function limit (opts) { + if (opts && opts.count) { + return this.limit(parseInt(opts.count)); + } + return this; + }; mongo.ensureIndexes = function(collection, fields) { fields.forEach(function (field) { console.info("ensuring index for: " + field); diff --git a/lib/treatments.js b/lib/treatments.js index cbf16d890e1..2db959b12f4 100644 --- a/lib/treatments.js +++ b/lib/treatments.js @@ -116,7 +116,7 @@ function storage (collection, storage, pushover) { return q; } - return api( ).find(find()).sort({created_at: -1}).toArray(fn); + return storage.limit.call(api().find(find( )).sort({created_at: -1}), opts).toArray(fn); } function api ( ) { diff --git a/tests/api.entries.test.js b/tests/api.entries.test.js index f3e368da7a6..5c906ae7cdf 100644 --- a/tests/api.entries.test.js +++ b/tests/api.entries.test.js @@ -10,13 +10,16 @@ describe('Entries REST api', function ( ) { this.wares = require('../lib/middleware/')(env); var store = require('../lib/storage')(env); this.archive = require('../lib/entries').storage(env.mongo_collection, store); + var bootevent = require('../lib/bootevent'); this.app = require('express')( ); this.app.enable('api'); var self = this; - store(function ( ) { - self.app.use('/', entries(self.app, self.wares, self.archive)); - self.archive.create(load('json'), done); + bootevent(env).boot(function booted (ctx) { + env.store = ctx.store; + self.app.use('/', entries(self.app, self.wares, ctx)); + self.archive.create(load('json'), done); }); + // store(function ( ) { }); }); after(function (done) { From 036ede01b8fb4dc41d55ae9f2940a731a905f8a1 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 19 Apr 2015 12:33:58 +0300 Subject: [PATCH 011/937] First pass at visualisation plugin architecture, using node modules with Browserify --- bundle/bundle.source.js | 27 ++++++++++++- bundle/index.js | 3 +- lib/boluswizardpreview.js | 58 ++++++++++++++++++++++++++++ lib/pluginbase.js | 17 ++++++++ lib/profilefunctions.js | 81 +++++++++++++++++++++++++++++++++++++++ package.json | 1 + static/js/client.js | 50 ++++++++++++++++++++++++ 7 files changed, 234 insertions(+), 3 deletions(-) create mode 100644 lib/boluswizardpreview.js create mode 100644 lib/pluginbase.js create mode 100644 lib/profilefunctions.js diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index e9c8f6e8dca..28b28f3177d 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -1,10 +1,33 @@ (function () { - + window.Nightscout = window.Nightscout || {}; window.Nightscout = { - iob: require('../lib/iob')() + iob: require('../lib/iob')(), + profile: require('../lib/profilefunctions')() + }; + + // Plugins + + var inherits = require("inherits"); + var PluginBase = require('../lib/pluginbase'); // Define any shared functionality in this class + + window.NightscoutPlugins = window.NightscoutPlugins || {}; + + window.NightscoutPlugins = { + bwp: require('../lib/boluswizardpreview')(PluginBase) }; + // class inheritance to the plugins from the base + map functions over + + for (var p in window.NightscoutPlugins) { + var plugin = window.NightscoutPlugins[p]; + inherits(plugin, PluginBase); + + for (var n in PluginBase.prototype) { + var item = PluginBase.prototype[n]; + plugin[n] = item; + } + } console.info("Nightscout bundle ready", window.Nightscout); diff --git a/bundle/index.js b/bundle/index.js index 5ea7ff424da..60f9bcf79ac 100644 --- a/bundle/index.js +++ b/bundle/index.js @@ -5,7 +5,8 @@ var browserify_express = require('browserify-express'); function bundle() { return browserify_express({ entry: __dirname + '/bundle.source.js', - watch: [__dirname + '../lib/', __dirname + '/bundle.source.js'], + watch: __dirname + '/../lib/', + // watch: [__dirname + '/../lib/', __dirname + '/bundle.source.js'], mount: '/public/js/bundle.js', verbose: true, //minify: true, diff --git a/lib/boluswizardpreview.js b/lib/boluswizardpreview.js new file mode 100644 index 00000000000..5be1d1b70db --- /dev/null +++ b/lib/boluswizardpreview.js @@ -0,0 +1,58 @@ +'use strict'; + +// class methods +function updateVisualisation() { + + var sgv = this.env.sgv; + var iob = this.env.iob; + + var pill = this.currentDetails.find('span.pill.bat'); + + if (!pill || pill.length == 0) { + pill = $(''); + this.currentDetails.append(pill); + } + + var bat = 0.0; + + sgv = Number(sgv)/18; + iob = Number(iob); + + // Above target -> calculate insulin dose against target_high + + if (sgv > this.profile.target_high) + { + var delta = sgv - this.profile.target_high; + bat = (delta / this.profile.sens) - iob; + } + + // between targets + + if (sgv >= this.profile.target_low && sgv <= this.profile.target_high && iob > 0) + { + // ... + } + + // Above target -> calculate insulin dose against target_low + + if (sgv < this.profile.target_low) + { + var delta = this.profile.target_low - sgv; + bat = 0-(delta / this.profile.sens) - iob; + } + + bat = Math.round(bat * 100) / 100; + pill.find('em').text(bat + 'U'); + +}; + + +function BWP(pluginBase) { + pluginBase.call(this); + + return { + updateVisualisation: updateVisualisation + }; +} + +module.exports = BWP; \ No newline at end of file diff --git a/lib/pluginbase.js b/lib/pluginbase.js new file mode 100644 index 00000000000..0bcb05f77b0 --- /dev/null +++ b/lib/pluginbase.js @@ -0,0 +1,17 @@ +'use strict'; + +function setEnv(env) { + this.profile = env.profile; + this.currentDetails = env.currentDetails; + this.env = env; +} + +function PluginBase() { + return { + setEnv: setEnv + }; +} + +PluginBase.prototype.setEnv = setEnv; + +module.exports = PluginBase; diff --git a/lib/profilefunctions.js b/lib/profilefunctions.js new file mode 100644 index 00000000000..ff684472b7f --- /dev/null +++ b/lib/profilefunctions.js @@ -0,0 +1,81 @@ +'use strict'; + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // multiple profile support for predictions + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + function timeStringToSeconds(time) { + var split = time.split(":"); + return parseInt(split[0])*3600 + parseInt(split[1])*60; + } + + // preprocess the timestamps to seconds for a couple orders of magnitude faster operation + function preprocessProfileOnLoad(container) + { + for (var key in container) { + var value = container[key]; + if( Object.prototype.toString.call(value) === '[object Array]' ) { + preprocessProfileOnLoad(value); + } else { + if (value.time) { + var sec = timeStringToSeconds(value.time); + if (!isNaN(sec)) value.timeAsSeconds = sec; + } + } + } + container.timestampsPreProcessed = true; + } + + + function getValueByTime(profile, time, valueContainer) + { + // If the container is an Array, assume it's a valid timestamped value container + + var returnValue = valueContainer; + + if( Object.prototype.toString.call(valueContainer) === '[object Array]' ) { + + var timeAsDate = new Date(time); + var timeAsSecondsFromMidnight = timeAsDate.getHours()*3600 + timeAsDate.getMinutes()*60; + + for (var t in valueContainer) { + var value = valueContainer[t]; + if (timeAsSecondsFromMidnight >= value.timeAsSeconds) { + returnValue = value.value; + } + } + } + + return returnValue; + } + + function getDIA(profile, time) + { + return getValueByTime(profile, time,profile.dia); + } + + function getSensitivity(profile, time) + { + return getValueByTime(profile, time,profile.sens); + } + + function getCarbRatio(profile, time) + { + return getValueByTime(profile, time,profile.carbratio); + } + + function getCarbAbsorptionRate(profile, time) + { + return getValueByTime(profile, time,profile.carbs_hr); + } + + +function Profile(opts) { + + return { + preprocessProfileOnLoad: preprocessProfileOnLoad + }; + +} + +module.exports = Profile; \ No newline at end of file diff --git a/package.json b/package.json index fd31ca30ef9..0ec1fd5c446 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "express": "^4.6.1", "express-extension-to-accept": "0.0.2", "git-rev": "git://github.com/bewest/git-rev.git", + "inherits": "~2.0.1", "long": "~2.2.3", "mongodb": "^1.4.7", "moment": "2.8.1", diff --git a/static/js/client.js b/static/js/client.js index c0a2dab5919..26e210d77e9 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -469,6 +469,32 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } + function updatePluginData(sgv, time) + { + var env = {}; + env.profile = profile; + env.currentDetails = currentDetails; + env.sgv = Number(sgv); + var iob = Nightscout.iob.calcTotal(treatments, profile, time); + env.iob = Number(iob.iob); + + for (var p in NightscoutPlugins) + { + var plugin = NightscoutPlugins[p]; + plugin.setEnv(env); + } + + } + + function updatePluginVisualisation() + { + for (var p in NightscoutPlugins) + { + var plugin = NightscoutPlugins[p]; + plugin.updateVisualisation(); + } + } + function updateIOBIndicator(time) { if (showIOB()) { var pill = currentDetails.find('span.pill.iob'); @@ -479,6 +505,20 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } var iob = Nightscout.iob.calcTotal(treatments, profile, time); pill.find('em').text(iob.display + 'U'); + +/* + if (typeof latestSGV !== 'undefined' && typeof latestSGV.y !== 'undefined') { + + var env = {}; + env.profile = profile; + env.currentDetails = currentDetails; + + //NightscoutPlugins.bwp.setEnv(env); + updatePluginData(time); + NightscoutPlugins.bwp.updateBATIndicator(Number(latestSGV.y), Number(iob.iob)); + } +*/ + } else { currentDetails.find('.pill.iob').remove(); } @@ -532,6 +572,11 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; bgButton.removeClass('urgent warning inrange'); } + // update plugins + + updatePluginData(focusPoint.y,retroTime); + updatePluginVisualisation(); + updateIOBIndicator(retroTime); $('#currentTime') @@ -568,6 +613,11 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; updateBGDelta(prevSGV, latestSGV); updateIOBIndicator(nowDate); + // update plugins + + updatePluginData(latestSGV.y,nowDate); + updatePluginVisualisation(); + currentDirection.html(latestSGV.y < 39 ? '✖' : latestSGV.direction); } From 24d00535a440d5ca809ea6d813c953cc434e8b31 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 19 Apr 2015 13:36:12 +0300 Subject: [PATCH 012/937] Plugins can now be data and visualisation providers. Moved IOB to be 100% a plugin --- bundle/bundle.source.js | 2 +- lib/boluswizardpreview.js | 5 ++- lib/iob.js | 26 +++++++++++- static/js/client.js | 85 +++++++++++++++++---------------------- 4 files changed, 68 insertions(+), 50 deletions(-) diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index ad5b40ae733..395221ee7ed 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -3,7 +3,6 @@ window.Nightscout = window.Nightscout || {}; window.Nightscout = { - iob: require('../lib/iob')(), units: require('../lib/units')(), profile: require('../lib/profilefunctions')() }; @@ -16,6 +15,7 @@ window.NightscoutPlugins = window.NightscoutPlugins || {}; window.NightscoutPlugins = { + iob: require('../lib/iob')(), bwp: require('../lib/boluswizardpreview')(PluginBase) }; // class inheritance to the plugins from the base + map functions over diff --git a/lib/boluswizardpreview.js b/lib/boluswizardpreview.js index 5be1d1b70db..3751ef05378 100644 --- a/lib/boluswizardpreview.js +++ b/lib/boluswizardpreview.js @@ -51,7 +51,10 @@ function BWP(pluginBase) { pluginBase.call(this); return { - updateVisualisation: updateVisualisation + updateVisualisation: updateVisualisation, + isDataProvider: false, + isVisualisationProvider: true + }; } diff --git a/lib/iob.js b/lib/iob.js index 1188d2249fe..6ea932dec51 100644 --- a/lib/iob.js +++ b/lib/iob.js @@ -1,5 +1,10 @@ 'use strict'; +function getData(environment,time) +{ + return calcTotal(environment.treatments,environment.profile,time); +} + function calcTotal(treatments, profile, time) { var iob = 0 @@ -67,10 +72,29 @@ function calcTreatment(treatment, profile, time) { } +function updateVisualisation() { + + var pill = this.currentDetails.find('span.pill.iob'); + + if (!pill || pill.length == 0) { + pill = $(''); + this.currentDetails.append(pill); + } + //var iob = Nightscout.iob.calcTotal(treatments, profile, time); + + pill.find('em').text(this.env.display + 'U'); + +} + + function IOB(opts) { return { - calcTotal: calcTotal + calcTotal: calcTotal, + getData: getData, + updateVisualisation: updateVisualisation, + isDataProvider: true, + isVisualisationProvider: true }; } diff --git a/static/js/client.js b/static/js/client.js index 7aba4635028..d3fbbdcbc27 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -222,11 +222,6 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; return Math.round(raw); } - function showIOB() { - return app.enabledOptions - && app.enabledOptions.indexOf('iob') > -1; - } - // initial setup of chart when data is first made available function initializeCharts() { @@ -468,60 +463,59 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } - function updatePluginData(sgv, time) - { + // PLUGIN MANAGEMENT CODE + + function updatePluginData(sgv, time) { var env = {}; env.profile = profile; env.currentDetails = currentDetails; env.sgv = Number(sgv); - var iob = Nightscout.iob.calcTotal(treatments, profile, time); - env.iob = Number(iob.iob); + +/* + return app.enabledOptions + && app.enabledOptions.indexOf('iob') > -1; + + +*/ + + // get additional data from data providers - for (var p in NightscoutPlugins) - { + for (var p in NightscoutPlugins) { + var plugin = NightscoutPlugins[p]; + + var dataProviderEnvironment = {}; + + dataProviderEnvironment.treatments = treatments; + dataProviderEnvironment.profile = profile; + + if (plugin.isDataProvider) { + var dataFromPlugin = plugin.getData(dataProviderEnvironment,time); + for (var i in dataFromPlugin) { + env[i] = dataFromPlugin[i]; + } + } + plugin.setEnv(env); + } + + // update data inside the plugins + + for (var p in NightscoutPlugins) { var plugin = NightscoutPlugins[p]; plugin.setEnv(env); } } - function updatePluginVisualisation() - { - for (var p in NightscoutPlugins) - { + function updatePluginVisualisation() { + for (var p in NightscoutPlugins) { var plugin = NightscoutPlugins[p]; - plugin.updateVisualisation(); + if (plugin.isVisualisationProvider) { + plugin.updateVisualisation(); + } } } - function updateIOBIndicator(time) { - if (showIOB()) { - var pill = currentDetails.find('span.pill.iob'); - - if (!pill || pill.length == 0) { - pill = $(''); - currentDetails.append(pill); - } - var iob = Nightscout.iob.calcTotal(treatments, profile, time); - pill.find('em').text(iob.display + 'U'); - -/* - if (typeof latestSGV !== 'undefined' && typeof latestSGV.y !== 'undefined') { - - var env = {}; - env.profile = profile; - env.currentDetails = currentDetails; - - //NightscoutPlugins.bwp.setEnv(env); - updatePluginData(time); - NightscoutPlugins.bwp.updateBATIndicator(Number(latestSGV.y), Number(iob.iob)); - } -*/ - - } else { - currentDetails.find('.pill.iob').remove(); - } - } + /// END PLUGIN CODE // predict for retrospective data // by changing lookback from 1 to 2, we modify the AR algorithm to determine its initial slope from 10m @@ -576,8 +570,6 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; updatePluginData(focusPoint.y,retroTime); updatePluginVisualisation(); - updateIOBIndicator(retroTime); - $('#currentTime') .text(formatTime(retroTime, true)) .css('text-decoration','line-through'); @@ -610,7 +602,6 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } updateBGDelta(prevSGV, latestSGV); - updateIOBIndicator(nowDate); // update plugins From ad6d286e574c02134bcf5c773f74f0efc19dbb82 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 19 Apr 2015 13:51:46 +0300 Subject: [PATCH 013/937] Plugins now check if they've been enabled (in addition to being mapped) --- static/js/client.js | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index d3fbbdcbc27..fd4217a97a2 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -464,6 +464,11 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } // PLUGIN MANAGEMENT CODE + + function isPluginEnabled(name) { + return app.enabledOptions + && app.enabledOptions.indexOf(name) > -1; + } function updatePluginData(sgv, time) { var env = {}; @@ -471,30 +476,27 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; env.currentDetails = currentDetails; env.sgv = Number(sgv); -/* - return app.enabledOptions - && app.enabledOptions.indexOf('iob') > -1; - - -*/ - // get additional data from data providers for (var p in NightscoutPlugins) { - var plugin = NightscoutPlugins[p]; - var dataProviderEnvironment = {}; + if (isPluginEnabled(p)) { + + var plugin = NightscoutPlugins[p]; + + var dataProviderEnvironment = {}; - dataProviderEnvironment.treatments = treatments; - dataProviderEnvironment.profile = profile; + dataProviderEnvironment.treatments = treatments; + dataProviderEnvironment.profile = profile; - if (plugin.isDataProvider) { - var dataFromPlugin = plugin.getData(dataProviderEnvironment,time); - for (var i in dataFromPlugin) { - env[i] = dataFromPlugin[i]; + if (plugin.isDataProvider) { + var dataFromPlugin = plugin.getData(dataProviderEnvironment,time); + for (var i in dataFromPlugin) { + env[i] = dataFromPlugin[i]; + } } + plugin.setEnv(env); } - plugin.setEnv(env); } // update data inside the plugins @@ -508,9 +510,11 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; function updatePluginVisualisation() { for (var p in NightscoutPlugins) { - var plugin = NightscoutPlugins[p]; - if (plugin.isVisualisationProvider) { - plugin.updateVisualisation(); + if (isPluginEnabled(p)) { + var plugin = NightscoutPlugins[p]; + if (plugin.isVisualisationProvider) { + plugin.updateVisualisation(); + } } } } From 1a2f1ec2c72f96c887f71dbbb029aaf47633ef6b Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 19 Apr 2015 15:27:16 +0300 Subject: [PATCH 014/937] Better namespacing for data from plugins --- bundle/bundle.source.js | 2 ++ lib/boluswizardpreview.js | 9 ++++----- lib/iob.js | 5 ++--- lib/pluginbase.js | 3 +++ static/js/client.js | 4 +++- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index 395221ee7ed..292f780da7a 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -2,6 +2,8 @@ window.Nightscout = window.Nightscout || {}; + // Default features + window.Nightscout = { units: require('../lib/units')(), profile: require('../lib/profilefunctions')() diff --git a/lib/boluswizardpreview.js b/lib/boluswizardpreview.js index 3751ef05378..169186d2e50 100644 --- a/lib/boluswizardpreview.js +++ b/lib/boluswizardpreview.js @@ -4,7 +4,6 @@ function updateVisualisation() { var sgv = this.env.sgv; - var iob = this.env.iob; var pill = this.currentDetails.find('span.pill.bat'); @@ -15,20 +14,20 @@ function updateVisualisation() { var bat = 0.0; + // TODO: MMOL sgv = Number(sgv)/18; - iob = Number(iob); // Above target -> calculate insulin dose against target_high if (sgv > this.profile.target_high) { var delta = sgv - this.profile.target_high; - bat = (delta / this.profile.sens) - iob; + bat = (delta / this.profile.sens) - this.iob.iob; } // between targets - if (sgv >= this.profile.target_low && sgv <= this.profile.target_high && iob > 0) + if (sgv >= this.profile.target_low && sgv <= this.profile.target_high && this.iob.iob > 0) { // ... } @@ -38,7 +37,7 @@ function updateVisualisation() { if (sgv < this.profile.target_low) { var delta = this.profile.target_low - sgv; - bat = 0-(delta / this.profile.sens) - iob; + bat = 0-(delta / this.profile.sens) - this.iob.iob; } bat = Math.round(bat * 100) / 100; diff --git a/lib/iob.js b/lib/iob.js index 6ea932dec51..218c779c1f8 100644 --- a/lib/iob.js +++ b/lib/iob.js @@ -80,9 +80,8 @@ function updateVisualisation() { pill = $(''); this.currentDetails.append(pill); } - //var iob = Nightscout.iob.calcTotal(treatments, profile, time); - - pill.find('em').text(this.env.display + 'U'); + + pill.find('em').text(this.iob.display + 'U'); } diff --git a/lib/pluginbase.js b/lib/pluginbase.js index 0bcb05f77b0..72ed50dd9f6 100644 --- a/lib/pluginbase.js +++ b/lib/pluginbase.js @@ -3,6 +3,9 @@ function setEnv(env) { this.profile = env.profile; this.currentDetails = env.currentDetails; + this.iob = env.iob; + + // TODO: clean! this.env = env; } diff --git a/static/js/client.js b/static/js/client.js index fd4217a97a2..490acb42d52 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -491,9 +491,11 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; if (plugin.isDataProvider) { var dataFromPlugin = plugin.getData(dataProviderEnvironment,time); + var container = {}; for (var i in dataFromPlugin) { - env[i] = dataFromPlugin[i]; + container[i] = dataFromPlugin[i]; } + env[p] = container; } plugin.setEnv(env); } From 34cf139d6ba58c9a3dd9548ad0ca31e41ac6b57a Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 19 Apr 2015 17:45:10 +0300 Subject: [PATCH 015/937] Quick and dirty port of the COB calculator. Data providers iteratively add data to the plugin env as chained. --- bundle/bundle.source.js | 3 +- lib/cob.js | 220 ++++++++++++++++++++++++++++++++++++++++ lib/iob.js | 4 +- static/js/client.js | 11 +- 4 files changed, 233 insertions(+), 5 deletions(-) create mode 100644 lib/cob.js diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index 292f780da7a..9a2f145a24a 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -17,7 +17,8 @@ window.NightscoutPlugins = window.NightscoutPlugins || {}; window.NightscoutPlugins = { - iob: require('../lib/iob')(), + iob: require('../lib/iob')(PluginBase), + cob: require('../lib/cob')(PluginBase), bwp: require('../lib/boluswizardpreview')(PluginBase) }; // class inheritance to the plugins from the base + map functions over diff --git a/lib/cob.js b/lib/cob.js new file mode 100644 index 00000000000..0932366e045 --- /dev/null +++ b/lib/cob.js @@ -0,0 +1,220 @@ +'use strict'; + +function getData(environment,time) +{ + return this.cobTotal(environment.treatments,time); +} + +function cobTotal(treatments, time) { + var liverSensRatio = 1; + var sens = this.profile.sens; + var carbratio = this.profile.carbratio; + var cob=0; + if (!treatments) return {}; + if (typeof time === 'undefined') { + var time = new Date(); + } + + var isDecaying = 0; + var lastDecayedBy = new Date('1/1/1970'); + var carbs_hr = this.profile.carbs_hr; + + for (var t in treatments) + { + var treatment = treatments[t]; + if(treatment.carbs && treatment.created_at < time) { + var cCalc = this.cobCalc(treatment, lastDecayedBy, time); + var decaysin_hr = (cCalc.decayedBy-time)/1000/60/60; + if (decaysin_hr > -10) { + var actStart = this.iobTotal(treatments, lastDecayedBy).activity; + var actEnd = this.iobTotal(treatments, cCalc.decayedBy).activity; + var avgActivity = (actStart+actEnd)/2; + var delayedCarbs = avgActivity*liverSensRatio*sens/carbratio; + var delayMinutes = Math.round(delayedCarbs/carbs_hr*60); + if (delayMinutes > 0) { + cCalc.decayedBy.setMinutes(cCalc.decayedBy.getMinutes() + delayMinutes); + decaysin_hr = (cCalc.decayedBy-time)/1000/60/60; + } + } + + if (cCalc) { + lastDecayedBy = cCalc.decayedBy; + } + + if (decaysin_hr > 0) { + //console.info('Adding ' + delayMinutes + ' minutes to decay of ' + treatment.carbs + 'g bolus at ' + treatment.created_at); + cob += Math.min(cCalc.initialCarbs, decaysin_hr * carbs_hr); + console.log("cob: " + Math.min(cCalc.initialCarbs, decaysin_hr * carbs_hr)); + isDecaying = cCalc.isDecaying; + } + else { + cob = 0; + } + + } + } + var rawCarbImpact = isDecaying*sens/carbratio*carbs_hr/60; + return { + decayedBy: lastDecayedBy, + isDecaying: isDecaying, + carbs_hr: carbs_hr, + rawCarbImpact: rawCarbImpact, + cob: cob + }; +} + + function iobTotal(treatments, time) { + var iob= 0; + var activity = 0; + if (!treatments) return {}; + if (typeof time === 'undefined') { + var time = new Date(); + } + + for (var t in treatments) { + var treatment = treatments[t]; + if(treatment.created_at < time) { + var tIOB = this.iobCalc(treatment, time); + if (tIOB && tIOB.iobContrib) iob += tIOB.iobContrib; + if (tIOB && tIOB.activityContrib) activity += tIOB.activityContrib; + } + }; + return { + iob: iob, + activity: activity + }; + } + + +function carbImpact(rawCarbImpact, insulinImpact) { + var liverSensRatio = 1.0; + var liverCarbImpactMax = 0.7; + var liverCarbImpact = Math.min(liverCarbImpactMax, liverSensRatio*insulinImpact); + //var liverCarbImpact = liverSensRatio*insulinImpact; + var netCarbImpact = Math.max(0, rawCarbImpact-liverCarbImpact); + var totalImpact = netCarbImpact - insulinImpact; + return { + netCarbImpact: netCarbImpact, + totalImpact: totalImpact + } +} + +function iobCalc(treatment, time) { + + var dia=this.profile.dia; + var scaleFactor = 3.0/dia; + var peak = 75; + var sens=this.profile.sens; + var iobContrib, activityContrib; + var t = time; + if (typeof t === 'undefined') { + t = new Date(); + } + + if (treatment.insulin) { + var bolusTime=new Date(treatment.created_at); + var minAgo=scaleFactor*(t-bolusTime)/1000/60; + + if (minAgo < 0) { + iobContrib=0; + activityContrib=0; + } + if (minAgo < peak) { + var x = minAgo/5+1; + iobContrib=treatment.insulin*(1-0.001852*x*x+0.001852*x); + activityContrib=sens*treatment.insulin*(2/dia/60/peak)*minAgo; + + } + else if (minAgo < 180) { + var x = (minAgo-75)/5; + iobContrib=treatment.insulin*(0.001323*x*x - .054233*x + .55556); + activityContrib=sens*treatment.insulin*(2/dia/60-(minAgo-peak)*2/dia/60/(60*dia-peak)); + } + else { + iobContrib=0; + activityContrib=0; + } + return { + iobContrib: iobContrib, + activityContrib: activityContrib + }; + } + else { + return ''; + } + } + +function cobCalc(treatment, lastDecayedBy, time) { + + var carbs_hr = this.profile.carbs_hr; + var delay = 20; + var carbs_min = carbs_hr / 60; + var isDecaying = 0; + var initialCarbs; + + if (treatment.carbs) { + var carbTime = new Date(treatment.created_at); + + var decayedBy = new Date(carbTime); + var minutesleft = (lastDecayedBy-carbTime)/1000/60; + decayedBy.setMinutes(decayedBy.getMinutes() + Math.max(delay,minutesleft) + treatment.carbs/carbs_min); + if(delay > minutesleft) { + initialCarbs = parseInt(treatment.carbs); + } + else { + initialCarbs = parseInt(treatment.carbs) + minutesleft*carbs_min; + } + var startDecay = new Date(carbTime); + startDecay.setMinutes(carbTime.getMinutes() + delay); + if (time < lastDecayedBy || time > startDecay) { + isDecaying = 1; + } + else { + isDecaying = 0; + } + return { + initialCarbs: initialCarbs, + decayedBy: decayedBy, + isDecaying: isDecaying, + carbTime: carbTime + }; + } + else { + return ''; + } +} + +function updateVisualisation() { + + var pill = this.currentDetails.find('span.pill.cob'); + + if (!pill || pill.length == 0) { + pill = $(''); + this.currentDetails.append(pill); + } + + var displayCob = Math.round(this.env.cob.cob * 10) / 10; + + pill.find('em').text(displayCob + " g"); + +} + + +function COB(pluginBase) { + + if (pluginBase) { pluginBase.call(this); } + + return { + cobTotal: cobTotal, + cobCalc: cobCalc, + iobTotal: iobTotal, + iobCalc: iobCalc, + getData: getData, + updateVisualisation: updateVisualisation, + isDataProvider: true, + isVisualisationProvider: true + }; + +} + +module.exports = COB; diff --git a/lib/iob.js b/lib/iob.js index 218c779c1f8..135c6ef11c1 100644 --- a/lib/iob.js +++ b/lib/iob.js @@ -86,8 +86,10 @@ function updateVisualisation() { } -function IOB(opts) { +function IOB(pluginBase) { + if (pluginBase) { pluginBase.call(this); } + return { calcTotal: calcTotal, getData: getData, diff --git a/static/js/client.js b/static/js/client.js index 490acb42d52..dd278cd07ce 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -475,7 +475,8 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; env.profile = profile; env.currentDetails = currentDetails; env.sgv = Number(sgv); - + env.treatments = treatments; + // get additional data from data providers for (var p in NightscoutPlugins) { @@ -489,6 +490,8 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; dataProviderEnvironment.treatments = treatments; dataProviderEnvironment.profile = profile; + plugin.setEnv(env); + if (plugin.isDataProvider) { var dataFromPlugin = plugin.getData(dataProviderEnvironment,time); var container = {}; @@ -497,17 +500,19 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } env[p] = container; } - plugin.setEnv(env); } } // update data inside the plugins + sendEnvToPlugins(env); + } + + function sendEnvToPlugins(env) { for (var p in NightscoutPlugins) { var plugin = NightscoutPlugins[p]; plugin.setEnv(env); } - } function updatePluginVisualisation() { From 0d3be281d1ffde43db348003c941cdcd6d262fcf Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 19 Apr 2015 17:52:13 +0300 Subject: [PATCH 016/937] Cleaner data env building --- lib/cob.js | 4 ++-- lib/iob.js | 4 ++-- static/js/client.js | 12 ++++-------- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/lib/cob.js b/lib/cob.js index 0932366e045..587ab117742 100644 --- a/lib/cob.js +++ b/lib/cob.js @@ -1,8 +1,8 @@ 'use strict'; -function getData(environment,time) +function getData() { - return this.cobTotal(environment.treatments,time); + return this.cobTotal(this.env.treatments,this.env.time); } function cobTotal(treatments, time) { diff --git a/lib/iob.js b/lib/iob.js index 135c6ef11c1..ee2f716cecd 100644 --- a/lib/iob.js +++ b/lib/iob.js @@ -1,8 +1,8 @@ 'use strict'; -function getData(environment,time) +function getData() { - return calcTotal(environment.treatments,environment.profile,time); + return calcTotal(this.env.treatments,this.env.profile,this.env.time); } function calcTotal(treatments, profile, time) { diff --git a/static/js/client.js b/static/js/client.js index dd278cd07ce..ce1bd1bdfda 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -476,8 +476,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; env.currentDetails = currentDetails; env.sgv = Number(sgv); env.treatments = treatments; + env.time = time; - // get additional data from data providers + // Update the env through data provider plugins for (var p in NightscoutPlugins) { @@ -485,15 +486,10 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var plugin = NightscoutPlugins[p]; - var dataProviderEnvironment = {}; - - dataProviderEnvironment.treatments = treatments; - dataProviderEnvironment.profile = profile; - plugin.setEnv(env); if (plugin.isDataProvider) { - var dataFromPlugin = plugin.getData(dataProviderEnvironment,time); + var dataFromPlugin = plugin.getData(); var container = {}; for (var i in dataFromPlugin) { container[i] = dataFromPlugin[i]; @@ -503,7 +499,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } } - // update data inside the plugins + // update data the plugins sendEnvToPlugins(env); } From 21ca447270360d32bf91c463cfb8ba9b77bae3d7 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 21 Apr 2015 22:44:15 -0700 Subject: [PATCH 017/937] make the stale data time input a little smaller to prevent wrapping on iOS --- static/css/drawer.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/css/drawer.css b/static/css/drawer.css index 559c69048d1..37174469a04 100644 --- a/static/css/drawer.css +++ b/static/css/drawer.css @@ -88,7 +88,7 @@ input[type=number]:invalid { } input.timeago-mins { - width: 40px; + width: 25px; } #eventTime { From 83bc16012b1e833cb4fecad672b66ec54c3ee4cd Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sat, 25 Apr 2015 13:35:21 +0300 Subject: [PATCH 018/937] Update once a second, not five times a second --- static/js/client.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/js/client.js b/static/js/client.js index ce1bd1bdfda..0210cc20e85 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1648,7 +1648,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; updateBrushToNow(); } updateChart(false); - }, 200); + }, 1000); } function visibilityChanged() { From 7cdb29fdac5b9d8c647c5c1874dde4f0e76b6766 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 3 May 2015 00:05:38 -0700 Subject: [PATCH 019/937] when stoping the alarm also remove the strike through --- static/js/client.js | 1 + 1 file changed, 1 insertion(+) diff --git a/static/js/client.js b/static/js/client.js index dd35bd3c6e6..88794c25137 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1514,6 +1514,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } if (alarmingNow() && ago.status == 'current' && isTimeAgoAlarmType(currentAlarmType)) { + $('#container').removeClass('alarming-timeago'); stopAlarm(true, ONE_MIN_IN_MS); } From 8fa49f2c06b047a0c7141fa7ed025cc2f5790556 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 3 May 2015 16:17:21 +0300 Subject: [PATCH 020/937] Plugin to calculate and show the cannula age based on latest Site Change event --- bundle/bundle.source.js | 3 ++- lib/cannulaage.js | 53 +++++++++++++++++++++++++++++++++++++++++ lib/websocket.js | 4 +++- 3 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 lib/cannulaage.js diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index 9a2f145a24a..75804ac9130 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -19,7 +19,8 @@ window.NightscoutPlugins = { iob: require('../lib/iob')(PluginBase), cob: require('../lib/cob')(PluginBase), - bwp: require('../lib/boluswizardpreview')(PluginBase) + bwp: require('../lib/boluswizardpreview')(PluginBase), + cage: require('../lib/cannulaage')(PluginBase) }; // class inheritance to the plugins from the base + map functions over diff --git a/lib/cannulaage.js b/lib/cannulaage.js new file mode 100644 index 00000000000..d601c422ee1 --- /dev/null +++ b/lib/cannulaage.js @@ -0,0 +1,53 @@ +'use strict'; + +// class methods +function updateVisualisation() { + + var sgv = this.env.sgv; + + var pill = this.currentDetails.find('span.pill.cage'); + + if (!pill || pill.length == 0) { + pill = $(''); + this.currentDetails.append(pill); + } + + var age = 0; + var found = false; + + for (var t in this.env.treatments) + { + var treatment = this.env.treatments[t]; + + if (treatment.eventType == "Site Change") + { + var treatmentDate = new Date(treatment.created_at); + var hours = Math.abs(new Date() - treatmentDate) / 36e5; + hours = Math.round( hours * 10 ) / 10; + + if (!found) { + found = true; + age = hours; + } else { + if (hours < age) { age = hours; } + } + } + } + + pill.find('em').text(age + 'h'); + +}; + + +function CAGE(pluginBase) { + pluginBase.call(this); + + return { + updateVisualisation: updateVisualisation, + isDataProvider: false, + isVisualisationProvider: true + + }; +} + +module.exports = CAGE; \ No newline at end of file diff --git a/lib/websocket.js b/lib/websocket.js index c3da8be29a9..3444c6ae60b 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -7,6 +7,7 @@ var ONE_HOUR = 3600000, ONE_MINUTE = 60000, FIVE_MINUTES = 300000, FORTY_MINUTES = 2400000, + ONE_DAY = 86400000, TWO_DAYS = 172800000; var dir2Char = { @@ -162,6 +163,7 @@ function update() { profileData = []; devicestatusData = {}; var earliest_data = now - TWO_DAYS; + var treatment_earliest_data = now - (ONE_DAY*8); async.parallel({ entries: function(callback) { @@ -216,7 +218,7 @@ function update() { }); } , treatments: function(callback) { - var tq = { find: {"created_at": {"$gte": new Date(earliest_data).toISOString()}} }; + var tq = { find: {"created_at": {"$gte": new Date(treatment_earliest_data).toISOString()}} }; treatments.list(tq, function (err, results) { treatmentData = results.map(function (treatment) { var timestamp = new Date(treatment.timestamp || treatment.created_at); From 3fdd5a6d67668766b1e33a64e324d2f645da6140 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Mon, 4 May 2015 10:02:55 +0300 Subject: [PATCH 021/937] PluginBase now injects the pills, instead of the plugin touching anything to do with the browser DOM --- bundle/bundle.source.js | 1 + lib/boluswizardpreview.js | 12 ++++-------- lib/cannulaage.js | 14 ++++---------- lib/cob.js | 13 +++---------- lib/pluginbase.js | 18 +++++++++++++++++- 5 files changed, 29 insertions(+), 29 deletions(-) diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index 75804ac9130..88b5c8a5a1d 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -27,6 +27,7 @@ for (var p in window.NightscoutPlugins) { var plugin = window.NightscoutPlugins[p]; inherits(plugin, PluginBase); + plugin.name = p; for (var n in PluginBase.prototype) { var item = PluginBase.prototype[n]; diff --git a/lib/boluswizardpreview.js b/lib/boluswizardpreview.js index 169186d2e50..224b2dae877 100644 --- a/lib/boluswizardpreview.js +++ b/lib/boluswizardpreview.js @@ -4,13 +4,6 @@ function updateVisualisation() { var sgv = this.env.sgv; - - var pill = this.currentDetails.find('span.pill.bat'); - - if (!pill || pill.length == 0) { - pill = $(''); - this.currentDetails.append(pill); - } var bat = 0.0; @@ -41,7 +34,10 @@ function updateVisualisation() { } bat = Math.round(bat * 100) / 100; - pill.find('em').text(bat + 'U'); + + // display text + + this.updateMajorPillText(bat + 'U','BWP'); }; diff --git a/lib/cannulaage.js b/lib/cannulaage.js index d601c422ee1..d36965ad72a 100644 --- a/lib/cannulaage.js +++ b/lib/cannulaage.js @@ -4,14 +4,6 @@ function updateVisualisation() { var sgv = this.env.sgv; - - var pill = this.currentDetails.find('span.pill.cage'); - - if (!pill || pill.length == 0) { - pill = $(''); - this.currentDetails.append(pill); - } - var age = 0; var found = false; @@ -23,7 +15,9 @@ function updateVisualisation() { { var treatmentDate = new Date(treatment.created_at); var hours = Math.abs(new Date() - treatmentDate) / 36e5; - hours = Math.round( hours * 10 ) / 10; + //hours = Math.round( hours * 10 ) / 10; + + hours = Math.round(hours); if (!found) { found = true; @@ -34,7 +28,7 @@ function updateVisualisation() { } } - pill.find('em').text(age + 'h'); + this.updateMajorPillText(age+'h', 'CAGE'); }; diff --git a/lib/cob.js b/lib/cob.js index 587ab117742..acf867d9edd 100644 --- a/lib/cob.js +++ b/lib/cob.js @@ -185,17 +185,10 @@ function cobCalc(treatment, lastDecayedBy, time) { } function updateVisualisation() { - - var pill = this.currentDetails.find('span.pill.cob'); - - if (!pill || pill.length == 0) { - pill = $(''); - this.currentDetails.append(pill); - } - - var displayCob = Math.round(this.env.cob.cob * 10) / 10; - pill.find('em').text(displayCob + " g"); + var displayCob = Math.round(this.env.cob.cob * 10) / 10; + + this.updateMajorPillText(displayCob + " g",'COB'); } diff --git a/lib/pluginbase.js b/lib/pluginbase.js index 72ed50dd9f6..028eed2db45 100644 --- a/lib/pluginbase.js +++ b/lib/pluginbase.js @@ -9,12 +9,28 @@ function setEnv(env) { this.env = env; } +function updateMajorPillText(updatedText, label) { + + var pillName = "span.pill." + this.name; + + var pill = this.currentDetails.find(pillName); + + if (!pill || pill.length == 0) { + pill = $(''); + this.currentDetails.append(pill); + } + + pill.find('em').text(updatedText); +} + function PluginBase() { return { - setEnv: setEnv + setEnv: setEnv, + updateMajorPillText: updateMajorPillText }; } PluginBase.prototype.setEnv = setEnv; +PluginBase.prototype.updateMajorPillText = updateMajorPillText; module.exports = PluginBase; From d17a6d6473af94b54f59f55edb100e6cb527beed Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Mon, 4 May 2015 10:16:00 +0300 Subject: [PATCH 022/937] Separate area for plugin pills --- lib/pluginbase.js | 5 +++-- static/css/main.css | 44 +++++++++++++++++++++++++++++++++++++------- static/index.html | 1 + static/js/client.js | 2 ++ 4 files changed, 43 insertions(+), 9 deletions(-) diff --git a/lib/pluginbase.js b/lib/pluginbase.js index 028eed2db45..d39cdddc225 100644 --- a/lib/pluginbase.js +++ b/lib/pluginbase.js @@ -3,6 +3,7 @@ function setEnv(env) { this.profile = env.profile; this.currentDetails = env.currentDetails; + this.pluginPills = env.pluginPills; this.iob = env.iob; // TODO: clean! @@ -13,11 +14,11 @@ function updateMajorPillText(updatedText, label) { var pillName = "span.pill." + this.name; - var pill = this.currentDetails.find(pillName); + var pill = this.pluginPills.find(pillName); if (!pill || pill.length == 0) { pill = $(''); - this.currentDetails.append(pill); + this.pluginPills.append(pill); } pill.find('em').text(updatedText); diff --git a/static/css/main.css b/static/css/main.css index de90b71f58f..870ce7b9a0f 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -29,9 +29,9 @@ body { .status { font-family: 'Ubuntu', Helvetica, Arial, sans-serif; - height: 180px; + height: 200px; vertical-align: middle; - clear: both; + clear: left right; } .bgStatus { @@ -76,10 +76,20 @@ body { font-size: 30px; } +.pluginPills { + font-size: 22px; + margin-top: 5px; + z-index: 500; +} + .currentDetails > span:not(:first-child) { margin-left: 5px; } +.pluginPills > span:not(:first-child) { + margin-left: 5px; +} + .pill { white-space: nowrap; border-radius: 5px; @@ -169,7 +179,7 @@ body { } #chartContainer { - top: 225px; /*(toolbar height + status height)*/ + top: 245px; /*(toolbar height + status height)*/ left:0; right:0; bottom:0; @@ -347,6 +357,10 @@ div.tooltip { font-size: 20px; } + .bgStatus .pluginPills { + font-size: 16px; + } + .time { font-size: 70px; line-height: 60px; @@ -375,7 +389,7 @@ div.tooltip { } #chartContainer { - top: 185px; + top: 195px; font-size: 14px; } #chartContainer svg { @@ -416,6 +430,10 @@ div.tooltip { font-size: 15px; } + .bgStatus .pluginPills { + font-size: 12px; + } + .time { font-size: 50px; line-height: 40px; @@ -423,7 +441,7 @@ div.tooltip { } #chartContainer { - top: 165px; + top: 175px; } #chartContainer svg { height: calc(100vh - 165px); @@ -470,6 +488,10 @@ div.tooltip { font-size: 15px; } + .bgStatus .pluginPills { + font-size: 12px; + } + #silenceBtn * { font-size: 20px; } @@ -512,7 +534,7 @@ div.tooltip { } #chartContainer { - top: 190px; + top: 200px; } #chartContainer svg { height: calc(100vh - (190px)); @@ -547,7 +569,7 @@ div.tooltip { } #chartContainer { - top: 130px; + top: 140px; font-size: 10px; } #chartContainer svg { @@ -574,6 +596,10 @@ div.tooltip { font-size: 15px; } + .bgStatus .pluginPills { + font-size: 12px; + } + .time { font-size: 50px; line-height: 40px; @@ -617,6 +643,10 @@ div.tooltip { font-size: 15px; } + .bgStatus .currentDetails { + font-size: 12px; + } + #silenceBtn { right: -25px; } diff --git a/static/index.html b/static/index.html index aed6265697a..6f7b9ea0d32 100644 --- a/static/index.html +++ b/static/index.html @@ -58,6 +58,7 @@

Nightscout

  • Silence for 120 minutes
  • +
    diff --git a/static/js/client.js b/static/js/client.js index 0210cc20e85..fc2eb093c8b 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -393,6 +393,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; , currentBG = $('.bgStatus .currentBG') , currentDirection = $('.bgStatus .currentDirection') , currentDetails = $('.bgStatus .currentDetails') + , pluginPills = $('.bgStatus .pluginPills') , rawNoise = bgButton.find('.rawnoise') , rawbg = rawNoise.find('em') , noiseLevel = rawNoise.find('label') @@ -474,6 +475,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var env = {}; env.profile = profile; env.currentDetails = currentDetails; + env.pluginPills = pluginPills; env.sgv = Number(sgv); env.treatments = treatments; env.time = time; From 6436e6bcbcf26225f47e56e2d1fbbe996b30bc34 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Mon, 4 May 2015 12:09:20 +0300 Subject: [PATCH 023/937] Simplified the code to detect if plugin implements data processing and/or visualisations --- lib/boluswizardpreview.js | 5 +---- lib/cannulaage.js | 5 +---- lib/cob.js | 4 +--- static/js/client.js | 8 ++++++-- 4 files changed, 9 insertions(+), 13 deletions(-) diff --git a/lib/boluswizardpreview.js b/lib/boluswizardpreview.js index 224b2dae877..1b837e2d5de 100644 --- a/lib/boluswizardpreview.js +++ b/lib/boluswizardpreview.js @@ -46,10 +46,7 @@ function BWP(pluginBase) { pluginBase.call(this); return { - updateVisualisation: updateVisualisation, - isDataProvider: false, - isVisualisationProvider: true - + updateVisualisation: updateVisualisation }; } diff --git a/lib/cannulaage.js b/lib/cannulaage.js index d36965ad72a..9c8f1e036a5 100644 --- a/lib/cannulaage.js +++ b/lib/cannulaage.js @@ -37,10 +37,7 @@ function CAGE(pluginBase) { pluginBase.call(this); return { - updateVisualisation: updateVisualisation, - isDataProvider: false, - isVisualisationProvider: true - + updateVisualisation: updateVisualisation }; } diff --git a/lib/cob.js b/lib/cob.js index acf867d9edd..f0bc520190e 100644 --- a/lib/cob.js +++ b/lib/cob.js @@ -203,9 +203,7 @@ function COB(pluginBase) { iobTotal: iobTotal, iobCalc: iobCalc, getData: getData, - updateVisualisation: updateVisualisation, - isDataProvider: true, - isVisualisationProvider: true + updateVisualisation: updateVisualisation }; } diff --git a/static/js/client.js b/static/js/client.js index fc2eb093c8b..3e37e9b7d6d 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -490,7 +490,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; plugin.setEnv(env); - if (plugin.isDataProvider) { + // check if the plugin implements processing data + + if (plugin.getData) { var dataFromPlugin = plugin.getData(); var container = {}; for (var i in dataFromPlugin) { @@ -517,7 +519,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; for (var p in NightscoutPlugins) { if (isPluginEnabled(p)) { var plugin = NightscoutPlugins[p]; - if (plugin.isVisualisationProvider) { + + // check if the plugin implements visualisations + if (plugin.updateVisualisation) { plugin.updateVisualisation(); } } From c5ca38ac9ff21868de11f142d6e618ac367feba4 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Mon, 4 May 2015 14:02:25 +0300 Subject: [PATCH 024/937] Small cleanup to cannula age code --- lib/cannulaage.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/cannulaage.js b/lib/cannulaage.js index 9c8f1e036a5..e55059956b4 100644 --- a/lib/cannulaage.js +++ b/lib/cannulaage.js @@ -3,7 +3,6 @@ // class methods function updateVisualisation() { - var sgv = this.env.sgv; var age = 0; var found = false; @@ -14,11 +13,8 @@ function updateVisualisation() { if (treatment.eventType == "Site Change") { var treatmentDate = new Date(treatment.created_at); - var hours = Math.abs(new Date() - treatmentDate) / 36e5; - //hours = Math.round( hours * 10 ) / 10; - - hours = Math.round(hours); - + var hours = Math.round(Math.abs(new Date() - treatmentDate) / 36e5); + if (!found) { found = true; age = hours; From af0b2f73df4afc77d4e12b1efada4940d5f41dd1 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Thu, 7 May 2015 10:42:56 +0300 Subject: [PATCH 025/937] Revert the timer change --- static/js/client.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/js/client.js b/static/js/client.js index 3e37e9b7d6d..3e3be4a581e 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1654,7 +1654,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; updateBrushToNow(); } updateChart(false); - }, 1000); + }, 200); } function visibilityChanged() { From 2c6b2b0002b4411f30b7a3ed54ab5a2320975fc1 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Thu, 7 May 2015 12:16:26 +0300 Subject: [PATCH 026/937] Don't assume we're using mmols --- lib/boluswizardpreview.js | 3 ++- lib/pluginbase.js | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/boluswizardpreview.js b/lib/boluswizardpreview.js index 1b837e2d5de..fdee01c15c1 100644 --- a/lib/boluswizardpreview.js +++ b/lib/boluswizardpreview.js @@ -8,7 +8,8 @@ function updateVisualisation() { var bat = 0.0; // TODO: MMOL - sgv = Number(sgv)/18; + + sgv = this.scaleBg(sgv); // Above target -> calculate insulin dose against target_high diff --git a/lib/pluginbase.js b/lib/pluginbase.js index d39cdddc225..293dc8df9be 100644 --- a/lib/pluginbase.js +++ b/lib/pluginbase.js @@ -24,6 +24,14 @@ function updateMajorPillText(updatedText, label) { pill.find('em').text(updatedText); } +function scaleBg(bg) { + if (browserSettings.units == 'mmol') { + return Nightscout.units.mgdlToMMOL(bg); + } else { + return bg; + } +} + function PluginBase() { return { setEnv: setEnv, @@ -31,6 +39,7 @@ function PluginBase() { }; } +PluginBase.prototype.scaleBg = scaleBg; PluginBase.prototype.setEnv = setEnv; PluginBase.prototype.updateMajorPillText = updateMajorPillText; From e5c6a8512b837adf8492bdcf38f81189314532ea Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 10 May 2015 20:23:40 +0300 Subject: [PATCH 027/937] Better logic for the bolus wizard preview --- lib/boluswizardpreview.js | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/lib/boluswizardpreview.js b/lib/boluswizardpreview.js index fdee01c15c1..43640ddc343 100644 --- a/lib/boluswizardpreview.js +++ b/lib/boluswizardpreview.js @@ -10,28 +10,21 @@ function updateVisualisation() { // TODO: MMOL sgv = this.scaleBg(sgv); - - // Above target -> calculate insulin dose against target_high - - if (sgv > this.profile.target_high) - { - var delta = sgv - this.profile.target_high; - bat = (delta / this.profile.sens) - this.iob.iob; - } - // between targets + var effect = this.iob.iob * this.profile.sens; + var outcome = sgv - effect; - if (sgv >= this.profile.target_low && sgv <= this.profile.target_high && this.iob.iob > 0) - { - // ... + if (outcome > this.profile.target_high) { + var delta = outcome - this.profile.target_high; + bat = delta / this.profile.sens; } - - // Above target -> calculate insulin dose against target_low - - if (sgv < this.profile.target_low) - { - var delta = this.profile.target_low - sgv; - bat = 0-(delta / this.profile.sens) - this.iob.iob; + + if (outcome < this.profile.target_low) { + + var delta = Math.abs( outcome - this.profile.target_low); + + bat = delta / this.profile.sens * -1; + } bat = Math.round(bat * 100) / 100; From ec1893113f0fed7b074f67db85ec04c775ea9172 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 24 May 2015 08:28:14 -0700 Subject: [PATCH 028/937] normalize indents; use hasOwnProperty; minor clean up --- bundle/bundle.source.js | 44 ++++++------- static/js/client.js | 134 ++++++++++++++++++++-------------------- 2 files changed, 91 insertions(+), 87 deletions(-) diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index 88b5c8a5a1d..1f85b2d5e06 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -1,18 +1,17 @@ (function () { - - window.Nightscout = window.Nightscout || {}; - // Default features + window.Nightscout = window.Nightscout || {}; + // Default features window.Nightscout = { units: require('../lib/units')(), profile: require('../lib/profilefunctions')() }; - // Plugins - - var inherits = require("inherits"); - var PluginBase = require('../lib/pluginbase'); // Define any shared functionality in this class + // Plugins + + var inherits = require("inherits"); + var PluginBase = require('../lib/pluginbase'); // Define any shared functionality in this class window.NightscoutPlugins = window.NightscoutPlugins || {}; @@ -22,20 +21,23 @@ bwp: require('../lib/boluswizardpreview')(PluginBase), cage: require('../lib/cannulaage')(PluginBase) }; - // class inheritance to the plugins from the base + map functions over - - for (var p in window.NightscoutPlugins) { - var plugin = window.NightscoutPlugins[p]; - inherits(plugin, PluginBase); - plugin.name = p; - - for (var n in PluginBase.prototype) { - var item = PluginBase.prototype[n]; - plugin[n] = item; - } - } - - console.info("Nightscout bundle ready", window.Nightscout); + + // class inheritance to the plugins from the base + map functions over + for (var p in window.NightscoutPlugins) { + if (window.NightscoutPlugins.hasOwnProperty(p)) { + var plugin = window.NightscoutPlugins[p]; + inherits(plugin, PluginBase); + plugin.name = p; + + for (var n in PluginBase.prototype) { + if (PluginBase.prototype.hasOwnProperty(n)) { + plugin[n] = PluginBase.prototype[n]; + } + } + } + } + + console.info("Nightscout bundle ready", window.Nightscout, window.NightscoutPlugins); })(); diff --git a/static/js/client.js b/static/js/client.js index 3e3be4a581e..029a045863f 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -464,71 +464,73 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } - // PLUGIN MANAGEMENT CODE - - function isPluginEnabled(name) { - return app.enabledOptions - && app.enabledOptions.indexOf(name) > -1; - } - - function updatePluginData(sgv, time) { - var env = {}; - env.profile = profile; - env.currentDetails = currentDetails; - env.pluginPills = pluginPills; - env.sgv = Number(sgv); - env.treatments = treatments; - env.time = time; - - // Update the env through data provider plugins - - for (var p in NightscoutPlugins) { - - if (isPluginEnabled(p)) { - - var plugin = NightscoutPlugins[p]; - - plugin.setEnv(env); - - // check if the plugin implements processing data - - if (plugin.getData) { - var dataFromPlugin = plugin.getData(); - var container = {}; - for (var i in dataFromPlugin) { - container[i] = dataFromPlugin[i]; - } - env[p] = container; - } - } - } - - // update data the plugins - - sendEnvToPlugins(env); - } - - function sendEnvToPlugins(env) { - for (var p in NightscoutPlugins) { - var plugin = NightscoutPlugins[p]; - plugin.setEnv(env); - } - } - - function updatePluginVisualisation() { - for (var p in NightscoutPlugins) { - if (isPluginEnabled(p)) { - var plugin = NightscoutPlugins[p]; - - // check if the plugin implements visualisations - if (plugin.updateVisualisation) { - plugin.updateVisualisation(); - } - } - } - } - - /// END PLUGIN CODE + // PLUGIN MANAGEMENT CODE + + function isPluginEnabled(name) { + return app.enabledOptions + && app.enabledOptions.indexOf(name) > -1; + } + + function updatePluginData(sgv, time) { + var env = {}; + env.profile = profile; + env.currentDetails = currentDetails; + env.pluginPills = pluginPills; + env.sgv = Number(sgv); + env.treatments = treatments; + env.time = time; + + // Update the env through data provider plugins + + for (var p in NightscoutPlugins) { + if (NightscoutPlugins.hasOwnProperty(p) && isPluginEnabled(p)) { + var plugin = NightscoutPlugins[p]; + + plugin.setEnv(env); + + // check if the plugin implements processing data + + if (plugin.getData) { + var dataFromPlugin = plugin.getData(); + var container = {}; + for (var i in dataFromPlugin) { + if (dataFromPlugin.hasOwnProperty(i)) { + container[i] = dataFromPlugin[i]; + } + } + env[p] = container; + } + } + } + + // update data the plugins + + sendEnvToPlugins(env); + } + + function sendEnvToPlugins(env) { + for (var p in NightscoutPlugins) { + if (NightscoutPlugins.hasOwnProperty(p)) { + var plugin = NightscoutPlugins[p]; + plugin.setEnv(env); + } + } + } + + function updatePluginVisualisation() { + for (var p in NightscoutPlugins) { + if (NightscoutPlugins.hasOwnProperty(p) && isPluginEnabled(p)) { + var plugin = NightscoutPlugins[p]; + + // check if the plugin implements visualisations + if (plugin.updateVisualisation) { + plugin.updateVisualisation(); + } + } + } + } + + /// END PLUGIN CODE // predict for retrospective data // by changing lookback from 1 to 2, we modify the AR algorithm to determine its initial slope from 10m @@ -1325,7 +1327,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } } else if (treatment.glucose) { //no units, assume everything is the same - console.warn('found an glucose value with any units, maybe from an old version?', treatment); + console.warn('found a glucose value without any units, maybe from an old version?', treatment); treatmentGlucose = treatment.glucose; } } From c298320dcd31b8aee43f87f2b06089d743396807 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 24 May 2015 11:30:00 -0700 Subject: [PATCH 029/937] tons of plugin refactoring moved plugins to new lib/plugins folder, moved most plugins logic out of bundle source, initial steps to be able to toggle visualisations on the client for enabled plugins --- bower.json | 5 +- bundle/bundle.source.js | 55 +++----- bundle/index.js | 2 +- lib/boluswizardpreview.js | 47 ------- lib/cannulaage.js | 40 ------ lib/cob.js | 211 ----------------------------- lib/pebble.js | 2 +- lib/pluginbase.js | 46 ------- lib/plugins/boluswizardpreview.js | 45 +++++++ lib/plugins/cannulaage.js | 43 ++++++ lib/plugins/cob.js | 214 ++++++++++++++++++++++++++++++ lib/plugins/index.js | 78 +++++++++++ lib/{ => plugins}/iob.js | 34 +++-- lib/plugins/pluginbase.js | 46 +++++++ package.json | 1 + static/index.html | 3 + static/js/client.js | 81 +++-------- static/js/ui-utils.js | 14 +- 18 files changed, 502 insertions(+), 465 deletions(-) delete mode 100644 lib/boluswizardpreview.js delete mode 100644 lib/cannulaage.js delete mode 100644 lib/cob.js delete mode 100644 lib/pluginbase.js create mode 100644 lib/plugins/boluswizardpreview.js create mode 100644 lib/plugins/cannulaage.js create mode 100644 lib/plugins/cob.js create mode 100644 lib/plugins/index.js rename lib/{ => plugins}/iob.js (74%) create mode 100644 lib/plugins/pluginbase.js diff --git a/bower.json b/bower.json index 41363897148..0928db8e8d2 100644 --- a/bower.json +++ b/bower.json @@ -7,8 +7,9 @@ "d3": "3.4.3", "jquery": "2.1.0", "jQuery-Storage-API": "~1.7.2", - "tipsy-jmalonzo": "~1.0.1", - "jsSHA": "~1.5.0" + "jsSHA": "~1.5.0", + "lodash": "~3.9.0", + "tipsy-jmalonzo": "~1.0.1" }, "resolutions": { "jquery": "2.1.0" diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index 1f85b2d5e06..8b8ed9d3e03 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -1,43 +1,22 @@ (function () { - window.Nightscout = window.Nightscout || {}; - - // Default features - window.Nightscout = { - units: require('../lib/units')(), - profile: require('../lib/profilefunctions')() - }; - - // Plugins - - var inherits = require("inherits"); - var PluginBase = require('../lib/pluginbase'); // Define any shared functionality in this class - - window.NightscoutPlugins = window.NightscoutPlugins || {}; - - window.NightscoutPlugins = { - iob: require('../lib/iob')(PluginBase), - cob: require('../lib/cob')(PluginBase), - bwp: require('../lib/boluswizardpreview')(PluginBase), - cage: require('../lib/cannulaage')(PluginBase) - }; - - // class inheritance to the plugins from the base + map functions over - for (var p in window.NightscoutPlugins) { - if (window.NightscoutPlugins.hasOwnProperty(p)) { - var plugin = window.NightscoutPlugins[p]; - inherits(plugin, PluginBase); - plugin.name = p; - - for (var n in PluginBase.prototype) { - if (PluginBase.prototype.hasOwnProperty(n)) { - plugin[n] = PluginBase.prototype[n]; - } - } - } - } - - console.info("Nightscout bundle ready", window.Nightscout, window.NightscoutPlugins); + window.Nightscout = window.Nightscout || {}; + + // Default features + window.Nightscout = { + units: require('../lib/units')(), + profile: require('../lib/profilefunctions')(), + plugins: require('../lib/plugins/')() + }; + + window.Nightscout.plugins.register({ + iob: require('../lib/plugins/iob'), + cob: require('../lib/plugins/cob'), + bwp: require('../lib/plugins/boluswizardpreview'), + cage: require('../lib/plugins/cannulaage') + }); + + console.info("Nightscout bundle ready", window.Nightscout); })(); diff --git a/bundle/index.js b/bundle/index.js index 4a8cd1563f2..2593c5a4621 100644 --- a/bundle/index.js +++ b/bundle/index.js @@ -5,7 +5,7 @@ var browserify_express = require('browserify-express'); function bundle() { return browserify_express({ entry: __dirname + '/bundle.source.js', - watch: __dirname + '/../lib/', + watch: __dirname + '/../lib/plugins/', mount: '/public/js/bundle.js', verbose: true, //minify: true, diff --git a/lib/boluswizardpreview.js b/lib/boluswizardpreview.js deleted file mode 100644 index 43640ddc343..00000000000 --- a/lib/boluswizardpreview.js +++ /dev/null @@ -1,47 +0,0 @@ -'use strict'; - -// class methods -function updateVisualisation() { - - var sgv = this.env.sgv; - - var bat = 0.0; - - // TODO: MMOL - - sgv = this.scaleBg(sgv); - - var effect = this.iob.iob * this.profile.sens; - var outcome = sgv - effect; - - if (outcome > this.profile.target_high) { - var delta = outcome - this.profile.target_high; - bat = delta / this.profile.sens; - } - - if (outcome < this.profile.target_low) { - - var delta = Math.abs( outcome - this.profile.target_low); - - bat = delta / this.profile.sens * -1; - - } - - bat = Math.round(bat * 100) / 100; - - // display text - - this.updateMajorPillText(bat + 'U','BWP'); - -}; - - -function BWP(pluginBase) { - pluginBase.call(this); - - return { - updateVisualisation: updateVisualisation - }; -} - -module.exports = BWP; \ No newline at end of file diff --git a/lib/cannulaage.js b/lib/cannulaage.js deleted file mode 100644 index e55059956b4..00000000000 --- a/lib/cannulaage.js +++ /dev/null @@ -1,40 +0,0 @@ -'use strict'; - -// class methods -function updateVisualisation() { - - var age = 0; - var found = false; - - for (var t in this.env.treatments) - { - var treatment = this.env.treatments[t]; - - if (treatment.eventType == "Site Change") - { - var treatmentDate = new Date(treatment.created_at); - var hours = Math.round(Math.abs(new Date() - treatmentDate) / 36e5); - - if (!found) { - found = true; - age = hours; - } else { - if (hours < age) { age = hours; } - } - } - } - - this.updateMajorPillText(age+'h', 'CAGE'); - -}; - - -function CAGE(pluginBase) { - pluginBase.call(this); - - return { - updateVisualisation: updateVisualisation - }; -} - -module.exports = CAGE; \ No newline at end of file diff --git a/lib/cob.js b/lib/cob.js deleted file mode 100644 index f0bc520190e..00000000000 --- a/lib/cob.js +++ /dev/null @@ -1,211 +0,0 @@ -'use strict'; - -function getData() -{ - return this.cobTotal(this.env.treatments,this.env.time); -} - -function cobTotal(treatments, time) { - var liverSensRatio = 1; - var sens = this.profile.sens; - var carbratio = this.profile.carbratio; - var cob=0; - if (!treatments) return {}; - if (typeof time === 'undefined') { - var time = new Date(); - } - - var isDecaying = 0; - var lastDecayedBy = new Date('1/1/1970'); - var carbs_hr = this.profile.carbs_hr; - - for (var t in treatments) - { - var treatment = treatments[t]; - if(treatment.carbs && treatment.created_at < time) { - var cCalc = this.cobCalc(treatment, lastDecayedBy, time); - var decaysin_hr = (cCalc.decayedBy-time)/1000/60/60; - if (decaysin_hr > -10) { - var actStart = this.iobTotal(treatments, lastDecayedBy).activity; - var actEnd = this.iobTotal(treatments, cCalc.decayedBy).activity; - var avgActivity = (actStart+actEnd)/2; - var delayedCarbs = avgActivity*liverSensRatio*sens/carbratio; - var delayMinutes = Math.round(delayedCarbs/carbs_hr*60); - if (delayMinutes > 0) { - cCalc.decayedBy.setMinutes(cCalc.decayedBy.getMinutes() + delayMinutes); - decaysin_hr = (cCalc.decayedBy-time)/1000/60/60; - } - } - - if (cCalc) { - lastDecayedBy = cCalc.decayedBy; - } - - if (decaysin_hr > 0) { - //console.info('Adding ' + delayMinutes + ' minutes to decay of ' + treatment.carbs + 'g bolus at ' + treatment.created_at); - cob += Math.min(cCalc.initialCarbs, decaysin_hr * carbs_hr); - console.log("cob: " + Math.min(cCalc.initialCarbs, decaysin_hr * carbs_hr)); - isDecaying = cCalc.isDecaying; - } - else { - cob = 0; - } - - } - } - var rawCarbImpact = isDecaying*sens/carbratio*carbs_hr/60; - return { - decayedBy: lastDecayedBy, - isDecaying: isDecaying, - carbs_hr: carbs_hr, - rawCarbImpact: rawCarbImpact, - cob: cob - }; -} - - function iobTotal(treatments, time) { - var iob= 0; - var activity = 0; - if (!treatments) return {}; - if (typeof time === 'undefined') { - var time = new Date(); - } - - for (var t in treatments) { - var treatment = treatments[t]; - if(treatment.created_at < time) { - var tIOB = this.iobCalc(treatment, time); - if (tIOB && tIOB.iobContrib) iob += tIOB.iobContrib; - if (tIOB && tIOB.activityContrib) activity += tIOB.activityContrib; - } - }; - return { - iob: iob, - activity: activity - }; - } - - -function carbImpact(rawCarbImpact, insulinImpact) { - var liverSensRatio = 1.0; - var liverCarbImpactMax = 0.7; - var liverCarbImpact = Math.min(liverCarbImpactMax, liverSensRatio*insulinImpact); - //var liverCarbImpact = liverSensRatio*insulinImpact; - var netCarbImpact = Math.max(0, rawCarbImpact-liverCarbImpact); - var totalImpact = netCarbImpact - insulinImpact; - return { - netCarbImpact: netCarbImpact, - totalImpact: totalImpact - } -} - -function iobCalc(treatment, time) { - - var dia=this.profile.dia; - var scaleFactor = 3.0/dia; - var peak = 75; - var sens=this.profile.sens; - var iobContrib, activityContrib; - var t = time; - if (typeof t === 'undefined') { - t = new Date(); - } - - if (treatment.insulin) { - var bolusTime=new Date(treatment.created_at); - var minAgo=scaleFactor*(t-bolusTime)/1000/60; - - if (minAgo < 0) { - iobContrib=0; - activityContrib=0; - } - if (minAgo < peak) { - var x = minAgo/5+1; - iobContrib=treatment.insulin*(1-0.001852*x*x+0.001852*x); - activityContrib=sens*treatment.insulin*(2/dia/60/peak)*minAgo; - - } - else if (minAgo < 180) { - var x = (minAgo-75)/5; - iobContrib=treatment.insulin*(0.001323*x*x - .054233*x + .55556); - activityContrib=sens*treatment.insulin*(2/dia/60-(minAgo-peak)*2/dia/60/(60*dia-peak)); - } - else { - iobContrib=0; - activityContrib=0; - } - return { - iobContrib: iobContrib, - activityContrib: activityContrib - }; - } - else { - return ''; - } - } - -function cobCalc(treatment, lastDecayedBy, time) { - - var carbs_hr = this.profile.carbs_hr; - var delay = 20; - var carbs_min = carbs_hr / 60; - var isDecaying = 0; - var initialCarbs; - - if (treatment.carbs) { - var carbTime = new Date(treatment.created_at); - - var decayedBy = new Date(carbTime); - var minutesleft = (lastDecayedBy-carbTime)/1000/60; - decayedBy.setMinutes(decayedBy.getMinutes() + Math.max(delay,minutesleft) + treatment.carbs/carbs_min); - if(delay > minutesleft) { - initialCarbs = parseInt(treatment.carbs); - } - else { - initialCarbs = parseInt(treatment.carbs) + minutesleft*carbs_min; - } - var startDecay = new Date(carbTime); - startDecay.setMinutes(carbTime.getMinutes() + delay); - if (time < lastDecayedBy || time > startDecay) { - isDecaying = 1; - } - else { - isDecaying = 0; - } - return { - initialCarbs: initialCarbs, - decayedBy: decayedBy, - isDecaying: isDecaying, - carbTime: carbTime - }; - } - else { - return ''; - } -} - -function updateVisualisation() { - - var displayCob = Math.round(this.env.cob.cob * 10) / 10; - - this.updateMajorPillText(displayCob + " g",'COB'); - -} - - -function COB(pluginBase) { - - if (pluginBase) { pluginBase.call(this); } - - return { - cobTotal: cobTotal, - cobCalc: cobCalc, - iobTotal: iobTotal, - iobCalc: iobCalc, - getData: getData, - updateVisualisation: updateVisualisation - }; - -} - -module.exports = COB; diff --git a/lib/pebble.js b/lib/pebble.js index ea9b9bbabc7..4a2910dbc52 100644 --- a/lib/pebble.js +++ b/lib/pebble.js @@ -13,7 +13,7 @@ var DIRECTIONS = { , 'RATE OUT OF RANGE': 9 }; -var iob = require("./iob")(); +var iob = require("./plugins/iob")(); var async = require('async'); var units = require('./units')(); diff --git a/lib/pluginbase.js b/lib/pluginbase.js deleted file mode 100644 index 293dc8df9be..00000000000 --- a/lib/pluginbase.js +++ /dev/null @@ -1,46 +0,0 @@ -'use strict'; - -function setEnv(env) { - this.profile = env.profile; - this.currentDetails = env.currentDetails; - this.pluginPills = env.pluginPills; - this.iob = env.iob; - - // TODO: clean! - this.env = env; -} - -function updateMajorPillText(updatedText, label) { - - var pillName = "span.pill." + this.name; - - var pill = this.pluginPills.find(pillName); - - if (!pill || pill.length == 0) { - pill = $(''); - this.pluginPills.append(pill); - } - - pill.find('em').text(updatedText); -} - -function scaleBg(bg) { - if (browserSettings.units == 'mmol') { - return Nightscout.units.mgdlToMMOL(bg); - } else { - return bg; - } -} - -function PluginBase() { - return { - setEnv: setEnv, - updateMajorPillText: updateMajorPillText - }; -} - -PluginBase.prototype.scaleBg = scaleBg; -PluginBase.prototype.setEnv = setEnv; -PluginBase.prototype.updateMajorPillText = updateMajorPillText; - -module.exports = PluginBase; diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js new file mode 100644 index 00000000000..402b4e5b1e5 --- /dev/null +++ b/lib/plugins/boluswizardpreview.js @@ -0,0 +1,45 @@ +'use strict'; + +// class methods +function updateVisualisation() { + + var sgv = this.env.sgv; + + var bat = 0.0; + + // TODO: MMOL + + sgv = this.scaleBg(sgv); + + var effect = this.iob.iob * this.profile.sens; + var outcome = sgv - effect; + var delta = 0; + + if (outcome > this.profile.target_high) { + delta = outcome - this.profile.target_high; + bat = delta / this.profile.sens; + } + + if (outcome < this.profile.target_low) { + delta = Math.abs( outcome - this.profile.target_low); + bat = delta / this.profile.sens * -1; + } + + bat = Math.round(bat * 100) / 100; + + // display text + this.updateMajorPillText(bat + 'U','BWP'); + +} + + +function BWP(pluginBase) { + pluginBase.call(this); + + return { + label: 'Bolus Wizard Preview', + updateVisualisation: updateVisualisation + }; +} + +module.exports = BWP; \ No newline at end of file diff --git a/lib/plugins/cannulaage.js b/lib/plugins/cannulaage.js new file mode 100644 index 00000000000..d7682669d37 --- /dev/null +++ b/lib/plugins/cannulaage.js @@ -0,0 +1,43 @@ +'use strict'; + +// class methods +function updateVisualisation() { + + var age = 0; + var found = false; + + for (var t in this.env.treatments) { + if (this.env.treatments.hasOwnProperty(t)) { + var treatment = this.env.treatments[t]; + + if (treatment.eventType == "Site Change") { + var treatmentDate = new Date(treatment.created_at); + var hours = Math.round(Math.abs(new Date() - treatmentDate) / 36e5); + + if (!found) { + found = true; + age = hours; + } else { + if (hours < age) { + age = hours; + } + } + } + } + } + + this.updateMajorPillText(age+'h', 'CAGE'); + +} + + +function CAGE(pluginBase) { + pluginBase.call(this); + + return { + label: 'Cannula Age', + updateVisualisation: updateVisualisation + }; +} + +module.exports = CAGE; \ No newline at end of file diff --git a/lib/plugins/cob.js b/lib/plugins/cob.js new file mode 100644 index 00000000000..472a330bbd4 --- /dev/null +++ b/lib/plugins/cob.js @@ -0,0 +1,214 @@ +'use strict'; + +function getData() { + return this.cobTotal(this.env.treatments,this.env.time); +} + +function cobTotal(treatments, time) { + var liverSensRatio = 1; + var sens = this.profile.sens; + var carbratio = this.profile.carbratio; + var cob=0; + if (!treatments) return {}; + if (typeof time === 'undefined') { + time = new Date(); + } + + var isDecaying = 0; + var lastDecayedBy = new Date('1/1/1970'); + var carbs_hr = this.profile.carbs_hr; + + for (var t in treatments) { + if (treatments.hasOwnProperty(t)) { + var treatment = treatments[t]; + if (treatment.carbs && treatment.created_at < time) { + var cCalc = this.cobCalc(treatment, lastDecayedBy, time); + var decaysin_hr = (cCalc.decayedBy - time) / 1000 / 60 / 60; + if (decaysin_hr > -10) { + var actStart = this.iobTotal(treatments, lastDecayedBy).activity; + var actEnd = this.iobTotal(treatments, cCalc.decayedBy).activity; + var avgActivity = (actStart + actEnd) / 2; + var delayedCarbs = avgActivity * liverSensRatio * sens / carbratio; + var delayMinutes = Math.round(delayedCarbs / carbs_hr * 60); + if (delayMinutes > 0) { + cCalc.decayedBy.setMinutes(cCalc.decayedBy.getMinutes() + delayMinutes); + decaysin_hr = (cCalc.decayedBy - time) / 1000 / 60 / 60; + } + } + + if (cCalc) { + lastDecayedBy = cCalc.decayedBy; + } + + if (decaysin_hr > 0) { + //console.info('Adding ' + delayMinutes + ' minutes to decay of ' + treatment.carbs + 'g bolus at ' + treatment.created_at); + cob += Math.min(cCalc.initialCarbs, decaysin_hr * carbs_hr); + console.log("cob: " + Math.min(cCalc.initialCarbs, decaysin_hr * carbs_hr)); + isDecaying = cCalc.isDecaying; + } + else { + cob = 0; + } + + } + } + } + var rawCarbImpact = isDecaying*sens/carbratio*carbs_hr/60; + return { + decayedBy: lastDecayedBy, + isDecaying: isDecaying, + carbs_hr: carbs_hr, + rawCarbImpact: rawCarbImpact, + cob: cob + }; +} + +function iobTotal(treatments, time) { + var iob= 0; + var activity = 0; + if (!treatments) return {}; + if (typeof time === 'undefined') { + time = new Date(); + } + + for (var t in treatments) { + if (treatments.hasOwnProperty(t)) { + var treatment = treatments[t]; + if (treatment.created_at < time) { + var tIOB = this.iobCalc(treatment, time); + if (tIOB && tIOB.iobContrib) iob += tIOB.iobContrib; + if (tIOB && tIOB.activityContrib) activity += tIOB.activityContrib; + } + } + } + + return { + iob: iob, + activity: activity + }; +} + + +function carbImpact(rawCarbImpact, insulinImpact) { + var liverSensRatio = 1.0; + var liverCarbImpactMax = 0.7; + var liverCarbImpact = Math.min(liverCarbImpactMax, liverSensRatio*insulinImpact); + //var liverCarbImpact = liverSensRatio*insulinImpact; + var netCarbImpact = Math.max(0, rawCarbImpact-liverCarbImpact); + var totalImpact = netCarbImpact - insulinImpact; + return { + netCarbImpact: netCarbImpact, + totalImpact: totalImpact + } +} + +function iobCalc(treatment, time) { + + var dia=this.profile.dia; + var scaleFactor = 3.0/dia; + var peak = 75; + var sens=this.profile.sens; + var iobContrib, activityContrib; + var t = time; + if (typeof t === 'undefined') { + t = new Date(); + } + + if (treatment.insulin) { + var bolusTime=new Date(treatment.created_at); + var minAgo=scaleFactor*(t-bolusTime)/1000/60; + + if (minAgo < 0) { + iobContrib=0; + activityContrib=0; + } + + if (minAgo < peak) { + var x1 = minAgo/5+1; + iobContrib=treatment.insulin*(1-0.001852*x1*x1+0.001852*x1); + activityContrib=sens*treatment.insulin*(2/dia/60/peak)*minAgo; + + } else if (minAgo < 180) { + var x2 = (minAgo-75)/5; + iobContrib=treatment.insulin*(0.001323*x2*x2 - .054233*x2 + .55556); + activityContrib=sens*treatment.insulin*(2/dia/60-(minAgo-peak)*2/dia/60/(60*dia-peak)); + } else { + iobContrib=0; + activityContrib=0; + } + return { + iobContrib: iobContrib, + activityContrib: activityContrib + }; + } + else { + return ''; + } +} + +function cobCalc(treatment, lastDecayedBy, time) { + + var carbs_hr = this.profile.carbs_hr; + var delay = 20; + var carbs_min = carbs_hr / 60; + var isDecaying = 0; + var initialCarbs; + + if (treatment.carbs) { + var carbTime = new Date(treatment.created_at); + + var decayedBy = new Date(carbTime); + var minutesleft = (lastDecayedBy-carbTime)/1000/60; + decayedBy.setMinutes(decayedBy.getMinutes() + Math.max(delay,minutesleft) + treatment.carbs/carbs_min); + if(delay > minutesleft) { + initialCarbs = parseInt(treatment.carbs); + } + else { + initialCarbs = parseInt(treatment.carbs) + minutesleft*carbs_min; + } + var startDecay = new Date(carbTime); + startDecay.setMinutes(carbTime.getMinutes() + delay); + if (time < lastDecayedBy || time > startDecay) { + isDecaying = 1; + } + else { + isDecaying = 0; + } + return { + initialCarbs: initialCarbs, + decayedBy: decayedBy, + isDecaying: isDecaying, + carbTime: carbTime + }; + } + else { + return ''; + } +} + +function updateVisualisation() { + + var displayCob = Math.round(this.env.cob.cob * 10) / 10; + + this.updateMajorPillText(displayCob + " g",'COB'); + +} + + +function COB(pluginBase) { + + if (pluginBase) { pluginBase.call(this); } + + return { + label: 'Carbs-on-Board', + cobTotal: cobTotal, + cobCalc: cobCalc, + iobTotal: iobTotal, + iobCalc: iobCalc, + getData: getData, + updateVisualisation: updateVisualisation + }; + +} + +module.exports = COB; diff --git a/lib/plugins/index.js b/lib/plugins/index.js new file mode 100644 index 00000000000..ba9c73e025a --- /dev/null +++ b/lib/plugins/index.js @@ -0,0 +1,78 @@ +'use strict'; + +var _ = require('lodash') + , inherits = require("inherits") + , PluginBase = require('./pluginbase') // Define any shared functionality in this class + , allPlugins = [] + , enabledPlugins = []; + +function register(all) { + + allPlugins = []; + + for (var p in all) { + if (all.hasOwnProperty(p)) { + var plugin = all[p](PluginBase); + + inherits(plugin, PluginBase); + plugin.name = p; + + for (var n in PluginBase.prototype) { + if (PluginBase.prototype.hasOwnProperty(n)) { + plugin[n] = PluginBase.prototype[n]; + } + } + allPlugins.push(plugin); + } + } + +} + +function clientInit(app) { + enabledPlugins = []; + console.info('NightscoutPlugins init', app); + function isEnabled(plugin) { + return app.enabledOptions + && app.enabledOptions.indexOf(plugin.name) > -1; + } + + _.forEach(allPlugins, function eachPlugin(plugin) { + plugin.enabled = isEnabled(plugin); + if (plugin.enabled) { + enabledPlugins.push(plugin); + } + }); + console.info('Plugins enabled', enabledPlugins); +} + +function setEnv(env) { + _.forEach(enabledPlugins, function eachPlugin(plugin) { + plugin.setEnv(env); + }); +} + +function updateVisualisations() { + _.forEach(enabledPlugins, function eachPlugin(plugin) { + plugin.updateVisualisation && plugin.updateVisualisation(); + }); +} + +function eachPlugin(f) { + _.forEach(allPlugins, f); +} + +function eachEnabledPlugin(f) { + _.forEach(enabledPlugins, f); +} + +function plugins() { + plugins.register = register; + plugins.clientInit = clientInit; + plugins.setEnv = setEnv; + plugins.updateVisualisations = updateVisualisations; + plugins.eachPlugin = eachPlugin; + plugins.eachEnabledPlugin = eachEnabledPlugin; + return plugins; +} + +module.exports = plugins; \ No newline at end of file diff --git a/lib/iob.js b/lib/plugins/iob.js similarity index 74% rename from lib/iob.js rename to lib/plugins/iob.js index ee2f716cecd..dac9ce089de 100644 --- a/lib/iob.js +++ b/lib/plugins/iob.js @@ -1,8 +1,7 @@ 'use strict'; -function getData() -{ - return calcTotal(this.env.treatments,this.env.profile,this.env.time); +function getData() { + return calcTotal(this.env.treatments,this.env.profile,this.env.time); } function calcTotal(treatments, profile, time) { @@ -50,13 +49,13 @@ function calcTreatment(treatment, profile, time) { var minAgo = scaleFactor * (time - bolusTime) / 1000 / 60; if (minAgo < peak) { - var x = minAgo / 5 + 1; - iobContrib = treatment.insulin * (1 - 0.001852 * x * x + 0.001852 * x); + var x1 = minAgo / 5 + 1; + iobContrib = treatment.insulin * (1 - 0.001852 * x1 * x1 + 0.001852 * x1); activityContrib = sens * treatment.insulin * (2 / dia / 60 / peak) * minAgo; } else if (minAgo < 180) { - var x = (minAgo - 75) / 5; - iobContrib = treatment.insulin * (0.001323 * x * x - .054233 * x + .55556); + var x2 = (minAgo - 75) / 5; + iobContrib = treatment.insulin * (0.001323 * x2 * x2 - .054233 * x2 + .55556); activityContrib = sens * treatment.insulin * (2 / dia / 60 - (minAgo - peak) * 2 / dia / 60 / (60 * dia - peak)); } else { iobContrib = 0; @@ -73,24 +72,23 @@ function calcTreatment(treatment, profile, time) { } function updateVisualisation() { - - var pill = this.currentDetails.find('span.pill.iob'); - - if (!pill || pill.length == 0) { - pill = $(''); - this.currentDetails.append(pill); - } - - pill.find('em').text(this.iob.display + 'U'); - + var pill = this.currentDetails.find('span.pill.iob'); + + if (!pill || pill.length == 0) { + pill = $(''); + this.currentDetails.append(pill); + } + + pill.find('em').text(this.iob.display + 'U'); } function IOB(pluginBase) { if (pluginBase) { pluginBase.call(this); } - + return { + label: 'Insulin-on-Board', calcTotal: calcTotal, getData: getData, updateVisualisation: updateVisualisation, diff --git a/lib/plugins/pluginbase.js b/lib/plugins/pluginbase.js new file mode 100644 index 00000000000..29b8724e617 --- /dev/null +++ b/lib/plugins/pluginbase.js @@ -0,0 +1,46 @@ +'use strict'; + +function setEnv(env) { + this.profile = env.profile; + this.currentDetails = env.currentDetails; + this.pluginPills = env.pluginPills; + this.iob = env.iob; + + // TODO: clean! + this.env = env; +} + +function updateMajorPillText(updatedText, label) { + + var pillName = "span.pill." + this.name; + + var pill = this.pluginPills.find(pillName); + + if (!pill || pill.length == 0) { + pill = $(''); + this.pluginPills.append(pill); + } + + pill.find('em').text(updatedText); +} + +function scaleBg(bg) { + if (browserSettings.units == 'mmol') { + return Nightscout.units.mgdlToMMOL(bg); + } else { + return bg; + } +} + +function PluginBase() { + return { + setEnv: setEnv, + updateMajorPillText: updateMajorPillText + }; +} + +PluginBase.prototype.scaleBg = scaleBg; +PluginBase.prototype.setEnv = setEnv; +PluginBase.prototype.updateMajorPillText = updateMajorPillText; + +module.exports = PluginBase; diff --git a/package.json b/package.json index 3deba772972..80267e0ba77 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "forever": "~0.13.0", "git-rev": "git://github.com/bewest/git-rev.git", "inherits": "~2.0.1", + "lodash": "^3.9.1", "long": "~2.2.3", "mongodb": "^1.4.7", "moment": "2.8.1", diff --git a/static/index.html b/static/index.html index 6f7b9ea0d32..5b25db9df83 100644 --- a/static/index.html +++ b/static/index.html @@ -132,6 +132,9 @@

    Nightscout

    +
    +
    Enable Plugins
    +
    diff --git a/static/js/client.js b/static/js/client.js index 029a045863f..f30a80804f1 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -464,14 +464,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } - // PLUGIN MANAGEMENT CODE - - function isPluginEnabled(name) { - return app.enabledOptions - && app.enabledOptions.indexOf(name) > -1; - } - - function updatePluginData(sgv, time) { + function updatePlugins(sgv, time) { var env = {}; env.profile = profile; env.currentDetails = currentDetails; @@ -482,56 +475,27 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; // Update the env through data provider plugins - for (var p in NightscoutPlugins) { - if (NightscoutPlugins.hasOwnProperty(p) && isPluginEnabled(p)) { - var plugin = NightscoutPlugins[p]; + Nightscout.plugins.eachEnabledPlugin(function updateEachPlugin(plugin) { + plugin.setEnv(env); - plugin.setEnv(env); - - // check if the plugin implements processing data - - if (plugin.getData) { - var dataFromPlugin = plugin.getData(); - var container = {}; - for (var i in dataFromPlugin) { - if (dataFromPlugin.hasOwnProperty(i)) { - container[i] = dataFromPlugin[i]; - } + // check if the plugin implements processing data + if (plugin.getData) { + var dataFromPlugin = plugin.getData(); + var container = {}; + for (var i in dataFromPlugin) { + if (dataFromPlugin.hasOwnProperty(i)) { + container[i] = dataFromPlugin[i]; } - env[p] = container; } + env[plugin.name] = container; } - } - - // update data the plugins - - sendEnvToPlugins(env); - } - - function sendEnvToPlugins(env) { - for (var p in NightscoutPlugins) { - if (NightscoutPlugins.hasOwnProperty(p)) { - var plugin = NightscoutPlugins[p]; - plugin.setEnv(env); - } - } - } - - function updatePluginVisualisation() { - for (var p in NightscoutPlugins) { - if (NightscoutPlugins.hasOwnProperty(p) && isPluginEnabled(p)) { - var plugin = NightscoutPlugins[p]; + }); - // check if the plugin implements visualisations - if (plugin.updateVisualisation) { - plugin.updateVisualisation(); - } - } - } + // update data for all the plugins + Nightscout.plugins.setEnv(env); + Nightscout.plugins.updateVisualisations(); } - /// END PLUGIN CODE - // predict for retrospective data // by changing lookback from 1 to 2, we modify the AR algorithm to determine its initial slope from 10m // of data instead of 5, which eliminates the incorrect and misleading predictions generated when @@ -580,10 +544,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; bgButton.removeClass('urgent warning inrange'); } - // update plugins - - updatePluginData(focusPoint.y,retroTime); - updatePluginVisualisation(); + // update plugins + + updatePlugins(focusPoint.y, retroTime); $('#currentTime') .text(formatTime(retroTime, true)) @@ -618,10 +581,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; updateBGDelta(prevSGV, latestSGV); - // update plugins - - updatePluginData(latestSGV.y,nowDate); - updatePluginVisualisation(); + // update plugins + + updatePlugins(latestSGV.y, nowDate); currentDirection.html(latestSGV.y < 39 ? '✖' : latestSGV.direction); } @@ -1842,6 +1804,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; , careportalEnabled: xhr.careportalEnabled , defaults: xhr.defaults }; + Nightscout.plugins.clientInit(app); } }).done(function() { $('.appName').text(app.name); diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js index 7b1c4a54ab7..2e9f0be010f 100644 --- a/static/js/ui-utils.js +++ b/static/js/ui-utils.js @@ -94,8 +94,18 @@ function getBrowserSettings(storage) { } else { $('#12-browser').prop('checked', true); } - } - catch(err) { + + var enablePlugins = $('#enable-plugins'); + Nightscout.plugins.eachPlugin(function each(plugin) { + var id = 'plugin-' + plugin.name; + var dd = $('
    '); + enablePlugins.append(dd); + dd.find('input').prop('checked', plugin.enabled); + console.info('appending plugin dd', dd); + }); + + + } catch(err) { console.error(err); showLocalstorageError(); } From 8625ec0c7dc662bb53f4e04b424d79056cd0f75a Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 24 May 2015 11:37:20 -0700 Subject: [PATCH 030/937] fix iob tests --- tests/iob.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/iob.test.js b/tests/iob.test.js index 58db5edaa7d..042141dd7cc 100644 --- a/tests/iob.test.js +++ b/tests/iob.test.js @@ -3,7 +3,7 @@ var should = require('should'); var FIVE_MINS = 10 * 60 * 1000; describe('IOB', function ( ) { - var iob = require('../lib/iob')(); + var iob = require('../lib/plugins/iob')(); it('should calculate IOB', function() { From 02d5c387b7b0d3e674b22d47345ace9ff8801b02 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 24 May 2015 12:04:11 -0700 Subject: [PATCH 031/937] not using lodash via bower yet --- bower.json | 1 - 1 file changed, 1 deletion(-) diff --git a/bower.json b/bower.json index 0928db8e8d2..372c86fdf38 100644 --- a/bower.json +++ b/bower.json @@ -8,7 +8,6 @@ "jquery": "2.1.0", "jQuery-Storage-API": "~1.7.2", "jsSHA": "~1.5.0", - "lodash": "~3.9.0", "tipsy-jmalonzo": "~1.0.1" }, "resolutions": { From f64f7ef1ac9cfa378880cdf7dc05f0ee0ac006f2 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 24 May 2015 12:08:30 -0700 Subject: [PATCH 032/937] remove some redundant comments after refactoring --- bundle/bundle.source.js | 1 - static/js/client.js | 4 ---- 2 files changed, 5 deletions(-) diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index 8b8ed9d3e03..2782c0304c1 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -2,7 +2,6 @@ window.Nightscout = window.Nightscout || {}; - // Default features window.Nightscout = { units: require('../lib/units')(), profile: require('../lib/profilefunctions')(), diff --git a/static/js/client.js b/static/js/client.js index f30a80804f1..638510f12f3 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -544,8 +544,6 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; bgButton.removeClass('urgent warning inrange'); } - // update plugins - updatePlugins(focusPoint.y, retroTime); $('#currentTime') @@ -581,8 +579,6 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; updateBGDelta(prevSGV, latestSGV); - // update plugins - updatePlugins(latestSGV.y, nowDate); currentDirection.html(latestSGV.y < 39 ? '✖' : latestSGV.direction); From 95ed7e44812aae703278a711ce44416fd312c2d9 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 24 May 2015 13:59:00 -0700 Subject: [PATCH 033/937] show plugin setting works, lots more refactoring --- README.md | 1 + bundle/bundle.source.js | 8 +- env.js | 1 + lib/plugins/boluswizardpreview.js | 4 +- lib/plugins/cannulaage.js | 4 +- lib/plugins/cob.js | 7 +- lib/plugins/index.js | 60 +-- lib/plugins/iob.js | 6 +- lib/plugins/pluginbase.js | 5 +- package.json | 1 - static/index.html | 4 +- static/js/client.js | 11 +- static/js/ui-utils.js | 644 +++++++++++++++--------------- 13 files changed, 379 insertions(+), 377 deletions(-) diff --git a/README.md b/README.md index a635bc91d92..136fd3cede1 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,7 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * `ALARM_TIMEAGO_WARN_MINS` (`15`) - minutes since the last reading to trigger a warning * `ALARM_TIMEAGO_URGENT` (`on`) - possible values `on` or `off` * `ALARM_TIMEAGO_URGENT_MINS` (`30`) - minutes since the last reading to trigger a urgent alarm + * `SHOW_PLUGINS` - enabled plugins that should have their visualisations shown, defaults to all enabled ## Setting environment variables diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index 2782c0304c1..bb601616de4 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -9,10 +9,10 @@ }; window.Nightscout.plugins.register({ - iob: require('../lib/plugins/iob'), - cob: require('../lib/plugins/cob'), - bwp: require('../lib/plugins/boluswizardpreview'), - cage: require('../lib/plugins/cannulaage') + iob: require('../lib/plugins/iob')(), + cob: require('../lib/plugins/cob')(), + bwp: require('../lib/plugins/boluswizardpreview')(), + cage: require('../lib/plugins/cannulaage')() }); console.info("Nightscout bundle ready", window.Nightscout); diff --git a/env.js b/env.js index 5c06e5c9a0a..a0f381031ff 100644 --- a/env.js +++ b/env.js @@ -92,6 +92,7 @@ function config ( ) { env.defaults.alarmTimeAgoWarnMins = readENV('ALARM_TIMEAGO_WARN_MINS', env.defaults.alarmTimeAgoWarnMins); env.defaults.alarmTimeAgoUrgent = readENV('ALARM_TIMEAGO_URGENT', env.defaults.alarmTimeAgoUrgent); env.defaults.alarmTimeAgoUrgentMins = readENV('ALARM_TIMEAGO_URGENT_MINS', env.defaults.alarmTimeAgoUrgentMins); + env.defaults.showPlugins = readENV('SHOW_PLUGINS', ''); //console.log(JSON.stringify(env.defaults)); diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 402b4e5b1e5..adce8df2d5b 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -33,9 +33,7 @@ function updateVisualisation() { } -function BWP(pluginBase) { - pluginBase.call(this); - +function BWP() { return { label: 'Bolus Wizard Preview', updateVisualisation: updateVisualisation diff --git a/lib/plugins/cannulaage.js b/lib/plugins/cannulaage.js index d7682669d37..2d218f23bee 100644 --- a/lib/plugins/cannulaage.js +++ b/lib/plugins/cannulaage.js @@ -31,9 +31,7 @@ function updateVisualisation() { } -function CAGE(pluginBase) { - pluginBase.call(this); - +function CAGE() { return { label: 'Cannula Age', updateVisualisation: updateVisualisation diff --git a/lib/plugins/cob.js b/lib/plugins/cob.js index 472a330bbd4..d49f7f8cf0b 100644 --- a/lib/plugins/cob.js +++ b/lib/plugins/cob.js @@ -43,7 +43,7 @@ function cobTotal(treatments, time) { if (decaysin_hr > 0) { //console.info('Adding ' + delayMinutes + ' minutes to decay of ' + treatment.carbs + 'g bolus at ' + treatment.created_at); cob += Math.min(cCalc.initialCarbs, decaysin_hr * carbs_hr); - console.log("cob: " + Math.min(cCalc.initialCarbs, decaysin_hr * carbs_hr)); + //console.log("cob: " + Math.min(cCalc.initialCarbs, decaysin_hr * carbs_hr)); isDecaying = cCalc.isDecaying; } else { @@ -195,10 +195,7 @@ function updateVisualisation() { } -function COB(pluginBase) { - - if (pluginBase) { pluginBase.call(this); } - +function COB() { return { label: 'Carbs-on-Board', cobTotal: cobTotal, diff --git a/lib/plugins/index.js b/lib/plugins/index.js index ba9c73e025a..ce14b1edefa 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -1,8 +1,7 @@ 'use strict'; var _ = require('lodash') - , inherits = require("inherits") - , PluginBase = require('./pluginbase') // Define any shared functionality in this class + , PluginBase = require('./pluginbase')() // Define any shared functionality in this class , allPlugins = [] , enabledPlugins = []; @@ -10,21 +9,11 @@ function register(all) { allPlugins = []; - for (var p in all) { - if (all.hasOwnProperty(p)) { - var plugin = all[p](PluginBase); - - inherits(plugin, PluginBase); - plugin.name = p; - - for (var n in PluginBase.prototype) { - if (PluginBase.prototype.hasOwnProperty(n)) { - plugin[n] = PluginBase.prototype[n]; - } - } - allPlugins.push(plugin); - } - } + _.forIn(all, function eachPlugin(plugin, name) { + plugin.name = name; + _.extend(plugin, PluginBase); + allPlugins.push(plugin); + }); } @@ -51,8 +40,8 @@ function setEnv(env) { }); } -function updateVisualisations() { - _.forEach(enabledPlugins, function eachPlugin(plugin) { +function updateVisualisations(clientSettings) { + eachShownPlugin(clientSettings, function eachPlugin(plugin) { plugin.updateVisualisation && plugin.updateVisualisation(); }); } @@ -62,17 +51,34 @@ function eachPlugin(f) { } function eachEnabledPlugin(f) { - _.forEach(enabledPlugins, f); + _.forEach(enabledPlugins, f); +} + +function eachShownPlugin(clientSettings, f) { + var filtered = _.filter(enabledPlugins, function filterPlugins(plugin) { + return clientSettings && clientSettings.showPlugins && clientSettings.showPlugins.indexOf(plugin.name) > -1; + }); + + _.forEach(filtered, f); +} + +function enabledPluginNames() { + return _.map(enabledPlugins, function mapped(plugin) { + return plugin.name; + }).join(' '); } function plugins() { - plugins.register = register; - plugins.clientInit = clientInit; - plugins.setEnv = setEnv; - plugins.updateVisualisations = updateVisualisations; - plugins.eachPlugin = eachPlugin; - plugins.eachEnabledPlugin = eachEnabledPlugin; - return plugins; + return { + register: register + , clientInit: clientInit + , setEnv: setEnv + , updateVisualisations: updateVisualisations + , eachPlugin: eachPlugin + , eachEnabledPlugin: eachEnabledPlugin + , eachShownPlugin: eachShownPlugin + , enabledPluginNames: enabledPluginNames + } } module.exports = plugins; \ No newline at end of file diff --git a/lib/plugins/iob.js b/lib/plugins/iob.js index dac9ce089de..6722e966e15 100644 --- a/lib/plugins/iob.js +++ b/lib/plugins/iob.js @@ -83,10 +83,7 @@ function updateVisualisation() { } -function IOB(pluginBase) { - - if (pluginBase) { pluginBase.call(this); } - +function IOB() { return { label: 'Insulin-on-Board', calcTotal: calcTotal, @@ -95,7 +92,6 @@ function IOB(pluginBase) { isDataProvider: true, isVisualisationProvider: true }; - } module.exports = IOB; \ No newline at end of file diff --git a/lib/plugins/pluginbase.js b/lib/plugins/pluginbase.js index 29b8724e617..a154801cf41 100644 --- a/lib/plugins/pluginbase.js +++ b/lib/plugins/pluginbase.js @@ -35,12 +35,9 @@ function scaleBg(bg) { function PluginBase() { return { setEnv: setEnv, + scaleBg: scaleBg, updateMajorPillText: updateMajorPillText }; } -PluginBase.prototype.scaleBg = scaleBg; -PluginBase.prototype.setEnv = setEnv; -PluginBase.prototype.updateMajorPillText = updateMajorPillText; - module.exports = PluginBase; diff --git a/package.json b/package.json index 80267e0ba77..605edb83489 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,6 @@ "express-extension-to-accept": "0.0.2", "forever": "~0.13.0", "git-rev": "git://github.com/bewest/git-rev.git", - "inherits": "~2.0.1", "lodash": "^3.9.1", "long": "~2.2.3", "mongodb": "^1.4.7", diff --git a/static/index.html b/static/index.html index 5b25db9df83..df6c473f343 100644 --- a/static/index.html +++ b/static/index.html @@ -132,8 +132,8 @@

    Nightscout

    -
    -
    Enable Plugins
    +
    +
    Show Plugins
    diff --git a/static/js/client.js b/static/js/client.js index 638510f12f3..d4624fc07b7 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -473,9 +473,8 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; env.treatments = treatments; env.time = time; - // Update the env through data provider plugins - - Nightscout.plugins.eachEnabledPlugin(function updateEachPlugin(plugin) { + Nightscout.plugins.eachShownPlugin(browserSettings, function updateEachPlugin(plugin) { + // Update the env through data provider plugins plugin.setEnv(env); // check if the plugin implements processing data @@ -491,9 +490,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } }); - // update data for all the plugins + // update data for all the plugins, before updating visualisations Nightscout.plugins.setEnv(env); - Nightscout.plugins.updateVisualisations(); + Nightscout.plugins.updateVisualisations(browserSettings); } // predict for retrospective data @@ -1800,7 +1799,6 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; , careportalEnabled: xhr.careportalEnabled , defaults: xhr.defaults }; - Nightscout.plugins.clientInit(app); } }).done(function() { $('.appName').text(app.name); @@ -1810,6 +1808,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; $('.serverSettings').show(); } $('#treatmentDrawerToggle').toggle(app.careportalEnabled); + Nightscout.plugins.clientInit(app); browserSettings = getBrowserSettings(browserStorage); init(); }); diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js index 2e9f0be010f..50acb03dc56 100644 --- a/static/js/ui-utils.js +++ b/static/js/ui-utils.js @@ -3,430 +3,440 @@ var openDraw = null; function rawBGsEnabled() { - return app.enabledOptions && app.enabledOptions.indexOf('rawbg') > -1; + return app.enabledOptions && app.enabledOptions.indexOf('rawbg') > -1; } function getBrowserSettings(storage) { - var json = {}; - - function scaleBg(bg) { - if (json.units == 'mmol') { - return Nightscout.units.mgdlToMMOL(bg); - } else { - return bg; - } + var json = {}; + + function scaleBg(bg) { + if (json.units == 'mmol') { + return Nightscout.units.mgdlToMMOL(bg); + } else { + return bg; + } + } + + function appendThresholdValue(threshold) { + return app.alarm_types.indexOf('simple') == -1 ? '' : ' (' + scaleBg(threshold) + ')'; + } + + try { + var json = { + 'units': storage.get('units'), + 'alarmUrgentHigh': storage.get('alarmUrgentHigh'), + 'alarmHigh': storage.get('alarmHigh'), + 'alarmLow': storage.get('alarmLow'), + 'alarmUrgentLow': storage.get('alarmUrgentLow'), + 'alarmTimeAgoWarn': storage.get('alarmTimeAgoWarn'), + 'alarmTimeAgoWarnMins': storage.get('alarmTimeAgoWarnMins'), + 'alarmTimeAgoUrgent': storage.get('alarmTimeAgoUrgent'), + 'alarmTimeAgoUrgentMins': storage.get('alarmTimeAgoUrgentMins'), + 'nightMode': storage.get('nightMode'), + 'showRawbg': storage.get('showRawbg'), + 'customTitle': storage.get('customTitle'), + 'theme': storage.get('theme'), + 'timeFormat': storage.get('timeFormat'), + 'showPlugins': storage.get('showPlugins') + }; + + // Default browser units to server units if undefined. + json.units = setDefault(json.units, app.units); + if (json.units == 'mmol') { + $('#mmol-browser').prop('checked', true); + } else { + $('#mgdl-browser').prop('checked', true); + } + + json.alarmUrgentHigh = setDefault(json.alarmUrgentHigh, app.defaults.alarmUrgentHigh); + json.alarmHigh = setDefault(json.alarmHigh, app.defaults.alarmHigh); + json.alarmLow = setDefault(json.alarmLow, app.defaults.alarmLow); + json.alarmUrgentLow = setDefault(json.alarmUrgentLow, app.defaults.alarmUrgentLow); + json.alarmTimeAgoWarn = setDefault(json.alarmTimeAgoWarn, app.defaults.alarmTimeAgoWarn); + json.alarmTimeAgoWarnMins = setDefault(json.alarmTimeAgoWarnMins, app.defaults.alarmTimeAgoWarnMins); + json.alarmTimeAgoUrgent = setDefault(json.alarmTimeAgoUrgent, app.defaults.alarmTimeAgoUrgent); + json.alarmTimeAgoUrgentMins = setDefault(json.alarmTimeAgoUrgentMins, app.defaults.alarmTimeAgoUrgentMins); + $('#alarm-urgenthigh-browser').prop('checked', json.alarmUrgentHigh).next().text('Urgent High Alarm' + appendThresholdValue(app.thresholds.bg_high)); + $('#alarm-high-browser').prop('checked', json.alarmHigh).next().text('High Alarm' + appendThresholdValue(app.thresholds.bg_target_top)); + $('#alarm-low-browser').prop('checked', json.alarmLow).next().text('Low Alarm' + appendThresholdValue(app.thresholds.bg_target_bottom)); + $('#alarm-urgentlow-browser').prop('checked', json.alarmUrgentLow).next().text('Urgent Low Alarm' + appendThresholdValue(app.thresholds.bg_low)); + $('#alarm-timeagowarn-browser').prop('checked', json.alarmTimeAgoWarn); + $('#alarm-timeagowarnmins-browser').val(json.alarmTimeAgoWarnMins); + $('#alarm-timeagourgent-browser').prop('checked', json.alarmTimeAgoUrgent); + $('#alarm-timeagourgentmins-browser').val(json.alarmTimeAgoUrgentMins); + + json.nightMode = setDefault(json.nightMode, app.defaults.nightMode); + $('#nightmode-browser').prop('checked', json.nightMode); + + if (rawBGsEnabled()) { + $('#show-rawbg-option').show(); + json.showRawbg = setDefault(json.showRawbg, app.defaults.showRawbg); + $('#show-rawbg-' + json.showRawbg).prop('checked', true); + } else { + json.showRawbg = 'never'; + $('#show-rawbg-option').hide(); } - function appendThresholdValue(threshold) { - return app.alarm_types.indexOf('simple') == -1 ? '' : ' (' + scaleBg(threshold) + ')'; + json.customTitle = setDefault(json.customTitle, app.defaults.customTitle); + $('h1.customTitle').text(json.customTitle); + $('input#customTitle').prop('value', json.customTitle); + + json.theme = setDefault(json.theme, app.defaults.theme); + if (json.theme == 'colors') { + $('#theme-colors-browser').prop('checked', true); + } else { + $('#theme-default-browser').prop('checked', true); } - try { - var json = { - 'units': storage.get('units'), - 'alarmUrgentHigh': storage.get('alarmUrgentHigh'), - 'alarmHigh': storage.get('alarmHigh'), - 'alarmLow': storage.get('alarmLow'), - 'alarmUrgentLow': storage.get('alarmUrgentLow'), - 'alarmTimeAgoWarn': storage.get('alarmTimeAgoWarn'), - 'alarmTimeAgoWarnMins': storage.get('alarmTimeAgoWarnMins'), - 'alarmTimeAgoUrgent': storage.get('alarmTimeAgoUrgent'), - 'alarmTimeAgoUrgentMins': storage.get('alarmTimeAgoUrgentMins'), - 'nightMode': storage.get('nightMode'), - 'showRawbg': storage.get('showRawbg'), - 'customTitle': storage.get('customTitle'), - 'theme': storage.get('theme'), - 'timeFormat': storage.get('timeFormat') - }; - - // Default browser units to server units if undefined. - json.units = setDefault(json.units, app.units); - if (json.units == 'mmol') { - $('#mmol-browser').prop('checked', true); - } else { - $('#mgdl-browser').prop('checked', true); - } - - json.alarmUrgentHigh = setDefault(json.alarmUrgentHigh, app.defaults.alarmUrgentHigh); - json.alarmHigh = setDefault(json.alarmHigh, app.defaults.alarmHigh); - json.alarmLow = setDefault(json.alarmLow, app.defaults.alarmLow); - json.alarmUrgentLow = setDefault(json.alarmUrgentLow, app.defaults.alarmUrgentLow); - json.alarmTimeAgoWarn = setDefault(json.alarmTimeAgoWarn, app.defaults.alarmTimeAgoWarn); - json.alarmTimeAgoWarnMins = setDefault(json.alarmTimeAgoWarnMins, app.defaults.alarmTimeAgoWarnMins); - json.alarmTimeAgoUrgent = setDefault(json.alarmTimeAgoUrgent, app.defaults.alarmTimeAgoUrgent); - json.alarmTimeAgoUrgentMins = setDefault(json.alarmTimeAgoUrgentMins, app.defaults.alarmTimeAgoUrgentMins); - $('#alarm-urgenthigh-browser').prop('checked', json.alarmUrgentHigh).next().text('Urgent High Alarm' + appendThresholdValue(app.thresholds.bg_high)); - $('#alarm-high-browser').prop('checked', json.alarmHigh).next().text('High Alarm' + appendThresholdValue(app.thresholds.bg_target_top)); - $('#alarm-low-browser').prop('checked', json.alarmLow).next().text('Low Alarm' + appendThresholdValue(app.thresholds.bg_target_bottom)); - $('#alarm-urgentlow-browser').prop('checked', json.alarmUrgentLow).next().text('Urgent Low Alarm' + appendThresholdValue(app.thresholds.bg_low)); - $('#alarm-timeagowarn-browser').prop('checked', json.alarmTimeAgoWarn); - $('#alarm-timeagowarnmins-browser').val(json.alarmTimeAgoWarnMins); - $('#alarm-timeagourgent-browser').prop('checked', json.alarmTimeAgoUrgent); - $('#alarm-timeagourgentmins-browser').val(json.alarmTimeAgoUrgentMins); - - json.nightMode = setDefault(json.nightMode, app.defaults.nightMode); - $('#nightmode-browser').prop('checked', json.nightMode); - - if (rawBGsEnabled()) { - $('#show-rawbg-option').show(); - json.showRawbg = setDefault(json.showRawbg, app.defaults.showRawbg); - $('#show-rawbg-' + json.showRawbg).prop('checked', true); - } else { - json.showRawbg = 'never'; - $('#show-rawbg-option').hide(); - } - - json.customTitle = setDefault(json.customTitle, app.defaults.customTitle); - $('h1.customTitle').text(json.customTitle); - $('input#customTitle').prop('value', json.customTitle); - - json.theme = setDefault(json.theme, app.defaults.theme); - if (json.theme == 'colors') { - $('#theme-colors-browser').prop('checked', true); - } else { - $('#theme-default-browser').prop('checked', true); - } - - json.timeFormat = setDefault(json.timeFormat, app.defaults.timeFormat); - - if (json.timeFormat == '24') { - $('#24-browser').prop('checked', true); - } else { - $('#12-browser').prop('checked', true); - } - - var enablePlugins = $('#enable-plugins'); - Nightscout.plugins.eachPlugin(function each(plugin) { - var id = 'plugin-' + plugin.name; - var dd = $('
    '); - enablePlugins.append(dd); - dd.find('input').prop('checked', plugin.enabled); - console.info('appending plugin dd', dd); - }); - - - } catch(err) { - console.error(err); - showLocalstorageError(); + json.timeFormat = setDefault(json.timeFormat, app.defaults.timeFormat); + + if (json.timeFormat == '24') { + $('#24-browser').prop('checked', true); + } else { + $('#12-browser').prop('checked', true); } - return json; + json.showPlugins = setDefault(json.showPlugins, app.defaults.showPlugins || Nightscout.plugins.enabledPluginNames()); + var showPluginsSettings = $('#show-plugins'); + Nightscout.plugins.eachPlugin(function each(plugin) { + var id = 'plugin-' + plugin.name; + var dd = $('
    '); + showPluginsSettings.append(dd); + dd.find('input').prop('checked', json.showPlugins.indexOf(plugin.name) > -1); + }); + + + } catch(err) { + console.error(err); + showLocalstorageError(); + } + + return json; } function setDefault(variable, defaultValue) { - if (typeof(variable) === 'object') { - return defaultValue; - } - return variable; + if (typeof(variable) === 'object') { + return defaultValue; + } + return variable; } function storeInBrowser(data) { - for (var k in data) { - if (data.hasOwnProperty(k)) { - browserStorage.set(k, data[k]); - } + for (var k in data) { + if (data.hasOwnProperty(k)) { + browserStorage.set(k, data[k]); } + } } function getQueryParms() { - var params = {}; - if (location.search) { - location.search.substr(1).split('&').forEach(function(item) { - params[item.split('=')[0]] = item.split('=')[1].replace(/[_\+]/g, ' '); - }); - } - return params; + var params = {}; + if (location.search) { + location.search.substr(1).split('&').forEach(function(item) { + params[item.split('=')[0]] = item.split('=')[1].replace(/[_\+]/g, ' '); + }); + } + return params; } function isTouch() { - try { document.createEvent('TouchEvent'); return true; } - catch (e) { return false; } + try { document.createEvent('TouchEvent'); return true; } + catch (e) { return false; } } function closeDrawer(id, callback) { - openDraw = null; - $("html, body").animate({ scrollTop: 0 }); - $(id).animate({right: '-300px'}, 300, function () { - $(id).css('display', 'none'); - if (callback) callback(); - }); + openDraw = null; + $("html, body").animate({ scrollTop: 0 }); + $(id).animate({right: '-300px'}, 300, function () { + $(id).css('display', 'none'); + if (callback) callback(); + }); } function toggleDrawer(id, openCallback, closeCallback) { - function openDrawer(id, callback) { - function closeOpenDraw(callback) { - if (openDraw) { - closeDrawer(openDraw, callback); - } else { - callback() - } - } - - closeOpenDraw(function () { - openDraw = id; - $(id).css('display', 'block').animate({right: '0'}, 300, function () { - if (callback) callback(); - }); - }); - + function openDrawer(id, callback) { + function closeOpenDraw(callback) { + if (openDraw) { + closeDrawer(openDraw, callback); + } else { + callback() + } } - if (openDraw == id) { - closeDrawer(id, closeCallback); - } else { - openDrawer(id, openCallback); - } + closeOpenDraw(function () { + openDraw = id; + $(id).css('display', 'block').animate({right: '0'}, 300, function () { + if (callback) callback(); + }); + }); + + } + + if (openDraw == id) { + closeDrawer(id, closeCallback); + } else { + openDrawer(id, openCallback); + } } function initTreatmentDrawer() { - $('#eventType').val('BG Check'); - $('#glucoseValue').val('').attr('placeholder', 'Value in ' + browserSettings.units); - $('#meter').prop('checked', true); - $('#carbsGiven').val(''); - $('#insulinGiven').val(''); - $('#preBolus').val(0); - $('#notes').val(''); - $('#enteredBy').val(browserStorage.get('enteredBy') || ''); - $('#nowtime').prop('checked', true); - $('#eventTimeValue').val(currentTime()); + $('#eventType').val('BG Check'); + $('#glucoseValue').val('').attr('placeholder', 'Value in ' + browserSettings.units); + $('#meter').prop('checked', true); + $('#carbsGiven').val(''); + $('#insulinGiven').val(''); + $('#preBolus').val(0); + $('#notes').val(''); + $('#enteredBy').val(browserStorage.get('enteredBy') || ''); + $('#nowtime').prop('checked', true); + $('#eventTimeValue').val(currentTime()); } function currentTime() { - var now = new Date(); - var hours = now.getHours(); - var minutes = now.getMinutes(); + var now = new Date(); + var hours = now.getHours(); + var minutes = now.getMinutes(); - if (hours<10) hours = '0' + hours; - if (minutes<10) minutes = '0' + minutes; + if (hours<10) hours = '0' + hours; + if (minutes<10) minutes = '0' + minutes; - return ''+ hours + ':' + minutes; + return ''+ hours + ':' + minutes; } function formatTime(date) { - var hours = date.getHours(); - var minutes = date.getMinutes(); - var ampm = hours >= 12 ? 'pm' : 'am'; - hours = hours % 12; - hours = hours ? hours : 12; // the hour '0' should be '12' - minutes = minutes < 10 ? '0' + minutes : minutes; - return hours + ':' + minutes + ' ' + ampm; + var hours = date.getHours(); + var minutes = date.getMinutes(); + var ampm = hours >= 12 ? 'pm' : 'am'; + hours = hours % 12; + hours = hours ? hours : 12; // the hour '0' should be '12' + minutes = minutes < 10 ? '0' + minutes : minutes; + return hours + ':' + minutes + ' ' + ampm; } function closeNotification() { - var notify = $('#notification'); - notify.hide(); - notify.find('span').html(''); + var notify = $('#notification'); + notify.hide(); + notify.find('span').html(''); } function showNotification(note, type) { - var notify = $('#notification'); - notify.hide(); + var notify = $('#notification'); + notify.hide(); - // Notification types: 'info', 'warn', 'success', 'urgent'. - // - default: 'urgent' - notify.removeClass('info warn urgent'); - notify.addClass(type ? type : 'urgent'); + // Notification types: 'info', 'warn', 'success', 'urgent'. + // - default: 'urgent' + notify.removeClass('info warn urgent'); + notify.addClass(type ? type : 'urgent'); - notify.find('span').html(note); - notify.css('left', 'calc(50% - ' + (notify.width() / 2) + 'px)'); - notify.show(); + notify.find('span').html(note); + notify.css('left', 'calc(50% - ' + (notify.width() / 2) + 'px)'); + notify.show(); } function showLocalstorageError() { - var msg = 'Settings are disabled.

    Please enable cookies so you may customize your Nightscout site.' - $('.browserSettings').html('Settings'+msg+''); - $('#save').hide(); + var msg = 'Settings are disabled.

    Please enable cookies so you may customize your Nightscout site.' + $('.browserSettings').html('Settings'+msg+''); + $('#save').hide(); } function treatmentSubmit(event) { - var data = {}; - data.enteredBy = $('#enteredBy').val(); - data.eventType = $('#eventType').val(); - data.glucose = $('#glucoseValue').val(); - data.glucoseType = $('#treatment-form input[name=glucoseType]:checked').val(); - data.carbs = $('#carbsGiven').val(); - data.insulin = $('#insulinGiven').val(); - data.preBolus = $('#preBolus').val(); - data.notes = $('#notes').val(); - data.units = browserSettings.units; - - var errors = []; - if (isNaN(data.glucose)) { - errors.push('Blood glucose must be a number'); - } - - if (isNaN(data.carbs)) { - errors.push('Carbs must be a number'); + var data = {}; + data.enteredBy = $('#enteredBy').val(); + data.eventType = $('#eventType').val(); + data.glucose = $('#glucoseValue').val(); + data.glucoseType = $('#treatment-form input[name=glucoseType]:checked').val(); + data.carbs = $('#carbsGiven').val(); + data.insulin = $('#insulinGiven').val(); + data.preBolus = $('#preBolus').val(); + data.notes = $('#notes').val(); + data.units = browserSettings.units; + + var errors = []; + if (isNaN(data.glucose)) { + errors.push('Blood glucose must be a number'); + } + + if (isNaN(data.carbs)) { + errors.push('Carbs must be a number'); + } + + if (isNaN(data.insulin)) { + errors.push('Insulin must be a number'); + } + + if (errors.length > 0) { + window.alert(errors.join('\n')); + } else { + var eventTimeDisplay = ''; + if ($('#treatment-form input[name=nowOrOther]:checked').val() != 'now') { + var value = $('#eventTimeValue').val(); + var eventTimeParts = value.split(':'); + data.eventTime = new Date(); + data.eventTime.setHours(eventTimeParts[0]); + data.eventTime.setMinutes(eventTimeParts[1]); + data.eventTime.setSeconds(0); + data.eventTime.setMilliseconds(0); + eventTimeDisplay = formatTime(data.eventTime); } - if (isNaN(data.insulin)) { - errors.push('Insulin must be a number'); - } - - if (errors.length > 0) { - window.alert(errors.join('\n')); - } else { - var eventTimeDisplay = ''; - if ($('#treatment-form input[name=nowOrOther]:checked').val() != 'now') { - var value = $('#eventTimeValue').val(); - var eventTimeParts = value.split(':'); - data.eventTime = new Date(); - data.eventTime.setHours(eventTimeParts[0]); - data.eventTime.setMinutes(eventTimeParts[1]); - data.eventTime.setSeconds(0); - data.eventTime.setMilliseconds(0); - eventTimeDisplay = formatTime(data.eventTime); - } - - var dataJson = JSON.stringify(data, null, ' '); - - var ok = window.confirm( - 'Please verify that the data entered is correct: ' + - '\nEvent type: ' + data.eventType + - '\nBlood glucose: ' + data.glucose + - '\nMethod: ' + data.glucoseType + - '\nCarbs Given: ' + data.carbs + - '\nInsulin Given: ' + data.insulin + - '\nPre Bolus: ' + data.preBolus + - '\nNotes: ' + data.notes + - '\nEntered By: ' + data.enteredBy + - '\nEvent Time: ' + eventTimeDisplay); - - if (ok) { - var xhr = new XMLHttpRequest(); - xhr.open('POST', '/api/v1/treatments/', true); - xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); - xhr.send(dataJson); - - browserStorage.set('enteredBy', data.enteredBy); - - closeDrawer('#treatmentDrawer'); - } + var dataJson = JSON.stringify(data, null, ' '); + + var ok = window.confirm( + 'Please verify that the data entered is correct: ' + + '\nEvent type: ' + data.eventType + + '\nBlood glucose: ' + data.glucose + + '\nMethod: ' + data.glucoseType + + '\nCarbs Given: ' + data.carbs + + '\nInsulin Given: ' + data.insulin + + '\nPre Bolus: ' + data.preBolus + + '\nNotes: ' + data.notes + + '\nEntered By: ' + data.enteredBy + + '\nEvent Time: ' + eventTimeDisplay); + + if (ok) { + var xhr = new XMLHttpRequest(); + xhr.open('POST', '/api/v1/treatments/', true); + xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); + xhr.send(dataJson); + + browserStorage.set('enteredBy', data.enteredBy); + + closeDrawer('#treatmentDrawer'); } + } - if (event) { - event.preventDefault(); - } + if (event) { + event.preventDefault(); + } } var querystring = getQueryParms(); function Dropdown(el) { - this.ddmenuitem = 0; + this.ddmenuitem = 0; - this.$el = $(el); - var that = this; + this.$el = $(el); + var that = this; - $(document).click(function() { that.close(); }); + $(document).click(function() { that.close(); }); } Dropdown.prototype.close = function () { - if (this.ddmenuitem) { - this.ddmenuitem.css('visibility', 'hidden'); - this.ddmenuitem = 0; - } + if (this.ddmenuitem) { + this.ddmenuitem.css('visibility', 'hidden'); + this.ddmenuitem = 0; + } }; Dropdown.prototype.open = function (e) { - this.close(); - this.ddmenuitem = $(this.$el).css('visibility', 'visible'); - e.stopPropagation(); + this.close(); + this.ddmenuitem = $(this.$el).css('visibility', 'visible'); + e.stopPropagation(); }; $('#drawerToggle').click(function(event) { - toggleDrawer('#drawer'); - event.preventDefault(); + toggleDrawer('#drawer'); + event.preventDefault(); }); $('#treatmentDrawerToggle').click(function(event) { - toggleDrawer('#treatmentDrawer', initTreatmentDrawer); - event.preventDefault(); + toggleDrawer('#treatmentDrawer', initTreatmentDrawer); + event.preventDefault(); }); $('#treatmentDrawer').find('button').click(treatmentSubmit); $('#eventTime input:radio').change(function (){ - if ($('#othertime').attr('checked')) { - $('#eventTimeValue').focus(); - } + if ($('#othertime').attr('checked')) { + $('#eventTimeValue').focus(); + } }); $('#eventTimeValue').focus(function () { - $('#othertime').attr('checked', 'checked'); + $('#othertime').attr('checked', 'checked'); }); $('#notification').click(function(event) { - closeNotification(); - event.preventDefault(); + closeNotification(); + event.preventDefault(); }); $('#save').click(function(event) { - storeInBrowser({ - 'units': $('input:radio[name=units-browser]:checked').val(), - 'alarmUrgentHigh': $('#alarm-urgenthigh-browser').prop('checked'), - 'alarmHigh': $('#alarm-high-browser').prop('checked'), - 'alarmLow': $('#alarm-low-browser').prop('checked'), - 'alarmUrgentLow': $('#alarm-urgentlow-browser').prop('checked'), - 'alarmTimeAgoWarn': $('#alarm-timeagowarn-browser').prop('checked'), - 'alarmTimeAgoWarnMins': parseInt($('#alarm-timeagowarnmins-browser').val()) || 15, - 'alarmTimeAgoUrgent': $('#alarm-timeagourgent-browser').prop('checked'), - 'alarmTimeAgoUrgentMins': parseInt($('#alarm-timeagourgentmins-browser').val()) || 30, - 'nightMode': $('#nightmode-browser').prop('checked'), - 'showRawbg': $('input:radio[name=show-rawbg]:checked').val(), - 'customTitle': $('input#customTitle').prop('value'), - 'theme': $('input:radio[name=theme-browser]:checked').val(), - 'timeFormat': $('input:radio[name=timeformat-browser]:checked').val() + function checkedPluginNames() { + var checkedPlugins = [] + $('#show-plugins input:checked').each(function eachPluginCheckbox(index, checkbox) { + checkedPlugins.push($(checkbox).val()); }); - - event.preventDefault(); - reload(); + return checkedPlugins.join(' '); + } + + storeInBrowser({ + 'units': $('input:radio[name=units-browser]:checked').val(), + 'alarmUrgentHigh': $('#alarm-urgenthigh-browser').prop('checked'), + 'alarmHigh': $('#alarm-high-browser').prop('checked'), + 'alarmLow': $('#alarm-low-browser').prop('checked'), + 'alarmUrgentLow': $('#alarm-urgentlow-browser').prop('checked'), + 'alarmTimeAgoWarn': $('#alarm-timeagowarn-browser').prop('checked'), + 'alarmTimeAgoWarnMins': parseInt($('#alarm-timeagowarnmins-browser').val()) || 15, + 'alarmTimeAgoUrgent': $('#alarm-timeagourgent-browser').prop('checked'), + 'alarmTimeAgoUrgentMins': parseInt($('#alarm-timeagourgentmins-browser').val()) || 30, + 'nightMode': $('#nightmode-browser').prop('checked'), + 'showRawbg': $('input:radio[name=show-rawbg]:checked').val(), + 'customTitle': $('input#customTitle').prop('value'), + 'theme': $('input:radio[name=theme-browser]:checked').val(), + 'timeFormat': $('input:radio[name=timeformat-browser]:checked').val(), + 'showPlugins': checkedPluginNames() + }); + + event.preventDefault(); + reload(); }); $('#useDefaults').click(function(event) { - //remove all known settings, since there might be something else is in localstorage - var settings = ['units', 'alarmUrgentHigh', 'alarmHigh', 'alarmLow', 'alarmUrgentLow', 'alarmTimeAgoWarn', 'alarmTimeAgoWarnMins', 'alarmTimeAgoUrgent', 'alarmTimeAgoUrgentMins', 'nightMode', 'showRawbg', 'customTitle', 'theme', 'timeFormat']; - settings.forEach(function(setting) { - browserStorage.remove(setting); - }); - event.preventDefault(); - reload(); + //remove all known settings, since there might be something else is in localstorage + var settings = ['units', 'alarmUrgentHigh', 'alarmHigh', 'alarmLow', 'alarmUrgentLow', 'alarmTimeAgoWarn', 'alarmTimeAgoWarnMins', 'alarmTimeAgoUrgent', 'alarmTimeAgoUrgentMins', 'nightMode', 'showRawbg', 'customTitle', 'theme', 'timeFormat', 'showPlugins']; + settings.forEach(function(setting) { + browserStorage.remove(setting); + }); + event.preventDefault(); + reload(); }); function reload() { - // reload for changes to take effect - // -- strip '#' so form submission does not fail - var url = window.location.href; - url = url.replace(/#$/, ''); - window.location = url; + // reload for changes to take effect + // -- strip '#' so form submission does not fail + var url = window.location.href; + url = url.replace(/#$/, ''); + window.location = url; } $(function() { - // Tooltips can remain in the way on touch screens. - var notTouchScreen = (!isTouch()); - if (notTouchScreen) { - $('.tip').tipsy(); - } else { - // Drawer info tips should be displayed on touchscreens. - $('#drawer').find('.tip').tipsy(); - } - $.fn.tipsy.defaults = { - fade: true, - gravity: 'n', - opacity: 0.75 - }; - - if (querystring.notify) { - showNotification(querystring.notify, querystring.notifytype); - } - - if (querystring.drawer) { - openDrawer('#drawer'); - } + // Tooltips can remain in the way on touch screens. + var notTouchScreen = (!isTouch()); + if (notTouchScreen) { + $('.tip').tipsy(); + } else { + // Drawer info tips should be displayed on touchscreens. + $('#drawer').find('.tip').tipsy(); + } + $.fn.tipsy.defaults = { + fade: true, + gravity: 'n', + opacity: 0.75 + }; + + if (querystring.notify) { + showNotification(querystring.notify, querystring.notifytype); + } + + if (querystring.drawer) { + openDrawer('#drawer'); + } }); From 736a32187b67b51bdac4937619651f5226cd236d Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 25 May 2015 01:29:32 -0700 Subject: [PATCH 034/937] added tooltips to plugin pills for extra info; lots more refactoring --- bundle/bundle.source.js | 2 + bundle/index.js | 2 +- lib/nsutils.js | 21 +++ lib/plugins/boluswizardpreview.js | 58 +++--- lib/plugins/cannulaage.js | 56 +++--- lib/plugins/cob.js | 300 ++++++++++++------------------ lib/plugins/index.js | 138 +++++++------- lib/plugins/iob.js | 155 +++++++-------- lib/plugins/pluginbase.js | 34 +++- static/js/client.js | 17 +- 10 files changed, 392 insertions(+), 391 deletions(-) create mode 100644 lib/nsutils.js diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index bb601616de4..85a1fd42b28 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -8,6 +8,8 @@ plugins: require('../lib/plugins/')() }; + console.info('plugins', window.Nightscout.plugins); + window.Nightscout.plugins.register({ iob: require('../lib/plugins/iob')(), cob: require('../lib/plugins/cob')(), diff --git a/bundle/index.js b/bundle/index.js index 2593c5a4621..4a8cd1563f2 100644 --- a/bundle/index.js +++ b/bundle/index.js @@ -5,7 +5,7 @@ var browserify_express = require('browserify-express'); function bundle() { return browserify_express({ entry: __dirname + '/bundle.source.js', - watch: __dirname + '/../lib/plugins/', + watch: __dirname + '/../lib/', mount: '/public/js/bundle.js', verbose: true, //minify: true, diff --git a/lib/nsutils.js b/lib/nsutils.js new file mode 100644 index 00000000000..c96f859575b --- /dev/null +++ b/lib/nsutils.js @@ -0,0 +1,21 @@ +'use strict'; + +function init() { + + function nsutils() { + return nsutils; + } + + nsutils.toFixed = function toFixed(value) { + if (value === 0) { + return '0'; + } else { + var fixed = value.toFixed(2); + return fixed == '-0.00' ? '0.00' : fixed; + } + }; + + return nsutils(); +} + +module.exports = init; \ No newline at end of file diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index adce8df2d5b..efe2a36f59a 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -1,43 +1,49 @@ 'use strict'; -// class methods -function updateVisualisation() { +var nsutils = require('../nsutils')(); - var sgv = this.env.sgv; +function init() { - var bat = 0.0; + function bwp() { + return bwp; + } - // TODO: MMOL + bwp.label = 'Bolus Wizard Preview'; - sgv = this.scaleBg(sgv); + bwp.updateVisualisation = function updateVisualisation() { - var effect = this.iob.iob * this.profile.sens; - var outcome = sgv - effect; - var delta = 0; + var sgv = this.env.sgv; - if (outcome > this.profile.target_high) { - delta = outcome - this.profile.target_high; - bat = delta / this.profile.sens; - } + var bat = 0.0; - if (outcome < this.profile.target_low) { - delta = Math.abs( outcome - this.profile.target_low); - bat = delta / this.profile.sens * -1; - } + // TODO: MMOL -- Jason: if we assume sens is in display units, we don't need to do any conversion + sgv = this.scaleBg(sgv); - bat = Math.round(bat * 100) / 100; + var effect = this.iob.iob * this.profile.sens; + var outcome = sgv - effect; + var delta = 0; + var info = null; - // display text - this.updateMajorPillText(bat + 'U','BWP'); + if (outcome > this.profile.target_high) { + delta = outcome - this.profile.target_high; + bat = delta / this.profile.sens; + } -} + if (outcome < this.profile.target_low) { + delta = Math.abs(outcome - this.profile.target_low); + bat = delta / this.profile.sens * -1; + } + bat = nsutils.toFixed((bat * 100) / 100); + + // display text + var info = [{label: 'Expected effect', value: '-' + Math.round(effect)}, {label: 'Expected outcome', value: Math.round(outcome)}]; + this.updatePillText(bat + 'U', 'BWP', info); -function BWP() { - return { - label: 'Bolus Wizard Preview', - updateVisualisation: updateVisualisation }; + + return bwp(); + } -module.exports = BWP; \ No newline at end of file +module.exports = init; \ No newline at end of file diff --git a/lib/plugins/cannulaage.js b/lib/plugins/cannulaage.js index 2d218f23bee..c23c9c24cc0 100644 --- a/lib/plugins/cannulaage.js +++ b/lib/plugins/cannulaage.js @@ -1,41 +1,47 @@ 'use strict'; -// class methods -function updateVisualisation() { +var moment = require('moment'); - var age = 0; - var found = false; +function init() { - for (var t in this.env.treatments) { - if (this.env.treatments.hasOwnProperty(t)) { - var treatment = this.env.treatments[t]; + function cage() { + return cage; + } + + cage.label = 'Cannula Age'; + + cage.updateVisualisation = function updateVisualisation() { - if (treatment.eventType == "Site Change") { - var treatmentDate = new Date(treatment.created_at); - var hours = Math.round(Math.abs(new Date() - treatmentDate) / 36e5); + var age = 0; + var found = false; + var treatmentDate = null; - if (!found) { - found = true; - age = hours; - } else { - if (hours < age) { + for (var t in this.env.treatments) { + if (this.env.treatments.hasOwnProperty(t)) { + var treatment = this.env.treatments[t]; + + if (treatment.eventType == "Site Change") { + treatmentDate = new Date(treatment.created_at); + var hours = Math.round(Math.abs(new Date() - treatmentDate) / 36e5); + + if (!found) { + found = true; age = hours; + } else { + if (hours < age) { + age = hours; + } } } } } - } - this.updateMajorPillText(age+'h', 'CAGE'); + this.updatePillText(age + 'h', 'CAGE', [{label: 'Inserted', value: moment(treatmentDate).format('lll')}]); -} - - -function CAGE() { - return { - label: 'Cannula Age', - updateVisualisation: updateVisualisation }; + + return cage(); } -module.exports = CAGE; \ No newline at end of file + +module.exports = init; \ No newline at end of file diff --git a/lib/plugins/cob.js b/lib/plugins/cob.js index d49f7f8cf0b..5565a46e505 100644 --- a/lib/plugins/cob.js +++ b/lib/plugins/cob.js @@ -1,211 +1,151 @@ 'use strict'; -function getData() { - return this.cobTotal(this.env.treatments,this.env.time); -} - -function cobTotal(treatments, time) { - var liverSensRatio = 1; - var sens = this.profile.sens; - var carbratio = this.profile.carbratio; - var cob=0; - if (!treatments) return {}; - if (typeof time === 'undefined') { - time = new Date(); - } - - var isDecaying = 0; - var lastDecayedBy = new Date('1/1/1970'); - var carbs_hr = this.profile.carbs_hr; - - for (var t in treatments) { - if (treatments.hasOwnProperty(t)) { - var treatment = treatments[t]; - if (treatment.carbs && treatment.created_at < time) { - var cCalc = this.cobCalc(treatment, lastDecayedBy, time); - var decaysin_hr = (cCalc.decayedBy - time) / 1000 / 60 / 60; - if (decaysin_hr > -10) { - var actStart = this.iobTotal(treatments, lastDecayedBy).activity; - var actEnd = this.iobTotal(treatments, cCalc.decayedBy).activity; - var avgActivity = (actStart + actEnd) / 2; - var delayedCarbs = avgActivity * liverSensRatio * sens / carbratio; - var delayMinutes = Math.round(delayedCarbs / carbs_hr * 60); - if (delayMinutes > 0) { - cCalc.decayedBy.setMinutes(cCalc.decayedBy.getMinutes() + delayMinutes); - decaysin_hr = (cCalc.decayedBy - time) / 1000 / 60 / 60; - } - } - - if (cCalc) { - lastDecayedBy = cCalc.decayedBy; - } +var iob = require('./iob')() + , moment = require('moment'); - if (decaysin_hr > 0) { - //console.info('Adding ' + delayMinutes + ' minutes to decay of ' + treatment.carbs + 'g bolus at ' + treatment.created_at); - cob += Math.min(cCalc.initialCarbs, decaysin_hr * carbs_hr); - //console.log("cob: " + Math.min(cCalc.initialCarbs, decaysin_hr * carbs_hr)); - isDecaying = cCalc.isDecaying; - } - else { - cob = 0; - } +function init() { - } - } + function cob() { + return cob; } - var rawCarbImpact = isDecaying*sens/carbratio*carbs_hr/60; - return { - decayedBy: lastDecayedBy, - isDecaying: isDecaying, - carbs_hr: carbs_hr, - rawCarbImpact: rawCarbImpact, - cob: cob - }; -} -function iobTotal(treatments, time) { - var iob= 0; - var activity = 0; - if (!treatments) return {}; - if (typeof time === 'undefined') { - time = new Date(); - } + cob.label = 'Carbs-on-Board'; - for (var t in treatments) { - if (treatments.hasOwnProperty(t)) { - var treatment = treatments[t]; - if (treatment.created_at < time) { - var tIOB = this.iobCalc(treatment, time); - if (tIOB && tIOB.iobContrib) iob += tIOB.iobContrib; - if (tIOB && tIOB.activityContrib) activity += tIOB.activityContrib; - } - } - } - - return { - iob: iob, - activity: activity + cob.getData = function getData() { + return cob.cobTotal(this.env.treatments, this.env.time); }; -} + cob.cobTotal = function cobTotal(treatments, time) { + var liverSensRatio = 1; + var sens = this.profile.sens; + var carbratio = this.profile.carbratio; + var totalCOB = 0; + var lastCarbs = null; + if (!treatments) return {}; + if (typeof time === 'undefined') { + time = new Date(); + } -function carbImpact(rawCarbImpact, insulinImpact) { - var liverSensRatio = 1.0; - var liverCarbImpactMax = 0.7; - var liverCarbImpact = Math.min(liverCarbImpactMax, liverSensRatio*insulinImpact); - //var liverCarbImpact = liverSensRatio*insulinImpact; - var netCarbImpact = Math.max(0, rawCarbImpact-liverCarbImpact); - var totalImpact = netCarbImpact - insulinImpact; - return { - netCarbImpact: netCarbImpact, - totalImpact: totalImpact - } -} - -function iobCalc(treatment, time) { + var isDecaying = 0; + var lastDecayedBy = new Date('1/1/1970'); + var carbs_hr = this.profile.carbs_hr; + + for (var t in treatments) { + if (treatments.hasOwnProperty(t)) { + var treatment = treatments[t]; + if (treatment.carbs && treatment.created_at < time) { + lastCarbs = treatment; + var cCalc = this.cobCalc(treatment, lastDecayedBy, time); + var decaysin_hr = (cCalc.decayedBy - time) / 1000 / 60 / 60; + if (decaysin_hr > -10) { + var actStart = iob.calcTotal(treatments, lastDecayedBy).activity; + var actEnd = iob.calcTotal(treatments, cCalc.decayedBy).activity; + var avgActivity = (actStart + actEnd) / 2; + var delayedCarbs = avgActivity * liverSensRatio * sens / carbratio; + var delayMinutes = Math.round(delayedCarbs / carbs_hr * 60); + if (delayMinutes > 0) { + cCalc.decayedBy.setMinutes(cCalc.decayedBy.getMinutes() + delayMinutes); + decaysin_hr = (cCalc.decayedBy - time) / 1000 / 60 / 60; + } + } - var dia=this.profile.dia; - var scaleFactor = 3.0/dia; - var peak = 75; - var sens=this.profile.sens; - var iobContrib, activityContrib; - var t = time; - if (typeof t === 'undefined') { - t = new Date(); - } + if (cCalc) { + lastDecayedBy = cCalc.decayedBy; + } - if (treatment.insulin) { - var bolusTime=new Date(treatment.created_at); - var minAgo=scaleFactor*(t-bolusTime)/1000/60; + if (decaysin_hr > 0) { + //console.info('Adding ' + delayMinutes + ' minutes to decay of ' + treatment.carbs + 'g bolus at ' + treatment.created_at); + totalCOB += Math.min(cCalc.initialCarbs, decaysin_hr * carbs_hr); + //console.log("cob: " + Math.min(cCalc.initialCarbs, decaysin_hr * carbs_hr)); + isDecaying = cCalc.isDecaying; + } + else { + totalCOB = 0; + } - if (minAgo < 0) { - iobContrib=0; - activityContrib=0; + } + } } + var rawCarbImpact = isDecaying * sens / carbratio * carbs_hr / 60; - if (minAgo < peak) { - var x1 = minAgo/5+1; - iobContrib=treatment.insulin*(1-0.001852*x1*x1+0.001852*x1); - activityContrib=sens*treatment.insulin*(2/dia/60/peak)*minAgo; - - } else if (minAgo < 180) { - var x2 = (minAgo-75)/5; - iobContrib=treatment.insulin*(0.001323*x2*x2 - .054233*x2 + .55556); - activityContrib=sens*treatment.insulin*(2/dia/60-(minAgo-peak)*2/dia/60/(60*dia-peak)); - } else { - iobContrib=0; - activityContrib=0; - } return { - iobContrib: iobContrib, - activityContrib: activityContrib + decayedBy: lastDecayedBy, + isDecaying: isDecaying, + carbs_hr: carbs_hr, + rawCarbImpact: rawCarbImpact, + cob: totalCOB, + lastCarbs: lastCarbs }; - } - else { - return ''; - } -} + }; -function cobCalc(treatment, lastDecayedBy, time) { + cob.carbImpact = function carbImpact(rawCarbImpact, insulinImpact) { + var liverSensRatio = 1.0; + var liverCarbImpactMax = 0.7; + var liverCarbImpact = Math.min(liverCarbImpactMax, liverSensRatio * insulinImpact); + //var liverCarbImpact = liverSensRatio*insulinImpact; + var netCarbImpact = Math.max(0, rawCarbImpact - liverCarbImpact); + var totalImpact = netCarbImpact - insulinImpact; + return { + netCarbImpact: netCarbImpact, + totalImpact: totalImpact + } + }; - var carbs_hr = this.profile.carbs_hr; - var delay = 20; - var carbs_min = carbs_hr / 60; - var isDecaying = 0; - var initialCarbs; + cob.cobCalc = function cobCalc(treatment, lastDecayedBy, time) { - if (treatment.carbs) { - var carbTime = new Date(treatment.created_at); + var carbs_hr = this.profile.carbs_hr; + var delay = 20; + var carbs_min = carbs_hr / 60; + var isDecaying = 0; + var initialCarbs; - var decayedBy = new Date(carbTime); - var minutesleft = (lastDecayedBy-carbTime)/1000/60; - decayedBy.setMinutes(decayedBy.getMinutes() + Math.max(delay,minutesleft) + treatment.carbs/carbs_min); - if(delay > minutesleft) { - initialCarbs = parseInt(treatment.carbs); - } - else { - initialCarbs = parseInt(treatment.carbs) + minutesleft*carbs_min; - } - var startDecay = new Date(carbTime); - startDecay.setMinutes(carbTime.getMinutes() + delay); - if (time < lastDecayedBy || time > startDecay) { - isDecaying = 1; + if (treatment.carbs) { + var carbTime = new Date(treatment.created_at); + + var decayedBy = new Date(carbTime); + var minutesleft = (lastDecayedBy - carbTime) / 1000 / 60; + decayedBy.setMinutes(decayedBy.getMinutes() + Math.max(delay, minutesleft) + treatment.carbs / carbs_min); + if (delay > minutesleft) { + initialCarbs = parseInt(treatment.carbs); + } + else { + initialCarbs = parseInt(treatment.carbs) + minutesleft * carbs_min; + } + var startDecay = new Date(carbTime); + startDecay.setMinutes(carbTime.getMinutes() + delay); + if (time < lastDecayedBy || time > startDecay) { + isDecaying = 1; + } + else { + isDecaying = 0; + } + return { + initialCarbs: initialCarbs, + decayedBy: decayedBy, + isDecaying: isDecaying, + carbTime: carbTime + }; } else { - isDecaying = 0; + return ''; } - return { - initialCarbs: initialCarbs, - decayedBy: decayedBy, - isDecaying: isDecaying, - carbTime: carbTime - }; - } - else { - return ''; - } -} - -function updateVisualisation() { - - var displayCob = Math.round(this.env.cob.cob * 10) / 10; + }; - this.updateMajorPillText(displayCob + " g",'COB'); - -} + cob.updateVisualisation = function updateVisualisation() { + var displayCob = Math.round(this.env.cob.cob * 10) / 10; + var info = null; + if (this.env.cob.lastCarbs) { + var when = moment(new Date(this.env.cob.lastCarbs.created_at)).format('lll'); + var amount = this.env.cob.lastCarbs.carbs + 'g'; + info = [{label: 'Last Carbs', value: amount + ' @ ' + when }] + } -function COB() { - return { - label: 'Carbs-on-Board', - cobTotal: cobTotal, - cobCalc: cobCalc, - iobTotal: iobTotal, - iobCalc: iobCalc, - getData: getData, - updateVisualisation: updateVisualisation + this.updatePillText(displayCob + " g", 'COB', info); }; + return cob(); + } -module.exports = COB; +module.exports = init; diff --git a/lib/plugins/index.js b/lib/plugins/index.js index ce14b1edefa..0603b28df34 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -1,84 +1,80 @@ 'use strict'; var _ = require('lodash') - , PluginBase = require('./pluginbase')() // Define any shared functionality in this class - , allPlugins = [] - , enabledPlugins = []; + , PluginBase = require('./pluginbase')(); // Define any shared functionality in this class -function register(all) { +function init() { - allPlugins = []; + var allPlugins = [] + , enabledPlugins = []; - _.forIn(all, function eachPlugin(plugin, name) { - plugin.name = name; - _.extend(plugin, PluginBase); - allPlugins.push(plugin); - }); - -} - -function clientInit(app) { - enabledPlugins = []; - console.info('NightscoutPlugins init', app); - function isEnabled(plugin) { - return app.enabledOptions - && app.enabledOptions.indexOf(plugin.name) > -1; + function plugins() { + return plugins; } - _.forEach(allPlugins, function eachPlugin(plugin) { - plugin.enabled = isEnabled(plugin); - if (plugin.enabled) { - enabledPlugins.push(plugin); + plugins.register = function register(all) { + allPlugins = []; + + _.forIn(all, function eachPlugin(plugin, name) { + if (!plugin.name) plugin.name = name; + _.extend(plugin, PluginBase); + allPlugins.push(plugin); + }); + }; + + plugins.clientInit = function clientInit(app) { + enabledPlugins = []; + console.info('NightscoutPlugins init', app); + function isEnabled(plugin) { + return app.enabledOptions + && app.enabledOptions.indexOf(plugin.name) > -1; } - }); - console.info('Plugins enabled', enabledPlugins); -} -function setEnv(env) { - _.forEach(enabledPlugins, function eachPlugin(plugin) { - plugin.setEnv(env); - }); -} - -function updateVisualisations(clientSettings) { - eachShownPlugin(clientSettings, function eachPlugin(plugin) { - plugin.updateVisualisation && plugin.updateVisualisation(); - }); -} - -function eachPlugin(f) { - _.forEach(allPlugins, f); -} + _.forEach(allPlugins, function eachPlugin(plugin) { + plugin.enabled = isEnabled(plugin); + if (plugin.enabled) { + enabledPlugins.push(plugin); + } + }); + console.info('Plugins enabled', enabledPlugins); + }; + + plugins.eachPlugin = function eachPlugin(f) { + _.forEach(allPlugins, f); + }; + + plugins.eachEnabledPlugin = function eachEnabledPlugin(f) { + _.forEach(enabledPlugins, f); + }; + + plugins.eachShownPlugin = function eachShownPlugin(clientSettings, f) { + var filtered = _.filter(enabledPlugins, function filterPlugins(plugin) { + return clientSettings && clientSettings.showPlugins && clientSettings.showPlugins.indexOf(plugin.name) > -1; + }); + + _.forEach(filtered, f); + }; + + plugins.setEnvs = function setEnvs(env) { + plugins.eachEnabledPlugin(function eachPlugin(plugin) { + plugin.setEnv(env); + }); + }; + + plugins.updateVisualisations = function updateVisualisations(clientSettings) { + plugins.eachShownPlugin(clientSettings, function eachPlugin(plugin) { + plugin.updateVisualisation && plugin.updateVisualisation(); + }); + }; + + plugins.enabledPluginNames = function enabledPluginNames() { + return _.map(enabledPlugins, function mapped(plugin) { + return plugin.name; + }).join(' '); + }; + + return plugins(); -function eachEnabledPlugin(f) { - _.forEach(enabledPlugins, f); -} - -function eachShownPlugin(clientSettings, f) { - var filtered = _.filter(enabledPlugins, function filterPlugins(plugin) { - return clientSettings && clientSettings.showPlugins && clientSettings.showPlugins.indexOf(plugin.name) > -1; - }); - - _.forEach(filtered, f); -} - -function enabledPluginNames() { - return _.map(enabledPlugins, function mapped(plugin) { - return plugin.name; - }).join(' '); -} - -function plugins() { - return { - register: register - , clientInit: clientInit - , setEnv: setEnv - , updateVisualisations: updateVisualisations - , eachPlugin: eachPlugin - , eachEnabledPlugin: eachEnabledPlugin - , eachShownPlugin: eachShownPlugin - , enabledPluginNames: enabledPluginNames - } } -module.exports = plugins; \ No newline at end of file +module.exports = init; \ No newline at end of file diff --git a/lib/plugins/iob.js b/lib/plugins/iob.js index 6722e966e15..2c941646659 100644 --- a/lib/plugins/iob.js +++ b/lib/plugins/iob.js @@ -1,97 +1,104 @@ 'use strict'; -function getData() { - return calcTotal(this.env.treatments,this.env.profile,this.env.time); -} +var _ = require('lodash') + , moment = require('moment') + , nsutils = require('../nsutils')(); -function calcTotal(treatments, profile, time) { +function init() { - var iob = 0 - , activity = 0; + function iob() { + return iob; + } - if (!treatments) return {}; + iob.label = 'Insulin-on-Board'; - if (profile === undefined) { - //if there is no profile default to 3 hour dia - profile = {dia: 3, sens: 0}; - } + iob.getData = function getData() { + return iob.calcTotal(this.env.treatments, this.env.profile, this.env.time); + }; - if (time === undefined) { - time = new Date(); - } + iob.calcTotal = function calcTotal(treatments, profile, time) { - treatments.forEach(function (treatment) { - if (new Date(treatment.created_at) < time) { - var tIOB = calcTreatment(treatment, profile, time); - if (tIOB && tIOB.iobContrib) iob += tIOB.iobContrib; - if (tIOB && tIOB.activityContrib) activity += tIOB.activityContrib; - } - }); + var totalIOB = 0 + , totalActivity = 0; - return { - iob: iob, - display: iob.toFixed(2) == '-0.00' ? '0.00' : iob.toFixed(2), - activity: activity - }; -} + if (!treatments) return {}; -function calcTreatment(treatment, profile, time) { - - var dia = profile.dia - , scaleFactor = 3.0 / dia - , peak = 75 - , sens = profile.sens - , iobContrib = 0 - , activityContrib = 0; - - if (treatment.insulin) { - var bolusTime = new Date(treatment.created_at); - var minAgo = scaleFactor * (time - bolusTime) / 1000 / 60; - - if (minAgo < peak) { - var x1 = minAgo / 5 + 1; - iobContrib = treatment.insulin * (1 - 0.001852 * x1 * x1 + 0.001852 * x1); - activityContrib = sens * treatment.insulin * (2 / dia / 60 / peak) * minAgo; - - } else if (minAgo < 180) { - var x2 = (minAgo - 75) / 5; - iobContrib = treatment.insulin * (0.001323 * x2 * x2 - .054233 * x2 + .55556); - activityContrib = sens * treatment.insulin * (2 / dia / 60 - (minAgo - peak) * 2 / dia / 60 / (60 * dia - peak)); - } else { - iobContrib = 0; - activityContrib = 0; + if (profile === undefined) { + //if there is no profile default to 3 hour dia + profile = {dia: 3, sens: 0}; } - } + if (time === undefined) { + time = new Date(); + } - return { - iobContrib: iobContrib, - activityContrib: activityContrib + var lastBolus = null; + + _.forEach(treatments, function eachTreatment(treatment) { + if (new Date(treatment.created_at) < time) { + var tIOB = iob.calcTreatment(treatment, profile, time); + if (tIOB.iobContrib > 0) { + lastBolus = treatment; + } + if (tIOB && tIOB.iobContrib) totalIOB += tIOB.iobContrib; + if (tIOB && tIOB.activityContrib) totalActivity += tIOB.activityContrib; + } + }); + + return { + iob: totalIOB, + display: nsutils.toFixed(totalIOB), + activity: totalActivity, + lastBolus: lastBolus + }; }; -} + iob.calcTreatment = function calcTreatment(treatment, profile, time) { -function updateVisualisation() { - var pill = this.currentDetails.find('span.pill.iob'); + var dia = profile.dia + , scaleFactor = 3.0 / dia + , peak = 75 + , sens = profile.sens + , result = { + iobContrib: 0 + , activityContrib: 0 + }; - if (!pill || pill.length == 0) { - pill = $(''); - this.currentDetails.append(pill); - } + if (treatment.insulin) { + var bolusTime = new Date(treatment.created_at); + var minAgo = scaleFactor * (time - bolusTime) / 1000 / 60; - pill.find('em').text(this.iob.display + 'U'); -} + if (minAgo < peak) { + var x1 = minAgo / 5 + 1; + result.iobContrib = treatment.insulin * (1 - 0.001852 * x1 * x1 + 0.001852 * x1); + result.activityContrib = sens * treatment.insulin * (2 / dia / 60 / peak) * minAgo; + + } else if (minAgo < 180) { + var x2 = (minAgo - 75) / 5; + result.iobContrib = treatment.insulin * (0.001323 * x2 * x2 - .054233 * x2 + .55556); + result.activityContrib = sens * treatment.insulin * (2 / dia / 60 - (minAgo - peak) * 2 / dia / 60 / (60 * dia - peak)); + } + + } + return result; -function IOB() { - return { - label: 'Insulin-on-Board', - calcTotal: calcTotal, - getData: getData, - updateVisualisation: updateVisualisation, - isDataProvider: true, - isVisualisationProvider: true }; + + iob.updateVisualisation = function updateVisualisation() { + var info = null; + + if (this.iob.lastBolus) { + var when = moment(new Date(this.iob.lastBolus.created_at)).format('lll'); + var amount = nsutils.toFixed(Number(this.iob.lastBolus.insulin)) + 'U'; + info = [{label: 'Last Bolus', value: amount + ' @ ' + when }] + } + + this.updatePillText(this.iob.display + 'U', 'IOB', info, true); + }; + + return iob(); + } -module.exports = IOB; \ No newline at end of file +module.exports = init; \ No newline at end of file diff --git a/lib/plugins/pluginbase.js b/lib/plugins/pluginbase.js index a154801cf41..7de702e7571 100644 --- a/lib/plugins/pluginbase.js +++ b/lib/plugins/pluginbase.js @@ -1,5 +1,7 @@ 'use strict'; +var _ = require('lodash'); + function setEnv(env) { this.profile = env.profile; this.currentDetails = env.currentDetails; @@ -10,18 +12,42 @@ function setEnv(env) { this.env = env; } -function updateMajorPillText(updatedText, label) { +function updatePillText(updatedText, label, info, major) { + + var self = this; var pillName = "span.pill." + this.name; - var pill = this.pluginPills.find(pillName); + var container = major ? this.currentDetails : this.pluginPills; + + var pill = container.find(pillName); if (!pill || pill.length == 0) { pill = $(''); - this.pluginPills.append(pill); + container.append(pill); } pill.find('em').text(updatedText); + + if (info) { + + var html = _.map(info, function mapInfo(i) { + return '' + i.label + ' ' + i.value; + }).join('
    \n'); + + pill.mouseover(function pillMouseover(event) { + self.env.tooltip.transition().duration(200).style('opacity', .9); + self.env.tooltip.html(html) + .style('left', (event.pageX) + 'px') + .style('top', (event.pageY + 15) + 'px'); + }); + + pill.mouseout(function pillMouseout() { + self.env.tooltip.transition() + .duration(200) + .style('opacity', 0); + }); + } } function scaleBg(bg) { @@ -36,7 +62,7 @@ function PluginBase() { return { setEnv: setEnv, scaleBg: scaleBg, - updateMajorPillText: updateMajorPillText + updatePillText: updatePillText }; } diff --git a/static/js/client.js b/static/js/client.js index d4624fc07b7..9f38a0a5583 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -472,26 +472,23 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; env.sgv = Number(sgv); env.treatments = treatments; env.time = time; + env.tooltip = tooltip; - Nightscout.plugins.eachShownPlugin(browserSettings, function updateEachPlugin(plugin) { + //all enabled plugins get a chance to add data, even if they aren't shown + Nightscout.plugins.eachEnabledPlugin(function updateEachPlugin(plugin) { // Update the env through data provider plugins plugin.setEnv(env); // check if the plugin implements processing data if (plugin.getData) { - var dataFromPlugin = plugin.getData(); - var container = {}; - for (var i in dataFromPlugin) { - if (dataFromPlugin.hasOwnProperty(i)) { - container[i] = dataFromPlugin[i]; - } - } - env[plugin.name] = container; + env[plugin.name] = plugin.getData(); } }); // update data for all the plugins, before updating visualisations - Nightscout.plugins.setEnv(env); + Nightscout.plugins.setEnvs(env); + + //only shown plugins get a chance to update visualisations Nightscout.plugins.updateVisualisations(browserSettings); } From fd70e1219f88c608d5d1f735b8a214e1eb900d20 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 25 May 2015 02:10:18 -0700 Subject: [PATCH 035/937] only make the header/status area taller when a minor pill is shown --- lib/plugins/boluswizardpreview.js | 1 + lib/plugins/cannulaage.js | 1 + lib/plugins/cob.js | 1 + lib/plugins/index.js | 5 ++- lib/plugins/iob.js | 3 +- lib/plugins/pluginbase.js | 6 +-- static/css/main.css | 61 +++++++++++++++++++++---------- static/index.html | 4 +- static/js/client.js | 24 +++++++++--- 9 files changed, 73 insertions(+), 33 deletions(-) diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index efe2a36f59a..58ea2a18e36 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -9,6 +9,7 @@ function init() { } bwp.label = 'Bolus Wizard Preview'; + bwp.pluginType = 'minor-pill'; bwp.updateVisualisation = function updateVisualisation() { diff --git a/lib/plugins/cannulaage.js b/lib/plugins/cannulaage.js index c23c9c24cc0..ad641112030 100644 --- a/lib/plugins/cannulaage.js +++ b/lib/plugins/cannulaage.js @@ -9,6 +9,7 @@ function init() { } cage.label = 'Cannula Age'; + cage.pluginType = 'minor-pill'; cage.updateVisualisation = function updateVisualisation() { diff --git a/lib/plugins/cob.js b/lib/plugins/cob.js index 5565a46e505..078c77a9a02 100644 --- a/lib/plugins/cob.js +++ b/lib/plugins/cob.js @@ -10,6 +10,7 @@ function init() { } cob.label = 'Carbs-on-Board'; + cob.pluginType = 'minor-pill'; cob.getData = function getData() { return cob.cobTotal(this.env.treatments, this.env.time); diff --git a/lib/plugins/index.js b/lib/plugins/index.js index 0603b28df34..32deffd248e 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -47,7 +47,8 @@ function init() { _.forEach(enabledPlugins, f); }; - plugins.eachShownPlugin = function eachShownPlugin(clientSettings, f) { + plugins.eachShownPlugins = function eachShownPlugins(clientSettings, f) { + console.info('eachShownPlugins'); var filtered = _.filter(enabledPlugins, function filterPlugins(plugin) { return clientSettings && clientSettings.showPlugins && clientSettings.showPlugins.indexOf(plugin.name) > -1; }); @@ -62,7 +63,7 @@ function init() { }; plugins.updateVisualisations = function updateVisualisations(clientSettings) { - plugins.eachShownPlugin(clientSettings, function eachPlugin(plugin) { + plugins.eachShownPlugins(clientSettings, function eachPlugin(plugin) { plugin.updateVisualisation && plugin.updateVisualisation(); }); }; diff --git a/lib/plugins/iob.js b/lib/plugins/iob.js index 2c941646659..c949bf5cbd4 100644 --- a/lib/plugins/iob.js +++ b/lib/plugins/iob.js @@ -11,6 +11,7 @@ function init() { } iob.label = 'Insulin-on-Board'; + iob.pluginType = 'major-pill'; iob.getData = function getData() { return iob.calcTotal(this.env.treatments, this.env.profile, this.env.time); @@ -94,7 +95,7 @@ function init() { info = [{label: 'Last Bolus', value: amount + ' @ ' + when }] } - this.updatePillText(this.iob.display + 'U', 'IOB', info, true); + this.updatePillText(this.iob.display + 'U', 'IOB', info); }; return iob(); diff --git a/lib/plugins/pluginbase.js b/lib/plugins/pluginbase.js index 7de702e7571..3b86d8794dc 100644 --- a/lib/plugins/pluginbase.js +++ b/lib/plugins/pluginbase.js @@ -4,8 +4,8 @@ var _ = require('lodash'); function setEnv(env) { this.profile = env.profile; - this.currentDetails = env.currentDetails; - this.pluginPills = env.pluginPills; + this.majorPills = env.majorPills; + this.minorPills = env.minorPills; this.iob = env.iob; // TODO: clean! @@ -18,7 +18,7 @@ function updatePillText(updatedText, label, info, major) { var pillName = "span.pill." + this.name; - var container = major ? this.currentDetails : this.pluginPills; + var container = this.pluginType == 'major-pill' ? this.majorPills : this.minorPills; var pill = container.find(pillName); diff --git a/static/css/main.css b/static/css/main.css index 870ce7b9a0f..8faaa1a4a0d 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -29,11 +29,15 @@ body { .status { font-family: 'Ubuntu', Helvetica, Arial, sans-serif; - height: 200px; + height: 180px; vertical-align: middle; clear: left right; } +.has-minor-pills .status { + height: 200px; +} + .bgStatus { float: right; text-align: center; @@ -72,21 +76,21 @@ body { vertical-align: middle; } -.bgStatus .currentDetails { +.bgStatus .majorPills { font-size: 30px; } -.pluginPills { +.minorPills { font-size: 22px; margin-top: 5px; z-index: 500; } -.currentDetails > span:not(:first-child) { +.majorPills > span:not(:first-child) { margin-left: 5px; } -.pluginPills > span:not(:first-child) { +.minorPills > span:not(:first-child) { margin-left: 5px; } @@ -179,7 +183,7 @@ body { } #chartContainer { - top: 245px; /*(toolbar height + status height)*/ + top: 225px; /*(toolbar height + status height)*/ left:0; right:0; bottom:0; @@ -190,6 +194,10 @@ body { position:absolute; } +.has-minor-pills #chartContainer { + top: 245px; +} + #chartContainer svg { height: calc(100vh - (180px + 45px)); width: 100%; @@ -353,11 +361,11 @@ div.tooltip { line-height: 70px; } - .bgStatus .currentDetails { + .bgStatus .majorPills { font-size: 20px; } - .bgStatus .pluginPills { + .bgStatus .minorPills { font-size: 16px; } @@ -389,9 +397,14 @@ div.tooltip { } #chartContainer { - top: 195px; + top: 185px; font-size: 14px; } + + .has-minor-pills #chartContainer { + top: 195px; + } + #chartContainer svg { height: calc(100vh - 185px); } @@ -426,11 +439,11 @@ div.tooltip { line-height: 50px; } - .bgStatus .currentDetails { + .bgStatus .majorPills { font-size: 15px; } - .bgStatus .pluginPills { + .bgStatus .minorPills { font-size: 12px; } @@ -484,11 +497,11 @@ div.tooltip { line-height: 60px; } - .bgStatus .currentDetails { + .bgStatus .majorPills { font-size: 15px; } - .bgStatus .pluginPills { + .bgStatus .minorPills { font-size: 12px; } @@ -534,8 +547,13 @@ div.tooltip { } #chartContainer { - top: 200px; + top: 190px; } + + .has-minor-pills #chartContainer { + top: 200px; + } + #chartContainer svg { height: calc(100vh - (190px)); } @@ -569,9 +587,14 @@ div.tooltip { } #chartContainer { - top: 140px; + top: 130px; font-size: 10px; } + + .has-minor-pills #chartContainer { + top: 140px; + } + #chartContainer svg { height: calc(100vh - 130px); } @@ -592,11 +615,11 @@ div.tooltip { line-height: 50px; } - .bgStatus .currentDetails { + .bgStatus .majorPills { font-size: 15px; } - .bgStatus .pluginPills { + .bgStatus .minorPills { font-size: 12px; } @@ -639,11 +662,11 @@ div.tooltip { line-height: 50px; } - .bgStatus .currentDetails { + .bgStatus .majorPills { font-size: 15px; } - .bgStatus .currentDetails { + .bgStatus .majorPills { font-size: 12px; } diff --git a/static/index.html b/static/index.html index df6c473f343..b422ed627bb 100644 --- a/static/index.html +++ b/static/index.html @@ -57,8 +57,8 @@

    Nightscout

  • Silence for 90 minutes
  • Silence for 120 minutes
  • -
    -
    +
    +
    diff --git a/static/js/client.js b/static/js/client.js index 9f38a0a5583..0a92c204e17 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -392,8 +392,8 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; , bgStatus = $('.bgStatus') , currentBG = $('.bgStatus .currentBG') , currentDirection = $('.bgStatus .currentDirection') - , currentDetails = $('.bgStatus .currentDetails') - , pluginPills = $('.bgStatus .pluginPills') + , majorPills = $('.bgStatus .majorPills') + , minorPills = $('.bgStatus .minorPills') , rawNoise = bgButton.find('.rawnoise') , rawbg = rawNoise.find('em') , noiseLevel = rawNoise.find('label') @@ -442,10 +442,10 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; function updateBGDelta(prevEntry, currentEntry) { - var pill = currentDetails.find('span.pill.bgdelta'); + var pill = majorPills.find('span.pill.bgdelta'); if (!pill || pill.length == 0) { pill = $(''); - currentDetails.append(pill); + majorPills.append(pill); } var deltaDisplay = calcDeltaDisplay(prevEntry, currentEntry); @@ -467,13 +467,15 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; function updatePlugins(sgv, time) { var env = {}; env.profile = profile; - env.currentDetails = currentDetails; - env.pluginPills = pluginPills; + env.majorPills = majorPills; + env.minorPills = minorPills; env.sgv = Number(sgv); env.treatments = treatments; env.time = time; env.tooltip = tooltip; + var hasMinorPill = false; + //all enabled plugins get a chance to add data, even if they aren't shown Nightscout.plugins.eachEnabledPlugin(function updateEachPlugin(plugin) { // Update the env through data provider plugins @@ -485,6 +487,16 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } }); + Nightscout.plugins.eachShownPlugins(browserSettings, function eachPlugin(plugin) { + console.info('plugin.pluginType', plugin.pluginType); + if (plugin.pluginType == 'minor-pill') { + hasMinorPill = true; + } + }); + + $('.container').toggleClass('has-minor-pills', hasMinorPill); + + // update data for all the plugins, before updating visualisations Nightscout.plugins.setEnvs(env); From aa49c9fc17663cc930d98aaecb44fc720981a08e Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Mon, 25 May 2015 22:01:20 +0300 Subject: [PATCH 036/937] Display rounding closer to pump display, improved display of units in preview message --- lib/plugins/boluswizardpreview.js | 4 +++- lib/plugins/cannulaage.js | 4 +++- lib/plugins/iob.js | 4 ++-- lib/plugins/pluginbase.js | 26 +++++++++++++++++++++++++- 4 files changed, 33 insertions(+), 5 deletions(-) diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index efe2a36f59a..2f3d08aa404 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -35,9 +35,11 @@ function init() { } bat = nsutils.toFixed((bat * 100) / 100); + outcome = this.roundBGToDisplayFormat(outcome); + var displayIOB = this.roundInsulinForDisplayFormat(this.iob.iob); // display text - var info = [{label: 'Expected effect', value: '-' + Math.round(effect)}, {label: 'Expected outcome', value: Math.round(outcome)}]; + var info = [{label: 'Insulin on Board', value: displayIOB}, {label: 'Expected effect', value: '-' + this.roundBGToDisplayFormat(effect) + ' ' + this.getBGUnits()}, {label: 'Expected outcome', value: outcome + ' ' + this.getBGUnits()}]; this.updatePillText(bat + 'U', 'BWP', info); }; diff --git a/lib/plugins/cannulaage.js b/lib/plugins/cannulaage.js index c23c9c24cc0..03b4298b54c 100644 --- a/lib/plugins/cannulaage.js +++ b/lib/plugins/cannulaage.js @@ -15,6 +15,7 @@ function init() { var age = 0; var found = false; var treatmentDate = null; + var message = 'no notes'; for (var t in this.env.treatments) { if (this.env.treatments.hasOwnProperty(t)) { @@ -30,13 +31,14 @@ function init() { } else { if (hours < age) { age = hours; + if (treatment.notes) { message = treatment.notes; } } } } } } - this.updatePillText(age + 'h', 'CAGE', [{label: 'Inserted', value: moment(treatmentDate).format('lll')}]); + this.updatePillText(age + 'h', 'CAGE', [{label: 'Inserted:', value: moment(treatmentDate).format('lll')}, {label: 'Notes:', value: message}]); }; diff --git a/lib/plugins/iob.js b/lib/plugins/iob.js index 2c941646659..2c16bf31801 100644 --- a/lib/plugins/iob.js +++ b/lib/plugins/iob.js @@ -90,11 +90,11 @@ function init() { if (this.iob.lastBolus) { var when = moment(new Date(this.iob.lastBolus.created_at)).format('lll'); - var amount = nsutils.toFixed(Number(this.iob.lastBolus.insulin)) + 'U'; + var amount = this.roundInsulinForDisplayFormat(Number(this.iob.lastBolus.insulin)) + 'U'; info = [{label: 'Last Bolus', value: amount + ' @ ' + when }] } - this.updatePillText(this.iob.display + 'U', 'IOB', info, true); + this.updatePillText(this.roundInsulinForDisplayFormat(this.iob.display) + 'U', 'IOB', info, true); }; return iob(); diff --git a/lib/plugins/pluginbase.js b/lib/plugins/pluginbase.js index 7de702e7571..ff4d551c219 100644 --- a/lib/plugins/pluginbase.js +++ b/lib/plugins/pluginbase.js @@ -50,6 +50,27 @@ function updatePillText(updatedText, label, info, major) { } } +function roundInsulinForDisplayFormat(iob) { + var denominator = 0.1; + + if (iob > 0.5 && iob <= 1) { denominator = 0.05; } + if (iob <= 0.5) { denominator = 0.025; } + + return Math.round(iob / denominator) * denominator; +} + +function getBGUnits() { + if (browserSettings.units == 'mmol') return 'mmol/L'; + return "mg/dl"; +} + +function roundBGToDisplayFormat(bg) { + if (browserSettings.units == 'mmol') { + return Math.round(bg * 100) / 100; + } + return Math.round(bg); +} + function scaleBg(bg) { if (browserSettings.units == 'mmol') { return Nightscout.units.mgdlToMMOL(bg); @@ -62,7 +83,10 @@ function PluginBase() { return { setEnv: setEnv, scaleBg: scaleBg, - updatePillText: updatePillText + updatePillText: updatePillText, + roundBGToDisplayFormat: roundBGToDisplayFormat, + roundInsulinForDisplayFormat: roundInsulinForDisplayFormat, + getBGUnits: getBGUnits }; } From d89436aa1e9e587e9d54849fe2e62f3a5f3dc797 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Mon, 25 May 2015 23:32:43 +0300 Subject: [PATCH 037/937] De-medtronicfied display and no more "no notes" message --- lib/plugins/cannulaage.js | 7 +++++-- lib/plugins/pluginbase.js | 20 +++++++++++++------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/lib/plugins/cannulaage.js b/lib/plugins/cannulaage.js index 5759bd102f5..ea27594de4a 100644 --- a/lib/plugins/cannulaage.js +++ b/lib/plugins/cannulaage.js @@ -16,7 +16,7 @@ function init() { var age = 0; var found = false; var treatmentDate = null; - var message = 'no notes'; + var message = ''; for (var t in this.env.treatments) { if (this.env.treatments.hasOwnProperty(t)) { @@ -39,7 +39,10 @@ function init() { } } - this.updatePillText(age + 'h', 'CAGE', [{label: 'Inserted:', value: moment(treatmentDate).format('lll')}, {label: 'Notes:', value: message}]); + var labels = [{label: 'Inserted:', value: moment(treatmentDate).format('lll')}]; + if (message != '') labels.push({label: 'Notes:', value: message}); + + this.updatePillText(age + 'h', 'CAGE', labels); }; diff --git a/lib/plugins/pluginbase.js b/lib/plugins/pluginbase.js index 556035e2dff..02dba2ec468 100644 --- a/lib/plugins/pluginbase.js +++ b/lib/plugins/pluginbase.js @@ -50,16 +50,22 @@ function updatePillText(updatedText, label, info, major) { } } -function roundInsulinForDisplayFormat(iob) { +function roundInsulinForDisplayFormat(iob, roundingStyle) { if (iob == 0) return 0; - var denominator = 0.1; - var digits = 1; - if (iob > 0.5 && iob < 1) { denominator = 0.05; digits = 2;} - if (iob <= 0.5) { denominator = 0.025; digits = 3;} - - return (Math.floor(iob / denominator) * denominator).toFixed(digits); + if (roundingStyle === undefined) roundingStyle = 'generic'; + + if (roundingStyle == 'medtronic') { + var denominator = 0.1; + var digits = 1; + if (iob > 0.5 && iob < 1) { denominator = 0.05; digits = 2;} + if (iob <= 0.5) { denominator = 0.025; digits = 3;} + return (Math.floor(iob / denominator) * denominator).toFixed(digits); + } + + return (Math.floor(iob / 0.01) * 0.01).toFixed(2); + } function getBGUnits() { From ff7f54d23169b3e5c7bcff8ffc418943ebb6116e Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 25 May 2015 13:39:26 -0700 Subject: [PATCH 038/937] moved plugin registration to the plugins/index.js --- bundle/bundle.source.js | 7 +------ lib/plugins/index.js | 14 ++++++++++---- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index 85a1fd42b28..2558c36e970 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -10,12 +10,7 @@ console.info('plugins', window.Nightscout.plugins); - window.Nightscout.plugins.register({ - iob: require('../lib/plugins/iob')(), - cob: require('../lib/plugins/cob')(), - bwp: require('../lib/plugins/boluswizardpreview')(), - cage: require('../lib/plugins/cannulaage')() - }); + window.Nightscout.plugins.registerDefaults(); console.info("Nightscout bundle ready", window.Nightscout); diff --git a/lib/plugins/index.js b/lib/plugins/index.js index 32deffd248e..ca241498b1f 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -12,11 +12,17 @@ function init() { return plugins; } - plugins.register = function register(all) { - allPlugins = []; + plugins.registerDefaults = function registerDefaults() { + plugins.register([ + require('./iob')(), + require('./cob')(), + require('./boluswizardpreview')(), + require('./cannulaage')() + ]) + }; - _.forIn(all, function eachPlugin(plugin, name) { - if (!plugin.name) plugin.name = name; + plugins.register = function register(all) { + _.forEach(all, function eachPlugin(plugin) { _.extend(plugin, PluginBase); allPlugins.push(plugin); }); From b5402e0f6327cb13d65f71c75191b69d6c577c4d Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 25 May 2015 13:44:07 -0700 Subject: [PATCH 039/937] minor clean up --- lib/plugins/boluswizardpreview.js | 14 ++++++++------ lib/plugins/cannulaage.js | 6 +++--- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 769fb8f4906..6f0eb73a42f 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -1,7 +1,5 @@ 'use strict'; -var nsutils = require('../nsutils')(); - function init() { function bwp() { @@ -23,7 +21,6 @@ function init() { var effect = this.iob.iob * this.profile.sens; var outcome = sgv - effect; var delta = 0; - var info = null; if (outcome > this.profile.target_high) { delta = outcome - this.profile.target_high; @@ -36,11 +33,16 @@ function init() { } bolusEstimate = this.roundInsulinForDisplayFormat(bolusEstimate); - outcome = this.roundBGToDisplayFormat(outcome); - var displayIOB = this.roundInsulinForDisplayFormat(this.iob.iob); + outcome = this.roundBGToDisplayFormat(outcome); + var displayIOB = this.roundInsulinForDisplayFormat(this.iob.iob); // display text - var info = [{label: 'Insulin on Board', value: displayIOB + 'U'}, {label: 'Expected effect', value: '-' + this.roundBGToDisplayFormat(effect) + ' ' + this.getBGUnits()}, {label: 'Expected outcome', value: outcome + ' ' + this.getBGUnits()}]; + var info = [ + {label: 'Insulin on Board', value: displayIOB + 'U'} + , {label: 'Expected effect', value: '-' + this.roundBGToDisplayFormat(effect) + ' ' + this.getBGUnits()} + , {label: 'Expected outcome', value: outcome + ' ' + this.getBGUnits()} + ]; + this.updatePillText(bolusEstimate + 'U', 'BWP', info); }; diff --git a/lib/plugins/cannulaage.js b/lib/plugins/cannulaage.js index ea27594de4a..997fd56bfc2 100644 --- a/lib/plugins/cannulaage.js +++ b/lib/plugins/cannulaage.js @@ -39,10 +39,10 @@ function init() { } } - var labels = [{label: 'Inserted:', value: moment(treatmentDate).format('lll')}]; - if (message != '') labels.push({label: 'Notes:', value: message}); + var info = [{label: 'Inserted:', value: moment(treatmentDate).format('lll')}]; + if (message != '') info.push({label: 'Notes:', value: message}); - this.updatePillText(age + 'h', 'CAGE', labels); + this.updatePillText(age + 'h', 'CAGE', info); }; From 2dcc8d4d785fdc02674216042d678a1157298d99 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 25 May 2015 23:19:06 -0700 Subject: [PATCH 040/937] removed settings; fixed profiles/profile mixups; fix current/id queries --- Makefile | 3 +- env.js | 6 +- lib/api/devicestatus/index.js | 4 +- lib/api/entries/index.js | 46 ++++++---- lib/api/index.js | 3 +- lib/api/settings/index.js | 73 --------------- lib/api/treatments/index.js | 6 +- lib/bootevent.js | 1 - lib/profile.js | 2 + lib/settings.js | 164 ---------------------------------- tests/api.entries.test.js | 2 +- tests/security.test.js | 3 +- 12 files changed, 43 insertions(+), 270 deletions(-) delete mode 100644 lib/api/settings/index.js delete mode 100644 lib/settings.js diff --git a/Makefile b/Makefile index 43db4909df3..ec7e4f21041 100644 --- a/Makefile +++ b/Makefile @@ -5,8 +5,7 @@ MONGO_CONNECTION?=mongodb://localhost/test_db CUSTOMCONNSTR_mongo_settings_collection?=test_settings CUSTOMCONNSTR_mongo_collection?=test_sgvs MONGO_SETTINGS=MONGO_CONNECTION=${MONGO_CONNECTION} \ - CUSTOMCONNSTR_mongo_collection=${CUSTOMCONNSTR_mongo_collection} \ - CUSTOMCONNSTR_mongo_settings_collection=${CUSTOMCONNSTR_mongo_settings_collection} + CUSTOMCONNSTR_mongo_collection=${CUSTOMCONNSTR_mongo_collection} # XXX.bewest: Mocha is an odd process, and since things are being # wrapped and transformed, this odd path needs to be used, not the diff --git a/env.js b/env.js index a0f381031ff..c455e62bbba 100644 --- a/env.js +++ b/env.js @@ -12,7 +12,6 @@ function config ( ) { * * PORT - serve http on this port * * MONGO_CONNECTION, CUSTOMCONNSTR_mongo - mongodb://... uri * * CUSTOMCONNSTR_mongo_collection - name of mongo collection with "sgv" documents - * * CUSTOMCONNSTR_mongo_settings_collection - name of mongo collection to store configurable settings * * API_SECRET - if defined, this passphrase is fed to a sha1 hash digest, the hex output is used to create a single-use token for API authorization * * NIGHTSCOUT_STATIC_FILES - the "base directory" to use for serving * static files over http. Default value is the included `static` @@ -50,7 +49,6 @@ function config ( ) { console.info('MQTT configured to use a custom client id, it will override the default: ', env.mqtt_client_id); } } - env.settings_collection = readENV('MONGO_SETTINGS_COLLECTION', 'settings'); env.treatments_collection = readENV('MONGO_TREATMENTS_COLLECTION', 'treatments'); env.profile_collection = readENV('MONGO_PROFILE_COLLECTION', 'profile'); env.devicestatus_collection = readENV('MONGO_DEVICESTATUS_COLLECTION', 'devicestatus'); @@ -176,11 +174,9 @@ function config ( ) { // This allows a provided json config to override environment variables var DB = require('./database_configuration.json'), DB_URL = DB.url ? DB.url : env.mongo, - DB_COLLECTION = DB.collection ? DB.collection : env.mongo_collection, - DB_SETTINGS_COLLECTION = DB.settings_collection ? DB.settings_collection : env.settings_collection; + DB_COLLECTION = DB.collection ? DB.collection : env.mongo_collection env.mongo = DB_URL; env.mongo_collection = DB_COLLECTION; - env.settings_collection = DB_SETTINGS_COLLECTION; env.static_files = readENV('NIGHTSCOUT_STATIC_FILES', __dirname + '/static/'); return env; diff --git a/lib/api/devicestatus/index.js b/lib/api/devicestatus/index.js index 40d3a3246d3..7a5f65ee1f0 100644 --- a/lib/api/devicestatus/index.js +++ b/lib/api/devicestatus/index.js @@ -21,8 +21,8 @@ function configure (app, wares, devicestatus) { if (!q.count) { q.count = 10; } - devicestatus.list(q, function (err, profiles) { - return res.json(profiles); + devicestatus.list(q, function (err, results) { + return res.json(results); }); }); diff --git a/lib/api/entries/index.js b/lib/api/entries/index.js index 53d3e4e339e..1593e543748 100644 --- a/lib/api/entries/index.js +++ b/lib/api/entries/index.js @@ -123,15 +123,14 @@ function configure (app, wares, core) { es.pipeline(inputs( ), persist(done)); } - api.param('model', function (req, res, next, model) { + function prepReqModel(req, model) { var find = { }; switch (model) { case 'treatments': case 'devicestatus': - case 'settings': - case 'profile': + //TODO: profiles not working now, maybe profiles are special + //case 'profiles': req.model = core[model]; - // find.type = model; break; case 'meter': @@ -155,14 +154,22 @@ function configure (app, wares, core) { req.model = core.entries; break; } + if (!req.query.find) { req.query.find = find; } else { req.query.find.type = find.type; } + } + + api.param('model', function (req, res, next, model) { + prepReqModel(req, model); next( ); }); + api.get('/entries/current', function(req, res, next) { + //assume sgv + req.params.model = 'sgv'; entries.list({count: 1}, function(err, records) { res.entries = records; res.entries_err = err; @@ -170,6 +177,25 @@ function configure (app, wares, core) { }); }, format_entries); + // Fetch one entry by id + api.get('/entries/:id', function(req, res, next) { + var ID_PATTERN = /^[a-f\d]{24}$/; + if (ID_PATTERN.test(req.params.id)) { + //assume sgv + req.params.model = 'sgv'; + entries.getEntry(req.params.id, function(err, entry) { + res.entries = [entry]; + res.entries_err = err; + next(); + }); + } else { + //TODO: /entries/:id is blocking /entries/:model + req.params.model = req.params.id; + prepReqModel(req, req.params.model); + query_models(req, res, next); + } + }, format_entries); + api.get('/entries/:model', query_models, format_entries); api.get('/entries', query_models, format_entries); @@ -180,7 +206,7 @@ function configure (app, wares, core) { req.model = core.entries; } var query = req.query; - if (!query.count) { query.count = 10 }; + if (!query.count) { query.count = 10 } req.model.list(query, function(err, entries) { res.entries = entries; res.entries_err = err; @@ -204,16 +230,6 @@ function configure (app, wares, core) { }, insert_entries, format_entries); } - // Fetch one entry by id - api.get('/entries/:id', function(req, res, next) { - entries.getEntry(req.params.id, function(err, entry) { - res.entries = [entry]; - res.entries_err = err; - next() - }); - }, format_entries); - - return api; } module.exports = configure; diff --git a/lib/api/index.js b/lib/api/index.js index 04280c607ea..7c67de12a3f 100644 --- a/lib/api/index.js +++ b/lib/api/index.js @@ -47,9 +47,8 @@ function create (env, core) { // Entries and settings app.use('/', require('./entries/')(app, wares, core)); - app.use('/', require('./settings/')(app, wares, core.settings)); app.use('/', require('./treatments/')(app, wares, core.treatments)); - app.use('/', require('./profile/')(app, wares, core.profile)); + app.use('/', require('./profile/')(app, wares, core.profiles)); app.use('/', require('./devicestatus/')(app, wares, core.devicestatus)); // Status diff --git a/lib/api/settings/index.js b/lib/api/settings/index.js deleted file mode 100644 index c997024270a..00000000000 --- a/lib/api/settings/index.js +++ /dev/null @@ -1,73 +0,0 @@ -'use strict'; - -var consts = require('../../constants'); - -function configure (app, wares, settings) { - var express = require('express'), - api = express.Router( ) - ; - // invoke common middleware - api.use(wares.sendJSONStatus); - // text body types get handled as raw buffer stream - api.use(wares.bodyParser.raw( )); - // json body types get handled as parsed json - api.use(wares.bodyParser.json( )); - // also support url-encoded content-type - api.use(wares.bodyParser.urlencoded({ extended: true })); - - - /**********\ - * Settings - \**********/ - // Handler for grabbing alias/profile - api.param('alias', function (req, res, next, alias) { - settings.alias(alias, function (err, profile) { - req.alias = profile; - next(err); - }); - }); - - // List settings available - api.get('/settings/', function(req, res) { - settings.list(function (err, profiles) { - return res.json(profiles); - }); - }); - - // Fetch settings - api.get('/settings/:alias', function(req, res) { - res.json(req.alias); - }); - - function config_authed (app, api, wares, settings) { - - // Delete settings - api.delete('/settings/:alias', wares.verifyAuthorization, function(req, res) { - settings.remove(req.alias.alias, function ( ) { - res.json({ }); - }); - }); - - function save_settings (req, res) { - var b = req.body; - b.alias = req.params.alias - settings.update(b, function (err, profile) { - res.json(profile); - }); - } - - // Update settings - api.put('/settings/:alias', wares.verifyAuthorization, save_settings); - api.post('/settings/:alias', wares.verifyAuthorization, save_settings); - - } - - if (app.enabled('api')) { - config_authed(app, api, wares, settings); - } - - return api; -} - -module.exports = configure; - diff --git a/lib/api/treatments/index.js b/lib/api/treatments/index.js index 259b7f00fe1..7d7e89d0b5d 100644 --- a/lib/api/treatments/index.js +++ b/lib/api/treatments/index.js @@ -15,10 +15,10 @@ function configure (app, wares, treatments) { // also support url-encoded content-type api.use(wares.bodyParser.urlencoded({ extended: true })); - // List settings available + // List treatments available api.get('/treatments/', function(req, res) { - treatments.list({find: req.params}, function (err, profiles) { - return res.json(profiles); + treatments.list({find: req.params}, function (err, results) { + return res.json(results); }); }); diff --git a/lib/bootevent.js b/lib/bootevent.js index 44ef41c8b36..9bb1fc16c47 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -18,7 +18,6 @@ function boot (env) { /////////////////////////////////////////////////// ctx.pushover = require('./pushover')(env); ctx.entries = require('./entries')(env.mongo_collection, ctx.store, ctx.pushover); - ctx.settings = require('./settings')(env.settings_collection, ctx.store); ctx.treatments = require('./treatments')(env.treatments_collection, ctx.store, ctx.pushover); ctx.devicestatus = require('./devicestatus')(env.devicestatus_collection, ctx.store); ctx.profiles = require('./profile')(env.profile_collection, ctx.store); diff --git a/lib/profile.js b/lib/profile.js index ec2182198db..08e7b9ca254 100644 --- a/lib/profile.js +++ b/lib/profile.js @@ -4,6 +4,8 @@ function storage (collection, storage) { var ObjectID = require('mongodb').ObjectID; + console.info('>>>>profile storage init', collection, storage); + function create (obj, fn) { obj.created_at = (new Date( )).toISOString( ); api().insert(obj, function (err, doc) { diff --git a/lib/settings.js b/lib/settings.js deleted file mode 100644 index 2d0b9984ab3..00000000000 --- a/lib/settings.js +++ /dev/null @@ -1,164 +0,0 @@ -'use strict'; - - -var ObjectID = require('mongodb').ObjectID; -function defaults ( ) { - var DEFAULT_SETTINGS_JSON = { - "units": "mg/dl" - }; // possible future settings: "theme": "subdued", "websockets": false, alertLow: 80, alertHigh: 180 - return DEFAULT_SETTINGS_JSON; -} - -function configure (collection, storage) { - var DEFAULT_SETTINGS_JSON = { - "units": "mg/dl" - }; - - function pop (fn) { - return function (err, results) { - if (err) fn(err); - fn(err, results.pop( )); - } - } - - function alias (alias, fn) { - return api( ).find({ alias: alias }).toArray(pop(fn)); - } - - function create (obj, fn) { - var result = merge(DEFAULT_SETTINGS_JSON, obj); - result.alias = obj.alias; - result.created_at = (new Date( )).toISOString( ); - api( ).insert(result, function (err, doc) { - fn(null, doc); - }); - } - - function lint (obj) { - var result = merge(DEFAULT_SETTINGS_JSON, json); - if (result.alias) return result; - } - - function list (fn) { - return api( ).find({ }, { alias: 1, nick: 1 }).toArray(pop(fn)); - } - - function remove (alias, fn) { - return api( ).remove({ alias: alias }, fn); - } - - var with_collection = storage.with_collection(collection); - function getSettings (fn) { - with_collection(function(err, collection) { - if (err) - fn(err); - else - // Retrieve the existing settings record. - collection.find().toArray(function(err, settings) { - if (err) - fn(err); - else - // Strip the record of the enclosing square brackets. - settings = settings.pop( ); - if (!settings) { - settings = DEFAULT_SETTINGS_JSON; - } - fn(null, settings); - }); - }); - } - var whitelist = [ 'units', 'data' ]; - - function merge (older, newer) { - var update = { }; - whitelist.forEach(function (prop) { - if (prop in newer) { - update[prop] = newer[prop]; - } - }); - return update; - } - - function update (json, fn) { - var updated = (new Date( )).toISOString( ); - alias(json.alias, function last (err, older) { - if (err) { return fn(err); } - var result; - if (older && older._id) { - // result = merge(older, json); - result = json; - result.updated_at = updated; - var deltas = Object.keys(result); - if (deltas.length < 1) { - fn("Bad Keys"); - return; - } - api( ).update( - { '_id' : new ObjectID(older._id) }, - { $set: result }, - function (err, res) { - // Return to the calling function to display our success. - fn(err, res); - } - ); - } else { - create(json, fn); - } - }); - } - - function updateSettings (json, fn) { - getSettings(function last (err, older) { - var result = merge(older, json); - var deltas = Object.keys(result); - if (deltas.length < 1) { - fn("Bad Keys"); - return; - } - result.updated_at = (new Date( )).toISOString( ); - with_collection(function(err, collection) { - if (err) { - console.log('error', result); - return fn(err); - } else if (older && older._id && Object.keys(deltas).length > 0) { - collection.update( - { '_id' : new ObjectID(older._id) }, - { $set: result }, - function (err, stats) { - // Return to the calling function to display our success. - fn(err, result); - } - ); - } else { - collection.insert(result, function (err, doc) { - fn(null, result); - - }); - - } - }); - }); - } - - function clear (fn) { - with_collection(function (err, collection) { - collection.remove({ }, fn); - }); - } - - function api ( ) { - return storage.pool.db.collection(collection); - } - - api.getSettings = getSettings; - api.updateSettings = updateSettings; - api.remove = remove; - api.clear = clear; - api.list = list; - api.create = create; - api.lint = lint; - api.alias = alias; - api.update = update; - return api; -} -module.exports = configure; diff --git a/tests/api.entries.test.js b/tests/api.entries.test.js index 7e0529a099f..5d0203528e0 100644 --- a/tests/api.entries.test.js +++ b/tests/api.entries.test.js @@ -69,7 +69,7 @@ describe('Entries REST api', function ( ) { }); }); - it('/entries/ID', function (done) { + it('/entries/sgv/ID', function (done) { var app = this.app; this.archive.list({count: 1}, function(err, records) { var currentId = records.pop()._id.toString(); diff --git a/tests/security.test.js b/tests/security.test.js index 42cfa4215e2..60b63f6aa25 100644 --- a/tests/security.test.js +++ b/tests/security.test.js @@ -14,10 +14,9 @@ describe('API_SECRET', function ( ) { ctx.wares = require('../lib/middleware/')(env); ctx.store = require('../lib/storage')(env); ctx.archive = require('../lib/entries').storage(env.mongo_collection, ctx.store); - ctx.settings = require('../lib/settings')(env.settings_collection, ctx.store); ctx.store(function ( ) { - ctx.app = api(env, ctx.wares, ctx.archive, ctx.settings); + ctx.app = api(env, ctx.wares, ctx.archive); scope.app = ctx.app; ctx.archive.create(load('json'), fn); scope.archive = ctx.archive; From 3d08a0185077646aac7aeade5328e6d58221e70d Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 25 May 2015 23:20:50 -0700 Subject: [PATCH 041/937] remove debug --- lib/profile.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/profile.js b/lib/profile.js index 08e7b9ca254..ec2182198db 100644 --- a/lib/profile.js +++ b/lib/profile.js @@ -4,8 +4,6 @@ function storage (collection, storage) { var ObjectID = require('mongodb').ObjectID; - console.info('>>>>profile storage init', collection, storage); - function create (obj, fn) { obj.created_at = (new Date( )).toISOString( ); api().insert(obj, function (err, doc) { From 149213677aa0804b57b5e23a1a19083fb55542d0 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 26 May 2015 00:04:07 -0700 Subject: [PATCH 042/937] only list enabled plugins in the settings panel, and some minor tweeks --- lib/plugins/index.js | 2 -- static/css/main.css | 34 +++++++++++++++++++++++----------- static/js/ui-utils.js | 2 +- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/lib/plugins/index.js b/lib/plugins/index.js index ca241498b1f..3cca62db8fb 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -30,7 +30,6 @@ function init() { plugins.clientInit = function clientInit(app) { enabledPlugins = []; - console.info('NightscoutPlugins init', app); function isEnabled(plugin) { return app.enabledOptions && app.enabledOptions.indexOf(plugin.name) > -1; @@ -54,7 +53,6 @@ function init() { }; plugins.eachShownPlugins = function eachShownPlugins(clientSettings, f) { - console.info('eachShownPlugins'); var filtered = _.filter(enabledPlugins, function filterPlugins(plugin) { return clientSettings && clientSettings.showPlugins && clientSettings.showPlugins.indexOf(plugin.name) > -1; }); diff --git a/static/css/main.css b/static/css/main.css index 8faaa1a4a0d..d338b93d23c 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -82,7 +82,7 @@ body { .minorPills { font-size: 22px; - margin-top: 5px; + margin-top: 10px; z-index: 500; } @@ -397,16 +397,20 @@ div.tooltip { } #chartContainer { - top: 185px; + top: 165px; font-size: 14px; } + #chartContainer svg { + height: calc(100vh - 165px); + } + .has-minor-pills #chartContainer { - top: 195px; + top: 175px; } #chartContainer svg { - height: calc(100vh - 185px); + height: calc(100vh - 175px); } } @@ -449,7 +453,7 @@ div.tooltip { .time { font-size: 50px; - line-height: 40px; + line-height: 35px; width: 250px; } @@ -550,12 +554,16 @@ div.tooltip { top: 190px; } + #chartContainer svg { + height: calc(100vh - (190px)); + } + .has-minor-pills #chartContainer { - top: 200px; + top: 210px; } - #chartContainer svg { - height: calc(100vh - (190px)); + .has-minor-pills #chartContainer svg { + height: calc(100vh - (210px)); } } @@ -591,12 +599,16 @@ div.tooltip { font-size: 10px; } + #chartContainer svg { + height: calc(100vh - 130px); + } + .has-minor-pills #chartContainer { - top: 140px; + top: 140px; } - #chartContainer svg { - height: calc(100vh - 130px); + .has-minor-pills #chartContainer svg { + height: calc(100vh - 140px); } } diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js index 50acb03dc56..ec744ae1318 100644 --- a/static/js/ui-utils.js +++ b/static/js/ui-utils.js @@ -98,7 +98,7 @@ function getBrowserSettings(storage) { json.showPlugins = setDefault(json.showPlugins, app.defaults.showPlugins || Nightscout.plugins.enabledPluginNames()); var showPluginsSettings = $('#show-plugins'); - Nightscout.plugins.eachPlugin(function each(plugin) { + Nightscout.plugins.eachEnabledPlugin(function each(plugin) { var id = 'plugin-' + plugin.name; var dd = $('
    '); showPluginsSettings.append(dd); From c236b55f70a2e1e4a6be0e027cd7a7c4c96dc906 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 27 May 2015 01:27:57 -0700 Subject: [PATCH 043/937] convert entries/treatment pushovers in to a server side plugin --- bundle/bundle.source.js | 4 +- lib/bootevent.js | 5 +- lib/entries.js | 152 ++---------------------------- lib/plugins/boluswizardpreview.js | 2 +- lib/plugins/cannulaage.js | 2 +- lib/plugins/cob.js | 2 +- lib/plugins/index.js | 18 +++- lib/plugins/iob.js | 2 +- lib/plugins/pluginbase.js | 2 +- lib/treatments.js | 69 ++------------ static/js/client.js | 4 +- tests/api.entries.test.js | 13 ++- tests/api.status.test.js | 7 +- 13 files changed, 50 insertions(+), 232 deletions(-) diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index 2558c36e970..1fd4f3a2d78 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -5,13 +5,11 @@ window.Nightscout = { units: require('../lib/units')(), profile: require('../lib/profilefunctions')(), - plugins: require('../lib/plugins/')() + plugins: require('../lib/plugins/')().registerClientDefaults() }; console.info('plugins', window.Nightscout.plugins); - window.Nightscout.plugins.registerDefaults(); - console.info("Nightscout bundle ready", window.Nightscout); })(); diff --git a/lib/bootevent.js b/lib/bootevent.js index 1f7cd8bc3a0..2f81a590b50 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -16,9 +16,10 @@ function boot (env) { /////////////////////////////////////////////////// // api and json object variables /////////////////////////////////////////////////// + ctx.plugins = require('./plugins')().registerServerDefaults().init(env); ctx.pushover = require('./pushover')(env); - ctx.entries = require('./entries')(env.mongo_collection, ctx.store, ctx.pushover, env); - ctx.treatments = require('./treatments')(env.treatments_collection, ctx.store, ctx.pushover); + ctx.entries = require('./entries')(env, ctx); + ctx.treatments = require('./treatments')(env, ctx); ctx.devicestatus = require('./devicestatus')(env.devicestatus_collection, ctx.store); ctx.profiles = require('./profile')(env.profile_collection, ctx.store); ctx.pebble = require('./pebble'); diff --git a/lib/entries.js b/lib/entries.js index 239d4f71130..adfe7dea56b 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -5,12 +5,6 @@ var sgvdata = require('sgvdata'); var units = require('./units')(); var ObjectID = require('mongodb').ObjectID; -// declare local constants for time differences -var TIME_10_MINS = 10 * 60 * 1000, - TIME_15_MINS = 15 * 60 * 1000, - TIME_30_MINS = TIME_15_MINS * 2; - - /**********\ * Entries * Encapsulate persistent storage of sgv entries. @@ -28,11 +22,11 @@ function find_sgv_query (opts) { return opts; } -function storage(name, storage, pushover, env) { +function storage(env, ctx) { // TODO: Code is a little redundant. - var with_collection = storage.with_collection(name); + var with_collection = ctx.store.with_collection(env.mongo_collection); // query for entries from storage function list (opts, fn) { @@ -44,8 +38,7 @@ function storage(name, storage, pushover, env) { // determine find options function find ( ) { var finder = find_sgv_query(opts); - var q = finder && finder.find ? finder.find : { }; - return q; + return finder && finder.find ? finder.find : { }; // return this.find(q); } @@ -126,143 +119,12 @@ function storage(name, storage, pushover, env) { fn(firstErr, docs); } }); - sendPushover(doc); - }); - }); - } - - function sendPushover(doc) { - if (doc.type && doc.date && pushover) { - if (doc.type == 'mbg') { - sendMBGPushover(doc); - } else if (doc.type == 'sgv') { - sendSGVPushover(doc); - } - } - } - //currently the Android upload will send the last MBG over and over, make sure we get a single notification - var lastMBGDate = 0; - - function sendMBGPushover(doc) { - - if (doc.mbg && doc.type == 'mbg' && doc.date != lastMBGDate) { - var offset = new Date().getTime() - doc.date; - if (offset > TIME_10_MINS) { - console.info('No MBG Pushover, offset: ' + offset + ' too big, doc.date: ' + doc.date + ', now: ' + new Date().getTime()); - } else { - var mbg = doc.mbg; - if (env.DISPLAY_UNITS == 'mmol') { - mbg = units.mgdlToMMOL(mbg); - } - var msg = { - expire: 14400, // 4 hours - message: '\nMeter BG: ' + mbg, - title: 'Calibration', - sound: 'magic', - timestamp: new Date(doc.date), - priority: 0, - retry: 30 - }; - - pushover.send(msg, function (err, result) { - console.log(result); + ctx.plugins.eachEnabledPlugin(function eachEnabled(plugin) { + if (plugin.processEntry) plugin.processEntry(doc, ctx, env) }); - } - lastMBGDate = doc.date; - } - } - - // global variable for last alert time - var lastAlert = 0; - var lastSGVDate = 0; - - function sendSGVPushover(doc) { - - if (!doc.sgv || doc.type != 'sgv') { - return; - } - - var now = new Date().getTime(), - offset = new Date().getTime() - doc.date; - - if (offset > TIME_10_MINS || doc.date == lastSGVDate) { - console.info('No SVG Pushover, offset: ' + offset + ' too big, doc.date: ' + doc.date + ', now: ' + new Date().getTime() + ', lastSGVDate: ' + lastSGVDate); - return; - } - - // initialize message data - var sinceLastAlert = now - lastAlert, - title = 'CGM Alert', - priority = 0, - sound = null, - readingtime = doc.date, - readago = now - readingtime; - - console.info('now: ' + now); - console.info('doc.sgv: ' + doc.sgv); - console.info('doc.direction: ' + doc.direction); - console.info('doc.date: ' + doc.date); - console.info('readingtime: ' + readingtime); - console.info('readago: ' + readago); - - // set vibration pattern; alert value; 0 nothing, 1 normal, 2 low, 3 high - if (doc.sgv < 39) { - if (sinceLastAlert > TIME_30_MINS) { - title = 'CGM Error'; - priority = 1; - sound = 'persistent'; - } - } else if (doc.sgv < env.thresholds.bg_low && sinceLastAlert > TIME_15_MINS) { - title = 'Urgent Low'; - priority = 2; - sound = 'persistent'; - } else if (doc.sgv < env.thresholds.bg_target_bottom && sinceLastAlert > TIME_15_MINS) { - title = 'Low'; - priority = 1; - sound = 'falling'; - } else if (doc.sgv < 120 && doc.direction == 'DoubleDown') { - title = 'Double Down'; - priority = 1; - sound = 'falling'; - } else if (doc.sgv == 100 && doc.direction == 'Flat' && sinceLastAlert > TIME_15_MINS) { //Perfect Score - a good time to take a picture :) - title = 'Perfect'; - priority = 0; - sound = 'cashregister'; - } else if (doc.sgv > 120 && doc.direction == 'DoubleUp' && sinceLastAlert > TIME_15_MINS) { - title = 'Double Up'; - priority = 1; - sound = 'climb'; - } else if (doc.sgv > env.thresholds.bg_target_top && sinceLastAlert > TIME_30_MINS) { - title = 'High'; - priority = 1; - sound = 'climb'; - } else if (doc.sgv > env.thresholds.bg_high && sinceLastAlert > TIME_30_MINS) { - title = 'Urgent High'; - priority = 1; - sound = 'persistent'; - } - - if (sound != null) { - lastAlert = now; - - var msg = { - expire: 14400, // 4 hours - message: 'BG NOW: ' + doc.sgv, - title: title, - sound: sound, - timestamp: new Date(doc.date), - priority: priority, - retry: 30 - }; - - pushover.send(msg, function (err, result) { - console.log(result); }); - } - - - lastSGVDate = doc.date; + }); } function getEntry(id, fn) { @@ -283,7 +145,7 @@ function storage(name, storage, pushover, env) { function api ( ) { // obtain handle usable for querying the collection associated // with these records - return storage.pool.db.collection(name); + return ctx.store.pool.db.collection(env.mongo_collection); } // Expose all the useful functions diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 6f0eb73a42f..e330258b881 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -7,7 +7,7 @@ function init() { } bwp.label = 'Bolus Wizard Preview'; - bwp.pluginType = 'minor-pill'; + bwp.pluginType = 'pill-minor'; bwp.updateVisualisation = function updateVisualisation() { diff --git a/lib/plugins/cannulaage.js b/lib/plugins/cannulaage.js index 997fd56bfc2..636fe0d5892 100644 --- a/lib/plugins/cannulaage.js +++ b/lib/plugins/cannulaage.js @@ -9,7 +9,7 @@ function init() { } cage.label = 'Cannula Age'; - cage.pluginType = 'minor-pill'; + cage.pluginType = 'pill-minor'; cage.updateVisualisation = function updateVisualisation() { diff --git a/lib/plugins/cob.js b/lib/plugins/cob.js index 078c77a9a02..572dd3ac84b 100644 --- a/lib/plugins/cob.js +++ b/lib/plugins/cob.js @@ -10,7 +10,7 @@ function init() { } cob.label = 'Carbs-on-Board'; - cob.pluginType = 'minor-pill'; + cob.pluginType = 'pill-minor'; cob.getData = function getData() { return cob.cobTotal(this.env.treatments, this.env.time); diff --git a/lib/plugins/index.js b/lib/plugins/index.js index 3cca62db8fb..306a4288041 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -12,13 +12,19 @@ function init() { return plugins; } - plugins.registerDefaults = function registerDefaults() { + plugins.registerServerDefaults = function registerServerDefaults() { + plugins.register([ require('./pushnotify')() ]); + return plugins; + }; + + plugins.registerClientDefaults = function registerClientDefaults() { plugins.register([ require('./iob')(), require('./cob')(), require('./boluswizardpreview')(), require('./cannulaage')() - ]) + ]); + return plugins; }; plugins.register = function register(all) { @@ -28,11 +34,12 @@ function init() { }); }; - plugins.clientInit = function clientInit(app) { + plugins.init = function init(envOrApp) { enabledPlugins = []; function isEnabled(plugin) { - return app.enabledOptions - && app.enabledOptions.indexOf(plugin.name) > -1; + //TODO: unify client/server env/app + var enable = envOrApp.enabledOptions || envOrApp.enable; + return enable && enable.indexOf(plugin.name) > -1; } _.forEach(allPlugins, function eachPlugin(plugin) { @@ -42,6 +49,7 @@ function init() { } }); console.info('Plugins enabled', enabledPlugins); + return plugins; }; plugins.eachPlugin = function eachPlugin(f) { diff --git a/lib/plugins/iob.js b/lib/plugins/iob.js index 7ff0828d22d..8202565502b 100644 --- a/lib/plugins/iob.js +++ b/lib/plugins/iob.js @@ -11,7 +11,7 @@ function init() { } iob.label = 'Insulin-on-Board'; - iob.pluginType = 'major-pill'; + iob.pluginType = 'pill-major'; iob.getData = function getData() { return iob.calcTotal(this.env.treatments, this.env.profile, this.env.time); diff --git a/lib/plugins/pluginbase.js b/lib/plugins/pluginbase.js index 02dba2ec468..abedce5ffb1 100644 --- a/lib/plugins/pluginbase.js +++ b/lib/plugins/pluginbase.js @@ -18,7 +18,7 @@ function updatePillText(updatedText, label, info, major) { var pillName = "span.pill." + this.name; - var container = this.pluginType == 'major-pill' ? this.majorPills : this.minorPills; + var container = this.pluginType == 'pill-major' ? this.majorPills : this.minorPills; var pill = container.find(pillName); diff --git a/lib/treatments.js b/lib/treatments.js index 2db959b12f4..c0859a07e5c 100644 --- a/lib/treatments.js +++ b/lib/treatments.js @@ -1,6 +1,6 @@ 'use strict'; -function storage (collection, storage, pushover) { +function storage (env, ctx) { function create (obj, fn) { @@ -47,80 +47,29 @@ function storage (collection, storage, pushover) { if (obj.notes) pbTreat.notes = obj.notes; api( ).insert(pbTreat, function() { - //nothing to do here + //nothing to do here }); } - if (pushover) { - - //since we don't know the time zone on the device viewing the push messeage - //we can only show the amount of adjustment - var timeAdjustment = calcTimeAdjustment(eventTime); - - var text = (obj.glucose ? 'BG: ' + obj.glucose + ' (' + obj.glucoseType + ')' : '') + - (obj.carbs ? '\nCarbs: ' + obj.carbs : '') + - (preBolusCarbs ? '\nCarbs: ' + preBolusCarbs + ' (in ' + obj.preBolus + ' minutes)' : '')+ - (obj.insulin ? '\nInsulin: ' + obj.insulin : '')+ - (obj.enteredBy ? '\nEntered By: ' + obj.enteredBy : '') + - (timeAdjustment ? '\nEvent Time: ' + timeAdjustment : '') + - (obj.notes ? '\nNotes: ' + obj.notes : ''); - - var msg = { - expire: 14400, // 4 hours - message: text, - title: obj.eventType, - sound: 'gamelan', - timestamp: new Date( ), - priority: (obj.eventType == 'Note' ? -1 : 0), - retry: 30 - }; + //TODO: call plugins with processTreatments + ctx.plugins.eachEnabledPlugin(function eachEnabled(plugin) { + if (plugin.processTreatment) plugin.processTreatment(obj, eventTime, preBolusCarbs, ctx, env) + }); - pushover.send( msg, function( err, result ) { - console.log(result); - }); - } }); } - function calcTimeAdjustment(eventTime) { - - if (!eventTime) return null; - - var now = (new Date()).getTime(), - other = eventTime.getTime(), - past = other < now, - offset = Math.abs(now - other); - - var MINUTE = 60 * 1000, - HOUR = 3600 * 1000; - - var parts = {}; - - if (offset <= MINUTE) - return 'now'; - else if (offset < (HOUR * 2)) - parts = { value: Math.round(Math.abs(offset / MINUTE)), label: 'mins' }; - else - parts = { value: Math.round(Math.abs(offset / HOUR)), label: 'hrs' }; - - if (past) - return parts.value + ' ' + parts.label + ' ago'; - else - return 'in ' + parts.value + ' ' + parts.label; - } - function list (opts, fn) { function find ( ) { - var q = opts && opts.find ? opts.find : { }; - return q; + return opts && opts.find ? opts.find : { }; } - return storage.limit.call(api().find(find( )).sort({created_at: -1}), opts).toArray(fn); + return ctx.store.limit.call(api().find(find( )).sort({created_at: -1}), opts).toArray(fn); } function api ( ) { - return storage.pool.db.collection(collection); + return ctx.store.pool.db.collection(env.treatments_collection); } api.list = list; diff --git a/static/js/client.js b/static/js/client.js index 33ab07ccd56..5ba08515df1 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -489,7 +489,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; Nightscout.plugins.eachShownPlugins(browserSettings, function eachPlugin(plugin) { console.info('plugin.pluginType', plugin.pluginType); - if (plugin.pluginType == 'minor-pill') { + if (plugin.pluginType == 'pill-minor') { hasMinorPill = true; } }); @@ -1818,7 +1818,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; $('.serverSettings').show(); } $('#treatmentDrawerToggle').toggle(app.careportalEnabled); - Nightscout.plugins.clientInit(app); + Nightscout.plugins.init(app); browserSettings = getBrowserSettings(browserStorage); init(); }); diff --git a/tests/api.entries.test.js b/tests/api.entries.test.js index 5d0203528e0..3c9026bf657 100644 --- a/tests/api.entries.test.js +++ b/tests/api.entries.test.js @@ -8,18 +8,17 @@ describe('Entries REST api', function ( ) { before(function (done) { var env = require('../env')( ); this.wares = require('../lib/middleware/')(env); - var store = require('../lib/storage')(env); - this.archive = require('../lib/entries').storage(env.mongo_collection, store); - var bootevent = require('../lib/bootevent'); + this.archive = null; this.app = require('express')( ); this.app.enable('api'); var self = this; + var bootevent = require('../lib/bootevent'); bootevent(env).boot(function booted (ctx) { - env.store = ctx.store; - self.app.use('/', entries(self.app, self.wares, ctx)); - self.archive.create(load('json'), done); + env.store = ctx.store; + self.app.use('/', entries(self.app, self.wares, ctx)); + self.archive = require('../lib/entries')(env, ctx); + self.archive.create(load('json'), done); }); - // store(function ( ) { }); }); after(function (done) { diff --git a/tests/api.status.test.js b/tests/api.status.test.js index c668bba9f2d..88a9bc23b9a 100644 --- a/tests/api.status.test.js +++ b/tests/api.status.test.js @@ -13,11 +13,12 @@ describe('Status REST api', function ( ) { this.app = require('express')( ); this.app.enable('api'); var self = this; - store(function ( ) { - var entriesStorage = require('../lib/entries').storage(env.mongo_collection, store); - self.app.use('/api', api(env, entriesStorage)); + var bootevent = require('../lib/bootevent'); + bootevent(env).boot(function booted (ctx) { + self.app.use('/api', api(env, ctx.entries)); done(); }); + }); it('should be a module', function ( ) { From 9c83998f87be5cba2cbd9bb65122a37f05f78428 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 27 May 2015 18:27:50 -0700 Subject: [PATCH 044/937] initial pushnotify plugin --- lib/plugins/pushnotify.js | 219 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 219 insertions(+) create mode 100644 lib/plugins/pushnotify.js diff --git a/lib/plugins/pushnotify.js b/lib/plugins/pushnotify.js new file mode 100644 index 00000000000..c938af66130 --- /dev/null +++ b/lib/plugins/pushnotify.js @@ -0,0 +1,219 @@ +'use strict'; + +var units = require('../units')(); + +function init() { + + // declare local constants for time differences + var TIME_10_MINS = 10 * 60 * 1000, + TIME_15_MINS = 15 * 60 * 1000, + TIME_30_MINS = TIME_15_MINS * 2; + + //the uploader may the last MBG multiple times, make sure we get a single notification + var lastMBGDate = 0; + + //simple SGV Alert throttling + //TODO: single snooze for websockets and push (when we add push callbacks) + var lastAlert = 0; + var lastSGVDate = 0; + + + function pushnotify() { + return pushnotify; + } + + pushnotify.label = 'Push Notify'; + pushnotify.pluginType = 'server-process'; + + pushnotify.processEntry = function processEntry(entry, ctx, env) { + if (entry.type && entry.date && ctx.pushover) { + if (entry.type == 'mbg' || entry.type == 'meter') { + sendMBGPushover(entry, ctx); + } else if (entry.type == 'sgv') { + sendSGVPushover(entry, ctx); + } + } + }; + + pushnotify.processTreatment = function processTreatment(treatment, eventTime, preBolusCarbs, ctx, env) { + + if (!ctx.pushover) return; + + //since we don't know the time zone on the device viewing the push message + //we can only show the amount of adjustment + var timeAdjustment = calcTimeAdjustment(eventTime); + + var text = (treatment.glucose ? 'BG: ' + treatment.glucose + ' (' + treatment.glucoseType + ')' : '') + + (treatment.carbs ? '\nCarbs: ' + treatment.carbs : '') + + (preBolusCarbs ? '\nCarbs: ' + preBolusCarbs + ' (in ' + treatment.preBolus + ' minutes)' : '')+ + (treatment.insulin ? '\nInsulin: ' + treatment.insulin : '')+ + (treatment.enteredBy ? '\nEntered By: ' + treatment.enteredBy : '') + + (timeAdjustment ? '\nEvent Time: ' + timeAdjustment : '') + + (treatment.notes ? '\nNotes: ' + treatment.notes : ''); + + var msg = { + expire: 14400, // 4 hours + message: text, + title: treatment.eventType, + sound: 'gamelan', + timestamp: new Date( ), + priority: (treatment.eventType == 'Note' ? -1 : 0), + retry: 30 + }; + + ctx.pushover.send( msg, function( err, result ) { + console.log(result); + }); + + }; + + + function sendMBGPushover(entry, ctx) { + + if (entry.mbg && entry.type == 'mbg' && entry.date != lastMBGDate) { + var offset = new Date().getTime() - entry.date; + if (offset > TIME_10_MINS) { + console.info('No MBG Pushover, offset: ' + offset + ' too big, doc.date: ' + entry.date + ', now: ' + new Date().getTime()); + } else { + var mbg = entry.mbg; + if (env.DISPLAY_UNITS == 'mmol') { + mbg = units.mgdlToMMOL(mbg); + } + var msg = { + expire: 14400, // 4 hours + message: '\nMeter BG: ' + mbg, + title: 'Calibration', + sound: 'magic', + timestamp: new Date(entry.date), + priority: 0, + retry: 30 + }; + + ctx.pushover.send(msg, function (err, result) { + console.log(result); + }); + } + lastMBGDate = entry.date; + } + } + + function sendSGVPushover(entry, ctx) { + + if (!entry.sgv || entry.type != 'sgv') { + return; + } + + var now = new Date().getTime(), + offset = new Date().getTime() - entry.date; + + if (offset > TIME_10_MINS || entry.date == lastSGVDate) { + console.info('No SVG Pushover, offset: ' + offset + ' too big, doc.date: ' + entry.date + ', now: ' + new Date().getTime() + ', lastSGVDate: ' + lastSGVDate); + return; + } + + // initialize message data + var sinceLastAlert = now - lastAlert, + title = 'CGM Alert', + priority = 0, + sound = null, + readingtime = entry.date, + readago = now - readingtime; + + console.info('now: ' + now); + console.info('doc.sgv: ' + entry.sgv); + console.info('doc.direction: ' + entry.direction); + console.info('doc.date: ' + entry.date); + console.info('readingtime: ' + readingtime); + console.info('readago: ' + readago); + + // set vibration pattern; alert value; 0 nothing, 1 normal, 2 low, 3 high + if (entry.sgv < 39) { + if (sinceLastAlert > TIME_30_MINS) { + title = 'CGM Error'; + priority = 1; + sound = 'persistent'; + } + } else if (entry.sgv < env.thresholds.bg_low && sinceLastAlert > TIME_15_MINS) { + title = 'Urgent Low'; + priority = 2; + sound = 'persistent'; + } else if (entry.sgv < env.thresholds.bg_target_bottom && sinceLastAlert > TIME_15_MINS) { + title = 'Low'; + priority = 1; + sound = 'falling'; + } else if (entry.sgv < 120 && entry.direction == 'DoubleDown') { + title = 'Double Down'; + priority = 1; + sound = 'falling'; + } else if (entry.sgv == 100 && entry.direction == 'Flat' && sinceLastAlert > TIME_15_MINS) { //Perfect Score - a good time to take a picture :) + title = 'Perfect'; + priority = 0; + sound = 'cashregister'; + } else if (entry.sgv > 120 && entry.direction == 'DoubleUp' && sinceLastAlert > TIME_15_MINS) { + title = 'Double Up'; + priority = 1; + sound = 'climb'; + } else if (entry.sgv > env.thresholds.bg_target_top && sinceLastAlert > TIME_30_MINS) { + title = 'High'; + priority = 1; + sound = 'climb'; + } else if (entry.sgv > env.thresholds.bg_high && sinceLastAlert > TIME_30_MINS) { + title = 'Urgent High'; + priority = 1; + sound = 'persistent'; + } + + if (sound != null) { + lastAlert = now; + + var msg = { + expire: 14400, // 4 hours + message: 'BG NOW: ' + entry.sgv, + title: title, + sound: sound, + timestamp: new Date(entry.date), + priority: priority, + retry: 30 + }; + + ctx.pushover.send(msg, function (err, result) { + console.log(result); + }); + } + + + lastSGVDate = entry.date; + } + + function calcTimeAdjustment(eventTime) { + + if (!eventTime) return null; + + var now = (new Date()).getTime(), + other = eventTime.getTime(), + past = other < now, + offset = Math.abs(now - other); + + var MINUTE = 60 * 1000, + HOUR = 3600 * 1000; + + var parts = {}; + + if (offset <= MINUTE) + return 'now'; + else if (offset < (HOUR * 2)) + parts = { value: Math.round(Math.abs(offset / MINUTE)), label: 'mins' }; + else + parts = { value: Math.round(Math.abs(offset / HOUR)), label: 'hrs' }; + + if (past) + return parts.value + ' ' + parts.label + ' ago'; + else + return 'in ' + parts.value + ' ' + parts.label; + } + + return pushnotify(); +} + + +module.exports = init; \ No newline at end of file From 96b7e438809f27eedbc7b1ad331fdf3d7d898730 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 29 May 2015 01:55:31 -0700 Subject: [PATCH 045/937] initial tests for cob --- tests/cob.test.js | 69 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 tests/cob.test.js diff --git a/tests/cob.test.js b/tests/cob.test.js new file mode 100644 index 00000000000..d2c2dddb121 --- /dev/null +++ b/tests/cob.test.js @@ -0,0 +1,69 @@ +'use strict'; + +var should = require('should'); + +describe('COB', function ( ) { + var cob = require('../lib/plugins/cob')(); + + cob.profile = { + sens: 95 + , carbratio: 18 + , carbs_hr: 30 + }; + + it('should calculate IOB, multiple treatments', function() { + + var treatments = [ + { + "carbs": "100", + "created_at": new Date("2015-05-29T02:03:48.827Z") + }, + { + "carbs": "10", + "created_at": new Date("2015-05-29T03:45:10.670Z") + } + ]; + + var after100 = cob.cobTotal(treatments, new Date("2015-05-29T02:03:49.827Z")); + var before10 = cob.cobTotal(treatments, new Date("2015-05-29T03:45:10.670Z")); + var after10 = cob.cobTotal(treatments, new Date("2015-05-29T03:45:11.670Z")); + + console.info('>>>>after100:', after100); + console.info('>>>>before10:', before10); + console.info('>>>>after2nd:', after10); + + after100.cob.should.equal(100); + Math.round(before10.cob).should.equal(59); + Math.round(after10.cob).should.equal(69); //WTF == 128 + }); + + it('should calculate IOB, single treatment', function() { + + var treatments = [ + { + "carbs": "8", + "created_at": new Date("2015-05-29T04:40:40.174Z") + } + ]; + + var rightAfterCorrection = new Date("2015-05-29T04:41:40.174Z"); + var later1 = new Date("2015-05-29T05:04:40.174Z"); + var later2 = new Date("2015-05-29T05:20:00.174Z"); + var later3 = new Date("2015-05-29T05:50:00.174Z"); + var later4 = new Date("2015-05-29T06:50:00.174Z"); + + var result1 = cob.cobTotal(treatments, rightAfterCorrection); + var result2 = cob.cobTotal(treatments, later1); + var result3 = cob.cobTotal(treatments, later2); + var result4 = cob.cobTotal(treatments, later3); + var result5 = cob.cobTotal(treatments, later4); + + result1.cob.should.equal(8); + result2.cob.should.equal(6); + result3.cob.should.equal(0); + result4.cob.should.equal(0); + result5.cob.should.equal(0); + }); + + +}); \ No newline at end of file From 3125df976ff276fa7471935e1bc5e3a592a47f84 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 29 May 2015 01:56:10 -0700 Subject: [PATCH 046/937] cob clean up --- lib/plugins/cob.js | 63 +++++++++++++++++++++++----------------------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/lib/plugins/cob.js b/lib/plugins/cob.js index 572dd3ac84b..a96a3084568 100644 --- a/lib/plugins/cob.js +++ b/lib/plugins/cob.js @@ -1,6 +1,7 @@ 'use strict'; -var iob = require('./iob')() +var _ = require('lodash') + , iob = require('./iob')() , moment = require('moment'); function init() { @@ -31,42 +32,42 @@ function init() { var lastDecayedBy = new Date('1/1/1970'); var carbs_hr = this.profile.carbs_hr; - for (var t in treatments) { - if (treatments.hasOwnProperty(t)) { - var treatment = treatments[t]; - if (treatment.carbs && treatment.created_at < time) { - lastCarbs = treatment; - var cCalc = this.cobCalc(treatment, lastDecayedBy, time); - var decaysin_hr = (cCalc.decayedBy - time) / 1000 / 60 / 60; - if (decaysin_hr > -10) { - var actStart = iob.calcTotal(treatments, lastDecayedBy).activity; - var actEnd = iob.calcTotal(treatments, cCalc.decayedBy).activity; - var avgActivity = (actStart + actEnd) / 2; - var delayedCarbs = avgActivity * liverSensRatio * sens / carbratio; - var delayMinutes = Math.round(delayedCarbs / carbs_hr * 60); - if (delayMinutes > 0) { - cCalc.decayedBy.setMinutes(cCalc.decayedBy.getMinutes() + delayMinutes); - decaysin_hr = (cCalc.decayedBy - time) / 1000 / 60 / 60; - } + _.forEach(treatments, function eachTreatment(treatment) { + if (treatment.carbs && treatment.created_at < time) { + lastCarbs = treatment; + var cCalc = cob.cobCalc(treatment, lastDecayedBy, time); + var decaysin_hr = (cCalc.decayedBy - time) / 1000 / 60 / 60; + if (decaysin_hr > -10) { + var actStart = iob.calcTotal(treatments, lastDecayedBy).activity; + var actEnd = iob.calcTotal(treatments, cCalc.decayedBy).activity; + var avgActivity = (actStart + actEnd) / 2; + var delayedCarbs = avgActivity * liverSensRatio * sens / carbratio; + var delayMinutes = Math.round(delayedCarbs / carbs_hr * 60); + if (delayMinutes > 0) { + cCalc.decayedBy.setMinutes(cCalc.decayedBy.getMinutes() + delayMinutes); + decaysin_hr = (cCalc.decayedBy - time) / 1000 / 60 / 60; } + } - if (cCalc) { - lastDecayedBy = cCalc.decayedBy; - } + console.info('cCalc', cCalc, ' --- treatment', treatment); - if (decaysin_hr > 0) { - //console.info('Adding ' + delayMinutes + ' minutes to decay of ' + treatment.carbs + 'g bolus at ' + treatment.created_at); - totalCOB += Math.min(cCalc.initialCarbs, decaysin_hr * carbs_hr); - //console.log("cob: " + Math.min(cCalc.initialCarbs, decaysin_hr * carbs_hr)); - isDecaying = cCalc.isDecaying; - } - else { - totalCOB = 0; - } + if (cCalc) { + lastDecayedBy = cCalc.decayedBy; + } + if (decaysin_hr > 0) { + //console.info('Adding ' + delayMinutes + ' minutes to decay of ' + treatment.carbs + 'g bolus at ' + treatment.created_at); + console.info('COB contrib', cCalc.initialCarbs, decaysin_hr * carbs_hr); + totalCOB += Math.min(cCalc.initialCarbs, decaysin_hr * carbs_hr); + //console.log("cob: " + Math.min(cCalc.initialCarbs, decaysin_hr * carbs_hr)); + isDecaying = cCalc.isDecaying; + } else { + totalCOB = 0; } + } - } + }); + var rawCarbImpact = isDecaying * sens / carbratio * carbs_hr / 60; return { From 049c9008db2a1acaf810252db38d0935b481b351 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 30 May 2015 08:58:26 -0700 Subject: [PATCH 047/937] fixed minor annoyance --- static/js/client.js | 1 + 1 file changed, 1 insertion(+) diff --git a/static/js/client.js b/static/js/client.js index 33ab07ccd56..50b3267aadc 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1206,6 +1206,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; $(this).removeClass('playing'); }); + closeNotification(); $('#container').removeClass('alarming'); // only emit ack if client invoke by button press From b57a4bd7d18ed58f51e63c4fe6f52bfc76480486 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 30 May 2015 19:14:00 -0700 Subject: [PATCH 048/937] improve client perf cache displayBG for treatments use _.debouce to avoid calling expensive functions too often --- bundle/bundle.source.js | 3 +- lib/plugins/index.js | 15 +++- static/js/client.js | 161 ++++++++++++++++++---------------------- 3 files changed, 86 insertions(+), 93 deletions(-) diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index 2558c36e970..d36e76b0974 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -8,8 +8,7 @@ plugins: require('../lib/plugins/')() }; - console.info('plugins', window.Nightscout.plugins); - + window._ = require('lodash'); window.Nightscout.plugins.registerDefaults(); console.info("Nightscout bundle ready", window.Nightscout); diff --git a/lib/plugins/index.js b/lib/plugins/index.js index 3cca62db8fb..1f29faea388 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -41,7 +41,6 @@ function init() { enabledPlugins.push(plugin); } }); - console.info('Plugins enabled', enabledPlugins); }; plugins.eachPlugin = function eachPlugin(f) { @@ -52,12 +51,20 @@ function init() { _.forEach(enabledPlugins, f); }; - plugins.eachShownPlugins = function eachShownPlugins(clientSettings, f) { - var filtered = _.filter(enabledPlugins, function filterPlugins(plugin) { + plugins.shownPlugins = function(clientSettings) { + return _.filter(enabledPlugins, function filterPlugins(plugin) { return clientSettings && clientSettings.showPlugins && clientSettings.showPlugins.indexOf(plugin.name) > -1; }); + }; + + plugins.eachShownPlugins = function eachShownPlugins(clientSettings, f) { + _.forEach(plugins.shownPlugins(clientSettings), f); + }; - _.forEach(filtered, f); + plugins.hasShownType = function hasShownType(pluginType, clientSettings) { + return _.find(plugins.shownPlugins(clientSettings), function findWithType(plugin) { + return plugin.pluginType == pluginType; + }) != undefined; }; plugins.setEnvs = function setEnvs(env) { diff --git a/static/js/client.js b/static/js/client.js index 50b3267aadc..44f2644798c 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -5,6 +5,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; 'use strict'; var BRUSH_TIMEOUT = 300000 // 5 minutes in ms + , DEBOUNCE_MS = 10 , TOOLTIP_TRANS_MS = 200 // milliseconds , UPDATE_TRANS_MS = 750 // milliseconds , ONE_MIN_IN_MS = 60000 @@ -293,7 +294,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } // clears the current user brush and resets to the current real time data - function updateBrushToNow(skipBrushing) { + var updateBrushToNow = _.debounce(function updateBrushToNow(skipBrushing) { // get current time range var dataRange = d3.extent(data, dateFn); @@ -310,7 +311,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; // clear user brush tracking brushInProgress = false; } - } + }, DEBOUNCE_MS); function brushStarted() { // update the opacity of the context data points to brush extent @@ -359,8 +360,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; return errorDisplay; } - // function to call when context chart is brushed - function brushed(skipTimer) { + var brushed = _.debounce(function brushed(skipTimer) { if (!skipTimer) { // set a timer to reset focus chart to real-time data @@ -487,16 +487,6 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } }); - Nightscout.plugins.eachShownPlugins(browserSettings, function eachPlugin(plugin) { - console.info('plugin.pluginType', plugin.pluginType); - if (plugin.pluginType == 'minor-pill') { - hasMinorPill = true; - } - }); - - $('.container').toggleClass('has-minor-pills', hasMinorPill); - - // update data for all the plugins, before updating visualisations Nightscout.plugins.setEnvs(env); @@ -552,7 +542,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; bgButton.removeClass('urgent warning inrange'); } - updatePlugins(focusPoint.y, retroTime); + updatePlugins(focusPoint && focusPoint.y, retroTime); $('#currentTime') .text(formatTime(retroTime, true)) @@ -738,7 +728,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; function prepareTreatCircles(sel) { sel.attr('cx', function (d) { return xScale(d.created_at); }) - .attr('cy', function (d) { return yScale(scaledTreatmentBG(d)); }) + .attr('cy', function (d) { return yScale(d.displayBG); }) .attr('r', function () { return dotRadius('mbg'); }) .attr('stroke-width', 2) .attr('stroke', function (d) { return d.glucose ? 'grey' : 'white'; }) @@ -747,46 +737,39 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; return sel; } - try { - - //NOTE: treatments with insulin or carbs are drawn by drawTreatment() - //TODO: integrate with drawTreatment() - - // bind up the focus chart data to an array of circles - var treatCircles = focus.selectAll('rect').data(treatments.filter(function(treatment) { - return !treatment.carbs && !treatment.insulin; - })); - - // if already existing then transition each circle to its new position - prepareTreatCircles(treatCircles.transition().duration(UPDATE_TRANS_MS)); - - // if new circle then just display - prepareTreatCircles(treatCircles.enter().append('circle')) - .on('mouseover', function (d) { - tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); - tooltip.html('Time: ' + formatTime(d.created_at) + '
    ' + - (d.eventType ? 'Treatment type: ' + d.eventType + '
    ' : '') + - (d.glucose ? 'BG: ' + d.glucose + (d.glucoseType ? ' (' + d.glucoseType + ')': '') + '
    ' : '') + - (d.enteredBy ? 'Entered by: ' + d.enteredBy + '
    ' : '') + - (d.notes ? 'Notes: ' + d.notes : '') - ) - .style('left', (d3.event.pageX) + 'px') - .style('top', (d3.event.pageY + 15) + 'px'); - }) - .on('mouseout', function () { - tooltip.transition() - .duration(TOOLTIP_TRANS_MS) - .style('opacity', 0); - }); - - treatCircles.attr('clip-path', 'url(#clip)'); - } catch (err) { - console.error(err); - } - } + //NOTE: treatments with insulin or carbs are drawn by drawTreatment() + // bind up the focus chart data to an array of circles + var treatCircles = focus.selectAll('rect').data(treatments.filter(function(treatment) { + return !treatment.carbs && !treatment.insulin; + })); + + // if already existing then transition each circle to its new position + prepareTreatCircles(treatCircles.transition().duration(UPDATE_TRANS_MS)); + + // if new circle then just display + prepareTreatCircles(treatCircles.enter().append('circle')) + .on('mouseover', function (d) { + tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); + tooltip.html('Time: ' + formatTime(d.created_at) + '
    ' + + (d.eventType ? 'Treatment type: ' + d.eventType + '
    ' : '') + + (d.glucose ? 'BG: ' + d.glucose + (d.glucoseType ? ' (' + d.glucoseType + ')': '') + '
    ' : '') + + (d.enteredBy ? 'Entered by: ' + d.enteredBy + '
    ' : '') + + (d.notes ? 'Notes: ' + d.notes : '') + ) + .style('left', (d3.event.pageX) + 'px') + .style('top', (d3.event.pageY + 15) + 'px'); + }) + .on('mouseout', function () { + tooltip.transition() + .duration(TOOLTIP_TRANS_MS) + .style('opacity', 0); + }); + + treatCircles.attr('clip-path', 'url(#clip)'); + }, DEBOUNCE_MS); // called for initial update and updates for resize - function updateChart(init) { + var updateChart = _.debounce(function updateChart(init) { if (documentHidden && !init) { console.info('Document Hidden, not updating - ' + (new Date())); @@ -1133,7 +1116,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; if (init) { $('.container').removeClass('loading'); } - } + }, DEBOUNCE_MS); function sgvToColor(sgv) { var color = 'grey'; @@ -1255,23 +1238,30 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } - function scaledTreatmentBG(treatment) { + function displayTreatmentBG(treatment) { function calcBGByTime(time) { - var closeBGs = data.filter(function(d) { - if (!d.y) { - return false; - } else { - return Math.abs((new Date(d.date)).getTime() - time) <= SIX_MINS_IN_MS; - } + var withBGs = _.filter(data, function(d) { + return d.y && d.type == 'sgv'; }); - var totalBG = 0; - closeBGs.forEach(function(d) { - totalBG += Number(d.y); + var beforeTreatment = _.findLast(withBGs, function (d) { + return d.date.getTime() <= time; + }); + var afterTreatment = _.find(withBGs, function (d) { + return d.date.getTime() >= time; }); - return totalBG > 0 ? (totalBG / closeBGs.length) : 450; + var calcedBG = 0; + if (beforeTreatment && afterTreatment) { + calcedBG = (Number(beforeTreatment.y) + Number(afterTreatment.y)) / 2; + } else if (beforeTreatment) { + calcedBG = Number(beforeTreatment.y); + } else if (afterTreatment) { + calcedBG = Number(afterTreatment.y); + } + + return calcedBG || 400; } var treatmentGlucose = null; @@ -1345,7 +1335,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; .data(arc_data) .enter() .append('g') - .attr('transform', 'translate(' + xScale(treatment.created_at.getTime()) + ', ' + yScale(scaledTreatmentBG(treatment)) + ')') + .attr('transform', 'translate(' + xScale(treatment.created_at.getTime()) + ', ' + yScale(treatment.displayBG) + ')') .on('mouseover', function () { tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); tooltip.html('Time: ' + formatTime(treatment.created_at) + '
    ' + 'Treatment type: ' + treatment.eventType + '
    ' + @@ -1615,16 +1605,12 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; context.append('g') .attr('class', 'y axis'); - // look for resize but use timer to only call the update script when a resize stops - var resizeTimer; - function updateChartSoon(updateToNow) { - clearTimeout(resizeTimer); - resizeTimer = setTimeout(function () { - if (updateToNow) { - updateBrushToNow(); - } - updateChart(false); - }, 200); + //updateBrushToNow and updateChart are both _.debounced + function refreshChart(updateToNow) { + if (updateToNow) { + updateBrushToNow(); + } + updateChart(false); } function visibilityChanged() { @@ -1633,13 +1619,11 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; if (prevHidden && !documentHidden) { console.info('Document now visible, updating - ' + (new Date())); - updateChartSoon(true); + refreshChart(true); } } - window.onresize = function () { - updateChartSoon() - }; + window.onresize = refreshChart; document.addEventListener('webkitvisibilitychange', visibilityChanged); @@ -1663,7 +1647,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; li.addClass('selected'); var hours = Number(li.data('hours')); foucusRangeMS = hours * 60 * 60 * 1000; - updateChartSoon(); + refreshChart(); }); //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -1680,11 +1664,6 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; prevSGV = d[0][d[0].length - 2]; } - treatments = d[3]; - treatments.forEach(function (d) { - d.created_at = new Date(d.created_at); - }); - profile = d[4][0]; cal = d[5][d[5].length-1]; devicestatusData = d[6]; @@ -1721,6 +1700,13 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; d.color = 'transparent'; }); + treatments = d[3]; + treatments.forEach(function (d) { + d.created_at = new Date(d.created_at); + //cache the displayBG for each treatment in DISPLAY_UNITS + d.displayBG = displayTreatmentBG(d); + }); + updateTitle(); if (!isInitialData) { isInitialData = true; @@ -1821,6 +1807,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; $('#treatmentDrawerToggle').toggle(app.careportalEnabled); Nightscout.plugins.clientInit(app); browserSettings = getBrowserSettings(browserStorage); + $('.container').toggleClass('has-minor-pills', Nightscout.plugins.hasShownType('minor-pill', browserSettings)); init(); }); From 601b1faa39dee023968a36ef1f5ee9ab46a5229e Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 30 May 2015 19:38:28 -0700 Subject: [PATCH 049/937] hide uploaderBattery pill till we have data for it --- static/css/main.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/static/css/main.css b/static/css/main.css index d338b93d23c..4f1bc73b5e9 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -178,6 +178,10 @@ body { background: #808080; } +#uploaderBattery { + display: none; +} + #uploaderBattery label { padding: 0 !important; } From bf03d74a9cfad78bf08dac12a98b5e311942527d Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 31 May 2015 09:26:30 +0300 Subject: [PATCH 050/937] Optimization for querySelectorAll, which seems to be non-performant and used by D3 a _lot_. One-liner to disable drawing of treatments not visible on screen. --- static/js/client.js | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/static/js/client.js b/static/js/client.js index 44f2644798c..14a1dcdb31b 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1,6 +1,46 @@ //TODO: clean up var app = {}, browserSettings = {}, browserStorage = $.localStorage; +/* + * query + * Abstraction to querySelectorAll for increased + * performance and greater usability + * @param {String} selector + * @param {Element} context (optional) + * @return {Array} + */ + +(function(win){ + 'use strict'; + + console.log("Foo"); + + var simpleRe = /^(#?[\w-]+|\.[\w-.]+)$/, + periodRe = /\./g, + slice = [].slice; + + win.query = function(selector, context){ + context = context || document; + // Redirect call to the more performant function + // if it's a simple selector and return an array + // for easier usage + if(simpleRe.test(selector)){ + switch(selector[0]){ + case '#': + return [context.getElementById(selector.substr(1))]; + case '.': + return slice.call(context.getElementsByClassName(selector.substr(1).replace(periodRe, ' '))); + default: + return slice.call(context.getElementsByTagName(selector)); + } + } + // If not a simple selector, query the DOM as usual + // and return an array for easier usage + return slice.call(context.querySelectorAll(selector)); + }; + +})(this); + (function () { 'use strict'; @@ -1299,6 +1339,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; if (!treatment.carbs && !treatment.insulin) return; + // don't render the treatment if it's not visible + if (Math.abs(xScale(treatment.created_at.getTime())) > window.innerWidth) return; + var CR = treatment.CR || 20; var carbs = treatment.carbs || CR; var insulin = treatment.insulin || 1; From 484610f150a17dcdaf24c7825cd21e632083b353 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 31 May 2015 09:34:10 +0300 Subject: [PATCH 051/937] Removing debug messaging --- static/js/client.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index 14a1dcdb31b..39ca213fee1 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -12,8 +12,6 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; (function(win){ 'use strict'; - - console.log("Foo"); var simpleRe = /^(#?[\w-]+|\.[\w-.]+)$/, periodRe = /\./g, From 7f09e037d4e6ed80a845c1cd141de4685e324a09 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 31 May 2015 09:39:22 -0700 Subject: [PATCH 052/937] upgrade to current d3.js --- bower.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bower.json b/bower.json index 372c86fdf38..da22cab72e3 100644 --- a/bower.json +++ b/bower.json @@ -4,7 +4,7 @@ "dependencies": { "angularjs": "1.3.0-beta.19", "bootstrap": "~3.2.0", - "d3": "3.4.3", + "d3": "~3.5.5", "jquery": "2.1.0", "jQuery-Storage-API": "~1.7.2", "jsSHA": "~1.5.0", From 2ca9c8ac73bae462d63c223d3a21527a6dc8f495 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 31 May 2015 09:40:47 -0700 Subject: [PATCH 053/937] is the querySelectorAll optimization used? --- static/js/client.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/static/js/client.js b/static/js/client.js index 39ca213fee1..d9912ad61fe 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -13,11 +13,12 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; (function(win){ 'use strict'; - var simpleRe = /^(#?[\w-]+|\.[\w-.]+)$/, + var simpleRe = /^(#?[\w-]+|\.[\w-.]+)$/, periodRe = /\./g, slice = [].slice; win.query = function(selector, context){ + console.info('win.query() called'); context = context || document; // Redirect call to the more performant function // if it's a simple selector and return an array From db85587df7896d9689fcca7a58e4df852dd7a99f Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 31 May 2015 10:04:20 -0700 Subject: [PATCH 054/937] remove the querySelectorAll optimization since it's not getting used --- static/js/client.js | 43 ++----------------------------------------- 1 file changed, 2 insertions(+), 41 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index d9912ad61fe..d6ee629b1ab 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1,45 +1,6 @@ //TODO: clean up var app = {}, browserSettings = {}, browserStorage = $.localStorage; -/* - * query - * Abstraction to querySelectorAll for increased - * performance and greater usability - * @param {String} selector - * @param {Element} context (optional) - * @return {Array} - */ - -(function(win){ - 'use strict'; - - var simpleRe = /^(#?[\w-]+|\.[\w-.]+)$/, - periodRe = /\./g, - slice = [].slice; - - win.query = function(selector, context){ - console.info('win.query() called'); - context = context || document; - // Redirect call to the more performant function - // if it's a simple selector and return an array - // for easier usage - if(simpleRe.test(selector)){ - switch(selector[0]){ - case '#': - return [context.getElementById(selector.substr(1))]; - case '.': - return slice.call(context.getElementsByClassName(selector.substr(1).replace(periodRe, ' '))); - default: - return slice.call(context.getElementsByTagName(selector)); - } - } - // If not a simple selector, query the DOM as usual - // and return an array for easier usage - return slice.call(context.querySelectorAll(selector)); - }; - -})(this); - (function () { 'use strict'; @@ -1338,8 +1299,8 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; if (!treatment.carbs && !treatment.insulin) return; - // don't render the treatment if it's not visible - if (Math.abs(xScale(treatment.created_at.getTime())) > window.innerWidth) return; + // don't render the treatment if it's not visible + if (Math.abs(xScale(treatment.created_at.getTime())) > window.innerWidth) return; var CR = treatment.CR || 20; var carbs = treatment.carbs || CR; From 982e0ca18e2a5df7559ee6842acdf1c1ea4d8946 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 31 May 2015 16:32:00 -0700 Subject: [PATCH 055/937] use the carbs set on the treatment, instead of cCalc.initialCarbs, keep it simple for now --- lib/plugins/cob.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/cob.js b/lib/plugins/cob.js index 078c77a9a02..0ab8dc7ee25 100644 --- a/lib/plugins/cob.js +++ b/lib/plugins/cob.js @@ -56,7 +56,7 @@ function init() { if (decaysin_hr > 0) { //console.info('Adding ' + delayMinutes + ' minutes to decay of ' + treatment.carbs + 'g bolus at ' + treatment.created_at); - totalCOB += Math.min(cCalc.initialCarbs, decaysin_hr * carbs_hr); + totalCOB += Math.min(Number(treatment.carbs), decaysin_hr * carbs_hr); //console.log("cob: " + Math.min(cCalc.initialCarbs, decaysin_hr * carbs_hr)); isDecaying = cCalc.isDecaying; } From 4dfa2c438c76ab00efd36b664c135c0241dcb898 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 4 Jun 2015 23:20:18 -0700 Subject: [PATCH 056/937] ctx everywhere; fixed security.test.js --- app.js | 7 +- lib/api/devicestatus/index.js | 10 +- lib/api/entries/index.js | 22 ++-- lib/api/index.js | 10 +- lib/api/profile/index.js | 14 +-- lib/api/treatments/index.js | 10 +- lib/bootevent.js | 8 +- lib/devicestatus.js | 6 +- lib/mqtt.js | 16 +-- lib/pebble.js | 17 ++- lib/profile.js | 4 +- lib/websocket.js | 12 +-- server.js | 7 +- tests/api.entries.test.js | 1 - tests/pebble.test.js | 193 +++++++++++++++++----------------- tests/security.test.js | 27 +++-- 16 files changed, 173 insertions(+), 191 deletions(-) diff --git a/app.js b/app.js index cab4dbe1655..c15b5e1a6df 100644 --- a/app.js +++ b/app.js @@ -6,13 +6,8 @@ function create (env, ctx) { // api and json object variables /////////////////////////////////////////////////// var api = require('./lib/api/')(env, ctx); - var pebble = ctx.pebble; var app = express(); - app.entries = ctx.entries; - app.treatments = ctx.treatments; - app.profiles = ctx.profiles; - app.devicestatus = ctx.devicestatus; var appInfo = env.name + ' ' + env.version; app.set('title', appInfo); app.enable('trust proxy'); // Allows req.secure test on heroku https connections. @@ -32,7 +27,7 @@ function create (env, ctx) { // pebble data - app.get('/pebble', pebble(ctx.entries, ctx.treatments, ctx.profiles, ctx.devicestatus, env)); + app.get('/pebble', ctx.pebble); //app.get('/package.json', software); diff --git a/lib/api/devicestatus/index.js b/lib/api/devicestatus/index.js index 7a5f65ee1f0..05980ffbe36 100644 --- a/lib/api/devicestatus/index.js +++ b/lib/api/devicestatus/index.js @@ -2,7 +2,7 @@ var consts = require('../../constants'); -function configure (app, wares, devicestatus) { +function configure (app, wares, ctx) { var express = require('express'), api = express.Router( ); @@ -21,16 +21,16 @@ function configure (app, wares, devicestatus) { if (!q.count) { q.count = 10; } - devicestatus.list(q, function (err, results) { + ctx.devicestatus.list(q, function (err, results) { return res.json(results); }); }); - function config_authed (app, api, wares, devicestatus) { + function config_authed (app, api, wares, ctx) { api.post('/devicestatus/', /*TODO: auth disabled for quick UI testing... wares.verifyAuthorization, */ function(req, res) { var obj = req.body; - devicestatus.create(obj, function (err, created) { + ctx.devicestatus.create(obj, function (err, created) { if (err) res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', err); else @@ -41,7 +41,7 @@ function configure (app, wares, devicestatus) { } if (app.enabled('api') || true /*TODO: auth disabled for quick UI testing...*/) { - config_authed(app, api, wares, devicestatus); + config_authed(app, api, wares, ctx); } return api; diff --git a/lib/api/entries/index.js b/lib/api/entries/index.js index 1593e543748..0e5e6a4c1fe 100644 --- a/lib/api/entries/index.js +++ b/lib/api/entries/index.js @@ -7,8 +7,8 @@ var sgvdata = require('sgvdata'); /**********\ * Entries \**********/ -function configure (app, wares, core) { - var entries = core.entries; +function configure (app, wares, ctx) { + var entries = ctx.entries; var express = require('express'), api = express.Router( ) ; @@ -128,30 +128,30 @@ function configure (app, wares, core) { switch (model) { case 'treatments': case 'devicestatus': - //TODO: profiles not working now, maybe profiles are special - //case 'profiles': - req.model = core[model]; + //TODO: profile not working now, maybe profiles are special + //case 'profile': + req.model = ctx[model]; break; case 'meter': case 'mbg': find.type = 'mbg'; - req.model = core.entries; + req.model = ctx.entries; break; case 'cal': find.type = 'cal'; - req.model = core.entries; + req.model = ctx.entries; break; case 'sensor': find.type = 'sensor'; - req.model = core.entries; + req.model = ctx.entries; break; case 'sgv': find.type = 'sgv'; - req.model = core.entries; + req.model = ctx.entries; break; default: - req.model = core.entries; + req.model = ctx.entries; break; } @@ -203,7 +203,7 @@ function configure (app, wares, core) { function query_models (req, res, next) { // If "?count=" is present, use that number to decided how many to return. if (!req.model) { - req.model = core.entries; + req.model = ctx.entries; } var query = req.query; if (!query.count) { query.count = 10 } diff --git a/lib/api/index.js b/lib/api/index.js index 7c67de12a3f..a53840fd5e7 100644 --- a/lib/api/index.js +++ b/lib/api/index.js @@ -1,6 +1,6 @@ 'use strict'; -function create (env, core) { +function create (env, ctx) { var express = require('express'), app = express( ) ; @@ -46,10 +46,10 @@ function create (env, core) { } // Entries and settings - app.use('/', require('./entries/')(app, wares, core)); - app.use('/', require('./treatments/')(app, wares, core.treatments)); - app.use('/', require('./profile/')(app, wares, core.profiles)); - app.use('/', require('./devicestatus/')(app, wares, core.devicestatus)); + app.use('/', require('./entries/')(app, wares, ctx)); + app.use('/', require('./treatments/')(app, wares, ctx)); + app.use('/', require('./profile/')(app, wares, ctx)); + app.use('/', require('./devicestatus/')(app, wares, ctx)); // Status app.use('/', require('./status')(app, wares)); diff --git a/lib/api/profile/index.js b/lib/api/profile/index.js index bc499141ffd..c8f62323ddd 100644 --- a/lib/api/profile/index.js +++ b/lib/api/profile/index.js @@ -2,7 +2,7 @@ var consts = require('../../constants'); -function configure (app, wares, profile) { +function configure (app, wares, ctx) { var express = require('express'), api = express.Router( ); @@ -17,24 +17,24 @@ function configure (app, wares, profile) { // List profiles available api.get('/profile/', function(req, res) { - profile.list(function (err, attribute) { + ctx.profile.list(function (err, attribute) { return res.json(attribute); }); }); // List current active record (in current state LAST record is current active) api.get('/profile/current', function(req, res) { - profile.last( function(err, records) { + ctx.profile.last( function(err, records) { return res.json(records.length > 0 ? records[0] : null); }); }); - function config_authed (app, api, wares, profile) { + function config_authed (app, api, wares, ctx) { // create new record api.post('/profile/', wares.verifyAuthorization, function(req, res) { var data = req.body; - profile.create(data, function (err, created) { + ctx.profile.create(data, function (err, created) { if (err) { res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', err); console.log('Error creating profile'); @@ -50,7 +50,7 @@ function configure (app, wares, profile) { // update record api.put('/profile/', wares.verifyAuthorization, function(req, res) { var data = req.body; - profile.save(data, function (err, created) { + ctx.profile.save(data, function (err, created) { if (err) { res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', err); console.log('Error saving profile'); @@ -65,7 +65,7 @@ function configure (app, wares, profile) { } if (app.enabled('api')) { - config_authed(app, api, wares, profile); + config_authed(app, api, wares, ctx); } return api; diff --git a/lib/api/treatments/index.js b/lib/api/treatments/index.js index 7d7e89d0b5d..eb093d2b4cb 100644 --- a/lib/api/treatments/index.js +++ b/lib/api/treatments/index.js @@ -2,7 +2,7 @@ var consts = require('../../constants'); -function configure (app, wares, treatments) { +function configure (app, wares, ctx) { var express = require('express'), api = express.Router( ); @@ -17,16 +17,16 @@ function configure (app, wares, treatments) { // List treatments available api.get('/treatments/', function(req, res) { - treatments.list({find: req.params}, function (err, results) { + ctx.treatments.list({find: req.params}, function (err, results) { return res.json(results); }); }); - function config_authed (app, api, wares, treatments) { + function config_authed (app, api, wares, ctx) { api.post('/treatments/', /*TODO: auth disabled for now, need to get login figured out... wares.verifyAuthorization, */ function(req, res) { var treatment = req.body; - treatments.create(treatment, function (err, created) { + ctx.treatments.create(treatment, function (err, created) { if (err) res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', err); else @@ -37,7 +37,7 @@ function configure (app, wares, treatments) { } if (app.enabled('api') && app.enabled('careportal')) { - config_authed(app, api, wares, treatments); + config_authed(app, api, wares, ctx); } return api; diff --git a/lib/bootevent.js b/lib/bootevent.js index 2f81a590b50..dd46a6eb92f 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -20,15 +20,15 @@ function boot (env) { ctx.pushover = require('./pushover')(env); ctx.entries = require('./entries')(env, ctx); ctx.treatments = require('./treatments')(env, ctx); - ctx.devicestatus = require('./devicestatus')(env.devicestatus_collection, ctx.store); - ctx.profiles = require('./profile')(env.profile_collection, ctx.store); - ctx.pebble = require('./pebble'); + ctx.devicestatus = require('./devicestatus')(env.devicestatus_collection, ctx); + ctx.profile = require('./profile')(env.profile_collection, ctx); + ctx.pebble = require('./pebble')(env, ctx); console.info("Ensuring indexes"); store.ensureIndexes(ctx.entries( ), ctx.entries.indexedFields); store.ensureIndexes(ctx.treatments( ), ctx.treatments.indexedFields); store.ensureIndexes(ctx.devicestatus( ), ctx.devicestatus.indexedFields); - store.ensureIndexes(ctx.profiles( ), ctx.profiles.indexedFields); + store.ensureIndexes(ctx.profile( ), ctx.profile.indexedFields); next( ); }) diff --git a/lib/devicestatus.js b/lib/devicestatus.js index 00d9ce2d9f1..a831e936bab 100644 --- a/lib/devicestatus.js +++ b/lib/devicestatus.js @@ -1,6 +1,6 @@ 'use strict'; -function storage (collection, storage) { +function storage (collection, ctx) { function create(obj, fn) { if (! obj.hasOwnProperty("created_at")){ @@ -29,11 +29,11 @@ function storage (collection, storage) { function list(opts, fn) { var q = opts && opts.find ? opts.find : { }; - return storage.limit.call(api().find(q).sort({created_at: -1}), opts).toArray(fn); + return ctx.store.limit.call(api().find(q).sort({created_at: -1}), opts).toArray(fn); } function api() { - return storage.pool.db.collection(collection); + return ctx.store.pool.db.collection(collection); } diff --git a/lib/mqtt.js b/lib/mqtt.js index cfc4675f9f9..f19a32641a3 100644 --- a/lib/mqtt.js +++ b/lib/mqtt.js @@ -168,7 +168,7 @@ function iter_mqtt_record_stream (packet, prop, sync) { return stream.pipe(es.map(map)); } -function configure(env, core, devicestatus) { +function configure(env, ctx) { var uri = env['MQTT_MONITOR']; var opts = { encoding: 'binary', @@ -212,37 +212,37 @@ function configure(env, core, devicestatus) { console.log("WRITE TO MONGO"); var download_timestamp = moment(packet.download_timestamp); if (packet.download_status === 0) { - es.readArray(sgvSensorMerge(packet)).pipe(core.persist(function empty(err, result) { + es.readArray(sgvSensorMerge(packet)).pipe(ctx.entries.persist(function empty(err, result) { console.log("DONE WRITING MERGED SGV TO MONGO", err, result); })); iter_mqtt_record_stream(packet, 'cal', toCal) - .pipe(core.persist(function empty(err, result) { + .pipe(ctx.entries.persist(function empty(err, result) { console.log("DONE WRITING Cal TO MONGO", err, result.length); })); iter_mqtt_record_stream(packet, 'meter', toMeter) - .pipe(core.persist(function empty(err, result) { + .pipe(ctx.entries.persist(function empty(err, result) { console.log("DONE WRITING Meter TO MONGO", err, result.length); })); } packet.type = "download"; - devicestatus.create({ + ctx.devicestatus.create({ uploaderBattery: packet.uploader_battery, created_at: download_timestamp.toISOString() }, function empty(err, result) { console.log("DONE WRITING TO MONGO devicestatus ", result, err); }); - core.create([ packet ], function empty(err, res) { + ctx.entries.create([ packet ], function empty(err, res) { console.log("Download written to mongo: ", packet) }); - // core.write(packet); + // ctx.entries.write(packet); break; default: console.log(topic, 'on message', 'msg', msg); - // core.write(msg); + // ctx.entries.write(msg); break; } }); diff --git a/lib/pebble.js b/lib/pebble.js index 4a2910dbc52..4804aa7be09 100644 --- a/lib/pebble.js +++ b/lib/pebble.js @@ -62,7 +62,7 @@ function pebble (req, res) { async.parallel({ devicestatus: function (callback) { - req.devicestatus.last(function (err, value) { + req.ctx.devicestatus.last(function (err, value) { if (!err && value) { uploaderBattery = value.uploaderBattery; } else { @@ -96,7 +96,7 @@ function pebble (req, res) { , cal: function(callback) { if (req.rawbg) { var cq = { count: req.count, find: {type: 'cal'} }; - req.entries.list(cq, function (err, results) { + req.ctx.entries.list(cq, function (err, results) { if (!err && results) { results.forEach(function (element) { if (element) { @@ -119,7 +119,7 @@ function pebble (req, res) { , entries: function(callback) { var q = { count: req.count + 1, find: { "sgv": { $exists: true }} }; - req.entries.list(q, function(err, results) { + req.ctx.entries.list(q, function(err, results) { if (!err && results) { results.forEach(function(element, index) { if (element) { @@ -162,7 +162,7 @@ function pebble (req, res) { function loadTreatments(req, earliest_data, fn) { if (req.iob) { var q = { find: {"created_at": {"$gte": new Date(earliest_data).toISOString()}} }; - req.treatments.list(q, fn); + req.ctx.treatments.list(q, fn); } else { fn(null, []); } @@ -170,18 +170,15 @@ function loadTreatments(req, earliest_data, fn) { function loadProfile(req, fn) { if (req.iob) { - req.profile.list(fn); + req.ctx.profile.list(fn); } else { fn(null, []); } } -function configure (entries, treatments, profile, devicestatus, env) { +function configure (env, ctx) { function middle (req, res, next) { - req.entries = entries; - req.treatments = treatments; - req.profile = profile; - req.devicestatus = devicestatus; + req.ctx = ctx; req.rawbg = env.enable && env.enable.indexOf('rawbg') > -1; req.iob = env.enable && env.enable.indexOf('iob') > -1; req.mmol = (req.query.units || env.DISPLAY_UNITS) === 'mmol'; diff --git a/lib/profile.js b/lib/profile.js index ec2182198db..7a7486411cb 100644 --- a/lib/profile.js +++ b/lib/profile.js @@ -1,7 +1,7 @@ 'use strict'; -function storage (collection, storage) { +function storage (collection, ctx) { var ObjectID = require('mongodb').ObjectID; function create (obj, fn) { @@ -28,7 +28,7 @@ function storage (collection, storage) { } function api () { - return storage.pool.db.collection(collection); + return ctx.store.pool.db.collection(collection); } api.list = list; diff --git a/lib/websocket.js b/lib/websocket.js index 3444c6ae60b..9681ae76b8b 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -1,6 +1,6 @@ var async = require('async'); -function websocket (env, server, entries, treatments, profiles, devicestatus) { +function websocket (env, ctx, server) { "use strict"; // CONSTANTS var ONE_HOUR = 3600000, @@ -168,7 +168,7 @@ function update() { async.parallel({ entries: function(callback) { var q = { find: {"date": {"$gte": earliest_data}} }; - entries.list(q, function (err, results) { + ctx.entries.list(q, function (err, results) { if (!err && results) { results.forEach(function (element) { if (element) { @@ -200,7 +200,7 @@ function update() { } , cal: function(callback) { var cq = { count: 1, find: {"type": "cal"} }; - entries.list(cq, function (err, results) { + ctx.entries.list(cq, function (err, results) { if (!err && results) { results.forEach(function (element) { if (element) { @@ -219,7 +219,7 @@ function update() { } , treatments: function(callback) { var tq = { find: {"created_at": {"$gte": new Date(treatment_earliest_data).toISOString()}} }; - treatments.list(tq, function (err, results) { + ctx.treatments.list(tq, function (err, results) { treatmentData = results.map(function (treatment) { var timestamp = new Date(treatment.timestamp || treatment.created_at); treatment.x = timestamp.getTime(); @@ -229,7 +229,7 @@ function update() { }); } , profile: function(callback) { - profiles.list(function (err, results) { + ctx.profile.list(function (err, results) { if (!err && results) { // There should be only one document in the profile collection with a DIA. If there are multiple, use the last one. results.forEach(function (element, index, array) { @@ -244,7 +244,7 @@ function update() { }); } , devicestatus: function(callback) { - devicestatus.last(function (err, result) { + ctx.devicestatus.last(function (err, result) { if (!err && result) { devicestatusData = { uploaderBattery: result.uploaderBattery diff --git a/server.js b/server.js index bd8c7e0497c..69ff7da48c2 100644 --- a/server.js +++ b/server.js @@ -45,22 +45,21 @@ function create (app) { var bootevent = require('./lib/bootevent'); bootevent(env).boot(function booted (ctx) { - env.store = ctx.store; var app = require('./app')(env, ctx); var server = create(app).listen(PORT); console.log('listening', PORT); if (env.MQTT_MONITOR) { - var mqtt = require('./lib/mqtt')(env, app.entries, app.devicestatus); + var mqtt = require('./lib/mqtt')(env, ctx); var es = require('event-stream'); - es.pipeline(mqtt.entries, app.entries.map( ), mqtt.every(app.entries)); + es.pipeline(mqtt.entries, ctx.entries.map( ), mqtt.every(ctx.entries)); } /////////////////////////////////////////////////// // setup socket io for data and message transmission /////////////////////////////////////////////////// var websocket = require('./lib/websocket'); - var io = websocket(env, server, app.entries, app.treatments, app.profiles, app.devicestatus); + var io = websocket(env, ctx, server); }) ; diff --git a/tests/api.entries.test.js b/tests/api.entries.test.js index 3c9026bf657..00e9df932bb 100644 --- a/tests/api.entries.test.js +++ b/tests/api.entries.test.js @@ -14,7 +14,6 @@ describe('Entries REST api', function ( ) { var self = this; var bootevent = require('../lib/bootevent'); bootevent(env).boot(function booted (ctx) { - env.store = ctx.store; self.app.use('/', entries(self.app, self.wares, ctx)); self.archive = require('../lib/entries')(env, ctx); self.archive.create(load('json'), done); diff --git a/tests/pebble.test.js b/tests/pebble.test.js index 030be48a222..ffef87af812 100644 --- a/tests/pebble.test.js +++ b/tests/pebble.test.js @@ -2,106 +2,101 @@ var request = require('supertest'); var should = require('should'); -//Mock entries -var entries = { - list: function(opts, callback) { - var sgvs = [ - { device: 'dexcom', - date: 1422727301000, - dateString: 'Sat Jan 31 10:01:41 PST 2015', - sgv: 82, - direction: 'Flat', - type: 'sgv', - filtered: 113984, - unfiltered: 111920, - rssi: 179, - noise: 1 - }, - { device: 'dexcom', - date: 1422727001000, - dateString: 'Sat Jan 31 09:56:41 PST 2015', - sgv: 84, - direction: 'Flat', - type: 'sgv', - filtered: 115680, - unfiltered: 113552, - rssi: 179, - noise: 1 - }, - { device: 'dexcom', - date: 1422726701000, - dateString: 'Sat Jan 31 09:51:41 PST 2015', - sgv: 86, - direction: 'Flat', - type: 'sgv', - filtered: 117808, - unfiltered: 114640, - rssi: 169, - noise: 1 - }, - { device: 'dexcom', - date: 1422726401000, - dateString: 'Sat Jan 31 09:46:41 PST 2015', - sgv: 88, - direction: 'Flat', - type: 'sgv', - filtered: 120464, - unfiltered: 116608, - rssi: 175, - noise: 1 - }, - { device: 'dexcom', - date: 1422726101000, - dateString: 'Sat Jan 31 09:41:41 PST 2015', - sgv: 91, - direction: 'Flat', - type: 'sgv', - filtered: 124048, - unfiltered: 118880, - rssi: 174, - noise: 1 +//Mocked ctx +var ctx = { + entries: { + list: function (opts, callback) { + var sgvs = [ + { device: 'dexcom', + date: 1422727301000, + dateString: 'Sat Jan 31 10:01:41 PST 2015', + sgv: 82, + direction: 'Flat', + type: 'sgv', + filtered: 113984, + unfiltered: 111920, + rssi: 179, + noise: 1 + }, + { device: 'dexcom', + date: 1422727001000, + dateString: 'Sat Jan 31 09:56:41 PST 2015', + sgv: 84, + direction: 'Flat', + type: 'sgv', + filtered: 115680, + unfiltered: 113552, + rssi: 179, + noise: 1 + }, + { device: 'dexcom', + date: 1422726701000, + dateString: 'Sat Jan 31 09:51:41 PST 2015', + sgv: 86, + direction: 'Flat', + type: 'sgv', + filtered: 117808, + unfiltered: 114640, + rssi: 169, + noise: 1 + }, + { device: 'dexcom', + date: 1422726401000, + dateString: 'Sat Jan 31 09:46:41 PST 2015', + sgv: 88, + direction: 'Flat', + type: 'sgv', + filtered: 120464, + unfiltered: 116608, + rssi: 175, + noise: 1 + }, + { device: 'dexcom', + date: 1422726101000, + dateString: 'Sat Jan 31 09:41:41 PST 2015', + sgv: 91, + direction: 'Flat', + type: 'sgv', + filtered: 124048, + unfiltered: 118880, + rssi: 174, + noise: 1 + } + ]; + + var cals = [ + { device: 'dexcom', + date: 1422647711000, + dateString: 'Fri Jan 30 11:55:11 PST 2015', + slope: 895.8571693029189, + intercept: 34281.06876195567, + scale: 1, + type: 'cal' + } + ]; + + var count = (opts && opts.count) || 1; + + if (opts && opts.find && opts.find.sgv) { + callback(null, sgvs.slice(0, count)); + } else if (opts && opts.find && opts.find.type == 'cal') { + callback(null, cals.slice(0, count)); } - ]; - - var cals = [ - { device: 'dexcom', - date: 1422647711000, - dateString: 'Fri Jan 30 11:55:11 PST 2015', - slope: 895.8571693029189, - intercept: 34281.06876195567, - scale: 1, - type: 'cal' - } - ]; - - var count = (opts && opts.count) || 1; - - if (opts && opts.find && opts.find.sgv) { - callback(null, sgvs.slice(0, count)); - } else if (opts && opts.find && opts.find.type == 'cal') { - callback(null, cals.slice(0, count)); + } + }, treatments: { + list: function (callback) { + callback(null, []); + } + }, profile: { + list: function (callback) { + callback(null, []); + } + }, devicestatus: { + last: function (callback) { + callback(null, {uploaderBattery: 100}); } } -}; - -//Mock devicestatus -var treatments = { - list: function(callback) { - callback(null, []); - } -}; - -var profile = { - list: function(callback) { - callback(null, []); - } -}; - -var devicestatus = { - last: function(callback) { - callback(null, {uploaderBattery: 100}); - } -}; +} describe('Pebble Endpoint without Raw', function ( ) { var pebble = require('../lib/pebble'); @@ -109,7 +104,7 @@ describe('Pebble Endpoint without Raw', function ( ) { var env = require('../env')( ); this.app = require('express')( ); this.app.enable('api'); - this.app.use('/pebble', pebble(entries, treatments, profile, devicestatus, env)); + this.app.use('/pebble', pebble(env, ctx)); done(); }); @@ -174,7 +169,7 @@ describe('Pebble Endpoint with Raw', function ( ) { envRaw.enable = "rawbg"; this.appRaw = require('express')( ); this.appRaw.enable('api'); - this.appRaw.use('/pebble', pebbleRaw(entries, treatments, profile, devicestatus, envRaw)); + this.appRaw.use('/pebble', pebbleRaw(envRaw, ctx)); done(); }); diff --git a/tests/security.test.js b/tests/security.test.js index 60b63f6aa25..8852aba855b 100644 --- a/tests/security.test.js +++ b/tests/security.test.js @@ -10,19 +10,16 @@ describe('API_SECRET', function ( ) { var scope = this; function setup_app (env, fn) { - var ctx = { }; - ctx.wares = require('../lib/middleware/')(env); - ctx.store = require('../lib/storage')(env); - ctx.archive = require('../lib/entries').storage(env.mongo_collection, ctx.store); - - ctx.store(function ( ) { - ctx.app = api(env, ctx.wares, ctx.archive); + var bootevent = require('../lib/bootevent'); + bootevent(env).boot(function booted (ctx) { + var wares = require('../lib/middleware/')(env); + ctx.app = api(env, wares, ctx); scope.app = ctx.app; - ctx.archive.create(load('json'), fn); - scope.archive = ctx.archive; + scope.entries = ctx.entries; + ctx.entries.create(load('json'), function () { + fn(ctx); + }); }); - - return ctx; } /* before(function (done) { @@ -30,14 +27,14 @@ describe('API_SECRET', function ( ) { }); */ after(function (done) { - scope.archive( ).remove({ }, done); + scope.entries( ).remove({ }, done); }); it('should work fine absent', function (done) { delete process.env.API_SECRET; var env = require('../env')( ); should.not.exist(env.api_secret); - var ctx = setup_app(env, function ( ) { + setup_app(env, function (ctx) { ctx.app.enabled('api').should.be.false; ping_status(ctx.app, again); function again ( ) { @@ -53,7 +50,7 @@ describe('API_SECRET', function ( ) { process.env.API_SECRET = 'this is my long pass phrase'; var env = require('../env')( ); env.api_secret.should.equal(known); - var ctx = setup_app(env, function ( ) { + setup_app(env, function (ctx) { // console.log(this.app.enabled('api')); ctx.app.enabled('api').should.be.true; // ping_status(ctx.app, done); @@ -74,7 +71,7 @@ describe('API_SECRET', function ( ) { process.env.API_SECRET = 'this is my long pass phrase'; var env = require('../env')( ); env.api_secret.should.equal(known); - var ctx = setup_app(env, function ( ) { + setup_app(env, function (ctx) { // console.log(this.app.enabled('api')); ctx.app.enabled('api').should.be.true; // ping_status(ctx.app, done); From cdc9bf0da983a81dcc8e4fed5218efb8240e97c5 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 4 Jun 2015 23:45:45 -0700 Subject: [PATCH 057/937] fix merge when there are no sensor records --- lib/mqtt.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/mqtt.js b/lib/mqtt.js index f19a32641a3..a89d5663386 100644 --- a/lib/mqtt.js +++ b/lib/mqtt.js @@ -106,9 +106,9 @@ function sgvSensorMerge(packet) { , sensorsLength = sensors.length; if (sgvsLength >= 0 && sensorsLength == 0) { - merged.concat(sgvs); + merged = sgvs; } else { - var smallerLength = sgvsLength < sensorsLength ? sgvsLength : sensorsLength; + var smallerLength = Math.min(sgvsLength, sensorsLength); for (var i = 1; i <= smallerLength; i++) { var sgv = sgvs[sgvsLength - i]; var sensor = sensors[sensorsLength - i]; From 6ce422136736dbfab65b8541c8e4380db02ad671 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 4 Jun 2015 23:46:19 -0700 Subject: [PATCH 058/937] clean up --- lib/websocket.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/websocket.js b/lib/websocket.js index 9681ae76b8b..c517fc85f9f 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -163,7 +163,7 @@ function update() { profileData = []; devicestatusData = {}; var earliest_data = now - TWO_DAYS; - var treatment_earliest_data = now - (ONE_DAY*8); + var treatment_earliest_data = now - (ONE_DAY*8); async.parallel({ entries: function(callback) { From e5431b625c511366d98ae2be557aecaf9a28ef87 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 5 Jun 2015 17:45:24 -0700 Subject: [PATCH 059/937] stop time ago and battery from showing till we have data --- static/js/client.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index 35304232d04..b33cc6abd12 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -438,6 +438,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; currentBG.toggleClass('icon-hourglass', value == 9); currentBG.toggleClass('error-code', value < 39); currentBG.toggleClass('bg-limit', value == 39 || value > 400); + + $('.container').removeClass('loading'); + } function updateBGDelta(prevEntry, currentEntry) { @@ -1111,9 +1114,6 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; context.select('.x') .call(xAxis2); - if (init) { - $('.container').removeClass('loading'); - } }, DEBOUNCE_MS); function sgvToColor(sgv) { From 2a201ca2afe2acbabe6057459a2f66fefcb56c29 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 5 Jun 2015 17:45:24 -0700 Subject: [PATCH 060/937] stop time ago and battery from showing till we have data --- static/js/client.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index d6ee629b1ab..ca222e187b7 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -438,6 +438,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; currentBG.toggleClass('icon-hourglass', value == 9); currentBG.toggleClass('error-code', value < 39); currentBG.toggleClass('bg-limit', value == 39 || value > 400); + + $('.container').removeClass('loading'); + } function updateBGDelta(prevEntry, currentEntry) { @@ -1113,9 +1116,6 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; context.select('.x') .call(xAxis2); - if (init) { - $('.container').removeClass('loading'); - } }, DEBOUNCE_MS); function sgvToColor(sgv) { From a7cf86e4842452d27f6e28612defbd0066f70d86 Mon Sep 17 00:00:00 2001 From: Ben West Date: Wed, 5 Nov 2014 16:32:19 -0800 Subject: [PATCH 061/937] pulled over @bewest's ticker --- lib/bootevent.js | 7 +++++++ lib/ticker.js | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 lib/ticker.js diff --git a/lib/bootevent.js b/lib/bootevent.js index dd46a6eb92f..12ed373d9ec 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -29,6 +29,13 @@ function boot (env) { store.ensureIndexes(ctx.treatments( ), ctx.treatments.indexedFields); store.ensureIndexes(ctx.devicestatus( ), ctx.devicestatus.indexedFields); store.ensureIndexes(ctx.profile( ), ctx.profile.indexedFields); + + ctx.heartbeat = require('./ticker')(env, ctx); + ctx.heartbeat.uptime( ); + + ctx.heartbeat.on('tick', function(tick) { + console.info('tick', tick) + }); next( ); }) diff --git a/lib/ticker.js b/lib/ticker.js new file mode 100644 index 00000000000..0be1293f297 --- /dev/null +++ b/lib/ticker.js @@ -0,0 +1,35 @@ + +var es = require('event-stream'); +var Stream = require('stream'); + +function heartbeat (env, ctx) { + var beats = 0; + var started = new Date( ); + var id; + var interval = env.HEARTBEAT || 20000; + function ictus ( ) { + var tick = { + now: new Date( ) + , type: 'heartbeat' + , sig: 'internal://' + ['heartbeat', beats ].join('/') + , beat: beats++ + , interval: interval + , started: started + }; + return tick; + } + function repeat ( ) { + stream.emit('tick', ictus( )); + } + function ender ( ) { + if (id) cancelInterval(id); + stream.emit('end'); + } + var stream = new Stream; + stream.readable = true; + stream.uptime = repeat; + id = setInterval(repeat, interval); + return stream; +} +module.exports = heartbeat; + From 84e655ad79cc2b1d8978ca0251be0cf2030e8168 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 6 Jun 2015 01:02:28 -0700 Subject: [PATCH 062/937] factored data loading and AR2 forecasts out of websockets --- lib/bootevent.js | 5 +- lib/data.js | 141 +++++++++++++ lib/entries.js | 2 +- lib/plugins/ar2.js | 67 +++++++ lib/websocket.js | 478 +++++++++++++-------------------------------- server.js | 15 +- 6 files changed, 353 insertions(+), 355 deletions(-) create mode 100644 lib/data.js create mode 100644 lib/plugins/ar2.js diff --git a/lib/bootevent.js b/lib/bootevent.js index 12ed373d9ec..f3d6a450265 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -31,11 +31,8 @@ function boot (env) { store.ensureIndexes(ctx.profile( ), ctx.profile.indexedFields); ctx.heartbeat = require('./ticker')(env, ctx); - ctx.heartbeat.uptime( ); - ctx.heartbeat.on('tick', function(tick) { - console.info('tick', tick) - }); + ctx.data = require('./data')(env, ctx); next( ); }) diff --git a/lib/data.js b/lib/data.js new file mode 100644 index 00000000000..bbea35845d2 --- /dev/null +++ b/lib/data.js @@ -0,0 +1,141 @@ +'use strict'; + +var async = require('async'); + +function init (env, ctx) { + + ctx.data = { + sgvs: [] + , treatments: [] + , mbgs: [] + , cals: [] + , profile: [] + , devicestatus: {} + , lastUpdated: 0 + }; + + var ONE_DAY = 86400000 + , TWO_DAYS = 172800000 + ; + + var dir2Char = { + 'NONE': '⇼', + 'DoubleUp': '⇈', + 'SingleUp': '↑', + 'FortyFiveUp': '↗', + 'Flat': '→', + 'FortyFiveDown': '↘', + 'SingleDown': '↓', + 'DoubleDown': '⇊', + 'NOT COMPUTABLE': '-', + 'RATE OUT OF RANGE': '↮' + }; + + function directionToChar(direction) { + return dir2Char[direction] || '-'; + } + + ctx.data.update = function update (done) { + + //save some chars + var d = ctx.data; + + console.log('running data.update'); + var now = d.lastUpdated = Date.now(); + + var earliest_data = now - TWO_DAYS; + var treatment_earliest_data = now - (ONE_DAY * 8); + + function sort (values) { + values.sort(function sorter (a, b) { + return a.x - b.x; + }); + } + + async.parallel({ + entries: function (callback) { + var q = { find: {"date": {"$gte": earliest_data}} }; + ctx.entries.list(q, function (err, results) { + if (!err && results) { + results.forEach(function (element) { + if (element) { + if (element.mbg) { + d.mbgs.push({ + y: element.mbg, x: element.date, d: element.dateString, device: element.device + }); + } else if (element.sgv) { + d.sgvs.push({ + y: element.sgv, x: element.date, d: element.dateString, device: element.device, direction: directionToChar(element.direction), filtered: element.filtered, unfiltered: element.unfiltered, noise: element.noise, rssi: element.rssi + }); + } + } + }); + } + + //FIXME: sort in mongo + sort(d.mbgs); + sort(d.sgvs); + callback(); + }) + }, cal: function (callback) { + //FIXME: date $gte????? + var cq = { count: 1, find: {"type": "cal"} }; + ctx.entries.list(cq, function (err, results) { + if (!err && results) { + results.forEach(function (element) { + if (element) { + d.cals.push({ + x: element.date, d: element.dateString, scale: element.scale, intercept: element.intercept, slope: element.slope + }); + } + }); + } + callback(); + }); + }, treatments: function (callback) { + var tq = { find: {"created_at": {"$gte": new Date(treatment_earliest_data).toISOString()}} }; + ctx.treatments.list(tq, function (err, results) { + d.treatments = results.map(function (treatment) { + var timestamp = new Date(treatment.timestamp || treatment.created_at); + treatment.x = timestamp.getTime(); + return treatment; + }); + + //FIXME: sort in mongo + d.treatments.sort(function(a, b) { + return a.x - b.x; + }); + + callback(); + }); + }, profile: function (callback) { + ctx.profile.list(function (err, results) { + if (!err && results) { + // There should be only one document in the profile collection with a DIA. If there are multiple, use the last one. + results.forEach(function (element, index, array) { + if (element) { + if (element.dia) { + d.profile[0] = element; + } + } + }); + } + callback(); + }); + }, devicestatus: function (callback) { + ctx.devicestatus.last(function (err, result) { + if (!err && result) { + d.devicestatus.uploaderBattery = result.uploaderBattery; + } + callback(); + }) + } + }, done); + + }; + + return ctx.data; + +} + +module.exports = init; \ No newline at end of file diff --git a/lib/entries.js b/lib/entries.js index adfe7dea56b..fe57606e6e9 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -121,7 +121,7 @@ function storage(env, ctx) { }); ctx.plugins.eachEnabledPlugin(function eachEnabled(plugin) { - if (plugin.processEntry) plugin.processEntry(doc, ctx, env) + if (plugin.processEntry) plugin.processEntry(doc, ctx, env); }); }); }); diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js new file mode 100644 index 00000000000..a0cb2fad1ea --- /dev/null +++ b/lib/plugins/ar2.js @@ -0,0 +1,67 @@ +'use strict'; + +function init() { + + function ar2() { + return ar2; + } + + ar2.label = 'AR2'; + ar2.pluginType = 'forecast'; + + var ONE_HOUR = 3600000; + var ONE_MINUTE = 60000; + var FIVE_MINUTES = 300000; + + ar2.forecast = function forecast(env, ctx) { + + var actual = ctx.data.sgvs; + var actualLength = actual.length - 1; + var lastUpdated = ctx.data.lastUpdated; + + var result = { + predicted: [] + , avgLoss: 0 + }; + + if (actualLength > 1) { + // predict using AR model + var lastValidReadingTime = actual[actualLength].x; + var elapsedMins = (actual[actualLength].x - actual[actualLength - 1].x) / ONE_MINUTE; + var BG_REF = 140; + var BG_MIN = 36; + var BG_MAX = 400; + var y = Math.log(actual[actualLength].y / BG_REF); + if (elapsedMins < 5.1) { + y = [Math.log(actual[actualLength - 1].y / BG_REF), y]; + } else { + y = [y, y]; + } + var n = Math.ceil(12 * (1 / 2 + (lastUpdated - lastValidReadingTime) / ONE_HOUR)); + var AR = [-0.723, 1.716]; + var dt = actual[actualLength].x; + for (var i = 0; i <= n; i++) { + y = [y[1], AR[0] * y[0] + AR[1] * y[1]]; + dt = dt + FIVE_MINUTES; + result.predicted[i] = { + x: dt, + y: Math.max(BG_MIN, Math.min(BG_MAX, Math.round(BG_REF * Math.exp(y[1])))) + }; + } + + // compute current loss + var size = Math.min(result.predicted.length - 1, 6); + for (var j = 0; j <= size; j++) { + result.avgLoss += 1 / size * Math.pow(log10(result.predicted[j].y / 120), 2); + } + } + + return result; + }; + + return ar2(); +} + +function log10(val) { return Math.log(val) / Math.LN10; } + +module.exports = init; \ No newline at end of file diff --git a/lib/websocket.js b/lib/websocket.js index c517fc85f9f..7404344816a 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -1,61 +1,34 @@ -var async = require('async'); - -function websocket (env, ctx, server) { -"use strict"; -// CONSTANTS -var ONE_HOUR = 3600000, - ONE_MINUTE = 60000, - FIVE_MINUTES = 300000, - FORTY_MINUTES = 2400000, - ONE_DAY = 86400000, - TWO_DAYS = 172800000; - -var dir2Char = { - 'NONE': '⇼', - 'DoubleUp': '⇈', - 'SingleUp': '↑', - 'FortyFiveUp': '↗', - 'Flat': '→', - 'FortyFiveDown': '↘', - 'SingleDown': '↓', - 'DoubleDown': '⇊', - 'NOT COMPUTABLE': '-', - 'RATE OUT OF RANGE': '↮' -}; +'use strict'; + +var ar2 = require('./plugins/ar2')(); + +function init (server) { + + function websocket ( ) { + return websocket; + } + + var FORTY_MINUTES = 2400000; + + var lastUpdated = 0; + var patientData = []; var io; var watchers = 0; - var now = new Date().getTime(); - - var cgmData = [], - treatmentData = [], - mbgData = [], - calData = [], - profileData = [], - patientData = [], - devicestatusData = {}; function start ( ) { io = require('socket.io').listen(server, { - //these only effect the socket.io.js file that is sent to the client, but better than nothing - 'browser client minification': true, - 'browser client etag': true, - 'browser client gzip': false + //these only effect the socket.io.js file that is sent to the client, but better than nothing + 'browser client minification': true, + 'browser client etag': true, + 'browser client gzip': false }); } - // get data from database and setup to update every minute - function kickstart (fn) { - //TODO: test server to see how data is stored (timestamps, entry values, etc) - //TODO: check server settings to configure alerts, entry units, etc - console.log(now, new Date(now), fn.name); - fn( ); - return fn; - } function emitData ( ) { - console.log('running emitData', now, patientData && patientData.length); if (patientData.length > 0) { - io.sockets.emit("sgv", patientData); + console.log('running websocket.emitData', lastUpdated, patientData[0] && patientData[0].length); + io.sockets.emit('sgv', patientData); } } @@ -65,355 +38,167 @@ var dir2Char = { //TODO: make websockets support an option io.configure(function () { - io.set('transports', ['xhr-polling']); + io.set('transports', ['xhr-polling']); }); } function listeners ( ) { io.sockets.on('connection', function (socket) { - io.sockets.emit("sgv", patientData); - io.sockets.emit("clients", ++watchers); - socket.on('ack', function(alarmType, silenceTime) { - ackAlarm(alarmType, silenceTime); - if (alarmType == "urgent_alarm") { - //also clean normal alarm so we don't get a double alarm as BG comes back into range - ackAlarm("alarm", silenceTime); - } - io.sockets.emit("clear_alarm", true); - console.log("alarm cleared"); - }); - socket.on('disconnect', function () { - io.sockets.emit("clients", --watchers); - }); + io.sockets.emit('sgv', patientData); + io.sockets.emit('clients', ++watchers); + socket.on('ack', function(alarmType, silenceTime) { + ackAlarm(alarmType, silenceTime); + if (alarmType == 'urgent_alarm') { + //also clean normal alarm so we don't get a double alarm as BG comes back into range + ackAlarm('alarm', silenceTime); + } + io.sockets.emit('clear_alarm', true); + console.log('alarm cleared'); + }); + socket.on('disconnect', function () { + io.sockets.emit('clients', --watchers); + }); }); } -/////////////////////////////////////////////////// -// data handling functions -/////////////////////////////////////////////////// - -function directionToChar(direction) { - return dir2Char[direction] || '-'; -} + /////////////////////////////////////////////////// + // data handling functions + /////////////////////////////////////////////////// -var Alarm = function(_typeName, _threshold) { + var Alarm = function(_typeName, _threshold) { this.typeName = _typeName; this.silenceTime = FORTY_MINUTES; this.lastAckTime = 0; this.threshold = _threshold; -}; + }; // list of alarms with their thresholds -var alarms = { - "alarm" : new Alarm("Regular", 0.05), - "urgent_alarm": new Alarm("Urgent", 0.10) -}; + var alarms = { + 'alarm' : new Alarm('Regular', 0.05), + 'urgent_alarm': new Alarm('Urgent', 0.10) + }; -function ackAlarm(alarmType, silenceTime) { + function ackAlarm(alarmType, silenceTime) { var alarm = alarms[alarmType]; if (!alarm) { - console.warn('Got an ack for an unknown alarm time'); - return; + console.warn('Got an ack for an unknown alarm time'); + return; } alarm.lastAckTime = new Date().getTime(); alarm.silenceTime = silenceTime ? silenceTime : FORTY_MINUTES; delete alarm.lastEmitTime; -} + } -//should only be used when auto acking the alarms after going back in range or when an error corrects -//setting the silence time to 1ms so the alarm will be retriggered as soon as the condition changes -//since this wasn't ack'd by a user action -function autoAckAlarms() { + //should only be used when auto acking the alarms after going back in range or when an error corrects + //setting the silence time to 1ms so the alarm will be retriggered as soon as the condition changes + //since this wasn't ack'd by a user action + function autoAckAlarms() { var sendClear = false; for (var alarmType in alarms) { - if (alarms.hasOwnProperty(alarmType)) { - var alarm = alarms[alarmType]; - if (alarm.lastEmitTime) { - console.info("auto acking " + alarmType); - ackAlarm(alarmType, 1); - sendClear = true; - } + if (alarms.hasOwnProperty(alarmType)) { + var alarm = alarms[alarmType]; + if (alarm.lastEmitTime) { + console.info('auto acking ' + alarmType); + ackAlarm(alarmType, 1); + sendClear = true; } + } } if (sendClear) { - io.sockets.emit("clear_alarm", true); - console.info("emitted clear_alarm to all clients"); + io.sockets.emit('clear_alarm', true); + console.info('emitted clear_alarm to all clients'); } -} + } -function emitAlarm(alarmType) { + function emitAlarm (alarmType) { var alarm = alarms[alarmType]; - if (now > alarm.lastAckTime + alarm.silenceTime) { - io.sockets.emit(alarmType); - alarm.lastEmitTime = now; - console.info("emitted " + alarmType + " to all clients"); + if (lastUpdated > alarm.lastAckTime + alarm.silenceTime) { + io.sockets.emit(alarmType); + alarm.lastEmitTime = lastUpdated; + console.info('emitted ' + alarmType + ' to all clients'); } else { - console.log(alarm.typeName + " alarm is silenced for " + Math.floor((alarm.silenceTime - (now - alarm.lastAckTime)) / 60000) + " minutes more"); + console.log(alarm.typeName + ' alarm is silenced for ' + Math.floor((alarm.silenceTime - (lastUpdated - alarm.lastAckTime)) / 60000) + ' minutes more'); } -} - -function update() { - - console.log('running update'); - now = Date.now(); - - cgmData = []; - treatmentData = []; - mbgData = []; - profileData = []; - devicestatusData = {}; - var earliest_data = now - TWO_DAYS; - var treatment_earliest_data = now - (ONE_DAY*8); - - async.parallel({ - entries: function(callback) { - var q = { find: {"date": {"$gte": earliest_data}} }; - ctx.entries.list(q, function (err, results) { - if (!err && results) { - results.forEach(function (element) { - if (element) { - if (element.mbg) { - mbgData.push({ - y: element.mbg - , x: element.date - , d: element.dateString - , device: element.device - }); - } else if (element.sgv) { - cgmData.push({ - y: element.sgv - , x: element.date - , d: element.dateString - , device: element.device - , direction: directionToChar(element.direction) - , filtered: element.filtered - , unfiltered: element.unfiltered - , noise: element.noise - , rssi: element.rssi - }); - } - } - }); - } - callback(); - }) - } - , cal: function(callback) { - var cq = { count: 1, find: {"type": "cal"} }; - ctx.entries.list(cq, function (err, results) { - if (!err && results) { - results.forEach(function (element) { - if (element) { - calData.push({ - x: element.date - , d: element.dateString - , scale: element.scale - , intercept: element.intercept - , slope: element.slope - }); - } - }); - } - callback(); - }); - } - , treatments: function(callback) { - var tq = { find: {"created_at": {"$gte": new Date(treatment_earliest_data).toISOString()}} }; - ctx.treatments.list(tq, function (err, results) { - treatmentData = results.map(function (treatment) { - var timestamp = new Date(treatment.timestamp || treatment.created_at); - treatment.x = timestamp.getTime(); - return treatment; - }); - callback(); - }); - } - , profile: function(callback) { - ctx.profile.list(function (err, results) { - if (!err && results) { - // There should be only one document in the profile collection with a DIA. If there are multiple, use the last one. - results.forEach(function (element, index, array) { - if (element) { - if (element.dia) { - profileData[0] = element; - } - } - }); - } - callback(); - }); - } - , devicestatus: function(callback) { - ctx.devicestatus.last(function (err, result) { - if (!err && result) { - devicestatusData = { - uploaderBattery: result.uploaderBattery - }; - } - callback(); - }) - } - }, loadData); - - return update; -} - -function loadData() { - - console.log('running loadData'); - - var actual = [], - actualCurrent, - treatment = [], - mbg = [], - cal = [], - errorCode; - - if (cgmData) { - actual = cgmData.slice(); - actual.sort(function(a, b) { - return a.x - b.x; - }); - - actualCurrent = actual.length > 0 ? actual[actual.length - 1].y : null; - } - - if (treatmentData) { - treatment = treatmentData.slice(); - treatment.sort(function(a, b) { - return a.x - b.x; - }); - } - - if (mbgData) { - mbg = mbgData.slice(); - mbg.sort(function(a, b) { - return a.x - b.x; - }); - } - - if (calData) { - cal = calData.slice(calData.length-200, calData.length); - cal.sort(function(a, b) { - return a.x - b.x; - }); - } - - if (profileData) { - var profile = profileData; - } - - if (actualCurrent && actualCurrent < 39) errorCode = actualCurrent; - - var actualLength = actual.length - 1; - - if (actualLength > 1) { - // predict using AR model - var predicted = []; - var lastValidReadingTime = actual[actualLength].x; - var elapsedMins = (actual[actualLength].x - actual[actualLength - 1].x) / ONE_MINUTE; - var BG_REF = 140; - var BG_MIN = 36; - var BG_MAX = 400; - var y = Math.log(actual[actualLength].y / BG_REF); - if (elapsedMins < 5.1) { - y = [Math.log(actual[actualLength - 1].y / BG_REF), y]; - } else { - y = [y, y]; - } - var n = Math.ceil(12 * (1 / 2 + (now - lastValidReadingTime) / ONE_HOUR)); - var AR = [-0.723, 1.716]; - var dt = actual[actualLength].x; - for (var i = 0; i <= n; i++) { - y = [y[1], AR[0] * y[0] + AR[1] * y[1]]; - dt = dt + FIVE_MINUTES; - predicted[i] = { - x: dt, - y: Math.max(BG_MIN, Math.min(BG_MAX, Math.round(BG_REF * Math.exp(y[1])))) - }; - } - - //TODO: need to consider when data being sent has less than the 2 day minimum - - // consolidate and send the data to the client - var shouldEmit = is_different(actual, predicted, mbg, treatment, cal, devicestatusData); - patientData = [actual, predicted, mbg, treatment, profile, cal, devicestatusData]; - console.log('patientData', patientData.length); - if (shouldEmit) { - emitData( ); - } + } - var emitAlarmType = null; - - if (env.alarm_types.indexOf("simple") > -1) { - var lastBG = actual[actualLength].y; - - if (lastBG > env.thresholds.bg_high) { - emitAlarmType = 'urgent_alarm'; - console.info(lastBG + " > " + env.thresholds.bg_high + " will emmit " + emitAlarmType); - } else if (lastBG > env.thresholds.bg_target_top) { - emitAlarmType = 'alarm'; - console.info(lastBG + " > " + env.thresholds.bg_target_top + " will emmit " + emitAlarmType); - } else if (lastBG < env.thresholds.bg_low) { - emitAlarmType = 'urgent_alarm'; - console.info(lastBG + " < " + env.thresholds.bg_low + " will emmit " + emitAlarmType); - } else if (lastBG < env.thresholds.bg_target_bottom) { - emitAlarmType = 'alarm'; - console.info(lastBG + " < " + env.thresholds.bg_target_bottom + " will emmit " + emitAlarmType); - } + websocket.processData = function processData (env, ctx) { + + var d = ctx.data; + lastUpdated = d.lastUpdated; + + console.log('running websocket.loadData'); + + var lastSGV = d.sgvs.length > 0 ? d.sgvs[d.sgvs.length - 1].y : null; + + if (lastSGV) { + var forecast = ar2.forecast(env, ctx); + + // consolidate and send the data to the client + if (is_different(d)) { + patientData = [d.sgvs, forecast.predicted, d.mbgs, d.treatments, d.profile, d.cals, d.devicestatus]; + emitData(); + } + + var emitAlarmType = null; + + if (env.alarm_types.indexOf('simple') > -1) { + if (lastSGV > env.thresholds.bg_high) { + emitAlarmType = 'urgent_alarm'; + console.info(lastSGV + ' > ' + env.thresholds.bg_high + ' will emmit ' + emitAlarmType); + } else if (lastSGV > env.thresholds.bg_target_top) { + emitAlarmType = 'alarm'; + console.info(lastSGV + ' > ' + env.thresholds.bg_target_top + ' will emmit ' + emitAlarmType); + } else if (lastSGV < env.thresholds.bg_low) { + emitAlarmType = 'urgent_alarm'; + console.info(lastSGV + ' < ' + env.thresholds.bg_low + ' will emmit ' + emitAlarmType); + } else if (lastSGV < env.thresholds.bg_target_bottom) { + emitAlarmType = 'alarm'; + console.info(lastSGV + ' < ' + env.thresholds.bg_target_bottom + ' will emmit ' + emitAlarmType); } - - if (!emitAlarmType && env.alarm_types.indexOf("predict") > -1) { - // compute current loss - var avgLoss = 0; - var size = Math.min(predicted.length - 1, 6); - for (var j = 0; j <= size; j++) { - avgLoss += 1 / size * Math.pow(log10(predicted[j].y / 120), 2); - } - - if (avgLoss > alarms['urgent_alarm'].threshold) { - emitAlarmType = 'urgent_alarm'; - console.info(avgLoss + " < " + alarms['urgent_alarm'].threshold + " will emmit " + emitAlarmType); - } else if (avgLoss > alarms['alarm'].threshold) { - emitAlarmType = 'alarm'; - console.info(avgLoss + " < " + alarms['alarm'].threshold + " will emmit " + emitAlarmType); - } + } + + if (!emitAlarmType && env.alarm_types.indexOf('predict') > -1) { + if (forecast.avgLoss > alarms['urgent_alarm'].threshold) { + emitAlarmType = 'urgent_alarm'; + console.info('Avg Loss:' + forecast.avgLoss + ' > ' + alarms['urgent_alarm'].threshold + ' will emmit ' + emitAlarmType); + } else if (forecast.avgLoss > alarms['alarm'].threshold) { + emitAlarmType = 'alarm'; + console.info('Avg Loss:' + forecast.avgLoss + ' > ' + alarms['alarm'].threshold + ' will emmit ' + emitAlarmType); } + } - if (errorCode) { - emitAlarmType = 'urgent_alarm'; - } + if (d.sgvs.length > 0 && d.sgvs[d.sgvs.length - 1].y < 39) { + emitAlarmType = 'urgent_alarm'; + } - if (emitAlarmType) { - emitAlarm(emitAlarmType); - } else { - autoAckAlarms(); - } + if (emitAlarmType) { + emitAlarm(emitAlarmType); + } else { + autoAckAlarms(); + } } -} + }; - function is_different (actual, predicted, mbg, treatment, cal) { + function is_different (data) { if (patientData && patientData.length < 3) { return true; } var old = { - actual: patientData[0].slice(-1).pop( ) - , predicted: patientData[1].slice(-1).pop( ) + sgv: patientData[0].slice(-1).pop( ) , mbg: patientData[2].slice(-1).pop( ) , treatment: patientData[3].slice(-1).pop( ) - , cal: patientData[4].slice(-1).pop( ) + , cal: patientData[5].slice(-1).pop( ) }; var last = { - actual: actual.slice(-1).pop( ) - , predicted: predicted.slice(-1).pop( ) - , mbg: mbg.slice(-1).pop( ) - , treatment: treatment.slice(-1).pop( ) - , cal: cal.slice(-1).pop( ) + sgv: data.sgvs.slice(-1).pop( ) + , mbg: data.mbgs.slice(-1).pop( ) + , treatment: data.treatments.slice(-1).pop( ) + , cal: data.cals.slice(-1).pop( ) }; // textual diff of objects if (JSON.stringify(old) == JSON.stringify(last)) { - console.info("data isn't different, will not send to clients"); + console.info('data is NOT different, will not send to clients'); return false; } return true; @@ -422,9 +207,8 @@ function loadData() { start( ); configure( ); listeners( ); - setInterval(kickstart(update), ONE_MINUTE); - return io; + return websocket(); } /////////////////////////////////////////////////// @@ -433,4 +217,4 @@ function loadData() { function log10(val) { return Math.log(val) / Math.LN10; } -module.exports = websocket; +module.exports = init; diff --git a/server.js b/server.js index 69ff7da48c2..134493f6124 100644 --- a/server.js +++ b/server.js @@ -58,9 +58,18 @@ bootevent(env).boot(function booted (ctx) { /////////////////////////////////////////////////// // setup socket io for data and message transmission /////////////////////////////////////////////////// - var websocket = require('./lib/websocket'); - var io = websocket(env, ctx, server); - }) + var websocket = require('./lib/websocket')(server); + + ctx.heartbeat.on('tick', function(tick) { + console.info('tick', tick.now); + ctx.data.update(function dataUpdated () { + websocket.processData(env, ctx); + }); + }); + + ctx.heartbeat.uptime( ); + +}) ; /////////////////////////////////////////////////// From d93b77a51534bdbabefd7ba57b515fa7e3d59a74 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 6 Jun 2015 01:31:58 -0700 Subject: [PATCH 063/937] some clean up --- lib/plugins/ar2.js | 19 +++++++++---------- lib/websocket.js | 6 ------ 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index a0cb2fad1ea..f35b0074b2c 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -15,31 +15,30 @@ function init() { ar2.forecast = function forecast(env, ctx) { - var actual = ctx.data.sgvs; - var actualLength = actual.length - 1; - var lastUpdated = ctx.data.lastUpdated; + var sgvs = ctx.data.sgvs; + var lastIndex = sgvs.length - 1; var result = { predicted: [] , avgLoss: 0 }; - if (actualLength > 1) { + if (lastIndex > 1) { // predict using AR model - var lastValidReadingTime = actual[actualLength].x; - var elapsedMins = (actual[actualLength].x - actual[actualLength - 1].x) / ONE_MINUTE; + var lastValidReadingTime = sgvs[lastIndex].x; + var elapsedMins = (sgvs[lastIndex].x - sgvs[lastIndex - 1].x) / ONE_MINUTE; var BG_REF = 140; var BG_MIN = 36; var BG_MAX = 400; - var y = Math.log(actual[actualLength].y / BG_REF); + var y = Math.log(sgvs[lastIndex].y / BG_REF); if (elapsedMins < 5.1) { - y = [Math.log(actual[actualLength - 1].y / BG_REF), y]; + y = [Math.log(sgvs[lastIndex - 1].y / BG_REF), y]; } else { y = [y, y]; } - var n = Math.ceil(12 * (1 / 2 + (lastUpdated - lastValidReadingTime) / ONE_HOUR)); + var n = Math.ceil(12 * (1 / 2 + (Date.now() - lastValidReadingTime) / ONE_HOUR)); var AR = [-0.723, 1.716]; - var dt = actual[actualLength].x; + var dt = sgvs[lastIndex].x; for (var i = 0; i <= n; i++) { y = [y[1], AR[0] * y[0] + AR[1] * y[1]]; dt = dt + FIVE_MINUTES; diff --git a/lib/websocket.js b/lib/websocket.js index 7404344816a..2853633b473 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -211,10 +211,4 @@ function init (server) { return websocket(); } -/////////////////////////////////////////////////// -// helper functions -/////////////////////////////////////////////////// - -function log10(val) { return Math.log(val) / Math.LN10; } - module.exports = init; From 8f9b23eef8363a7de7c5acdd7b76f1939c6a5786 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sat, 6 Jun 2015 18:59:29 +0300 Subject: [PATCH 064/937] Only send recent treatments and svgs with update packets --- lib/data.js | 37 ++++++++- lib/websocket.js | 54 +++++++++---- static/js/client.js | 187 ++++++++++++++++++++++++++++++-------------- 3 files changed, 200 insertions(+), 78 deletions(-) diff --git a/lib/data.js b/lib/data.js index bbea35845d2..b1c91f3026e 100644 --- a/lib/data.js +++ b/lib/data.js @@ -6,7 +6,9 @@ function init (env, ctx) { ctx.data = { sgvs: [] + , recentsgvs: [] , treatments: [] + , recentTreatments: [] , mbgs: [] , cals: [] , profile: [] @@ -16,7 +18,9 @@ function init (env, ctx) { var ONE_DAY = 86400000 , TWO_DAYS = 172800000 - ; + , FORTY_MINUTES = 2400000 + , UPDATE_DELTA = FORTY_MINUTES; + var dir2Char = { 'NONE': '⇼', @@ -54,6 +58,7 @@ function init (env, ctx) { async.parallel({ entries: function (callback) { + var now = new Date(); var q = { find: {"date": {"$gte": earliest_data}} }; ctx.entries.list(q, function (err, results) { if (!err && results) { @@ -64,9 +69,20 @@ function init (env, ctx) { y: element.mbg, x: element.date, d: element.dateString, device: element.device }); } else if (element.sgv) { - d.sgvs.push({ - y: element.sgv, x: element.date, d: element.dateString, device: element.device, direction: directionToChar(element.direction), filtered: element.filtered, unfiltered: element.unfiltered, noise: element.noise, rssi: element.rssi - }); + var sgvElement = { + y: element.sgv + , x: element.date + , d: element.dateString + , device: element.device + , direction: directionToChar(element.direction) + , filtered: element.filtered + , unfiltered: element.unfiltered + , noise: element.noise + , rssi: element.rssi + }; + d.sgvs.push(sgvElement); + var sgvDate = new Date(element.date); + if (now-sgvDate < UPDATE_DELTA) d.recentsgvs.push(sgvElement); } } }); @@ -106,6 +122,19 @@ function init (env, ctx) { return a.x - b.x; }); + var now = new Date(); + var length = d.treatments.length; + for (var i = 0; i < length; i++) { + var data = d.treatments[i]; + var date = new Date(data.d); + if (now - date <= UPDATE_DELTA) { + d.recentTreatments.push(data); + }; + + d.recentTreatments.sort(function(a, b) { + return a.x - b.x; + }); + } callback(); }); }, profile: function (callback) { diff --git a/lib/websocket.js b/lib/websocket.js index 2853633b473..400fac5f1d2 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -9,9 +9,10 @@ function init (server) { } var FORTY_MINUTES = 2400000; - + var lastUpdated = 0; - var patientData = []; + var patientData = {}; + var patientDataUpdate = {}; var io; var watchers = 0; @@ -26,9 +27,9 @@ function init (server) { } function emitData ( ) { - if (patientData.length > 0) { - console.log('running websocket.emitData', lastUpdated, patientData[0] && patientData[0].length); - io.sockets.emit('sgv', patientData); + if (patientData.cal) { + console.log('running websocket.emitData', lastUpdated, patientDataUpdate.recentsgvs && patientDataUpdate.sgvdataupdate.length); + io.sockets.emit("sgv", patientDataUpdate); } } @@ -44,7 +45,8 @@ function init (server) { function listeners ( ) { io.sockets.on('connection', function (socket) { - io.sockets.emit('sgv', patientData); + // send all data upon new connection + io.sockets.socket(socket.id).emit('sgv',patientData); io.sockets.emit('clients', ++watchers); socket.on('ack', function(alarmType, silenceTime) { ackAlarm(alarmType, silenceTime); @@ -135,7 +137,30 @@ function init (server) { // consolidate and send the data to the client if (is_different(d)) { - patientData = [d.sgvs, forecast.predicted, d.mbgs, d.treatments, d.profile, d.cals, d.devicestatus]; + + patientDataUpdate = { + 'sgvdataupdate': d.recentsgvs + , 'predicted': forecast.predicted + , 'mbg': d.mbgs + , 'treatmentdataupdate': d.recentTreatments + , 'cal': d.cals + , 'devicestatusData': d.devicestatus + }; + + patientData = { + 'actual': d.sgvs + , 'predicted': forecast.predicted + , 'mbg': d.mbgs + , 'treatment': d.treatments + , 'profile': d.profile + , 'cal': d.cals + , 'devicestatusData': d.devicestatus + }; + + console.log('patientData total sgv records', patientData.actual.length, ' (', JSON.stringify(patientData).length,'bytes)'); + console.log('patientData update sgv records', patientDataUpdate.sgvdataupdate.length, ' (', JSON.stringify(patientDataUpdate).length,'bytes)'); + +// patientData = [d.sgvs, forecast.predicted, d.mbgs, d.treatments, d.profile, d.cals, d.devicestatus]; emitData(); } @@ -179,15 +204,16 @@ function init (server) { } }; + function is_different (data) { - if (patientData && patientData.length < 3) { - return true; - } + + if (!patientData.actual) return true; + var old = { - sgv: patientData[0].slice(-1).pop( ) - , mbg: patientData[2].slice(-1).pop( ) - , treatment: patientData[3].slice(-1).pop( ) - , cal: patientData[5].slice(-1).pop( ) + sgv: patientData.actual.slice(-1).pop( ) + , mbg: patientData.mbg.slice(-1).pop( ) + , treatment: patientData.treatment.slice(-1).pop( ) + , cal: patientData.cal.slice(-1).pop( ) }; var last = { sgv: data.sgvs.slice(-1).pop( ) diff --git a/static/js/client.js b/static/js/client.js index b33cc6abd12..eb97ad8d044 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -30,6 +30,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var socket , isInitialData = false + , SGVdata = [] , latestSGV , latestUpdateTime , prevSGV @@ -1656,67 +1657,132 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// socket = io.connect(); - socket.on('sgv', function (d) { - if (d.length > 1) { - // change the next line so that it uses the prediction if the signal gets lost (max 1/2 hr) - if (d[0].length) { - latestUpdateTime = Date.now(); - latestSGV = d[0][d[0].length - 1]; - prevSGV = d[0][d[0].length - 2]; - } + function recordAlreadyStored(array,record) { + var l = array.length -1; + for (var i = 0; i <= l; i++) { + var oldRecord = array[i]; + if (record.x == oldRecord.x) return true; + } + } + + socket.on('sgv', function receivedSGV(d) { + + if (!d) return; + + var dataUpdate = d.actual ? false : true; + + if (!dataUpdate) { + // replace all locally stored SGV data + console.log('Replacing all local sgv records'); + SGVdata = d.actual; + } else { + var newRecords = []; + + var l = d.sgvdataupdate.length; + for (var i = 0; i < l; i++) { + var record = d.sgvdataupdate[i]; + if (!recordAlreadyStored(SGVdata,record)) { + newRecords.push(record); + } + } + console.log('SGV data updated with', newRecords.length, 'new records'); + SGVdata = SGVdata.concat(newRecords); + + if (newRecords.length > 0) + { + console.log(newRecords); + } + + SGVdata.sort(function(a, b) { + return a.x - b.x; + }); + + } + + console.log('Total CGM data size', SGVdata.length); + + // change the next line so that it uses the prediction if the signal gets lost (max 1/2 hr) + // if (if !dataUpdate && d.actual.length) { + latestUpdateTime = Date.now(); + latestSGV = SGVdata[SGVdata.length - 1]; + prevSGV = SGVdata[SGVdata.length - 2]; + // } + + if (d.profile) profile = d.profile[0]; + + cal = d.cal[d.cal.length-1]; + devicestatusData = d.devicestatusData; + + var temp1 = [ ]; + if (cal && isRawBGEnabled()) { + temp1 = SGVdata.map(function (entry) { + var rawBg = showRawBGs(entry.y, entry.noise, cal) ? rawIsigToRawBg(entry, cal) : 0; + if (rawBg > 0) { + return { date: new Date(entry.x - 2000), y: rawBg, sgv: scaleBg(rawBg), color: 'white', type: 'rawbg' }; + } else { + return null; + } + }).filter(function(entry) { return entry != null; }); + } + var temp2 = SGVdata.map(function (obj) { + return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), direction: obj.direction, color: sgvToColor(obj.y), type: 'sgv', noise: obj.noise, filtered: obj.filtered, unfiltered: obj.unfiltered}; + }); + data = []; + data = data.concat(temp1, temp2); + + // TODO: This is a kludge to advance the time as data becomes stale by making old predictor clear (using color = 'none') + // This shouldn't have to be sent and can be fixed by using xScale.domain([x0,x1]) function with + // 2 days before now as x0 and 30 minutes from now for x1 for context plot, but this will be + // required to happen when 'now' event is sent from websocket.js every minute. When fixed, + // remove all 'color != 'none'' code + data = data.concat(d.predicted.map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), color: 'none', type: 'server-forecast'} })); + + //Add MBG's also, pretend they are SGV's + data = data.concat(d.mbg.map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), color: 'red', type: 'mbg', device: obj.device } })); + + data.forEach(function (d) { + if (d.y < 39) + d.color = 'transparent'; + }); + + // Update treatment data with new if delta + + if (!dataUpdate) { + treatments = d.treatment; + } else { + var newRecords = []; + var l = d.treatmentdataupdate.length; + for (var i = 0; i < l; i++) { + var record = d.treatmentdataupdate[i]; + if (!recordAlreadyStored(treatments,record)) { + newRecods.push(record); + } + } + console.log('treatment data updated with', newRecords.length, 'new records'); + treatments = treatments.concat(newRecords); + treatments.sort(function(a, b) { + return a.x - b.x; + }); + + } + + console.log('Total treatment data size', treatments.length); + + treatments.forEach(function (d) { + d.created_at = new Date(d.created_at); + //cache the displayBG for each treatment in DISPLAY_UNITS + d.displayBG = displayTreatmentBG(d); + }); + + updateTitle(); + if (!isInitialData) { + isInitialData = true; + initializeCharts(); + } + else { + updateChart(false); + } - profile = d[4][0]; - cal = d[5][d[5].length-1]; - devicestatusData = d[6]; - - var temp1 = [ ]; - if (cal && isRawBGEnabled()) { - temp1 = d[0].map(function (entry) { - var rawBg = showRawBGs(entry.y, entry.noise, cal) ? rawIsigToRawBg(entry, cal) : 0; - if (rawBg > 0) { - return { date: new Date(entry.x - 2000), y: rawBg, sgv: scaleBg(rawBg), color: 'white', type: 'rawbg' }; - } else { - return null; - } - }).filter(function(entry) { return entry != null; }); - } - var temp2 = d[0].map(function (obj) { - return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), direction: obj.direction, color: sgvToColor(obj.y), type: 'sgv', noise: obj.noise, filtered: obj.filtered, unfiltered: obj.unfiltered}; - }); - data = []; - data = data.concat(temp1, temp2); - - // TODO: This is a kludge to advance the time as data becomes stale by making old predictor clear (using color = 'none') - // This shouldn't have to be sent and can be fixed by using xScale.domain([x0,x1]) function with - // 2 days before now as x0 and 30 minutes from now for x1 for context plot, but this will be - // required to happen when 'now' event is sent from websocket.js every minute. When fixed, - // remove all 'color != 'none'' code - data = data.concat(d[1].map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), color: 'none', type: 'server-forecast'} })); - - //Add MBG's also, pretend they are SGV's - data = data.concat(d[2].map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), color: 'red', type: 'mbg', device: obj.device } })); - - data.forEach(function (d) { - if (d.y < 39) - d.color = 'transparent'; - }); - - treatments = d[3]; - treatments.forEach(function (d) { - d.created_at = new Date(d.created_at); - //cache the displayBG for each treatment in DISPLAY_UNITS - d.displayBG = displayTreatmentBG(d); - }); - - updateTitle(); - if (!isInitialData) { - isInitialData = true; - initializeCharts(); - } - else { - updateChart(false); - } - } }); //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -1726,6 +1792,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; console.log('Client connected to server.') }); + //with predicted alarms, latestSGV may still be in target so to see if the alarm // is for a HIGH we can only check if it's >= the bottom of the target function isAlarmForHigh() { From e13290850ef19ea17484a667c88c956828be40a8 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 6 Jun 2015 09:25:37 -0700 Subject: [PATCH 065/937] check for err and results when getting treatments --- lib/data.js | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/lib/data.js b/lib/data.js index bbea35845d2..9b4e0778435 100644 --- a/lib/data.js +++ b/lib/data.js @@ -95,17 +95,18 @@ function init (env, ctx) { }, treatments: function (callback) { var tq = { find: {"created_at": {"$gte": new Date(treatment_earliest_data).toISOString()}} }; ctx.treatments.list(tq, function (err, results) { - d.treatments = results.map(function (treatment) { - var timestamp = new Date(treatment.timestamp || treatment.created_at); - treatment.x = timestamp.getTime(); - return treatment; - }); - - //FIXME: sort in mongo - d.treatments.sort(function(a, b) { - return a.x - b.x; - }); + if (!err && results) { + d.treatments = results.map(function (treatment) { + var timestamp = new Date(treatment.timestamp || treatment.created_at); + treatment.x = timestamp.getTime(); + return treatment; + }); + //FIXME: sort in mongo + d.treatments.sort(function(a, b) { + return a.x - b.x; + }); + } callback(); }); }, profile: function (callback) { From 508090ffeb039bd18f0cbc087c82d664dcd5239c Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 6 Jun 2015 09:32:59 -0700 Subject: [PATCH 066/937] some data loader clean up --- lib/data.js | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/lib/data.js b/lib/data.js index 9b4e0778435..ad3bdbb271b 100644 --- a/lib/data.js +++ b/lib/data.js @@ -4,7 +4,7 @@ var async = require('async'); function init (env, ctx) { - ctx.data = { + var data = { sgvs: [] , treatments: [] , mbgs: [] @@ -35,16 +35,13 @@ function init (env, ctx) { return dir2Char[direction] || '-'; } - ctx.data.update = function update (done) { - - //save some chars - var d = ctx.data; + data.update = function update (done) { console.log('running data.update'); - var now = d.lastUpdated = Date.now(); + data.lastUpdated = Date.now(); - var earliest_data = now - TWO_DAYS; - var treatment_earliest_data = now - (ONE_DAY * 8); + var earliest_data = data.lastUpdated - TWO_DAYS; + var treatment_earliest_data = data.lastUpdated - (ONE_DAY * 8); function sort (values) { values.sort(function sorter (a, b) { @@ -60,11 +57,11 @@ function init (env, ctx) { results.forEach(function (element) { if (element) { if (element.mbg) { - d.mbgs.push({ + data.mbgs.push({ y: element.mbg, x: element.date, d: element.dateString, device: element.device }); } else if (element.sgv) { - d.sgvs.push({ + data.sgvs.push({ y: element.sgv, x: element.date, d: element.dateString, device: element.device, direction: directionToChar(element.direction), filtered: element.filtered, unfiltered: element.unfiltered, noise: element.noise, rssi: element.rssi }); } @@ -73,8 +70,8 @@ function init (env, ctx) { } //FIXME: sort in mongo - sort(d.mbgs); - sort(d.sgvs); + sort(data.mbgs); + sort(data.sgvs); callback(); }) }, cal: function (callback) { @@ -84,7 +81,7 @@ function init (env, ctx) { if (!err && results) { results.forEach(function (element) { if (element) { - d.cals.push({ + data.cals.push({ x: element.date, d: element.dateString, scale: element.scale, intercept: element.intercept, slope: element.slope }); } @@ -96,14 +93,14 @@ function init (env, ctx) { var tq = { find: {"created_at": {"$gte": new Date(treatment_earliest_data).toISOString()}} }; ctx.treatments.list(tq, function (err, results) { if (!err && results) { - d.treatments = results.map(function (treatment) { + data.treatments = results.map(function (treatment) { var timestamp = new Date(treatment.timestamp || treatment.created_at); treatment.x = timestamp.getTime(); return treatment; }); //FIXME: sort in mongo - d.treatments.sort(function(a, b) { + data.treatments.sort(function(a, b) { return a.x - b.x; }); } @@ -116,7 +113,7 @@ function init (env, ctx) { results.forEach(function (element, index, array) { if (element) { if (element.dia) { - d.profile[0] = element; + data.profile[0] = element; } } }); @@ -126,7 +123,7 @@ function init (env, ctx) { }, devicestatus: function (callback) { ctx.devicestatus.last(function (err, result) { if (!err && result) { - d.devicestatus.uploaderBattery = result.uploaderBattery; + data.devicestatus.uploaderBattery = result.uploaderBattery; } callback(); }) @@ -135,7 +132,7 @@ function init (env, ctx) { }; - return ctx.data; + return data; } From 690f051e6ecb612466087c9c007d72347c35a86a Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sat, 6 Jun 2015 21:31:33 +0300 Subject: [PATCH 067/937] calculate actual delta --- lib/data.js | 11 ++--- lib/websocket.js | 113 ++++++++++++++++++++++++++++++++------------ static/js/client.js | 109 ++++++++++++++++++++---------------------- 3 files changed, 135 insertions(+), 98 deletions(-) diff --git a/lib/data.js b/lib/data.js index b1c91f3026e..f1bf48d09d2 100644 --- a/lib/data.js +++ b/lib/data.js @@ -91,6 +91,7 @@ function init (env, ctx) { //FIXME: sort in mongo sort(d.mbgs); sort(d.sgvs); + sort(d.recentsgvs); callback(); }) }, cal: function (callback) { @@ -117,11 +118,6 @@ function init (env, ctx) { return treatment; }); - //FIXME: sort in mongo - d.treatments.sort(function(a, b) { - return a.x - b.x; - }); - var now = new Date(); var length = d.treatments.length; for (var i = 0; i < length; i++) { @@ -131,9 +127,8 @@ function init (env, ctx) { d.recentTreatments.push(data); }; - d.recentTreatments.sort(function(a, b) { - return a.x - b.x; - }); + sort(d.treatments); + sort(d.recentTreatments); } callback(); }); diff --git a/lib/websocket.js b/lib/websocket.js index 400fac5f1d2..cf7971d72e5 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -2,6 +2,16 @@ var ar2 = require('./plugins/ar2')(); +Array.prototype.diff = function(a) { + return this.filter(function(i) {return a.indexOf(i) < 0;}); +}; + +function sort (values) { + values.sort(function sorter (a, b) { + return a.x - b.x; + }); +} + function init (server) { function websocket ( ) { @@ -27,9 +37,9 @@ function init (server) { } function emitData ( ) { - if (patientData.cal) { + if (patientData.cals) { console.log('running websocket.emitData', lastUpdated, patientDataUpdate.recentsgvs && patientDataUpdate.sgvdataupdate.length); - io.sockets.emit("sgv", patientDataUpdate); + io.sockets.emit('dataUpdate', patientDataUpdate); } } @@ -46,7 +56,7 @@ function init (server) { function listeners ( ) { io.sockets.on('connection', function (socket) { // send all data upon new connection - io.sockets.socket(socket.id).emit('sgv',patientData); + io.sockets.socket(socket.id).emit('dataUpdate',patientData); io.sockets.emit('clients', ++watchers); socket.on('ack', function(alarmType, silenceTime) { ackAlarm(alarmType, silenceTime); @@ -135,34 +145,20 @@ function init (server) { if (lastSGV) { var forecast = ar2.forecast(env, ctx); - // consolidate and send the data to the client - if (is_different(d)) { - - patientDataUpdate = { - 'sgvdataupdate': d.recentsgvs - , 'predicted': forecast.predicted - , 'mbg': d.mbgs - , 'treatmentdataupdate': d.recentTreatments - , 'cal': d.cals - , 'devicestatusData': d.devicestatus - }; - - patientData = { - 'actual': d.sgvs - , 'predicted': forecast.predicted - , 'mbg': d.mbgs - , 'treatment': d.treatments - , 'profile': d.profile - , 'cal': d.cals - , 'devicestatusData': d.devicestatus - }; - - console.log('patientData total sgv records', patientData.actual.length, ' (', JSON.stringify(patientData).length,'bytes)'); - console.log('patientData update sgv records', patientDataUpdate.sgvdataupdate.length, ' (', JSON.stringify(patientDataUpdate).length,'bytes)'); - -// patientData = [d.sgvs, forecast.predicted, d.mbgs, d.treatments, d.profile, d.cals, d.devicestatus]; - emitData(); - } + if (!patientData.sgvs) { + console.log('First data load, setting patientData'); + patientData = d; + } else { + var delta = calculateDelta(d); + if (delta.delta) { + patientDataUpdate = delta; + console.log('patientData total sgv records', patientData.sgvs.length, ' (', JSON.stringify(patientData).length,'bytes)'); + if (delta.sgvs) console.log('patientData update sgv records', patientDataUpdate.sgvs.length, ' (', JSON.stringify(patientDataUpdate).length,'bytes)'); + emitData(); + } else { + console.log('Delta calculation did not find new data'); + } + } var emitAlarmType = null; @@ -204,7 +200,62 @@ function init (server) { } }; + function calculateDelta(d) { + + var delta = {'delta': true}; + + if (patientData.sgvs) { + + var sgvDelta = patientData.sgvs.diff(d.sgvs); + + if (sgvDelta.length > 0) { + changesFound = true; + sort(sgvDelta); + delta.sgvs = sgvDelta; + }; + + var treatmentDelta = patientData.treatments.diff(d.treatments); + + if (treatmentDelta.length > 0) { + changesFound = true; + sort(treatmentDelta); + delta.treatments = treatmentDelta; + }; + + var mbgsDelta = patientData.mbgs.diff(d.mbgs); + + if (mbgsDelta.length > 0) { + changesFound = true; + sort(mbgsDelta); + delta.mbgs = mbgsDelta; + }; + + var calsDelta = patientData.cals.diff(d.cals); + + if (calsDelta.length > 0) { + changesFound = true; + sort(calsDelta); + delta.cals = calsDelta; + }; + + if (JSON.stringify(patientData.devicestatus) != JSON.stringify(d.devicestatus)) { + changesFound = true; + delta.devicestatus = d.devicestatus; + }; + if (JSON.stringify(patientData.profile) != JSON.stringify(d.profile)) { + changesFound = true; + delta.profile = d.profile; + }; + + } else { + return delta; + } + + return d; + + } + function is_different (data) { if (!patientData.actual) return true; diff --git a/static/js/client.js b/static/js/client.js index eb97ad8d044..6e528bd86b5 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1,6 +1,10 @@ //TODO: clean up var app = {}, browserSettings = {}, browserStorage = $.localStorage; +Array.prototype.diff = function(a) { + return this.filter(function(i) {return a.indexOf(i) < 0;}); +}; + (function () { 'use strict'; @@ -1665,53 +1669,41 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } } - socket.on('sgv', function receivedSGV(d) { + socket.on('dataUpdate', function receivedSGV(d) { if (!d) return; - var dataUpdate = d.actual ? false : true; + // SGV - if (!dataUpdate) { - // replace all locally stored SGV data - console.log('Replacing all local sgv records'); - SGVdata = d.actual; - } else { - var newRecords = []; - - var l = d.sgvdataupdate.length; - for (var i = 0; i < l; i++) { - var record = d.sgvdataupdate[i]; - if (!recordAlreadyStored(SGVdata,record)) { - newRecords.push(record); - } - } - console.log('SGV data updated with', newRecords.length, 'new records'); - SGVdata = SGVdata.concat(newRecords); - - if (newRecords.length > 0) - { - console.log(newRecords); - } - - SGVdata.sort(function(a, b) { - return a.x - b.x; - }); - + if (d.sgvs) { + + if (!d.delta) { + // replace all locally stored SGV data + console.log('Replacing all local sgv records'); + SGVdata = d.sgvs; + } else { + var diff = SGVdata.diff(d.sgvs); + console.log('SGV data updated with', diff.length, 'new records'); + SGVdata = SGVdata.concat(diff); + } + + // change the next line so that it uses the prediction if the signal gets lost (max 1/2 hr) + latestUpdateTime = Date.now(); + latestSGV = SGVdata[SGVdata.length - 1]; + prevSGV = SGVdata[SGVdata.length - 2]; } - console.log('Total CGM data size', SGVdata.length); + SGVdata.sort(function(a, b) { + return a.x - b.x; + }); - // change the next line so that it uses the prediction if the signal gets lost (max 1/2 hr) - // if (if !dataUpdate && d.actual.length) { - latestUpdateTime = Date.now(); - latestSGV = SGVdata[SGVdata.length - 1]; - prevSGV = SGVdata[SGVdata.length - 2]; - // } + console.log('Total SGV data size', SGVdata.length); + + // profile, calibration and device status if (d.profile) profile = d.profile[0]; - - cal = d.cal[d.cal.length-1]; - devicestatusData = d.devicestatusData; + if (d.cal) cal = d.cal[d.cal.length-1]; + if (d.devicestatus) devicestatusData = d.devicestatusData; var temp1 = [ ]; if (cal && isRawBGEnabled()) { @@ -1735,11 +1727,16 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; // 2 days before now as x0 and 30 minutes from now for x1 for context plot, but this will be // required to happen when 'now' event is sent from websocket.js every minute. When fixed, // remove all 'color != 'none'' code - data = data.concat(d.predicted.map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), color: 'none', type: 'server-forecast'} })); - + + if (d.predicted) { + data = data.concat(d.predicted.map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), color: 'none', type: 'server-forecast'} })); + } + //Add MBG's also, pretend they are SGV's - data = data.concat(d.mbg.map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), color: 'red', type: 'mbg', device: obj.device } })); - + if (d.mbgs) { + data = data.concat(d.mbgs.map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), color: 'red', type: 'mbg', device: obj.device } })); + } + data.forEach(function (d) { if (d.y < 39) d.color = 'transparent'; @@ -1747,25 +1744,19 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; // Update treatment data with new if delta - if (!dataUpdate) { - treatments = d.treatment; - } else { - var newRecords = []; - var l = d.treatmentdataupdate.length; - for (var i = 0; i < l; i++) { - var record = d.treatmentdataupdate[i]; - if (!recordAlreadyStored(treatments,record)) { - newRecods.push(record); - } + if (d.treatments) { + if (!d.delta) { + treatments = d.treatments; + } else { + var newTreatments = treatments.diff(d.treatments); + console.log('treatment data updated with', newTreatments.length, 'new records'); + treatments = treatments.concat(newRecords); + treatments.sort(function(a, b) { + return a.x - b.x; + }); } - console.log('treatment data updated with', newRecords.length, 'new records'); - treatments = treatments.concat(newRecords); - treatments.sort(function(a, b) { - return a.x - b.x; - }); - } - + console.log('Total treatment data size', treatments.length); treatments.forEach(function (d) { From 3f68a04dc822459ebce04c61948fe5689f3a840a Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 6 Jun 2015 11:56:27 -0700 Subject: [PATCH 068/937] use slice to make a shallow copy of data added to patientData to make is_different work; rename profile to profiles --- lib/data.js | 4 ++-- lib/websocket.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/data.js b/lib/data.js index ad3bdbb271b..8fe10c6552c 100644 --- a/lib/data.js +++ b/lib/data.js @@ -9,7 +9,7 @@ function init (env, ctx) { , treatments: [] , mbgs: [] , cals: [] - , profile: [] + , profiles: [] , devicestatus: {} , lastUpdated: 0 }; @@ -113,7 +113,7 @@ function init (env, ctx) { results.forEach(function (element, index, array) { if (element) { if (element.dia) { - data.profile[0] = element; + data.profiles[0] = element; } } }); diff --git a/lib/websocket.js b/lib/websocket.js index 2853633b473..f3dee45e191 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -135,7 +135,7 @@ function init (server) { // consolidate and send the data to the client if (is_different(d)) { - patientData = [d.sgvs, forecast.predicted, d.mbgs, d.treatments, d.profile, d.cals, d.devicestatus]; + patientData = [d.sgvs.slice(), forecast.predicted, d.mbgs.slice(), d.treatments.slice(), d.profiles.slice(), d.cals.slice(), d.devicestatus]; emitData(); } From f35745f64fdc462230ab912a33cc7a3c5d9e4939 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sat, 6 Jun 2015 23:20:05 +0300 Subject: [PATCH 069/937] Send deltas from server to client --- lib/data.js | 28 +++++++----------- lib/websocket.js | 71 ++++++++++++++++++++------------------------- static/js/client.js | 16 +++++----- 3 files changed, 50 insertions(+), 65 deletions(-) diff --git a/lib/data.js b/lib/data.js index f1bf48d09d2..8948dccf5f4 100644 --- a/lib/data.js +++ b/lib/data.js @@ -2,13 +2,18 @@ var async = require('async'); +function uniq(a) { + var seen = {}; + return a.filter(function(item) { + return seen.hasOwnProperty(item.x) ? false : (seen[item.x] = true); + }); +} + function init (env, ctx) { ctx.data = { sgvs: [] - , recentsgvs: [] , treatments: [] - , recentTreatments: [] , mbgs: [] , cals: [] , profile: [] @@ -81,17 +86,16 @@ function init (env, ctx) { , rssi: element.rssi }; d.sgvs.push(sgvElement); - var sgvDate = new Date(element.date); - if (now-sgvDate < UPDATE_DELTA) d.recentsgvs.push(sgvElement); } } }); } - //FIXME: sort in mongo + uniq(d.mbgs); sort(d.mbgs); + uniq(d.sgvs); sort(d.sgvs); - sort(d.recentsgvs); + callback(); }) }, cal: function (callback) { @@ -118,18 +122,6 @@ function init (env, ctx) { return treatment; }); - var now = new Date(); - var length = d.treatments.length; - for (var i = 0; i < length; i++) { - var data = d.treatments[i]; - var date = new Date(data.d); - if (now - date <= UPDATE_DELTA) { - d.recentTreatments.push(data); - }; - - sort(d.treatments); - sort(d.recentTreatments); - } callback(); }); }, profile: function (callback) { diff --git a/lib/websocket.js b/lib/websocket.js index cf7971d72e5..e201dc3e7fe 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -2,9 +2,23 @@ var ar2 = require('./plugins/ar2')(); -Array.prototype.diff = function(a) { - return this.filter(function(i) {return a.indexOf(i) < 0;}); -}; +Array.prototype.nsDiff = function(a) { + var seen = {}; + var l = this.length; + for (var i = 0; i < l; i++) { seen[this[i].x] = true }; + var result = []; + l = a.length; + for (var i = 0; i < l; i++) { if (!seen.hasOwnProperty(a[i].x)) result.push(a[i])}; + return result; +} + +function uniq(a) { + var seen = {}; + return a.filter(function(item) { + return seen.hasOwnProperty(item.x) ? false : (seen[item.x] = true); + }); +} + function sort (values) { values.sort(function sorter (a, b) { @@ -148,10 +162,12 @@ function init (server) { if (!patientData.sgvs) { console.log('First data load, setting patientData'); patientData = d; + patientData.predicted = forecast.predicted; } else { var delta = calculateDelta(d); if (delta.delta) { patientDataUpdate = delta; + patientDataUpdate.predicted = forecast.predicted; console.log('patientData total sgv records', patientData.sgvs.length, ' (', JSON.stringify(patientData).length,'bytes)'); if (delta.sgvs) console.log('patientData update sgv records', patientDataUpdate.sgvs.length, ' (', JSON.stringify(patientDataUpdate).length,'bytes)'); emitData(); @@ -203,10 +219,12 @@ function init (server) { function calculateDelta(d) { var delta = {'delta': true}; - - if (patientData.sgvs) { + var changesFound = false; - var sgvDelta = patientData.sgvs.diff(d.sgvs); + // if there's no updates done so far, just return the full set + if (!patientData.sgvs) return d; + + var sgvDelta = patientData.sgvs.nsDiff(d.sgvs); if (sgvDelta.length > 0) { changesFound = true; @@ -214,7 +232,7 @@ function init (server) { delta.sgvs = sgvDelta; }; - var treatmentDelta = patientData.treatments.diff(d.treatments); + var treatmentDelta = patientData.treatments.nsDiff(d.treatments); if (treatmentDelta.length > 0) { changesFound = true; @@ -222,7 +240,7 @@ function init (server) { delta.treatments = treatmentDelta; }; - var mbgsDelta = patientData.mbgs.diff(d.mbgs); + var mbgsDelta = patientData.mbgs.nsDiff(d.mbgs); if (mbgsDelta.length > 0) { changesFound = true; @@ -230,7 +248,7 @@ function init (server) { delta.mbgs = mbgsDelta; }; - var calsDelta = patientData.cals.diff(d.cals); + var calsDelta = patientData.cals.nsDiff(d.cals); if (calsDelta.length > 0) { changesFound = true; @@ -247,40 +265,13 @@ function init (server) { changesFound = true; delta.profile = d.profile; }; - - } else { - return delta; - } - - return d; + + if (changesFound) return delta; + + return d; } - function is_different (data) { - - if (!patientData.actual) return true; - - var old = { - sgv: patientData.actual.slice(-1).pop( ) - , mbg: patientData.mbg.slice(-1).pop( ) - , treatment: patientData.treatment.slice(-1).pop( ) - , cal: patientData.cal.slice(-1).pop( ) - }; - var last = { - sgv: data.sgvs.slice(-1).pop( ) - , mbg: data.mbgs.slice(-1).pop( ) - , treatment: data.treatments.slice(-1).pop( ) - , cal: data.cals.slice(-1).pop( ) - }; - - // textual diff of objects - if (JSON.stringify(old) == JSON.stringify(last)) { - console.info('data is NOT different, will not send to clients'); - return false; - } - return true; - } - start( ); configure( ); listeners( ); diff --git a/static/js/client.js b/static/js/client.js index 6e528bd86b5..c8c9d76c657 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1686,6 +1686,10 @@ Array.prototype.diff = function(a) { console.log('SGV data updated with', diff.length, 'new records'); SGVdata = SGVdata.concat(diff); } + + SGVdata.sort(function(a, b) { + return a.x - b.x; + }); // change the next line so that it uses the prediction if the signal gets lost (max 1/2 hr) latestUpdateTime = Date.now(); @@ -1693,17 +1697,15 @@ Array.prototype.diff = function(a) { prevSGV = SGVdata[SGVdata.length - 2]; } - SGVdata.sort(function(a, b) { - return a.x - b.x; - }); - console.log('Total SGV data size', SGVdata.length); + console.log('Latest SGV', latestSGV.date); + console.log('prevSGV', prevSGV.date); // profile, calibration and device status if (d.profile) profile = d.profile[0]; - if (d.cal) cal = d.cal[d.cal.length-1]; - if (d.devicestatus) devicestatusData = d.devicestatusData; + if (d.cals) cal = d.cals[d.cals.length-1]; + if (d.devicestatus) devicestatusData = d.devicestatus; var temp1 = [ ]; if (cal && isRawBGEnabled()) { @@ -1750,7 +1752,7 @@ Array.prototype.diff = function(a) { } else { var newTreatments = treatments.diff(d.treatments); console.log('treatment data updated with', newTreatments.length, 'new records'); - treatments = treatments.concat(newRecords); + treatments = treatments.concat(newTreatments); treatments.sort(function(a, b) { return a.x - b.x; }); From 7987562d8ab7b4a6927d4bdb179d440aa3ec7886 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 6 Jun 2015 13:24:11 -0700 Subject: [PATCH 070/937] replace data fields when loading, instead of appending forever --- lib/data.js | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/lib/data.js b/lib/data.js index 8fe10c6552c..c80cbd400c5 100644 --- a/lib/data.js +++ b/lib/data.js @@ -54,24 +54,28 @@ function init (env, ctx) { var q = { find: {"date": {"$gte": earliest_data}} }; ctx.entries.list(q, function (err, results) { if (!err && results) { + var mbgs = []; + var sgvs = []; results.forEach(function (element) { if (element) { if (element.mbg) { - data.mbgs.push({ + mbgs.push({ y: element.mbg, x: element.date, d: element.dateString, device: element.device }); } else if (element.sgv) { - data.sgvs.push({ + sgvs.push({ y: element.sgv, x: element.date, d: element.dateString, device: element.device, direction: directionToChar(element.direction), filtered: element.filtered, unfiltered: element.unfiltered, noise: element.noise, rssi: element.rssi }); } } }); - } - //FIXME: sort in mongo - sort(data.mbgs); - sort(data.sgvs); + //FIXME: sort in mongo + sort(mbgs); + sort(sgvs); + data.mbgs = mbgs; + data.sgvs = sgvs; + } callback(); }) }, cal: function (callback) { @@ -79,13 +83,15 @@ function init (env, ctx) { var cq = { count: 1, find: {"type": "cal"} }; ctx.entries.list(cq, function (err, results) { if (!err && results) { + var cals = []; results.forEach(function (element) { if (element) { - data.cals.push({ + cals.push({ x: element.date, d: element.dateString, scale: element.scale, intercept: element.intercept, slope: element.slope }); } }); + data.cals = cals; } callback(); }); @@ -93,16 +99,19 @@ function init (env, ctx) { var tq = { find: {"created_at": {"$gte": new Date(treatment_earliest_data).toISOString()}} }; ctx.treatments.list(tq, function (err, results) { if (!err && results) { - data.treatments = results.map(function (treatment) { + var treatments = []; + treatments = results.map(function (treatment) { var timestamp = new Date(treatment.timestamp || treatment.created_at); treatment.x = timestamp.getTime(); return treatment; }); //FIXME: sort in mongo - data.treatments.sort(function(a, b) { + treatments.sort(function(a, b) { return a.x - b.x; }); + + data.treatments = treatments; } callback(); }); @@ -110,13 +119,15 @@ function init (env, ctx) { ctx.profile.list(function (err, results) { if (!err && results) { // There should be only one document in the profile collection with a DIA. If there are multiple, use the last one. + var profiles = []; results.forEach(function (element, index, array) { if (element) { if (element.dia) { - data.profiles[0] = element; + profiles[0] = element; } } }); + data.profiles = profiles; } callback(); }); From 524084bf5e5a0691022ae9d9f567b91fb466b1f1 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sat, 6 Jun 2015 23:47:02 +0300 Subject: [PATCH 071/937] Don't mess with Array prototype --- lib/data.js | 4 +--- lib/websocket.js | 18 +++++++++--------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/lib/data.js b/lib/data.js index 8948dccf5f4..abc9cbb4f93 100644 --- a/lib/data.js +++ b/lib/data.js @@ -22,9 +22,7 @@ function init (env, ctx) { }; var ONE_DAY = 86400000 - , TWO_DAYS = 172800000 - , FORTY_MINUTES = 2400000 - , UPDATE_DELTA = FORTY_MINUTES; + , TWO_DAYS = 172800000; var dir2Char = { diff --git a/lib/websocket.js b/lib/websocket.js index e201dc3e7fe..d80538ece10 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -2,13 +2,13 @@ var ar2 = require('./plugins/ar2')(); -Array.prototype.nsDiff = function(a) { +function nsArrayDiff(oldArray, newArray) { var seen = {}; - var l = this.length; - for (var i = 0; i < l; i++) { seen[this[i].x] = true }; + var l = oldArray.length; + for (var i = 0; i < l; i++) { seen[oldArray[i].x] = true }; var result = []; - l = a.length; - for (var i = 0; i < l; i++) { if (!seen.hasOwnProperty(a[i].x)) result.push(a[i])}; + l = newArray.length; + for (var i = 0; i < l; i++) { if (!seen.hasOwnProperty(newArray[i].x)) { result.push(newArray[i]); console.log('delta data found'); } }; return result; } @@ -224,7 +224,7 @@ function init (server) { // if there's no updates done so far, just return the full set if (!patientData.sgvs) return d; - var sgvDelta = patientData.sgvs.nsDiff(d.sgvs); + var sgvDelta = nsArrayDiff(patientData.sgvs,d.sgvs); if (sgvDelta.length > 0) { changesFound = true; @@ -232,7 +232,7 @@ function init (server) { delta.sgvs = sgvDelta; }; - var treatmentDelta = patientData.treatments.nsDiff(d.treatments); + var treatmentDelta = nsArrayDiff(patientData.treatments,d.treatments); if (treatmentDelta.length > 0) { changesFound = true; @@ -240,7 +240,7 @@ function init (server) { delta.treatments = treatmentDelta; }; - var mbgsDelta = patientData.mbgs.nsDiff(d.mbgs); + var mbgsDelta = nsArrayDiff(patientData.mbgs,d.mbgs); if (mbgsDelta.length > 0) { changesFound = true; @@ -248,7 +248,7 @@ function init (server) { delta.mbgs = mbgsDelta; }; - var calsDelta = patientData.cals.nsDiff(d.cals); + var calsDelta = nsArrayDiff(patientData.cals,d.cals); if (calsDelta.length > 0) { changesFound = true; From 23509d11e6942cbe31bff9539f764193baa95997 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 7 Jun 2015 00:59:25 +0300 Subject: [PATCH 072/937] Fix mbg data updates --- static/js/client.js | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index 161292b481b..078c337b144 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -41,6 +41,7 @@ function nsArrayDiff(oldArray, newArray) { var socket , isInitialData = false , SGVdata = [] + , MBGdata = [] , latestSGV , latestUpdateTime , prevSGV @@ -1693,7 +1694,7 @@ function nsArrayDiff(oldArray, newArray) { SGVdata = SGVdata.concat(diff); } - SGVdata.sort(function(a, b) { + SGVdata.sort(function(a, b) { return a.x - b.x; }); @@ -1742,7 +1743,22 @@ function nsArrayDiff(oldArray, newArray) { //Add MBG's also, pretend they are SGV's if (d.mbgs) { - data = data.concat(d.mbgs.map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), color: 'red', type: 'mbg', device: obj.device } })); + + if (!d.delta) { + // replace all locally stored SGV data + console.log('Replacing all local sgv records'); + MBGdata = d.mbgs; + } else { + var diff = nsArrayDiff(MBGdata,d.mbgs); + console.log('MBG data updated with', diff.length, 'new records'); + MBGdata = MBGdata.concat(diff); + } + + MBGdata.sort(function(a, b) { + return a.x - b.x; + }); + + data = data.concat(MBGdata.map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), color: 'red', type: 'mbg', device: obj.device } })); } data.forEach(function (d) { From 7fc8042201983913bb5d8259f71837aa24cde724 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 7 Jun 2015 01:15:28 +0300 Subject: [PATCH 073/937] Now actually fixed the MBGs --- static/js/client.js | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index 078c337b144..31627ac35e8 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1705,8 +1705,6 @@ function nsArrayDiff(oldArray, newArray) { } console.log('Total SGV data size', SGVdata.length); - console.log('Latest SGV', latestSGV.date); - console.log('prevSGV', prevSGV.date); // profile, calibration and device status @@ -1741,26 +1739,21 @@ function nsArrayDiff(oldArray, newArray) { data = data.concat(d.predicted.map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), color: 'none', type: 'server-forecast'} })); } - //Add MBG's also, pretend they are SGV's + //Add MBG's also, pretend they are MBG's if (d.mbgs) { - if (!d.delta) { - // replace all locally stored SGV data - console.log('Replacing all local sgv records'); + // replace all locally stored MBG data + console.log('Replacing all local MBG records'); MBGdata = d.mbgs; } else { var diff = nsArrayDiff(MBGdata,d.mbgs); console.log('MBG data updated with', diff.length, 'new records'); MBGdata = MBGdata.concat(diff); - } - - MBGdata.sort(function(a, b) { - return a.x - b.x; - }); - - data = data.concat(MBGdata.map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), color: 'red', type: 'mbg', device: obj.device } })); + } } + data = data.concat(MBGdata.map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), color: 'red', type: 'mbg', device: obj.device } })); + data.forEach(function (d) { if (d.y < 39) d.color = 'transparent'; From ad27d8f6b130076a4d078413ddec50a0b2b2a806 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 6 Jun 2015 15:27:14 -0700 Subject: [PATCH 074/937] changed indent of client.js to 2 spaces like everything else --- static/js/client.js | 3374 +++++++++++++++++++++---------------------- 1 file changed, 1687 insertions(+), 1687 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index 31627ac35e8..fbb8fabfc61 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -7,1884 +7,1884 @@ function nsArrayDiff(oldArray, newArray) { for (var i = 0; i < l; i++) { seen[oldArray[i].x] = true }; var result = []; l = newArray.length; - for (var i = 0; i < l; i++) { if (!seen.hasOwnProperty(newArray[i].x)) { result.push(newArray[i]); console.log('delta data found'); } }; + for (var j = 0; j < l; j++) { if (!seen.hasOwnProperty(newArray[j].x)) { result.push(newArray[j]); console.log('delta data found'); } } return result; } (function () { - 'use strict'; - - var BRUSH_TIMEOUT = 300000 // 5 minutes in ms - , DEBOUNCE_MS = 10 - , TOOLTIP_TRANS_MS = 200 // milliseconds - , UPDATE_TRANS_MS = 750 // milliseconds - , ONE_MIN_IN_MS = 60000 - , FIVE_MINS_IN_MS = 300000 - , SIX_MINS_IN_MS = 360000 - , THREE_HOURS_MS = 3 * 60 * 60 * 1000 - , TWELVE_HOURS_MS = 12 * 60 * 60 * 1000 - , TWENTY_FIVE_MINS_IN_MS = 1500000 - , THIRTY_MINS_IN_MS = 1800000 - , SIXTY_MINS_IN_MS = 3600000 - , FORMAT_TIME_12 = '%-I:%M %p' - , FORMAT_TIME_12_COMAPCT = '%-I:%M' - , FORMAT_TIME_24 = '%H:%M%' - , FORMAT_TIME_12_SCALE = '%-I %p' - , FORMAT_TIME_24_SCALE = '%H' - , WIDTH_SMALL_DOTS = 400 - , WIDTH_BIG_DOTS = 800 - , MINUTE_IN_SECS = 60 - , HOUR_IN_SECS = 3600 - , DAY_IN_SECS = 86400 - , WEEK_IN_SECS = 604800; - - var socket - , isInitialData = false - , SGVdata = [] - , MBGdata = [] - , latestSGV - , latestUpdateTime - , prevSGV - , treatments - , profile - , cal - , devicestatusData - , padding = { top: 0, right: 10, bottom: 30, left: 10 } - , opacity = {current: 1, DAY: 1, NIGHT: 0.5} - , now = Date.now() - , data = [] - , foucusRangeMS = THREE_HOURS_MS - , clientAlarms = {} - , audio = document.getElementById('audio') - , alarmInProgress = false - , currentAlarmType = null - , alarmSound = 'alarm.mp3' - , urgentAlarmSound = 'alarm2.mp3'; - - var jqWindow - , tooltip - , tickValues - , charts - , futureOpacity - , focus - , context - , xScale, xScale2, yScale, yScale2 - , xAxis, yAxis, xAxis2, yAxis2 - , prevChartWidth = 0 - , prevChartHeight = 0 - , focusHeight - , contextHeight - , dateFn = function (d) { return new Date(d.date) } - , documentHidden = false - , brush - , brushTimer - , brushInProgress = false - , clip; - - function formatTime(time, compact) { - var timeFormat = getTimeFormat(false, compact); - time = d3.time.format(timeFormat)(time); - if (!isTimeFormat24()) { - time = time.toLowerCase(); - } - return time; + 'use strict'; + + var BRUSH_TIMEOUT = 300000 // 5 minutes in ms + , DEBOUNCE_MS = 10 + , TOOLTIP_TRANS_MS = 200 // milliseconds + , UPDATE_TRANS_MS = 750 // milliseconds + , ONE_MIN_IN_MS = 60000 + , FIVE_MINS_IN_MS = 300000 + , SIX_MINS_IN_MS = 360000 + , THREE_HOURS_MS = 3 * 60 * 60 * 1000 + , TWELVE_HOURS_MS = 12 * 60 * 60 * 1000 + , TWENTY_FIVE_MINS_IN_MS = 1500000 + , THIRTY_MINS_IN_MS = 1800000 + , SIXTY_MINS_IN_MS = 3600000 + , FORMAT_TIME_12 = '%-I:%M %p' + , FORMAT_TIME_12_COMAPCT = '%-I:%M' + , FORMAT_TIME_24 = '%H:%M%' + , FORMAT_TIME_12_SCALE = '%-I %p' + , FORMAT_TIME_24_SCALE = '%H' + , WIDTH_SMALL_DOTS = 400 + , WIDTH_BIG_DOTS = 800 + , MINUTE_IN_SECS = 60 + , HOUR_IN_SECS = 3600 + , DAY_IN_SECS = 86400 + , WEEK_IN_SECS = 604800; + + var socket + , isInitialData = false + , SGVdata = [] + , MBGdata = [] + , latestSGV + , latestUpdateTime + , prevSGV + , treatments + , profile + , cal + , devicestatusData + , padding = { top: 0, right: 10, bottom: 30, left: 10 } + , opacity = {current: 1, DAY: 1, NIGHT: 0.5} + , now = Date.now() + , data = [] + , foucusRangeMS = THREE_HOURS_MS + , clientAlarms = {} + , audio = document.getElementById('audio') + , alarmInProgress = false + , currentAlarmType = null + , alarmSound = 'alarm.mp3' + , urgentAlarmSound = 'alarm2.mp3'; + + var jqWindow + , tooltip + , tickValues + , charts + , futureOpacity + , focus + , context + , xScale, xScale2, yScale, yScale2 + , xAxis, yAxis, xAxis2, yAxis2 + , prevChartWidth = 0 + , prevChartHeight = 0 + , focusHeight + , contextHeight + , dateFn = function (d) { return new Date(d.date) } + , documentHidden = false + , brush + , brushTimer + , brushInProgress = false + , clip; + + function formatTime(time, compact) { + var timeFormat = getTimeFormat(false, compact); + time = d3.time.format(timeFormat)(time); + if (!isTimeFormat24()) { + time = time.toLowerCase(); } - - function isTimeFormat24() { - return browserSettings && browserSettings.timeFormat && parseInt(browserSettings.timeFormat) == 24; + return time; + } + + function isTimeFormat24() { + return browserSettings && browserSettings.timeFormat && parseInt(browserSettings.timeFormat) == 24; + } + + function getTimeFormat(isForScale, compact) { + var timeFormat = FORMAT_TIME_12; + if (isTimeFormat24()) { + timeFormat = isForScale ? FORMAT_TIME_24_SCALE : FORMAT_TIME_24; + } else { + timeFormat = isForScale ? FORMAT_TIME_12_SCALE : (compact ? FORMAT_TIME_12_COMAPCT : FORMAT_TIME_12); } - function getTimeFormat(isForScale, compact) { - var timeFormat = FORMAT_TIME_12; - if (isTimeFormat24()) { - timeFormat = isForScale ? FORMAT_TIME_24_SCALE : FORMAT_TIME_24; - } else { - timeFormat = isForScale ? FORMAT_TIME_12_SCALE : (compact ? FORMAT_TIME_12_COMAPCT : FORMAT_TIME_12); - } + return timeFormat; + } - return timeFormat; + // lixgbg: Convert mg/dL BG value to metric mmol + function scaleBg(bg) { + if (browserSettings.units == 'mmol') { + return Nightscout.units.mgdlToMMOL(bg); + } else { + return bg; } + } + + //see http://stackoverflow.com/a/9609450 + var decodeEntities = (function() { + // this prevents any overhead from creating the object each time + var element = document.createElement('div'); + + function decodeHTMLEntities (str) { + if(str && typeof str === 'string') { + // strip script/html tags + str = str.replace(/]*>([\S\s]*?)<\/script>/gmi, ''); + str = str.replace(/<\/?\w(?:[^"'>]|"[^"]*"|'[^']*')*>/gmi, ''); + element.innerHTML = str; + str = element.textContent; + element.textContent = ''; + } - // lixgbg: Convert mg/dL BG value to metric mmol - function scaleBg(bg) { - if (browserSettings.units == 'mmol') { - return Nightscout.units.mgdlToMMOL(bg); - } else { - return bg; - } + return str; } - //see http://stackoverflow.com/a/9609450 - var decodeEntities = (function() { - // this prevents any overhead from creating the object each time - var element = document.createElement('div'); - - function decodeHTMLEntities (str) { - if(str && typeof str === 'string') { - // strip script/html tags - str = str.replace(/]*>([\S\s]*?)<\/script>/gmi, ''); - str = str.replace(/<\/?\w(?:[^"'>]|"[^"]*"|'[^']*')*>/gmi, ''); - element.innerHTML = str; - str = element.textContent; - element.textContent = ''; - } - - return str; - } + return decodeHTMLEntities; + })(); - return decodeHTMLEntities; - })(); + function updateTitle() { - function updateTitle() { + var time = latestSGV ? new Date(latestSGV.x).getTime() : (prevSGV ? new Date(prevSGV.x).getTime() : -1) + , ago = timeAgo(time); - var time = latestSGV ? new Date(latestSGV.x).getTime() : (prevSGV ? new Date(prevSGV.x).getTime() : -1) - , ago = timeAgo(time); + var bg_title = browserSettings.customTitle || ''; - var bg_title = browserSettings.customTitle || ''; + function s(value, sep) { return value ? value + ' ' : sep || ''; } - function s(value, sep) { return value ? value + ' ' : sep || ''; } + if (ago && ago.status !== 'current') { + bg_title = s(ago.value) + s(ago.label, ' - ') + bg_title; + } else if (latestSGV) { + var currentMgdl = latestSGV.y; - if (ago && ago.status !== 'current') { - bg_title = s(ago.value) + s(ago.label, ' - ') + bg_title; - } else if (latestSGV) { - var currentMgdl = latestSGV.y; + if (currentMgdl < 39) { + bg_title = s(errorCodeToDisplay(currentMgdl), ' - ') + bg_title; + } else { + var deltaDisplay = calcDeltaDisplay(prevSGV, latestSGV); + bg_title = s(scaleBg(currentMgdl)) + s(deltaDisplay) + s(decodeEntities(latestSGV.direction)) + bg_title; + } + } - if (currentMgdl < 39) { - bg_title = s(errorCodeToDisplay(currentMgdl), ' - ') + bg_title; - } else { - var deltaDisplay = calcDeltaDisplay(prevSGV, latestSGV); - bg_title = s(scaleBg(currentMgdl)) + s(deltaDisplay) + s(decodeEntities(latestSGV.direction)) + bg_title; - } - } + $(document).attr('title', bg_title); + } + + function calcDeltaDisplay(prevEntry, currentEntry) { + var delta = null + , prevMgdl = prevEntry && prevEntry.y + , currentMgdl = currentEntry && currentEntry.y; + + if (prevMgdl === undefined || currentMgdl == undefined || prevMgdl < 40 || prevMgdl > 400 || currentMgdl < 40 || currentMgdl > 400) { + //TODO consider using raw data here + delta = null; + } else { + delta = scaleBg(currentMgdl) - scaleBg(prevMgdl); + if (browserSettings.units == 'mmol') { + delta = delta.toFixed(1); + } - $(document).attr('title', bg_title); + delta = (delta >= 0 ? '+' : '') + delta; } - function calcDeltaDisplay(prevEntry, currentEntry) { - var delta = null - , prevMgdl = prevEntry && prevEntry.y - , currentMgdl = currentEntry && currentEntry.y; - - if (prevMgdl === undefined || currentMgdl == undefined || prevMgdl < 40 || prevMgdl > 400 || currentMgdl < 40 || currentMgdl > 400) { - //TODO consider using raw data here - delta = null; + return delta; + } + + function isRawBGEnabled() { + return app.enabledOptions && app.enabledOptions.indexOf('rawbg') > -1; + } + + function showRawBGs(sgv, noise, cal) { + return cal + && isRawBGEnabled() + && (browserSettings.showRawbg == 'always' + || (browserSettings.showRawbg == 'noise' && (noise >= 2 || sgv < 40)) + ); + } + + function noiseCodeToDisplay(sgv, noise) { + var display; + switch (parseInt(noise)) { + case 0: display = '---'; break; + case 1: display = 'Clean'; break; + case 2: display = 'Light'; break; + case 3: display = 'Medium'; break; + case 4: display = 'Heavy'; break; + default: + if (sgv < 40) { + display = 'Heavy'; } else { - delta = scaleBg(currentMgdl) - scaleBg(prevMgdl); - if (browserSettings.units == 'mmol') { - delta = delta.toFixed(1); - } - - delta = (delta >= 0 ? '+' : '') + delta; + display = '~~~'; } - - return delta; + break; } - function isRawBGEnabled() { - return app.enabledOptions && app.enabledOptions.indexOf('rawbg') > -1; - } + return display; + } - function showRawBGs(sgv, noise, cal) { - return cal - && isRawBGEnabled() - && (browserSettings.showRawbg == 'always' - || (browserSettings.showRawbg == 'noise' && (noise >= 2 || sgv < 40)) - ); - } + function rawIsigToRawBg(entry, cal) { + + var raw = 0 + , unfiltered = parseInt(entry.unfiltered) || 0 + , filtered = parseInt(entry.filtered) || 0 + , sgv = entry.y + , scale = parseFloat(cal.scale) || 0 + , intercept = parseFloat(cal.intercept) || 0 + , slope = parseFloat(cal.slope) || 0; - function noiseCodeToDisplay(sgv, noise) { - var display; - switch (parseInt(noise)) { - case 0: display = '---'; break; - case 1: display = 'Clean'; break; - case 2: display = 'Light'; break; - case 3: display = 'Medium'; break; - case 4: display = 'Heavy'; break; - default: - if (sgv < 40) { - display = 'Heavy'; - } else { - display = '~~~'; - } - break; - } - return display; + if (slope == 0 || unfiltered == 0 || scale == 0) { + raw = 0; + } else if (filtered == 0 || sgv < 40) { + raw = scale * (unfiltered - intercept) / slope; + } else { + var ratio = scale * (filtered - intercept) / slope / sgv; + raw = scale * ( unfiltered - intercept) / slope / ratio; } - function rawIsigToRawBg(entry, cal) { + return Math.round(raw); + } + + // initial setup of chart when data is first made available + function initializeCharts() { + + // define the parts of the axis that aren't dependent on width or height + xScale = d3.time.scale() + .domain(d3.extent(data, function (d) { return d.date; })); + + yScale = d3.scale.log() + .domain([scaleBg(30), scaleBg(510)]); + + xScale2 = d3.time.scale() + .domain(d3.extent(data, function (d) { return d.date; })); + + yScale2 = d3.scale.log() + .domain([scaleBg(36), scaleBg(420)]); + + var tickFormat = d3.time.format.multi( [ + ['.%L', function(d) { return d.getMilliseconds(); }], + [':%S', function(d) { return d.getSeconds(); }], + ['%I:%M', function(d) { return d.getMinutes(); }], + [isTimeFormat24() ? '%H:%M' : '%-I %p', function(d) { return d.getHours(); }], + ['%a %d', function(d) { return d.getDay() && d.getDate() != 1; }], + ['%b %d', function(d) { return d.getDate() != 1; }], + ['%B', function(d) { return d.getMonth(); }], + ['%Y', function() { return true; }] + ]); + + xAxis = d3.svg.axis() + .scale(xScale) + .tickFormat(tickFormat) + .ticks(4) + .orient('bottom'); + + yAxis = d3.svg.axis() + .scale(yScale) + .tickFormat(d3.format('d')) + .tickValues(tickValues) + .orient('left'); + + xAxis2 = d3.svg.axis() + .scale(xScale2) + .tickFormat(tickFormat) + .ticks(6) + .orient('bottom'); + + yAxis2 = d3.svg.axis() + .scale(yScale2) + .tickFormat(d3.format('d')) + .tickValues(tickValues) + .orient('right'); + + // setup a brush + brush = d3.svg.brush() + .x(xScale2) + .on('brushstart', brushStarted) + .on('brush', brushed) + .on('brushend', brushEnded); + + updateChart(true); + } + + // get the desired opacity for context chart based on the brush extent + function highlightBrushPoints(data) { + if (data.date.getTime() >= brush.extent()[0].getTime() && data.date.getTime() <= brush.extent()[1].getTime()) { + return futureOpacity(data.date.getTime() - latestSGV.x); + } else { + return 0.5; + } + } - var raw = 0 - , unfiltered = parseInt(entry.unfiltered) || 0 - , filtered = parseInt(entry.filtered) || 0 - , sgv = entry.y - , scale = parseFloat(cal.scale) || 0 - , intercept = parseFloat(cal.intercept) || 0 - , slope = parseFloat(cal.slope) || 0; + // clears the current user brush and resets to the current real time data + var updateBrushToNow = _.debounce(function updateBrushToNow(skipBrushing) { + // get current time range + var dataRange = d3.extent(data, dateFn); - if (slope == 0 || unfiltered == 0 || scale == 0) { - raw = 0; - } else if (filtered == 0 || sgv < 40) { - raw = scale * (unfiltered - intercept) / slope; - } else { - var ratio = scale * (filtered - intercept) / slope / sgv; - raw = scale * ( unfiltered - intercept) / slope / ratio; - } + // update brush and focus chart with recent data + d3.select('.brush') + .transition() + .duration(UPDATE_TRANS_MS) + .call(brush.extent([new Date(dataRange[1].getTime() - foucusRangeMS), dataRange[1]])); - return Math.round(raw); - } + if (!skipBrushing) { + brushed(true); - // initial setup of chart when data is first made available - function initializeCharts() { - - // define the parts of the axis that aren't dependent on width or height - xScale = d3.time.scale() - .domain(d3.extent(data, function (d) { return d.date; })); - - yScale = d3.scale.log() - .domain([scaleBg(30), scaleBg(510)]); - - xScale2 = d3.time.scale() - .domain(d3.extent(data, function (d) { return d.date; })); - - yScale2 = d3.scale.log() - .domain([scaleBg(36), scaleBg(420)]); - - var tickFormat = d3.time.format.multi( [ - ['.%L', function(d) { return d.getMilliseconds(); }], - [':%S', function(d) { return d.getSeconds(); }], - ['%I:%M', function(d) { return d.getMinutes(); }], - [isTimeFormat24() ? '%H:%M' : '%-I %p', function(d) { return d.getHours(); }], - ['%a %d', function(d) { return d.getDay() && d.getDate() != 1; }], - ['%b %d', function(d) { return d.getDate() != 1; }], - ['%B', function(d) { return d.getMonth(); }], - ['%Y', function() { return true; }] - ]); - - xAxis = d3.svg.axis() - .scale(xScale) - .tickFormat(tickFormat) - .ticks(4) - .orient('bottom'); - - yAxis = d3.svg.axis() - .scale(yScale) - .tickFormat(d3.format('d')) - .tickValues(tickValues) - .orient('left'); - - xAxis2 = d3.svg.axis() - .scale(xScale2) - .tickFormat(tickFormat) - .ticks(6) - .orient('bottom'); - - yAxis2 = d3.svg.axis() - .scale(yScale2) - .tickFormat(d3.format('d')) - .tickValues(tickValues) - .orient('right'); - - // setup a brush - brush = d3.svg.brush() - .x(xScale2) - .on('brushstart', brushStarted) - .on('brush', brushed) - .on('brushend', brushEnded); - - updateChart(true); + // clear user brush tracking + brushInProgress = false; + } + }, DEBOUNCE_MS); + + function brushStarted() { + // update the opacity of the context data points to brush extent + context.selectAll('circle') + .data(data) + .style('opacity', function (d) { return 1; }); + } + + function brushEnded() { + // update the opacity of the context data points to brush extent + context.selectAll('circle') + .data(data) + .style('opacity', function (d) { return highlightBrushPoints(d) }); + } + + function alarmingNow() { + return $('#container').hasClass('alarming'); + } + + function inRetroMode() { + if (!brush) return false; + + var time = brush.extent()[1].getTime(); + + return !alarmingNow() && time - THIRTY_MINS_IN_MS < now; + } + + function errorCodeToDisplay(errorCode) { + var errorDisplay; + + switch (parseInt(errorCode)) { + case 0: errorDisplay = '??0'; break; //None + case 1: errorDisplay = '?SN'; break; //SENSOR_NOT_ACTIVE + case 2: errorDisplay = '??2'; break; //MINIMAL_DEVIATION + case 3: errorDisplay = '?NA'; break; //NO_ANTENNA + case 5: errorDisplay = '?NC'; break; //SENSOR_NOT_CALIBRATED + case 6: errorDisplay = '?CD'; break; //COUNTS_DEVIATION + case 7: errorDisplay = '??7'; break; //? + case 8: errorDisplay = '??8'; break; //? + case 9: errorDisplay = '?HG'; break; //ABSOLUTE_DEVIATION + case 10: errorDisplay = '???'; break; //POWER_DEVIATION + case 12: errorDisplay = '?RF'; break; //BAD_RF + default: errorDisplay = '?' + parseInt(errorCode) + '?'; break; } - // get the desired opacity for context chart based on the brush extent - function highlightBrushPoints(data) { - if (data.date.getTime() >= brush.extent()[0].getTime() && data.date.getTime() <= brush.extent()[1].getTime()) { - return futureOpacity(data.date.getTime() - latestSGV.x); - } else { - return 0.5; - } + return errorDisplay; + } + + var brushed = _.debounce(function brushed(skipTimer) { + + if (!skipTimer) { + // set a timer to reset focus chart to real-time data + clearTimeout(brushTimer); + brushTimer = setTimeout(updateBrushToNow, BRUSH_TIMEOUT); + brushInProgress = true; } - // clears the current user brush and resets to the current real time data - var updateBrushToNow = _.debounce(function updateBrushToNow(skipBrushing) { + var brushExtent = brush.extent(); - // get current time range - var dataRange = d3.extent(data, dateFn); + // ensure that brush extent is fixed at 3.5 hours + if (brushExtent[1].getTime() - brushExtent[0].getTime() != foucusRangeMS) { - // update brush and focus chart with recent data + // ensure that brush updating is with the time range + if (brushExtent[0].getTime() + foucusRangeMS > d3.extent(data, dateFn)[1].getTime()) { + brushExtent[0] = new Date(brushExtent[1].getTime() - foucusRangeMS); + d3.select('.brush') + .call(brush.extent([brushExtent[0], brushExtent[1]])); + } else { + brushExtent[1] = new Date(brushExtent[0].getTime() + foucusRangeMS); d3.select('.brush') - .transition() - .duration(UPDATE_TRANS_MS) - .call(brush.extent([new Date(dataRange[1].getTime() - foucusRangeMS), dataRange[1]])); + .call(brush.extent([brushExtent[0], brushExtent[1]])); + } + } - if (!skipBrushing) { - brushed(true); + var nowDate = new Date(brushExtent[1] - THIRTY_MINS_IN_MS); + + var bgButton = $('.bgButton') + , bgStatus = $('.bgStatus') + , currentBG = $('.bgStatus .currentBG') + , currentDirection = $('.bgStatus .currentDirection') + , majorPills = $('.bgStatus .majorPills') + , minorPills = $('.bgStatus .minorPills') + , rawNoise = bgButton.find('.rawnoise') + , rawbg = rawNoise.find('em') + , noiseLevel = rawNoise.find('label') + , lastEntry = $('#lastEntry'); + + + function updateCurrentSGV(entry) { + var value = entry.y + , time = new Date(entry.x).getTime() + , ago = timeAgo(time) + , isCurrent = ago.status === 'current'; + + if (value == 9) { + currentBG.text(''); + } else if (value < 39) { + currentBG.html(errorCodeToDisplay(value)); + } else if (value < 40) { + currentBG.text('LOW'); + } else if (value > 400) { + currentBG.text('HIGH'); + } else { + currentBG.text(scaleBg(value)); + } - // clear user brush tracking - brushInProgress = false; + bgStatus.toggleClass('current', alarmingNow() || (isCurrent && !inRetroMode())); + if (!alarmingNow()) { + bgButton.removeClass('urgent warning inrange'); + if (isCurrent && !inRetroMode()) { + bgButton.addClass(sgvToColoredRange(value)); } - }, DEBOUNCE_MS); + } - function brushStarted() { - // update the opacity of the context data points to brush extent - context.selectAll('circle') - .data(data) - .style('opacity', function (d) { return 1; }); - } + if (showRawBGs(entry.y, entry.noise, cal)) { + rawNoise.css('display', 'inline-block'); + rawbg.text(scaleBg(rawIsigToRawBg(entry, cal))); + noiseLevel.text(noiseCodeToDisplay(entry.y, entry.noise)); + } else { + rawNoise.hide(); + } - function brushEnded() { - // update the opacity of the context data points to brush extent - context.selectAll('circle') - .data(data) - .style('opacity', function (d) { return highlightBrushPoints(d) }); - } - function alarmingNow() { - return $('#container').hasClass('alarming'); + currentBG.toggleClass('icon-hourglass', value == 9); + currentBG.toggleClass('error-code', value < 39); + currentBG.toggleClass('bg-limit', value == 39 || value > 400); + + $('.container').removeClass('loading'); + } - function inRetroMode() { - if (!brush) return false; + function updateBGDelta(prevEntry, currentEntry) { + + var pill = majorPills.find('span.pill.bgdelta'); + if (!pill || pill.length == 0) { + pill = $(''); + majorPills.append(pill); + } + + var deltaDisplay = calcDeltaDisplay(prevEntry, currentEntry); - var time = brush.extent()[1].getTime(); + if (deltaDisplay == null) { + pill.children('em').hide(); + } else { + pill.children('em').text(deltaDisplay).show(); + } + + if (browserSettings.units == 'mmol') { + pill.children('label').text('mmol/L'); + } else { + pill.children('label').text('mg/dL'); + } - return !alarmingNow() && time - THIRTY_MINS_IN_MS < now; } - function errorCodeToDisplay(errorCode) { - var errorDisplay; - - switch (parseInt(errorCode)) { - case 0: errorDisplay = '??0'; break; //None - case 1: errorDisplay = '?SN'; break; //SENSOR_NOT_ACTIVE - case 2: errorDisplay = '??2'; break; //MINIMAL_DEVIATION - case 3: errorDisplay = '?NA'; break; //NO_ANTENNA - case 5: errorDisplay = '?NC'; break; //SENSOR_NOT_CALIBRATED - case 6: errorDisplay = '?CD'; break; //COUNTS_DEVIATION - case 7: errorDisplay = '??7'; break; //? - case 8: errorDisplay = '??8'; break; //? - case 9: errorDisplay = '?HG'; break; //ABSOLUTE_DEVIATION - case 10: errorDisplay = '???'; break; //POWER_DEVIATION - case 12: errorDisplay = '?RF'; break; //BAD_RF - default: errorDisplay = '?' + parseInt(errorCode) + '?'; break; + function updatePlugins(sgv, time) { + var env = {}; + env.profile = profile; + env.majorPills = majorPills; + env.minorPills = minorPills; + env.sgv = Number(sgv); + env.treatments = treatments; + env.time = time; + env.tooltip = tooltip; + + //all enabled plugins get a chance to add data, even if they aren't shown + Nightscout.plugins.eachEnabledPlugin(function updateEachPlugin(plugin) { + // Update the env through data provider plugins + plugin.setEnv(env); + + // check if the plugin implements processing data + if (plugin.getData) { + env[plugin.name] = plugin.getData(); } + }); - return errorDisplay; + // update data for all the plugins, before updating visualisations + Nightscout.plugins.setEnvs(env); + + //only shown plugins get a chance to update visualisations + Nightscout.plugins.updateVisualisations(browserSettings); } - var brushed = _.debounce(function brushed(skipTimer) { + // predict for retrospective data + // by changing lookback from 1 to 2, we modify the AR algorithm to determine its initial slope from 10m + // of data instead of 5, which eliminates the incorrect and misleading predictions generated when + // the dexcom switches from unfiltered to filtered at the start of a rapid rise or fall, while preserving + // almost identical predications at other times. + var lookback = 2; - if (!skipTimer) { - // set a timer to reset focus chart to real-time data - clearTimeout(brushTimer); - brushTimer = setTimeout(updateBrushToNow, BRUSH_TIMEOUT); - brushInProgress = true; - } + var nowData = data.filter(function(d) { + return d.type == 'sgv'; + }); - var brushExtent = brush.extent(); - - // ensure that brush extent is fixed at 3.5 hours - if (brushExtent[1].getTime() - brushExtent[0].getTime() != foucusRangeMS) { - - // ensure that brush updating is with the time range - if (brushExtent[0].getTime() + foucusRangeMS > d3.extent(data, dateFn)[1].getTime()) { - brushExtent[0] = new Date(brushExtent[1].getTime() - foucusRangeMS); - d3.select('.brush') - .call(brush.extent([brushExtent[0], brushExtent[1]])); - } else { - brushExtent[1] = new Date(brushExtent[0].getTime() + foucusRangeMS); - d3.select('.brush') - .call(brush.extent([brushExtent[0], brushExtent[1]])); - } + if (inRetroMode()) { + var retroTime = new Date(brushExtent[1] - THIRTY_MINS_IN_MS); + + // filter data for -12 and +5 minutes from reference time for retrospective focus data prediction + var lookbackTime = (lookback + 2) * FIVE_MINS_IN_MS + 2 * ONE_MIN_IN_MS; + nowData = nowData.filter(function(d) { + return d.date.getTime() >= brushExtent[1].getTime() - TWENTY_FIVE_MINS_IN_MS - lookbackTime && + d.date.getTime() <= brushExtent[1].getTime() - TWENTY_FIVE_MINS_IN_MS + }); + + // sometimes nowData contains duplicates. uniq it. + var lastDate = new Date('1/1/1970'); + nowData = nowData.filter(function(d) { + var ok = (lastDate.getTime() + ONE_MIN_IN_MS) < d.date.getTime(); + lastDate = d.date; + return ok; + }); + + var focusPoint = nowData.length > 0 ? nowData[nowData.length - 1] : null; + if (focusPoint) { + updateCurrentSGV(focusPoint); + currentDirection.html(focusPoint.y < 39 ? '✖' : focusPoint.direction); + + var prevfocusPoint = nowData.length > lookback ? nowData[nowData.length - 2] : null; + if (prevfocusPoint) { + updateBGDelta(prevfocusPoint, focusPoint); + } else { + updateBGDelta(); } + } else { + updateBGDelta(); + currentBG.text('---'); + currentDirection.text('-'); + rawNoise.hide(); + bgButton.removeClass('urgent warning inrange'); + } - var nowDate = new Date(brushExtent[1] - THIRTY_MINS_IN_MS); - - var bgButton = $('.bgButton') - , bgStatus = $('.bgStatus') - , currentBG = $('.bgStatus .currentBG') - , currentDirection = $('.bgStatus .currentDirection') - , majorPills = $('.bgStatus .majorPills') - , minorPills = $('.bgStatus .minorPills') - , rawNoise = bgButton.find('.rawnoise') - , rawbg = rawNoise.find('em') - , noiseLevel = rawNoise.find('label') - , lastEntry = $('#lastEntry'); - - - function updateCurrentSGV(entry) { - var value = entry.y - , time = new Date(entry.x).getTime() - , ago = timeAgo(time) - , isCurrent = ago.status === 'current'; - - if (value == 9) { - currentBG.text(''); - } else if (value < 39) { - currentBG.html(errorCodeToDisplay(value)); - } else if (value < 40) { - currentBG.text('LOW'); - } else if (value > 400) { - currentBG.text('HIGH'); - } else { - currentBG.text(scaleBg(value)); - } - - bgStatus.toggleClass('current', alarmingNow() || (isCurrent && !inRetroMode())); - if (!alarmingNow()) { - bgButton.removeClass('urgent warning inrange'); - if (isCurrent && !inRetroMode()) { - bgButton.addClass(sgvToColoredRange(value)); - } - } - - if (showRawBGs(entry.y, entry.noise, cal)) { - rawNoise.css('display', 'inline-block'); - rawbg.text(scaleBg(rawIsigToRawBg(entry, cal))); - noiseLevel.text(noiseCodeToDisplay(entry.y, entry.noise)); - } else { - rawNoise.hide(); - } - - - currentBG.toggleClass('icon-hourglass', value == 9); - currentBG.toggleClass('error-code', value < 39); - currentBG.toggleClass('bg-limit', value == 39 || value > 400); - - $('.container').removeClass('loading'); + updatePlugins(focusPoint && focusPoint.y, retroTime); + + $('#currentTime') + .text(formatTime(retroTime, true)) + .css('text-decoration','line-through'); + + updateTimeAgo(); + } else { + // if the brush comes back into the current time range then it should reset to the current time and sg + nowData = nowData.slice(nowData.length - 1 - lookback, nowData.length); + nowDate = new Date(now); + + updateCurrentSGV(latestSGV); + updateClockDisplay(); + updateTimeAgo(); + + var battery = devicestatusData && devicestatusData.uploaderBattery; + if (battery) { + $('#uploaderBattery em').text(battery + '%'); + $('#uploaderBattery label') + .toggleClass('icon-battery-100', battery >= 95) + .toggleClass('icon-battery-75', battery < 95 && battery >= 55) + .toggleClass('icon-battery-50', battery < 55 && battery >= 30) + .toggleClass('icon-battery-25', battery < 30); + + $('#uploaderBattery') + .show() + .toggleClass('warn', battery <= 30 && battery > 20) + .toggleClass('urgent', battery <= 20); + } else { + $('#uploaderBattery').hide(); + } - } + updateBGDelta(prevSGV, latestSGV); - function updateBGDelta(prevEntry, currentEntry) { + updatePlugins(latestSGV.y, nowDate); - var pill = majorPills.find('span.pill.bgdelta'); - if (!pill || pill.length == 0) { - pill = $(''); - majorPills.append(pill); - } + currentDirection.html(latestSGV.y < 39 ? '✖' : latestSGV.direction); + } - var deltaDisplay = calcDeltaDisplay(prevEntry, currentEntry); + xScale.domain(brush.extent()); - if (deltaDisplay == null) { - pill.children('em').hide(); - } else { - pill.children('em').text(deltaDisplay).show(); - } + // get slice of data so that concatenation of predictions do not interfere with subsequent updates + var focusData = data.slice(); + if (nowData.length > lookback) { + focusData = focusData.concat(predictAR(nowData, lookback)); + } - if (browserSettings.units == 'mmol') { - pill.children('label').text('mmol/L'); - } else { - pill.children('label').text('mg/dL'); - } + // bind up the focus chart data to an array of circles + // selects all our data into data and uses date function to get current max date + var focusCircles = focus.selectAll('circle').data(focusData, dateFn); - } + var focusRangeAdjustment = foucusRangeMS == THREE_HOURS_MS ? 1 : 1 + ((foucusRangeMS - THREE_HOURS_MS) / THREE_HOURS_MS / 8); - function updatePlugins(sgv, time) { - var env = {}; - env.profile = profile; - env.majorPills = majorPills; - env.minorPills = minorPills; - env.sgv = Number(sgv); - env.treatments = treatments; - env.time = time; - env.tooltip = tooltip; - - //all enabled plugins get a chance to add data, even if they aren't shown - Nightscout.plugins.eachEnabledPlugin(function updateEachPlugin(plugin) { - // Update the env through data provider plugins - plugin.setEnv(env); - - // check if the plugin implements processing data - if (plugin.getData) { - env[plugin.name] = plugin.getData(); - } - }); - - // update data for all the plugins, before updating visualisations - Nightscout.plugins.setEnvs(env); - - //only shown plugins get a chance to update visualisations - Nightscout.plugins.updateVisualisations(browserSettings); - } + var dotRadius = function(type) { + var radius = prevChartWidth > WIDTH_BIG_DOTS ? 4 : (prevChartWidth < WIDTH_SMALL_DOTS ? 2 : 3); + if (type == 'mbg') radius *= 2; + else if (type == 'rawbg') radius = Math.min(2, radius - 1); - // predict for retrospective data - // by changing lookback from 1 to 2, we modify the AR algorithm to determine its initial slope from 10m - // of data instead of 5, which eliminates the incorrect and misleading predictions generated when - // the dexcom switches from unfiltered to filtered at the start of a rapid rise or fall, while preserving - // almost identical predications at other times. - var lookback = 2; + return radius / focusRangeAdjustment; + }; - var nowData = data.filter(function(d) { - return d.type == 'sgv'; - }); + function isDexcom(device) { + return device && device.toLowerCase().indexOf('dexcom') == 0; + } - if (inRetroMode()) { - var retroTime = new Date(brushExtent[1] - THIRTY_MINS_IN_MS); - - // filter data for -12 and +5 minutes from reference time for retrospective focus data prediction - var lookbackTime = (lookback + 2) * FIVE_MINS_IN_MS + 2 * ONE_MIN_IN_MS; - nowData = nowData.filter(function(d) { - return d.date.getTime() >= brushExtent[1].getTime() - TWENTY_FIVE_MINS_IN_MS - lookbackTime && - d.date.getTime() <= brushExtent[1].getTime() - TWENTY_FIVE_MINS_IN_MS - }); - - // sometimes nowData contains duplicates. uniq it. - var lastDate = new Date('1/1/1970'); - nowData = nowData.filter(function(d) { - var ok = (lastDate.getTime() + ONE_MIN_IN_MS) < d.date.getTime(); - lastDate = d.date; - return ok; - }); - - var focusPoint = nowData.length > 0 ? nowData[nowData.length - 1] : null; - if (focusPoint) { - updateCurrentSGV(focusPoint); - currentDirection.html(focusPoint.y < 39 ? '✖' : focusPoint.direction); - - var prevfocusPoint = nowData.length > lookback ? nowData[nowData.length - 2] : null; - if (prevfocusPoint) { - updateBGDelta(prevfocusPoint, focusPoint); - } else { - updateBGDelta(); - } - } else { - updateBGDelta(); - currentBG.text('---'); - currentDirection.text('-'); - rawNoise.hide(); - bgButton.removeClass('urgent warning inrange'); - } - - updatePlugins(focusPoint && focusPoint.y, retroTime); - - $('#currentTime') - .text(formatTime(retroTime, true)) - .css('text-decoration','line-through'); - - updateTimeAgo(); - } else { - // if the brush comes back into the current time range then it should reset to the current time and sg - nowData = nowData.slice(nowData.length - 1 - lookback, nowData.length); - nowDate = new Date(now); - - updateCurrentSGV(latestSGV); - updateClockDisplay(); - updateTimeAgo(); - - var battery = devicestatusData && devicestatusData.uploaderBattery; - if (battery) { - $('#uploaderBattery em').text(battery + '%'); - $('#uploaderBattery label') - .toggleClass('icon-battery-100', battery >= 95) - .toggleClass('icon-battery-75', battery < 95 && battery >= 55) - .toggleClass('icon-battery-50', battery < 55 && battery >= 30) - .toggleClass('icon-battery-25', battery < 30); - - $('#uploaderBattery') - .show() - .toggleClass('warn', battery <= 30 && battery > 20) - .toggleClass('urgent', battery <= 20); - } else { - $('#uploaderBattery').hide(); - } - - updateBGDelta(prevSGV, latestSGV); - - updatePlugins(latestSGV.y, nowDate); - - currentDirection.html(latestSGV.y < 39 ? '✖' : latestSGV.direction); - } + function prepareFocusCircles(sel) { + var badData = []; + sel.attr('cx', function (d) { return xScale(d.date); }) + .attr('cy', function (d) { + if (isNaN(d.sgv)) { + badData.push(d); + return yScale(scaleBg(450)); + } else { + return yScale(d.sgv); + } + }) + .attr('fill', function (d) { return d.color; }) + .attr('opacity', function (d) { return futureOpacity(d.date.getTime() - latestSGV.x); }) + .attr('stroke-width', function (d) { if (d.type == 'mbg') return 2; else return 0; }) + .attr('stroke', function (d) { + return (isDexcom(d.device) ? 'white' : '#0099ff'); + }) + .attr('r', function (d) { return dotRadius(d.type); }); + + if (badData.length > 0) { + console.warn("Bad Data: isNaN(sgv)", badData); + } + + return sel; + } + + // if already existing then transition each circle to its new position + prepareFocusCircles(focusCircles.transition().duration(UPDATE_TRANS_MS)); - xScale.domain(brush.extent()); + // if new circle then just display + prepareFocusCircles(focusCircles.enter().append('circle')) + .on('mouseover', function (d) { + if (d.type != 'sgv' && d.type != 'mbg') return; - // get slice of data so that concatenation of predictions do not interfere with subsequent updates - var focusData = data.slice(); - if (nowData.length > lookback) { - focusData = focusData.concat(predictAR(nowData, lookback)); + var bgType = (d.type == 'sgv' ? 'CGM' : (isDexcom(d.device) ? 'Calibration' : 'Meter')) + , rawBG = 0 + , noiseLabel = ''; + + if (d.type == 'sgv') { + if (showRawBGs(d.y, d.noise, cal)) { + rawBG = scaleBg(rawIsigToRawBg(d, cal)); + } + noiseLabel = noiseCodeToDisplay(d.y, d.noise); } - // bind up the focus chart data to an array of circles - // selects all our data into data and uses date function to get current max date - var focusCircles = focus.selectAll('circle').data(focusData, dateFn); + tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); + tooltip.html('' + bgType + ' BG: ' + d.sgv + + (d.type == 'mbg' ? '
    Device: ' + d.device : '') + + (rawBG ? '
    Raw BG: ' + rawBG : '') + + (noiseLabel ? '
    Noise: ' + noiseLabel : '') + + '
    Time: ' + formatTime(d.date)) + .style('left', (d3.event.pageX) + 'px') + .style('top', (d3.event.pageY + 15) + 'px'); + }) + .on('mouseout', function (d) { + if (d.type != 'sgv' && d.type != 'mbg') return; + tooltip.transition() + .duration(TOOLTIP_TRANS_MS) + .style('opacity', 0); + }); + + focusCircles.exit() + .remove(); + + // remove all insulin/carb treatment bubbles so that they can be redrawn to correct location + d3.selectAll('.path').remove(); + + // add treatment bubbles + // a higher bubbleScale will produce smaller bubbles (it's not a radius like focusDotRadius) + var bubbleScale = (prevChartWidth < WIDTH_SMALL_DOTS ? 4 : (prevChartWidth < WIDTH_BIG_DOTS ? 3 : 2)) * focusRangeAdjustment; + + focus.selectAll('circle') + .data(treatments) + .each(function (d) { drawTreatment(d, bubbleScale, true) }); + + // transition open-top line to correct location + focus.select('.open-top') + .attr('x1', xScale2(brush.extent()[0])) + .attr('y1', yScale(scaleBg(30))) + .attr('x2', xScale2(brush.extent()[1])) + .attr('y2', yScale(scaleBg(30))); + + // transition open-left line to correct location + focus.select('.open-left') + .attr('x1', xScale2(brush.extent()[0])) + .attr('y1', focusHeight) + .attr('x2', xScale2(brush.extent()[0])) + .attr('y2', prevChartHeight); + + // transition open-right line to correct location + focus.select('.open-right') + .attr('x1', xScale2(brush.extent()[1])) + .attr('y1', focusHeight) + .attr('x2', xScale2(brush.extent()[1])) + .attr('y2', prevChartHeight); + + focus.select('.now-line') + .transition() + .duration(UPDATE_TRANS_MS) + .attr('x1', xScale(nowDate)) + .attr('y1', yScale(scaleBg(36))) + .attr('x2', xScale(nowDate)) + .attr('y2', yScale(scaleBg(420))); + + context.select('.now-line') + .transition() + .attr('x1', xScale2(new Date(brush.extent()[1]- THIRTY_MINS_IN_MS))) + .attr('y1', yScale2(scaleBg(36))) + .attr('x2', xScale2(new Date(brush.extent()[1]- THIRTY_MINS_IN_MS))) + .attr('y2', yScale2(scaleBg(420))); + + // update x axis + focus.select('.x.axis') + .call(xAxis); + + // add clipping path so that data stays within axis + focusCircles.attr('clip-path', 'url(#clip)'); + + function prepareTreatCircles(sel) { + sel.attr('cx', function (d) { return xScale(d.created_at); }) + .attr('cy', function (d) { return yScale(d.displayBG); }) + .attr('r', function () { return dotRadius('mbg'); }) + .attr('stroke-width', 2) + .attr('stroke', function (d) { return d.glucose ? 'grey' : 'white'; }) + .attr('fill', function (d) { return d.glucose ? 'red' : 'grey'; }); + + return sel; + } - var focusRangeAdjustment = foucusRangeMS == THREE_HOURS_MS ? 1 : 1 + ((foucusRangeMS - THREE_HOURS_MS) / THREE_HOURS_MS / 8); + //NOTE: treatments with insulin or carbs are drawn by drawTreatment() + // bind up the focus chart data to an array of circles + var treatCircles = focus.selectAll('rect').data(treatments.filter(function(treatment) { + return !treatment.carbs && !treatment.insulin; + })); + + // if already existing then transition each circle to its new position + prepareTreatCircles(treatCircles.transition().duration(UPDATE_TRANS_MS)); + + // if new circle then just display + prepareTreatCircles(treatCircles.enter().append('circle')) + .on('mouseover', function (d) { + tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); + tooltip.html('Time: ' + formatTime(d.created_at) + '
    ' + + (d.eventType ? 'Treatment type: ' + d.eventType + '
    ' : '') + + (d.glucose ? 'BG: ' + d.glucose + (d.glucoseType ? ' (' + d.glucoseType + ')': '') + '
    ' : '') + + (d.enteredBy ? 'Entered by: ' + d.enteredBy + '
    ' : '') + + (d.notes ? 'Notes: ' + d.notes : '') + ) + .style('left', (d3.event.pageX) + 'px') + .style('top', (d3.event.pageY + 15) + 'px'); + }) + .on('mouseout', function () { + tooltip.transition() + .duration(TOOLTIP_TRANS_MS) + .style('opacity', 0); + }); + + treatCircles.attr('clip-path', 'url(#clip)'); + }, DEBOUNCE_MS); + + // called for initial update and updates for resize + var updateChart = _.debounce(function updateChart(init) { + + if (documentHidden && !init) { + console.info('Document Hidden, not updating - ' + (new Date())); + return; + } + // get current data range + var dataRange = d3.extent(data, dateFn); - var dotRadius = function(type) { - var radius = prevChartWidth > WIDTH_BIG_DOTS ? 4 : (prevChartWidth < WIDTH_SMALL_DOTS ? 2 : 3); - if (type == 'mbg') radius *= 2; - else if (type == 'rawbg') radius = Math.min(2, radius - 1); + // get the entire container height and width subtracting the padding + var chartWidth = (document.getElementById('chartContainer') + .getBoundingClientRect().width) - padding.left - padding.right; - return radius / focusRangeAdjustment; - }; + var chartHeight = (document.getElementById('chartContainer') + .getBoundingClientRect().height) - padding.top - padding.bottom; - function isDexcom(device) { - return device && device.toLowerCase().indexOf('dexcom') == 0; - } + // get the height of each chart based on its container size ratio + focusHeight = chartHeight * .7; + contextHeight = chartHeight * .2; - function prepareFocusCircles(sel) { - var badData = []; - sel.attr('cx', function (d) { return xScale(d.date); }) - .attr('cy', function (d) { - if (isNaN(d.sgv)) { - badData.push(d); - return yScale(scaleBg(450)); - } else { - return yScale(d.sgv); - } - }) - .attr('fill', function (d) { return d.color; }) - .attr('opacity', function (d) { return futureOpacity(d.date.getTime() - latestSGV.x); }) - .attr('stroke-width', function (d) { if (d.type == 'mbg') return 2; else return 0; }) - .attr('stroke', function (d) { - return (isDexcom(d.device) ? 'white' : '#0099ff'); - }) - .attr('r', function (d) { return dotRadius(d.type); }); - - if (badData.length > 0) { - console.warn("Bad Data: isNaN(sgv)", badData); - } - - return sel; - } + // get current brush extent + var currentBrushExtent = brush.extent(); - // if already existing then transition each circle to its new position - prepareFocusCircles(focusCircles.transition().duration(UPDATE_TRANS_MS)); - - // if new circle then just display - prepareFocusCircles(focusCircles.enter().append('circle')) - .on('mouseover', function (d) { - if (d.type != 'sgv' && d.type != 'mbg') return; - - var bgType = (d.type == 'sgv' ? 'CGM' : (isDexcom(d.device) ? 'Calibration' : 'Meter')) - , rawBG = 0 - , noiseLabel = ''; - - if (d.type == 'sgv') { - if (showRawBGs(d.y, d.noise, cal)) { - rawBG = scaleBg(rawIsigToRawBg(d, cal)); - } - noiseLabel = noiseCodeToDisplay(d.y, d.noise); - } - - tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); - tooltip.html('' + bgType + ' BG: ' + d.sgv + - (d.type == 'mbg' ? '
    Device: ' + d.device : '') + - (rawBG ? '
    Raw BG: ' + rawBG : '') + - (noiseLabel ? '
    Noise: ' + noiseLabel : '') + - '
    Time: ' + formatTime(d.date)) - .style('left', (d3.event.pageX) + 'px') - .style('top', (d3.event.pageY + 15) + 'px'); - }) - .on('mouseout', function (d) { - if (d.type != 'sgv' && d.type != 'mbg') return; - tooltip.transition() - .duration(TOOLTIP_TRANS_MS) - .style('opacity', 0); - }); - - focusCircles.exit() - .remove(); - - // remove all insulin/carb treatment bubbles so that they can be redrawn to correct location - d3.selectAll('.path').remove(); - - // add treatment bubbles - // a higher bubbleScale will produce smaller bubbles (it's not a radius like focusDotRadius) - var bubbleScale = (prevChartWidth < WIDTH_SMALL_DOTS ? 4 : (prevChartWidth < WIDTH_BIG_DOTS ? 3 : 2)) * focusRangeAdjustment; - - focus.selectAll('circle') - .data(treatments) - .each(function (d) { drawTreatment(d, bubbleScale, true) }); + // only redraw chart if chart size has changed + if ((prevChartWidth != chartWidth) || (prevChartHeight != chartHeight)) { + + prevChartWidth = chartWidth; + prevChartHeight = chartHeight; + + //set the width and height of the SVG element + charts.attr('width', chartWidth + padding.left + padding.right) + .attr('height', chartHeight + padding.top + padding.bottom); + + // ranges are based on the width and height available so reset + xScale.range([0, chartWidth]); + xScale2.range([0, chartWidth]); + yScale.range([focusHeight, 0]); + yScale2.range([chartHeight, chartHeight - contextHeight]); + + if (init) { + + // if first run then just display axis with no transition + focus.select('.x') + .attr('transform', 'translate(0,' + focusHeight + ')') + .call(xAxis); + + focus.select('.y') + .attr('transform', 'translate(' + chartWidth + ',0)') + .call(yAxis); + + // if first run then just display axis with no transition + context.select('.x') + .attr('transform', 'translate(0,' + chartHeight + ')') + .call(xAxis2); + + context.append('g') + .attr('class', 'x brush') + .call(d3.svg.brush().x(xScale2).on('brush', brushed)) + .selectAll('rect') + .attr('y', focusHeight) + .attr('height', chartHeight - focusHeight); + + // disable resizing of brush + d3.select('.x.brush').select('.background').style('cursor', 'move'); + d3.select('.x.brush').select('.resize.e').style('cursor', 'move'); + d3.select('.x.brush').select('.resize.w').style('cursor', 'move'); + + // create a clipPath for when brushing + clip = charts.append('defs') + .append('clipPath') + .attr('id', 'clip') + .append('rect') + .attr('height', chartHeight) + .attr('width', chartWidth); + + // add a line that marks the current time + focus.append('line') + .attr('class', 'now-line') + .attr('x1', xScale(new Date(now))) + .attr('y1', yScale(scaleBg(30))) + .attr('x2', xScale(new Date(now))) + .attr('y2', yScale(scaleBg(420))) + .style('stroke-dasharray', ('3, 3')) + .attr('stroke', 'grey'); + + // add a y-axis line that shows the high bg threshold + focus.append('line') + .attr('class', 'high-line') + .attr('x1', xScale(dataRange[0])) + .attr('y1', yScale(scaleBg(app.thresholds.bg_high))) + .attr('x2', xScale(dataRange[1])) + .attr('y2', yScale(scaleBg(app.thresholds.bg_high))) + .style('stroke-dasharray', ('1, 6')) + .attr('stroke', '#777'); + + // add a y-axis line that shows the high bg threshold + focus.append('line') + .attr('class', 'target-top-line') + .attr('x1', xScale(dataRange[0])) + .attr('y1', yScale(scaleBg(app.thresholds.bg_target_top))) + .attr('x2', xScale(dataRange[1])) + .attr('y2', yScale(scaleBg(app.thresholds.bg_target_top))) + .style('stroke-dasharray', ('3, 3')) + .attr('stroke', 'grey'); + + // add a y-axis line that shows the low bg threshold + focus.append('line') + .attr('class', 'target-bottom-line') + .attr('x1', xScale(dataRange[0])) + .attr('y1', yScale(scaleBg(app.thresholds.bg_target_bottom))) + .attr('x2', xScale(dataRange[1])) + .attr('y2', yScale(scaleBg(app.thresholds.bg_target_bottom))) + .style('stroke-dasharray', ('3, 3')) + .attr('stroke', 'grey'); + + // add a y-axis line that shows the low bg threshold + focus.append('line') + .attr('class', 'low-line') + .attr('x1', xScale(dataRange[0])) + .attr('y1', yScale(scaleBg(app.thresholds.bg_low))) + .attr('x2', xScale(dataRange[1])) + .attr('y2', yScale(scaleBg(app.thresholds.bg_low))) + .style('stroke-dasharray', ('1, 6')) + .attr('stroke', '#777'); + + // add a y-axis line that opens up the brush extent from the context to the focus + focus.append('line') + .attr('class', 'open-top') + .attr('stroke', 'black') + .attr('stroke-width', 2); + + // add a x-axis line that closes the the brush container on left side + focus.append('line') + .attr('class', 'open-left') + .attr('stroke', 'white'); + + // add a x-axis line that closes the the brush container on right side + focus.append('line') + .attr('class', 'open-right') + .attr('stroke', 'white'); + + // add a line that marks the current time + context.append('line') + .attr('class', 'now-line') + .attr('x1', xScale(new Date(now))) + .attr('y1', yScale2(scaleBg(36))) + .attr('x2', xScale(new Date(now))) + .attr('y2', yScale2(scaleBg(420))) + .style('stroke-dasharray', ('3, 3')) + .attr('stroke', 'grey'); + + // add a y-axis line that shows the high bg threshold + context.append('line') + .attr('class', 'high-line') + .attr('x1', xScale(dataRange[0])) + .attr('y1', yScale2(scaleBg(app.thresholds.bg_target_top))) + .attr('x2', xScale(dataRange[1])) + .attr('y2', yScale2(scaleBg(app.thresholds.bg_target_top))) + .style('stroke-dasharray', ('3, 3')) + .attr('stroke', 'grey'); + + // add a y-axis line that shows the low bg threshold + context.append('line') + .attr('class', 'low-line') + .attr('x1', xScale(dataRange[0])) + .attr('y1', yScale2(scaleBg(app.thresholds.bg_target_bottom))) + .attr('x2', xScale(dataRange[1])) + .attr('y2', yScale2(scaleBg(app.thresholds.bg_target_bottom))) + .style('stroke-dasharray', ('3, 3')) + .attr('stroke', 'grey'); + + } else { + + // for subsequent updates use a transition to animate the axis to the new position + var focusTransition = focus.transition().duration(UPDATE_TRANS_MS); + + focusTransition.select('.x') + .attr('transform', 'translate(0,' + focusHeight + ')') + .call(xAxis); + + focusTransition.select('.y') + .attr('transform', 'translate(' + chartWidth + ', 0)') + .call(yAxis); + + var contextTransition = context.transition().duration(UPDATE_TRANS_MS); + + contextTransition.select('.x') + .attr('transform', 'translate(0,' + chartHeight + ')') + .call(xAxis2); + + // reset clip to new dimensions + clip.transition() + .attr('width', chartWidth) + .attr('height', chartHeight); + + // reset brush location + context.select('.x.brush') + .selectAll('rect') + .attr('y', focusHeight) + .attr('height', chartHeight - focusHeight); + + // clear current brush + d3.select('.brush').call(brush.clear()); + + // redraw old brush with new dimensions + d3.select('.brush').transition().duration(UPDATE_TRANS_MS).call(brush.extent(currentBrushExtent)); + + // transition lines to correct location + focus.select('.high-line') + .transition() + .duration(UPDATE_TRANS_MS) + .attr('x1', xScale(currentBrushExtent[0])) + .attr('y1', yScale(scaleBg(app.thresholds.bg_high))) + .attr('x2', xScale(currentBrushExtent[1])) + .attr('y2', yScale(scaleBg(app.thresholds.bg_high))); + + focus.select('.target-top-line') + .transition() + .duration(UPDATE_TRANS_MS) + .attr('x1', xScale(currentBrushExtent[0])) + .attr('y1', yScale(scaleBg(app.thresholds.bg_target_top))) + .attr('x2', xScale(currentBrushExtent[1])) + .attr('y2', yScale(scaleBg(app.thresholds.bg_target_top))); + + focus.select('.target-bottom-line') + .transition() + .duration(UPDATE_TRANS_MS) + .attr('x1', xScale(currentBrushExtent[0])) + .attr('y1', yScale(scaleBg(app.thresholds.bg_target_bottom))) + .attr('x2', xScale(currentBrushExtent[1])) + .attr('y2', yScale(scaleBg(app.thresholds.bg_target_bottom))); + + focus.select('.low-line') + .transition() + .duration(UPDATE_TRANS_MS) + .attr('x1', xScale(currentBrushExtent[0])) + .attr('y1', yScale(scaleBg(app.thresholds.bg_low))) + .attr('x2', xScale(currentBrushExtent[1])) + .attr('y2', yScale(scaleBg(app.thresholds.bg_low))); // transition open-top line to correct location focus.select('.open-top') - .attr('x1', xScale2(brush.extent()[0])) - .attr('y1', yScale(scaleBg(30))) - .attr('x2', xScale2(brush.extent()[1])) - .attr('y2', yScale(scaleBg(30))); + .transition() + .duration(UPDATE_TRANS_MS) + .attr('x1', xScale2(currentBrushExtent[0])) + .attr('y1', yScale(scaleBg(30))) + .attr('x2', xScale2(currentBrushExtent[1])) + .attr('y2', yScale(scaleBg(30))); // transition open-left line to correct location focus.select('.open-left') - .attr('x1', xScale2(brush.extent()[0])) - .attr('y1', focusHeight) - .attr('x2', xScale2(brush.extent()[0])) - .attr('y2', prevChartHeight); + .transition() + .duration(UPDATE_TRANS_MS) + .attr('x1', xScale2(currentBrushExtent[0])) + .attr('y1', focusHeight) + .attr('x2', xScale2(currentBrushExtent[0])) + .attr('y2', chartHeight); // transition open-right line to correct location focus.select('.open-right') - .attr('x1', xScale2(brush.extent()[1])) - .attr('y1', focusHeight) - .attr('x2', xScale2(brush.extent()[1])) - .attr('y2', prevChartHeight); - - focus.select('.now-line') - .transition() - .duration(UPDATE_TRANS_MS) - .attr('x1', xScale(nowDate)) - .attr('y1', yScale(scaleBg(36))) - .attr('x2', xScale(nowDate)) - .attr('y2', yScale(scaleBg(420))); - - context.select('.now-line') - .transition() - .attr('x1', xScale2(new Date(brush.extent()[1]- THIRTY_MINS_IN_MS))) - .attr('y1', yScale2(scaleBg(36))) - .attr('x2', xScale2(new Date(brush.extent()[1]- THIRTY_MINS_IN_MS))) - .attr('y2', yScale2(scaleBg(420))); - - // update x axis - focus.select('.x.axis') - .call(xAxis); - - // add clipping path so that data stays within axis - focusCircles.attr('clip-path', 'url(#clip)'); - - function prepareTreatCircles(sel) { - sel.attr('cx', function (d) { return xScale(d.created_at); }) - .attr('cy', function (d) { return yScale(d.displayBG); }) - .attr('r', function () { return dotRadius('mbg'); }) - .attr('stroke-width', 2) - .attr('stroke', function (d) { return d.glucose ? 'grey' : 'white'; }) - .attr('fill', function (d) { return d.glucose ? 'red' : 'grey'; }); - - return sel; - } - - //NOTE: treatments with insulin or carbs are drawn by drawTreatment() - // bind up the focus chart data to an array of circles - var treatCircles = focus.selectAll('rect').data(treatments.filter(function(treatment) { - return !treatment.carbs && !treatment.insulin; - })); - - // if already existing then transition each circle to its new position - prepareTreatCircles(treatCircles.transition().duration(UPDATE_TRANS_MS)); - - // if new circle then just display - prepareTreatCircles(treatCircles.enter().append('circle')) - .on('mouseover', function (d) { - tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); - tooltip.html('Time: ' + formatTime(d.created_at) + '
    ' + - (d.eventType ? 'Treatment type: ' + d.eventType + '
    ' : '') + - (d.glucose ? 'BG: ' + d.glucose + (d.glucoseType ? ' (' + d.glucoseType + ')': '') + '
    ' : '') + - (d.enteredBy ? 'Entered by: ' + d.enteredBy + '
    ' : '') + - (d.notes ? 'Notes: ' + d.notes : '') - ) - .style('left', (d3.event.pageX) + 'px') - .style('top', (d3.event.pageY + 15) + 'px'); - }) - .on('mouseout', function () { - tooltip.transition() - .duration(TOOLTIP_TRANS_MS) - .style('opacity', 0); - }); - - treatCircles.attr('clip-path', 'url(#clip)'); - }, DEBOUNCE_MS); - - // called for initial update and updates for resize - var updateChart = _.debounce(function updateChart(init) { - - if (documentHidden && !init) { - console.info('Document Hidden, not updating - ' + (new Date())); - return; - } - // get current data range - var dataRange = d3.extent(data, dateFn); - - // get the entire container height and width subtracting the padding - var chartWidth = (document.getElementById('chartContainer') - .getBoundingClientRect().width) - padding.left - padding.right; - - var chartHeight = (document.getElementById('chartContainer') - .getBoundingClientRect().height) - padding.top - padding.bottom; - - // get the height of each chart based on its container size ratio - focusHeight = chartHeight * .7; - contextHeight = chartHeight * .2; - - // get current brush extent - var currentBrushExtent = brush.extent(); - - // only redraw chart if chart size has changed - if ((prevChartWidth != chartWidth) || (prevChartHeight != chartHeight)) { - - prevChartWidth = chartWidth; - prevChartHeight = chartHeight; - - //set the width and height of the SVG element - charts.attr('width', chartWidth + padding.left + padding.right) - .attr('height', chartHeight + padding.top + padding.bottom); - - // ranges are based on the width and height available so reset - xScale.range([0, chartWidth]); - xScale2.range([0, chartWidth]); - yScale.range([focusHeight, 0]); - yScale2.range([chartHeight, chartHeight - contextHeight]); - - if (init) { - - // if first run then just display axis with no transition - focus.select('.x') - .attr('transform', 'translate(0,' + focusHeight + ')') - .call(xAxis); - - focus.select('.y') - .attr('transform', 'translate(' + chartWidth + ',0)') - .call(yAxis); - - // if first run then just display axis with no transition - context.select('.x') - .attr('transform', 'translate(0,' + chartHeight + ')') - .call(xAxis2); - - context.append('g') - .attr('class', 'x brush') - .call(d3.svg.brush().x(xScale2).on('brush', brushed)) - .selectAll('rect') - .attr('y', focusHeight) - .attr('height', chartHeight - focusHeight); - - // disable resizing of brush - d3.select('.x.brush').select('.background').style('cursor', 'move'); - d3.select('.x.brush').select('.resize.e').style('cursor', 'move'); - d3.select('.x.brush').select('.resize.w').style('cursor', 'move'); - - // create a clipPath for when brushing - clip = charts.append('defs') - .append('clipPath') - .attr('id', 'clip') - .append('rect') - .attr('height', chartHeight) - .attr('width', chartWidth); - - // add a line that marks the current time - focus.append('line') - .attr('class', 'now-line') - .attr('x1', xScale(new Date(now))) - .attr('y1', yScale(scaleBg(30))) - .attr('x2', xScale(new Date(now))) - .attr('y2', yScale(scaleBg(420))) - .style('stroke-dasharray', ('3, 3')) - .attr('stroke', 'grey'); - - // add a y-axis line that shows the high bg threshold - focus.append('line') - .attr('class', 'high-line') - .attr('x1', xScale(dataRange[0])) - .attr('y1', yScale(scaleBg(app.thresholds.bg_high))) - .attr('x2', xScale(dataRange[1])) - .attr('y2', yScale(scaleBg(app.thresholds.bg_high))) - .style('stroke-dasharray', ('1, 6')) - .attr('stroke', '#777'); - - // add a y-axis line that shows the high bg threshold - focus.append('line') - .attr('class', 'target-top-line') - .attr('x1', xScale(dataRange[0])) - .attr('y1', yScale(scaleBg(app.thresholds.bg_target_top))) - .attr('x2', xScale(dataRange[1])) - .attr('y2', yScale(scaleBg(app.thresholds.bg_target_top))) - .style('stroke-dasharray', ('3, 3')) - .attr('stroke', 'grey'); - - // add a y-axis line that shows the low bg threshold - focus.append('line') - .attr('class', 'target-bottom-line') - .attr('x1', xScale(dataRange[0])) - .attr('y1', yScale(scaleBg(app.thresholds.bg_target_bottom))) - .attr('x2', xScale(dataRange[1])) - .attr('y2', yScale(scaleBg(app.thresholds.bg_target_bottom))) - .style('stroke-dasharray', ('3, 3')) - .attr('stroke', 'grey'); - - // add a y-axis line that shows the low bg threshold - focus.append('line') - .attr('class', 'low-line') - .attr('x1', xScale(dataRange[0])) - .attr('y1', yScale(scaleBg(app.thresholds.bg_low))) - .attr('x2', xScale(dataRange[1])) - .attr('y2', yScale(scaleBg(app.thresholds.bg_low))) - .style('stroke-dasharray', ('1, 6')) - .attr('stroke', '#777'); - - // add a y-axis line that opens up the brush extent from the context to the focus - focus.append('line') - .attr('class', 'open-top') - .attr('stroke', 'black') - .attr('stroke-width', 2); - - // add a x-axis line that closes the the brush container on left side - focus.append('line') - .attr('class', 'open-left') - .attr('stroke', 'white'); - - // add a x-axis line that closes the the brush container on right side - focus.append('line') - .attr('class', 'open-right') - .attr('stroke', 'white'); - - // add a line that marks the current time - context.append('line') - .attr('class', 'now-line') - .attr('x1', xScale(new Date(now))) - .attr('y1', yScale2(scaleBg(36))) - .attr('x2', xScale(new Date(now))) - .attr('y2', yScale2(scaleBg(420))) - .style('stroke-dasharray', ('3, 3')) - .attr('stroke', 'grey'); - - // add a y-axis line that shows the high bg threshold - context.append('line') - .attr('class', 'high-line') - .attr('x1', xScale(dataRange[0])) - .attr('y1', yScale2(scaleBg(app.thresholds.bg_target_top))) - .attr('x2', xScale(dataRange[1])) - .attr('y2', yScale2(scaleBg(app.thresholds.bg_target_top))) - .style('stroke-dasharray', ('3, 3')) - .attr('stroke', 'grey'); - - // add a y-axis line that shows the low bg threshold - context.append('line') - .attr('class', 'low-line') - .attr('x1', xScale(dataRange[0])) - .attr('y1', yScale2(scaleBg(app.thresholds.bg_target_bottom))) - .attr('x2', xScale(dataRange[1])) - .attr('y2', yScale2(scaleBg(app.thresholds.bg_target_bottom))) - .style('stroke-dasharray', ('3, 3')) - .attr('stroke', 'grey'); - - } else { - - // for subsequent updates use a transition to animate the axis to the new position - var focusTransition = focus.transition().duration(UPDATE_TRANS_MS); - - focusTransition.select('.x') - .attr('transform', 'translate(0,' + focusHeight + ')') - .call(xAxis); - - focusTransition.select('.y') - .attr('transform', 'translate(' + chartWidth + ', 0)') - .call(yAxis); - - var contextTransition = context.transition().duration(UPDATE_TRANS_MS); - - contextTransition.select('.x') - .attr('transform', 'translate(0,' + chartHeight + ')') - .call(xAxis2); - - // reset clip to new dimensions - clip.transition() - .attr('width', chartWidth) - .attr('height', chartHeight); - - // reset brush location - context.select('.x.brush') - .selectAll('rect') - .attr('y', focusHeight) - .attr('height', chartHeight - focusHeight); - - // clear current brush - d3.select('.brush').call(brush.clear()); - - // redraw old brush with new dimensions - d3.select('.brush').transition().duration(UPDATE_TRANS_MS).call(brush.extent(currentBrushExtent)); - - // transition lines to correct location - focus.select('.high-line') - .transition() - .duration(UPDATE_TRANS_MS) - .attr('x1', xScale(currentBrushExtent[0])) - .attr('y1', yScale(scaleBg(app.thresholds.bg_high))) - .attr('x2', xScale(currentBrushExtent[1])) - .attr('y2', yScale(scaleBg(app.thresholds.bg_high))); - - focus.select('.target-top-line') - .transition() - .duration(UPDATE_TRANS_MS) - .attr('x1', xScale(currentBrushExtent[0])) - .attr('y1', yScale(scaleBg(app.thresholds.bg_target_top))) - .attr('x2', xScale(currentBrushExtent[1])) - .attr('y2', yScale(scaleBg(app.thresholds.bg_target_top))); - - focus.select('.target-bottom-line') - .transition() - .duration(UPDATE_TRANS_MS) - .attr('x1', xScale(currentBrushExtent[0])) - .attr('y1', yScale(scaleBg(app.thresholds.bg_target_bottom))) - .attr('x2', xScale(currentBrushExtent[1])) - .attr('y2', yScale(scaleBg(app.thresholds.bg_target_bottom))); - - focus.select('.low-line') - .transition() - .duration(UPDATE_TRANS_MS) - .attr('x1', xScale(currentBrushExtent[0])) - .attr('y1', yScale(scaleBg(app.thresholds.bg_low))) - .attr('x2', xScale(currentBrushExtent[1])) - .attr('y2', yScale(scaleBg(app.thresholds.bg_low))); - - // transition open-top line to correct location - focus.select('.open-top') - .transition() - .duration(UPDATE_TRANS_MS) - .attr('x1', xScale2(currentBrushExtent[0])) - .attr('y1', yScale(scaleBg(30))) - .attr('x2', xScale2(currentBrushExtent[1])) - .attr('y2', yScale(scaleBg(30))); - - // transition open-left line to correct location - focus.select('.open-left') - .transition() - .duration(UPDATE_TRANS_MS) - .attr('x1', xScale2(currentBrushExtent[0])) - .attr('y1', focusHeight) - .attr('x2', xScale2(currentBrushExtent[0])) - .attr('y2', chartHeight); - - // transition open-right line to correct location - focus.select('.open-right') - .transition() - .duration(UPDATE_TRANS_MS) - .attr('x1', xScale2(currentBrushExtent[1])) - .attr('y1', focusHeight) - .attr('x2', xScale2(currentBrushExtent[1])) - .attr('y2', chartHeight); - - // transition high line to correct location - context.select('.high-line') - .transition() - .duration(UPDATE_TRANS_MS) - .attr('x1', xScale2(dataRange[0])) - .attr('y1', yScale2(scaleBg(app.thresholds.bg_target_top))) - .attr('x2', xScale2(dataRange[1])) - .attr('y2', yScale2(scaleBg(app.thresholds.bg_target_top))); - - // transition low line to correct location - context.select('.low-line') - .transition() - .duration(UPDATE_TRANS_MS) - .attr('x1', xScale2(dataRange[0])) - .attr('y1', yScale2(scaleBg(app.thresholds.bg_target_bottom))) - .attr('x2', xScale2(dataRange[1])) - .attr('y2', yScale2(scaleBg(app.thresholds.bg_target_bottom))); - } - } + .transition() + .duration(UPDATE_TRANS_MS) + .attr('x1', xScale2(currentBrushExtent[1])) + .attr('y1', focusHeight) + .attr('x2', xScale2(currentBrushExtent[1])) + .attr('y2', chartHeight); + + // transition high line to correct location + context.select('.high-line') + .transition() + .duration(UPDATE_TRANS_MS) + .attr('x1', xScale2(dataRange[0])) + .attr('y1', yScale2(scaleBg(app.thresholds.bg_target_top))) + .attr('x2', xScale2(dataRange[1])) + .attr('y2', yScale2(scaleBg(app.thresholds.bg_target_top))); + + // transition low line to correct location + context.select('.low-line') + .transition() + .duration(UPDATE_TRANS_MS) + .attr('x1', xScale2(dataRange[0])) + .attr('y1', yScale2(scaleBg(app.thresholds.bg_target_bottom))) + .attr('x2', xScale2(dataRange[1])) + .attr('y2', yScale2(scaleBg(app.thresholds.bg_target_bottom))); + } + } - // update domain - xScale2.domain(dataRange); + // update domain + xScale2.domain(dataRange); + + // only if a user brush is not active, update brush and focus chart with recent data + // else, just transition brush + var updateBrush = d3.select('.brush').transition().duration(UPDATE_TRANS_MS); + if (!brushInProgress) { + updateBrush + .call(brush.extent([new Date(dataRange[1].getTime() - foucusRangeMS), dataRange[1]])); + brushed(true); + } else { + updateBrush + .call(brush.extent([new Date(currentBrushExtent[1].getTime() - foucusRangeMS), currentBrushExtent[1]])); + brushed(true); + } - // only if a user brush is not active, update brush and focus chart with recent data - // else, just transition brush - var updateBrush = d3.select('.brush').transition().duration(UPDATE_TRANS_MS); - if (!brushInProgress) { - updateBrush - .call(brush.extent([new Date(dataRange[1].getTime() - foucusRangeMS), dataRange[1]])); - brushed(true); - } else { - updateBrush - .call(brush.extent([new Date(currentBrushExtent[1].getTime() - foucusRangeMS), currentBrushExtent[1]])); - brushed(true); - } + // bind up the context chart data to an array of circles + var contextCircles = context.selectAll('circle') + .data(data); + + function prepareContextCircles(sel) { + var badData = []; + sel.attr('cx', function (d) { return xScale2(d.date); }) + .attr('cy', function (d) { + if (isNaN(d.sgv)) { + badData.push(d); + return yScale2(scaleBg(450)); + } else { + return yScale2(d.sgv); + } + }) + .attr('fill', function (d) { return d.color; }) + .style('opacity', function (d) { return highlightBrushPoints(d) }) + .attr('stroke-width', function (d) {if (d.type == 'mbg') return 2; else return 0; }) + .attr('stroke', function (d) { return 'white'; }) + .attr('r', function(d) { if (d.type == 'mbg') return 4; else return 2;}); + + if (badData.length > 0) { + console.warn("Bad Data: isNaN(sgv)", badData); + } - // bind up the context chart data to an array of circles - var contextCircles = context.selectAll('circle') - .data(data); - - function prepareContextCircles(sel) { - var badData = []; - sel.attr('cx', function (d) { return xScale2(d.date); }) - .attr('cy', function (d) { - if (isNaN(d.sgv)) { - badData.push(d); - return yScale2(scaleBg(450)); - } else { - return yScale2(d.sgv); - } - }) - .attr('fill', function (d) { return d.color; }) - .style('opacity', function (d) { return highlightBrushPoints(d) }) - .attr('stroke-width', function (d) {if (d.type == 'mbg') return 2; else return 0; }) - .attr('stroke', function (d) { return 'white'; }) - .attr('r', function(d) { if (d.type == 'mbg') return 4; else return 2;}); - - if (badData.length > 0) { - console.warn("Bad Data: isNaN(sgv)", badData); - } - - return sel; - } + return sel; + } - // if already existing then transition each circle to its new position - prepareContextCircles(contextCircles.transition().duration(UPDATE_TRANS_MS)); + // if already existing then transition each circle to its new position + prepareContextCircles(contextCircles.transition().duration(UPDATE_TRANS_MS)); - // if new circle then just display - prepareContextCircles(contextCircles.enter().append('circle')); + // if new circle then just display + prepareContextCircles(contextCircles.enter().append('circle')); - contextCircles.exit() - .remove(); + contextCircles.exit() + .remove(); - // update x axis domain - context.select('.x') - .call(xAxis2); - - }, DEBOUNCE_MS); - - function sgvToColor(sgv) { - var color = 'grey'; - - if (browserSettings.theme == 'colors') { - if (sgv > app.thresholds.bg_high) { - color = 'red'; - } else if (sgv > app.thresholds.bg_target_top) { - color = 'yellow'; - } else if (sgv >= app.thresholds.bg_target_bottom && sgv <= app.thresholds.bg_target_top) { - color = '#4cff00'; - } else if (sgv < app.thresholds.bg_low) { - color = 'red'; - } else if (sgv < app.thresholds.bg_target_bottom) { - color = 'yellow'; - } - } + // update x axis domain + context.select('.x') + .call(xAxis2); - return color; - } + }, DEBOUNCE_MS); - function sgvToColoredRange(sgv) { - var range = ''; - - if (browserSettings.theme == 'colors') { - if (sgv > app.thresholds.bg_high) { - range = 'urgent'; - } else if (sgv > app.thresholds.bg_target_top) { - range = 'warning'; - } else if (sgv >= app.thresholds.bg_target_bottom && sgv <= app.thresholds.bg_target_top) { - range = 'inrange'; - } else if (sgv < app.thresholds.bg_low) { - range = 'urgent'; - } else if (sgv < app.thresholds.bg_target_bottom) { - range = 'warning'; - } - } + function sgvToColor(sgv) { + var color = 'grey'; - return range; + if (browserSettings.theme == 'colors') { + if (sgv > app.thresholds.bg_high) { + color = 'red'; + } else if (sgv > app.thresholds.bg_target_top) { + color = 'yellow'; + } else if (sgv >= app.thresholds.bg_target_bottom && sgv <= app.thresholds.bg_target_top) { + color = '#4cff00'; + } else if (sgv < app.thresholds.bg_low) { + color = 'red'; + } else if (sgv < app.thresholds.bg_target_bottom) { + color = 'yellow'; + } } - - function generateAlarm(file) { - alarmInProgress = true; - var selector = '.audio.alarms audio.' + file; - d3.select(selector).each(function (d, i) { - var audio = this; - playAlarm(audio); - $(this).addClass('playing'); - }); - $('.bgButton').addClass(file == urgentAlarmSound ? 'urgent' : 'warning'); - $('#container').addClass('alarming'); + return color; + } + + function sgvToColoredRange(sgv) { + var range = ''; + + if (browserSettings.theme == 'colors') { + if (sgv > app.thresholds.bg_high) { + range = 'urgent'; + } else if (sgv > app.thresholds.bg_target_top) { + range = 'warning'; + } else if (sgv >= app.thresholds.bg_target_bottom && sgv <= app.thresholds.bg_target_top) { + range = 'inrange'; + } else if (sgv < app.thresholds.bg_low) { + range = 'urgent'; + } else if (sgv < app.thresholds.bg_target_bottom) { + range = 'warning'; + } } - function playAlarm(audio) { - // ?mute=true disables alarms to testers. - if (querystring.mute != 'true') { - audio.play(); - } else { - showNotification('Alarm was muted (?mute=true)'); - } - } + return range; + } - function stopAlarm(isClient, silenceTime) { - alarmInProgress = false; - $('.bgButton').removeClass('urgent warning'); - d3.selectAll('audio.playing').each(function () { - var audio = this; - audio.pause(); - $(this).removeClass('playing'); - }); - closeNotification(); - $('#container').removeClass('alarming'); - - // only emit ack if client invoke by button press - if (isClient) { - if (isTimeAgoAlarmType(currentAlarmType)) { - $('#container').removeClass('alarming-timeago'); - var alarm = getClientAlarm(currentAlarmType); - alarm.lastAckTime = Date.now(); - alarm.silenceTime = silenceTime; - console.info('time ago alarm (' + currentAlarmType + ', not acking to server'); - } else { - socket.emit('ack', currentAlarmType || 'alarm', silenceTime); - } - } - - brushed(false); + function generateAlarm(file) { + alarmInProgress = true; + var selector = '.audio.alarms audio.' + file; + d3.select(selector).each(function (d, i) { + var audio = this; + playAlarm(audio); + $(this).addClass('playing'); + }); + $('.bgButton').addClass(file == urgentAlarmSound ? 'urgent' : 'warning'); + $('#container').addClass('alarming'); + } + + function playAlarm(audio) { + // ?mute=true disables alarms to testers. + if (querystring.mute != 'true') { + audio.play(); + } else { + showNotification('Alarm was muted (?mute=true)'); } + } + + function stopAlarm(isClient, silenceTime) { + alarmInProgress = false; + $('.bgButton').removeClass('urgent warning'); + d3.selectAll('audio.playing').each(function () { + var audio = this; + audio.pause(); + $(this).removeClass('playing'); + }); - function timeAgo(time) { - - var now = Date.now() - , offset = time == -1 ? -1 : (now - time) / 1000 - , parts = {}; - - if (offset < MINUTE_IN_SECS * -5) parts = { value: 'in the future' }; - else if (offset == -1) parts = { label: 'time ago' }; - else if (offset <= MINUTE_IN_SECS * 2) parts = { value: 1, label: 'min ago' }; - else if (offset < (MINUTE_IN_SECS * 60)) parts = { value: Math.round(Math.abs(offset / MINUTE_IN_SECS)), label: 'mins ago' }; - else if (offset < (HOUR_IN_SECS * 2)) parts = { value: 1, label: 'hr ago' }; - else if (offset < (HOUR_IN_SECS * 24)) parts = { value: Math.round(Math.abs(offset / HOUR_IN_SECS)), label: 'hrs ago' }; - else if (offset < DAY_IN_SECS) parts = { value: 1, label: 'day ago' }; - else if (offset <= (DAY_IN_SECS * 7)) parts = { value: Math.round(Math.abs(offset / DAY_IN_SECS)), label: 'day ago' }; - else parts = { value: 'long ago' }; - - if (offset > DAY_IN_SECS * 7) { - parts.status = 'warn'; - } else if (offset < MINUTE_IN_SECS * -5 || offset > (MINUTE_IN_SECS * browserSettings.alarmTimeAgoUrgentMins)) { - parts.status = 'urgent'; - } else if (offset > (MINUTE_IN_SECS * browserSettings.alarmTimeAgoWarnMins)) { - parts.status = 'warn'; - } else { - parts.status = 'current'; - } - - return parts; + closeNotification(); + $('#container').removeClass('alarming'); + + // only emit ack if client invoke by button press + if (isClient) { + if (isTimeAgoAlarmType(currentAlarmType)) { + $('#container').removeClass('alarming-timeago'); + var alarm = getClientAlarm(currentAlarmType); + alarm.lastAckTime = Date.now(); + alarm.silenceTime = silenceTime; + console.info('time ago alarm (' + currentAlarmType + ', not acking to server'); + } else { + socket.emit('ack', currentAlarmType || 'alarm', silenceTime); + } + } + brushed(false); + } + + function timeAgo(time) { + + var now = Date.now() + , offset = time == -1 ? -1 : (now - time) / 1000 + , parts = {}; + + if (offset < MINUTE_IN_SECS * -5) parts = { value: 'in the future' }; + else if (offset == -1) parts = { label: 'time ago' }; + else if (offset <= MINUTE_IN_SECS * 2) parts = { value: 1, label: 'min ago' }; + else if (offset < (MINUTE_IN_SECS * 60)) parts = { value: Math.round(Math.abs(offset / MINUTE_IN_SECS)), label: 'mins ago' }; + else if (offset < (HOUR_IN_SECS * 2)) parts = { value: 1, label: 'hr ago' }; + else if (offset < (HOUR_IN_SECS * 24)) parts = { value: Math.round(Math.abs(offset / HOUR_IN_SECS)), label: 'hrs ago' }; + else if (offset < DAY_IN_SECS) parts = { value: 1, label: 'day ago' }; + else if (offset <= (DAY_IN_SECS * 7)) parts = { value: Math.round(Math.abs(offset / DAY_IN_SECS)), label: 'day ago' }; + else parts = { value: 'long ago' }; + + if (offset > DAY_IN_SECS * 7) { + parts.status = 'warn'; + } else if (offset < MINUTE_IN_SECS * -5 || offset > (MINUTE_IN_SECS * browserSettings.alarmTimeAgoUrgentMins)) { + parts.status = 'urgent'; + } else if (offset > (MINUTE_IN_SECS * browserSettings.alarmTimeAgoWarnMins)) { + parts.status = 'warn'; + } else { + parts.status = 'current'; } - function displayTreatmentBG(treatment) { + return parts; - function calcBGByTime(time) { - var withBGs = _.filter(data, function(d) { - return d.y && d.type == 'sgv'; - }); + } - var beforeTreatment = _.findLast(withBGs, function (d) { - return d.date.getTime() <= time; - }); - var afterTreatment = _.find(withBGs, function (d) { - return d.date.getTime() >= time; - }); + function displayTreatmentBG(treatment) { - var calcedBG = 0; - if (beforeTreatment && afterTreatment) { - calcedBG = (Number(beforeTreatment.y) + Number(afterTreatment.y)) / 2; - } else if (beforeTreatment) { - calcedBG = Number(beforeTreatment.y); - } else if (afterTreatment) { - calcedBG = Number(afterTreatment.y); - } + function calcBGByTime(time) { + var withBGs = _.filter(data, function(d) { + return d.y && d.type == 'sgv'; + }); + + var beforeTreatment = _.findLast(withBGs, function (d) { + return d.date.getTime() <= time; + }); + var afterTreatment = _.find(withBGs, function (d) { + return d.date.getTime() >= time; + }); - return calcedBG || 400; + var calcedBG = 0; + if (beforeTreatment && afterTreatment) { + calcedBG = (Number(beforeTreatment.y) + Number(afterTreatment.y)) / 2; + } else if (beforeTreatment) { + calcedBG = Number(beforeTreatment.y); + } else if (afterTreatment) { + calcedBG = Number(afterTreatment.y); } - var treatmentGlucose = null; + return calcedBG || 400; + } - if (treatment.glucose && isNaN(treatment.glucose)) { - console.warn('found an invalid glucose value', treatment); - } else { - if (treatment.glucose && treatment.units && browserSettings.units) { - if (treatment.units != browserSettings.units) { - console.info('found mismatched glucose units, converting ' + treatment.units + ' into ' + browserSettings.units, treatment); - if (treatment.units == 'mmol') { - //BG is in mmol and display in mg/dl - treatmentGlucose = Math.round(treatment.glucose * 18) - } else { - //BG is in mg/dl and display in mmol - treatmentGlucose = scaleBg(treatment.glucose); - } + var treatmentGlucose = null; + + if (treatment.glucose && isNaN(treatment.glucose)) { + console.warn('found an invalid glucose value', treatment); + } else { + if (treatment.glucose && treatment.units && browserSettings.units) { + if (treatment.units != browserSettings.units) { + console.info('found mismatched glucose units, converting ' + treatment.units + ' into ' + browserSettings.units, treatment); + if (treatment.units == 'mmol') { + //BG is in mmol and display in mg/dl + treatmentGlucose = Math.round(treatment.glucose * 18) } else { - treatmentGlucose = treatment.glucose; + //BG is in mg/dl and display in mmol + treatmentGlucose = scaleBg(treatment.glucose); } - } else if (treatment.glucose) { - //no units, assume everything is the same - console.warn('found a glucose value without any units, maybe from an old version?', treatment); + } else { treatmentGlucose = treatment.glucose; } + } else if (treatment.glucose) { + //no units, assume everything is the same + console.warn('found a glucose value without any units, maybe from an old version?', treatment); + treatmentGlucose = treatment.glucose; } - - return treatmentGlucose || scaleBg(calcBGByTime(treatment.created_at.getTime())); } - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - //draw a compact visualization of a treatment (carbs, insulin) - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - function drawTreatment(treatment, scale, showValues) { + return treatmentGlucose || scaleBg(calcBGByTime(treatment.created_at.getTime())); + } - if (!treatment.carbs && !treatment.insulin) return; + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + //draw a compact visualization of a treatment (carbs, insulin) + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + function drawTreatment(treatment, scale, showValues) { - // don't render the treatment if it's not visible - if (Math.abs(xScale(treatment.created_at.getTime())) > window.innerWidth) return; + if (!treatment.carbs && !treatment.insulin) return; - var CR = treatment.CR || 20; - var carbs = treatment.carbs || CR; - var insulin = treatment.insulin || 1; + // don't render the treatment if it's not visible + if (Math.abs(xScale(treatment.created_at.getTime())) > window.innerWidth) return; - var R1 = Math.sqrt(Math.min(carbs, insulin * CR)) / scale, - R2 = Math.sqrt(Math.max(carbs, insulin * CR)) / scale, - R3 = R2 + 8 / scale; + var CR = treatment.CR || 20; + var carbs = treatment.carbs || CR; + var insulin = treatment.insulin || 1; - if (isNaN(R1) || isNaN(R3) || isNaN(R3)) { - console.warn("Bad Data: Found isNaN value in treatment", treatment); - return; - } + var R1 = Math.sqrt(Math.min(carbs, insulin * CR)) / scale, + R2 = Math.sqrt(Math.max(carbs, insulin * CR)) / scale, + R3 = R2 + 8 / scale; - var arc_data = [ - { 'element': '', 'color': 'white', 'start': -1.5708, 'end': 1.5708, 'inner': 0, 'outer': R1 }, - { 'element': '', 'color': 'transparent', 'start': -1.5708, 'end': 1.5708, 'inner': R2, 'outer': R3 }, - { 'element': '', 'color': '#0099ff', 'start': 1.5708, 'end': 4.7124, 'inner': 0, 'outer': R1 }, - { 'element': '', 'color': 'transparent', 'start': 1.5708, 'end': 4.7124, 'inner': R2, 'outer': R3 } - ]; - - 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'; - if (treatment.insulin > 0) arc_data[3].element = Math.round(treatment.insulin * 100) / 100 + ' U'; - - var arc = d3.svg.arc() - .innerRadius(function (d) { return 5 * d.inner; }) - .outerRadius(function (d) { return 5 * d.outer; }) - .endAngle(function (d) { return d.start; }) - .startAngle(function (d) { return d.end; }); - - var treatmentDots = focus.selectAll('treatment-dot') - .data(arc_data) - .enter() - .append('g') - .attr('transform', 'translate(' + xScale(treatment.created_at.getTime()) + ', ' + yScale(treatment.displayBG) + ')') - .on('mouseover', function () { - tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); - tooltip.html('Time: ' + formatTime(treatment.created_at) + '
    ' + 'Treatment type: ' + treatment.eventType + '
    ' + - (treatment.carbs ? 'Carbs: ' + treatment.carbs + '
    ' : '') + - (treatment.insulin ? 'Insulin: ' + treatment.insulin + '
    ' : '') + - (treatment.glucose ? 'BG: ' + treatment.glucose + (treatment.glucoseType ? ' (' + treatment.glucoseType + ')': '') + '
    ' : '') + - (treatment.enteredBy ? 'Entered by: ' + treatment.enteredBy + '
    ' : '') + - (treatment.notes ? 'Notes: ' + treatment.notes : '') - ) - .style('left', (d3.event.pageX) + 'px') - .style('top', (d3.event.pageY + 15) + 'px'); - }) - .on('mouseout', function () { - tooltip.transition() - .duration(TOOLTIP_TRANS_MS) - .style('opacity', 0); - }); - var arcs = treatmentDots.append('path') - .attr('class', 'path') - .attr('fill', function (d, i) { if (d.outlineOnly) return 'transparent'; else return d.color; }) - .attr('stroke-width', function (d) {if (d.outlineOnly) return 1; else return 0; }) - .attr('stroke', function (d) { return d.color; }) - .attr('id', function (d, i) { return 's' + i; }) - .attr('d', arc); - - - // labels for carbs and insulin - if (showValues) { - var label = treatmentDots.append('g') - .attr('class', 'path') - .attr('id', 'label') - .style('fill', 'white'); - label.append('text') - .style('font-size', 40 / scale) - .style('text-shadow', '0px 0px 10px rgba(0, 0, 0, 1)') - .attr('text-anchor', 'middle') - .attr('dy', '.35em') - .attr('transform', function (d) { - d.outerRadius = d.outerRadius * 2.1; - d.innerRadius = d.outerRadius * 2.1; - return 'translate(' + arc.centroid(d) + ')'; - }) - .text(function (d) { return d.element; }); - } + if (isNaN(R1) || isNaN(R3) || isNaN(R3)) { + console.warn("Bad Data: Found isNaN value in treatment", treatment); + return; } - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // function to predict - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - function predictAR(actual, lookback) { - var ONE_MINUTE = 60 * 1000; - var FIVE_MINUTES = 5 * ONE_MINUTE; - var predicted = []; - var BG_REF = scaleBg(140); - var BG_MIN = scaleBg(36); - var BG_MAX = scaleBg(400); - - function roundByUnits(value) { - if (browserSettings.units == 'mmol') { - return value.toFixed(1); - } else { - return Math.round(value); - } - } + var arc_data = [ + { 'element': '', 'color': 'white', 'start': -1.5708, 'end': 1.5708, 'inner': 0, 'outer': R1 }, + { 'element': '', 'color': 'transparent', 'start': -1.5708, 'end': 1.5708, 'inner': R2, 'outer': R3 }, + { 'element': '', 'color': '#0099ff', 'start': 1.5708, 'end': 4.7124, 'inner': 0, 'outer': R1 }, + { 'element': '', 'color': 'transparent', 'start': 1.5708, 'end': 4.7124, 'inner': R2, 'outer': R3 } + ]; + + 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'; + if (treatment.insulin > 0) arc_data[3].element = Math.round(treatment.insulin * 100) / 100 + ' U'; + + var arc = d3.svg.arc() + .innerRadius(function (d) { return 5 * d.inner; }) + .outerRadius(function (d) { return 5 * d.outer; }) + .endAngle(function (d) { return d.start; }) + .startAngle(function (d) { return d.end; }); + + var treatmentDots = focus.selectAll('treatment-dot') + .data(arc_data) + .enter() + .append('g') + .attr('transform', 'translate(' + xScale(treatment.created_at.getTime()) + ', ' + yScale(treatment.displayBG) + ')') + .on('mouseover', function () { + tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); + tooltip.html('Time: ' + formatTime(treatment.created_at) + '
    ' + 'Treatment type: ' + treatment.eventType + '
    ' + + (treatment.carbs ? 'Carbs: ' + treatment.carbs + '
    ' : '') + + (treatment.insulin ? 'Insulin: ' + treatment.insulin + '
    ' : '') + + (treatment.glucose ? 'BG: ' + treatment.glucose + (treatment.glucoseType ? ' (' + treatment.glucoseType + ')': '') + '
    ' : '') + + (treatment.enteredBy ? 'Entered by: ' + treatment.enteredBy + '
    ' : '') + + (treatment.notes ? 'Notes: ' + treatment.notes : '') + ) + .style('left', (d3.event.pageX) + 'px') + .style('top', (d3.event.pageY + 15) + 'px'); + }) + .on('mouseout', function () { + tooltip.transition() + .duration(TOOLTIP_TRANS_MS) + .style('opacity', 0); + }); + var arcs = treatmentDots.append('path') + .attr('class', 'path') + .attr('fill', function (d, i) { if (d.outlineOnly) return 'transparent'; else return d.color; }) + .attr('stroke-width', function (d) {if (d.outlineOnly) return 1; else return 0; }) + .attr('stroke', function (d) { return d.color; }) + .attr('id', function (d, i) { return 's' + i; }) + .attr('d', arc); + + + // labels for carbs and insulin + if (showValues) { + var label = treatmentDots.append('g') + .attr('class', 'path') + .attr('id', 'label') + .style('fill', 'white'); + label.append('text') + .style('font-size', 40 / scale) + .style('text-shadow', '0px 0px 10px rgba(0, 0, 0, 1)') + .attr('text-anchor', 'middle') + .attr('dy', '.35em') + .attr('transform', function (d) { + d.outerRadius = d.outerRadius * 2.1; + d.innerRadius = d.outerRadius * 2.1; + return 'translate(' + arc.centroid(d) + ')'; + }) + .text(function (d) { return d.element; }); + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // function to predict + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + function predictAR(actual, lookback) { + var ONE_MINUTE = 60 * 1000; + var FIVE_MINUTES = 5 * ONE_MINUTE; + var predicted = []; + var BG_REF = scaleBg(140); + var BG_MIN = scaleBg(36); + var BG_MAX = scaleBg(400); + + function roundByUnits(value) { + if (browserSettings.units == 'mmol') { + return value.toFixed(1); + } else { + return Math.round(value); + } + } - // these are the one sigma limits for the first 13 prediction interval uncertainties (65 minutes) - var CONE = [0.020, 0.041, 0.061, 0.081, 0.099, 0.116, 0.132, 0.146, 0.159, 0.171, 0.182, 0.192, 0.201]; - // these are modified to make the cone much blunter - //var CONE = [0.030, 0.060, 0.090, 0.120, 0.140, 0.150, 0.160, 0.170, 0.180, 0.185, 0.190, 0.195, 0.200]; - // for testing - //var CONE = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - if (actual.length < lookback+1) { - var y = [Math.log(actual[actual.length-1].sgv / BG_REF), Math.log(actual[actual.length-1].sgv / BG_REF)]; - } else { - var elapsedMins = (actual[actual.length-1].date - actual[actual.length-1-lookback].date) / ONE_MINUTE; - // construct a '5m ago' sgv offset from current sgv by the average change over the lookback interval - var lookbackSgvChange = actual[lookback].sgv-actual[0].sgv; - var fiveMinAgoSgv = actual[lookback].sgv - lookbackSgvChange/elapsedMins*5; - y = [Math.log(fiveMinAgoSgv / BG_REF), Math.log(actual[lookback].sgv / BG_REF)]; - /* - if (elapsedMins < lookback * 5.1) { - y = [Math.log(actual[0].sgv / BG_REF), Math.log(actual[lookback].sgv / BG_REF)]; - } else { - y = [Math.log(actual[lookback].sgv / BG_REF), Math.log(actual[lookback].sgv / BG_REF)]; - } - */ - } - var AR = [-0.723, 1.716]; - var dt = actual[lookback].date.getTime(); - var predictedColor = 'blue'; - if (browserSettings.theme == 'colors') { - predictedColor = 'cyan'; - } - for (var i = 0; i < CONE.length; i++) { - y = [y[1], AR[0] * y[0] + AR[1] * y[1]]; - dt = dt + FIVE_MINUTES; - // Add 2000 ms so not same point as SG - predicted[i * 2] = { - date: new Date(dt + 2000), - sgv: Math.max(BG_MIN, Math.min(BG_MAX, roundByUnits(BG_REF * Math.exp((y[1] - 2 * CONE[i]))))), - color: predictedColor - }; - // Add 4000 ms so not same point as SG - predicted[i * 2 + 1] = { - date: new Date(dt + 4000), - sgv: Math.max(BG_MIN, Math.min(BG_MAX, roundByUnits(BG_REF * Math.exp((y[1] + 2 * CONE[i]))))), - color: predictedColor - }; - predicted.forEach(function (d) { - d.type = 'forecast'; - if (d.sgv < BG_MIN) - d.color = 'transparent'; - }) - } - return predicted; + // these are the one sigma limits for the first 13 prediction interval uncertainties (65 minutes) + var CONE = [0.020, 0.041, 0.061, 0.081, 0.099, 0.116, 0.132, 0.146, 0.159, 0.171, 0.182, 0.192, 0.201]; + // these are modified to make the cone much blunter + //var CONE = [0.030, 0.060, 0.090, 0.120, 0.140, 0.150, 0.160, 0.170, 0.180, 0.185, 0.190, 0.195, 0.200]; + // for testing + //var CONE = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + if (actual.length < lookback+1) { + var y = [Math.log(actual[actual.length-1].sgv / BG_REF), Math.log(actual[actual.length-1].sgv / BG_REF)]; + } else { + var elapsedMins = (actual[actual.length-1].date - actual[actual.length-1-lookback].date) / ONE_MINUTE; + // construct a '5m ago' sgv offset from current sgv by the average change over the lookback interval + var lookbackSgvChange = actual[lookback].sgv-actual[0].sgv; + var fiveMinAgoSgv = actual[lookback].sgv - lookbackSgvChange/elapsedMins*5; + y = [Math.log(fiveMinAgoSgv / BG_REF), Math.log(actual[lookback].sgv / BG_REF)]; + /* + if (elapsedMins < lookback * 5.1) { + y = [Math.log(actual[0].sgv / BG_REF), Math.log(actual[lookback].sgv / BG_REF)]; + } else { + y = [Math.log(actual[lookback].sgv / BG_REF), Math.log(actual[lookback].sgv / BG_REF)]; + } + */ + } + var AR = [-0.723, 1.716]; + var dt = actual[lookback].date.getTime(); + var predictedColor = 'blue'; + if (browserSettings.theme == 'colors') { + predictedColor = 'cyan'; } + for (var i = 0; i < CONE.length; i++) { + y = [y[1], AR[0] * y[0] + AR[1] * y[1]]; + dt = dt + FIVE_MINUTES; + // Add 2000 ms so not same point as SG + predicted[i * 2] = { + date: new Date(dt + 2000), + sgv: Math.max(BG_MIN, Math.min(BG_MAX, roundByUnits(BG_REF * Math.exp((y[1] - 2 * CONE[i]))))), + color: predictedColor + }; + // Add 4000 ms so not same point as SG + predicted[i * 2 + 1] = { + date: new Date(dt + 4000), + sgv: Math.max(BG_MIN, Math.min(BG_MAX, roundByUnits(BG_REF * Math.exp((y[1] + 2 * CONE[i]))))), + color: predictedColor + }; + predicted.forEach(function (d) { + d.type = 'forecast'; + if (d.sgv < BG_MIN) + d.color = 'transparent'; + }) + } + return predicted; + } - function updateClock() { - updateClockDisplay(); - var interval = (60 - (new Date()).getSeconds()) * 1000 + 5; - setTimeout(updateClock,interval); - - updateTimeAgo(); - - // Dim the screen by reducing the opacity when at nighttime - if (browserSettings.nightMode) { - var dateTime = new Date(); - if (opacity.current != opacity.NIGHT && (dateTime.getHours() > 21 || dateTime.getHours() < 7)) { - $('body').css({ 'opacity': opacity.NIGHT }); - } else { - $('body').css({ 'opacity': opacity.DAY }); - } - } + function updateClock() { + updateClockDisplay(); + var interval = (60 - (new Date()).getSeconds()) * 1000 + 5; + setTimeout(updateClock,interval); + + updateTimeAgo(); + + // Dim the screen by reducing the opacity when at nighttime + if (browserSettings.nightMode) { + var dateTime = new Date(); + if (opacity.current != opacity.NIGHT && (dateTime.getHours() > 21 || dateTime.getHours() < 7)) { + $('body').css({ 'opacity': opacity.NIGHT }); + } else { + $('body').css({ 'opacity': opacity.DAY }); + } + } + } + + function updateClockDisplay() { + if (inRetroMode()) return; + now = Date.now(); + var dateTime = new Date(now); + $('#currentTime').text(formatTime(dateTime, true)).css('text-decoration', ''); + } + + function getClientAlarm(type) { + var alarm = clientAlarms[type]; + if (!alarm) { + alarm = { type: type }; + clientAlarms[type] = alarm; } + return alarm; + } + + function isTimeAgoAlarmType(alarmType) { + return alarmType == 'warnTimeAgo' || alarmType == 'urgentTimeAgo'; + } + + function checkTimeAgoAlarm(ago) { + var level = ago.status + , alarm = getClientAlarm(level + 'TimeAgo'); + + if (!alarmingNow() && Date.now() >= (alarm.lastAckTime || 0) + (alarm.silenceTime || 0)) { + currentAlarmType = alarm.type; + console.info('generating timeAgoAlarm', alarm.type); + $('#container').addClass('alarming-timeago'); + if (level == 'warn') { + generateAlarm(alarmSound); + } else { + generateAlarm(urgentAlarmSound); + } + } + } + + function updateTimeAgo() { + var lastEntry = $('#lastEntry') + , time = latestSGV ? new Date(latestSGV.x).getTime() : -1 + , ago = timeAgo(time) + , retroMode = inRetroMode(); - function updateClockDisplay() { - if (inRetroMode()) return; - now = Date.now(); - var dateTime = new Date(now); - $('#currentTime').text(formatTime(dateTime, true)).css('text-decoration', ''); + lastEntry.removeClass('current warn urgent'); + lastEntry.addClass(ago.status); + + if (ago.status !== 'current') { + updateTitle(); } - function getClientAlarm(type) { - var alarm = clientAlarms[type]; - if (!alarm) { - alarm = { type: type }; - clientAlarms[type] = alarm; - } - return alarm; + if ( + (browserSettings.alarmTimeAgoWarn && ago.status == 'warn') + || (browserSettings.alarmTimeAgoUrgent && ago.status == 'urgent')) { + checkTimeAgoAlarm(ago); } - function isTimeAgoAlarmType(alarmType) { - return alarmType == 'warnTimeAgo' || alarmType == 'urgentTimeAgo'; + if (alarmingNow() && ago.status == 'current' && isTimeAgoAlarmType(currentAlarmType)) { + $('#container').removeClass('alarming-timeago'); + stopAlarm(true, ONE_MIN_IN_MS); } - function checkTimeAgoAlarm(ago) { - var level = ago.status - , alarm = getClientAlarm(level + 'TimeAgo'); - - if (!alarmingNow() && Date.now() >= (alarm.lastAckTime || 0) + (alarm.silenceTime || 0)) { - currentAlarmType = alarm.type; - console.info('generating timeAgoAlarm', alarm.type); - $('#container').addClass('alarming-timeago'); - if (level == 'warn') { - generateAlarm(alarmSound); - } else { - generateAlarm(urgentAlarmSound); - } - } + if (retroMode || !ago.value) { + lastEntry.find('em').hide(); + } else { + lastEntry.find('em').show().text(ago.value); } - function updateTimeAgo() { - var lastEntry = $('#lastEntry') - , time = latestSGV ? new Date(latestSGV.x).getTime() : -1 - , ago = timeAgo(time) - , retroMode = inRetroMode(); + if (retroMode || ago.label) { + lastEntry.find('label').show().text(retroMode ? 'RETRO' : ago.label); + } else { + lastEntry.find('label').hide(); + } + } + + function init() { + + jqWindow = $(window); + + tooltip = d3.select('body').append('div') + .attr('class', 'tooltip') + .style('opacity', 0); + + // Tick Values + if (browserSettings.units == 'mmol') { + tickValues = [ + 2.0 + , Math.round(scaleBg(app.thresholds.bg_low)) + , Math.round(scaleBg(app.thresholds.bg_target_bottom)) + , 6.0 + , Math.round(scaleBg(app.thresholds.bg_target_top)) + , Math.round(scaleBg(app.thresholds.bg_high)) + , 22.0 + ]; + } else { + tickValues = [ + 40 + , app.thresholds.bg_low + , app.thresholds.bg_target_bottom + , 120 + , app.thresholds.bg_target_top + , app.thresholds.bg_high + , 400 + ]; + } - lastEntry.removeClass('current warn urgent'); - lastEntry.addClass(ago.status); + futureOpacity = d3.scale.linear( ) + .domain([TWENTY_FIVE_MINS_IN_MS, SIXTY_MINS_IN_MS]) + .range([0.8, 0.1]); - if (ago.status !== 'current') { - updateTitle(); - } + // create svg and g to contain the chart contents + charts = d3.select('#chartContainer').append('svg') + .append('g') + .attr('class', 'chartContainer') + .attr('transform', 'translate(' + padding.left + ',' + padding.top + ')'); - if ( - (browserSettings.alarmTimeAgoWarn && ago.status == 'warn') - || (browserSettings.alarmTimeAgoUrgent && ago.status == 'urgent')) { - checkTimeAgoAlarm(ago); - } + focus = charts.append('g'); - if (alarmingNow() && ago.status == 'current' && isTimeAgoAlarmType(currentAlarmType)) { - $('#container').removeClass('alarming-timeago'); - stopAlarm(true, ONE_MIN_IN_MS); - } + // create the x axis container + focus.append('g') + .attr('class', 'x axis'); - if (retroMode || !ago.value) { - lastEntry.find('em').hide(); - } else { - lastEntry.find('em').show().text(ago.value); - } + // create the y axis container + focus.append('g') + .attr('class', 'y axis'); - if (retroMode || ago.label) { - lastEntry.find('label').show().text(retroMode ? 'RETRO' : ago.label); - } else { - lastEntry.find('label').hide(); - } + context = charts.append('g'); + + // create the x axis container + context.append('g') + .attr('class', 'x axis'); + + // create the y axis container + context.append('g') + .attr('class', 'y axis'); + + //updateBrushToNow and updateChart are both _.debounced + function refreshChart(updateToNow) { + if (updateToNow) { + updateBrushToNow(); + } + updateChart(false); } - function init() { - - jqWindow = $(window); - - tooltip = d3.select('body').append('div') - .attr('class', 'tooltip') - .style('opacity', 0); - - // Tick Values - if (browserSettings.units == 'mmol') { - tickValues = [ - 2.0 - , Math.round(scaleBg(app.thresholds.bg_low)) - , Math.round(scaleBg(app.thresholds.bg_target_bottom)) - , 6.0 - , Math.round(scaleBg(app.thresholds.bg_target_top)) - , Math.round(scaleBg(app.thresholds.bg_high)) - , 22.0 - ]; - } else { - tickValues = [ - 40 - , app.thresholds.bg_low - , app.thresholds.bg_target_bottom - , 120 - , app.thresholds.bg_target_top - , app.thresholds.bg_high - , 400 - ]; - } + function visibilityChanged() { + var prevHidden = documentHidden; + documentHidden = (document.hidden || document.webkitHidden || document.mozHidden || document.msHidden); - futureOpacity = d3.scale.linear( ) - .domain([TWENTY_FIVE_MINS_IN_MS, SIXTY_MINS_IN_MS]) - .range([0.8, 0.1]); + if (prevHidden && !documentHidden) { + console.info('Document now visible, updating - ' + (new Date())); + refreshChart(true); + } + } - // create svg and g to contain the chart contents - charts = d3.select('#chartContainer').append('svg') - .append('g') - .attr('class', 'chartContainer') - .attr('transform', 'translate(' + padding.left + ',' + padding.top + ')'); + window.onresize = refreshChart; - focus = charts.append('g'); + document.addEventListener('webkitvisibilitychange', visibilityChanged); - // create the x axis container - focus.append('g') - .attr('class', 'x axis'); - // create the y axis container - focus.append('g') - .attr('class', 'y axis'); + updateClock(); - context = charts.append('g'); + var silenceDropdown = new Dropdown('.dropdown-menu'); - // create the x axis container - context.append('g') - .attr('class', 'x axis'); + $('.bgButton').click(function (e) { + if (alarmingNow()) silenceDropdown.open(e); + }); - // create the y axis container - context.append('g') - .attr('class', 'y axis'); - - //updateBrushToNow and updateChart are both _.debounced - function refreshChart(updateToNow) { - if (updateToNow) { - updateBrushToNow(); - } - updateChart(false); - } + $('#silenceBtn').find('a').click(function (e) { + stopAlarm(true, $(this).data('snooze-time')); + e.preventDefault(); + }); - function visibilityChanged() { - var prevHidden = documentHidden; - documentHidden = (document.hidden || document.webkitHidden || document.mozHidden || document.msHidden); + $('.focus-range li').click(function(e) { + var li = $(e.target); + $('.focus-range li').removeClass('selected'); + li.addClass('selected'); + var hours = Number(li.data('hours')); + foucusRangeMS = hours * 60 * 60 * 1000; + refreshChart(); + }); - if (prevHidden && !documentHidden) { - console.info('Document now visible, updating - ' + (new Date())); - refreshChart(true); - } - } + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Client-side code to connect to server and handle incoming data + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + socket = io.connect(); - window.onresize = refreshChart; + function recordAlreadyStored(array,record) { + var l = array.length -1; + for (var i = 0; i <= l; i++) { + var oldRecord = array[i]; + if (record.x == oldRecord.x) return true; + } + } - document.addEventListener('webkitvisibilitychange', visibilityChanged); + socket.on('dataUpdate', function receivedSGV(d) { + if (!d) return; - updateClock(); + // SGV - var silenceDropdown = new Dropdown('.dropdown-menu'); + if (d.sgvs) { - $('.bgButton').click(function (e) { - if (alarmingNow()) silenceDropdown.open(e); - }); + if (!d.delta) { + // replace all locally stored SGV data + console.log('Replacing all local sgv records'); + SGVdata = d.sgvs; + } else { + var diff = nsArrayDiff(SGVdata,d.sgvs); + console.log('SGV data updated with', diff.length, 'new records'); + SGVdata = SGVdata.concat(diff); + } - $('#silenceBtn').find('a').click(function (e) { - stopAlarm(true, $(this).data('snooze-time')); - e.preventDefault(); + SGVdata.sort(function(a, b) { + return a.x - b.x; }); - $('.focus-range li').click(function(e) { - var li = $(e.target); - $('.focus-range li').removeClass('selected'); - li.addClass('selected'); - var hours = Number(li.data('hours')); - foucusRangeMS = hours * 60 * 60 * 1000; - refreshChart(); - }); + // change the next line so that it uses the prediction if the signal gets lost (max 1/2 hr) + latestUpdateTime = Date.now(); + latestSGV = SGVdata[SGVdata.length - 1]; + prevSGV = SGVdata[SGVdata.length - 2]; + } - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // Client-side code to connect to server and handle incoming data - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - socket = io.connect(); - - function recordAlreadyStored(array,record) { - var l = array.length -1; - for (var i = 0; i <= l; i++) { - var oldRecord = array[i]; - if (record.x == oldRecord.x) return true; - } - } - - socket.on('dataUpdate', function receivedSGV(d) { - - if (!d) return; - - // SGV - - if (d.sgvs) { - - if (!d.delta) { - // replace all locally stored SGV data - console.log('Replacing all local sgv records'); - SGVdata = d.sgvs; + console.log('Total SGV data size', SGVdata.length); + + // profile, calibration and device status + + if (d.profiles) profile = d.profiles[0]; + if (d.cals) cal = d.cals[d.cals.length-1]; + if (d.devicestatus) devicestatusData = d.devicestatus; + + var temp1 = [ ]; + if (cal && isRawBGEnabled()) { + temp1 = SGVdata.map(function (entry) { + var rawBg = showRawBGs(entry.y, entry.noise, cal) ? rawIsigToRawBg(entry, cal) : 0; + if (rawBg > 0) { + return { date: new Date(entry.x - 2000), y: rawBg, sgv: scaleBg(rawBg), color: 'white', type: 'rawbg' }; } else { - var diff = nsArrayDiff(SGVdata,d.sgvs); - console.log('SGV data updated with', diff.length, 'new records'); - SGVdata = SGVdata.concat(diff); + return null; } - - SGVdata.sort(function(a, b) { - return a.x - b.x; - }); - - // change the next line so that it uses the prediction if the signal gets lost (max 1/2 hr) - latestUpdateTime = Date.now(); - latestSGV = SGVdata[SGVdata.length - 1]; - prevSGV = SGVdata[SGVdata.length - 2]; + }).filter(function(entry) { return entry != null; }); + } + var temp2 = SGVdata.map(function (obj) { + return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), direction: obj.direction, color: sgvToColor(obj.y), type: 'sgv', noise: obj.noise, filtered: obj.filtered, unfiltered: obj.unfiltered}; + }); + data = []; + data = data.concat(temp1, temp2); + + // TODO: This is a kludge to advance the time as data becomes stale by making old predictor clear (using color = 'none') + // This shouldn't have to be sent and can be fixed by using xScale.domain([x0,x1]) function with + // 2 days before now as x0 and 30 minutes from now for x1 for context plot, but this will be + // required to happen when 'now' event is sent from websocket.js every minute. When fixed, + // remove all 'color != 'none'' code + + if (d.predicted) { + data = data.concat(d.predicted.map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), color: 'none', type: 'server-forecast'} })); + } + + //Add MBG's also, pretend they are MBG's + if (d.mbgs) { + if (!d.delta) { + // replace all locally stored MBG data + console.log('Replacing all local MBG records'); + MBGdata = d.mbgs; + } else { + var diff = nsArrayDiff(MBGdata,d.mbgs); + console.log('MBG data updated with', diff.length, 'new records'); + MBGdata = MBGdata.concat(diff); } - - console.log('Total SGV data size', SGVdata.length); - - // profile, calibration and device status - - if (d.profiles) profile = d.profiles[0]; - if (d.cals) cal = d.cals[d.cals.length-1]; - if (d.devicestatus) devicestatusData = d.devicestatus; - - var temp1 = [ ]; - if (cal && isRawBGEnabled()) { - temp1 = SGVdata.map(function (entry) { - var rawBg = showRawBGs(entry.y, entry.noise, cal) ? rawIsigToRawBg(entry, cal) : 0; - if (rawBg > 0) { - return { date: new Date(entry.x - 2000), y: rawBg, sgv: scaleBg(rawBg), color: 'white', type: 'rawbg' }; - } else { - return null; - } - }).filter(function(entry) { return entry != null; }); - } - var temp2 = SGVdata.map(function (obj) { - return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), direction: obj.direction, color: sgvToColor(obj.y), type: 'sgv', noise: obj.noise, filtered: obj.filtered, unfiltered: obj.unfiltered}; - }); - data = []; - data = data.concat(temp1, temp2); - - // TODO: This is a kludge to advance the time as data becomes stale by making old predictor clear (using color = 'none') - // This shouldn't have to be sent and can be fixed by using xScale.domain([x0,x1]) function with - // 2 days before now as x0 and 30 minutes from now for x1 for context plot, but this will be - // required to happen when 'now' event is sent from websocket.js every minute. When fixed, - // remove all 'color != 'none'' code - - if (d.predicted) { - data = data.concat(d.predicted.map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), color: 'none', type: 'server-forecast'} })); - } - - //Add MBG's also, pretend they are MBG's - if (d.mbgs) { - if (!d.delta) { - // replace all locally stored MBG data - console.log('Replacing all local MBG records'); - MBGdata = d.mbgs; - } else { - var diff = nsArrayDiff(MBGdata,d.mbgs); - console.log('MBG data updated with', diff.length, 'new records'); - MBGdata = MBGdata.concat(diff); - } - } - - data = data.concat(MBGdata.map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), color: 'red', type: 'mbg', device: obj.device } })); - - data.forEach(function (d) { - if (d.y < 39) - d.color = 'transparent'; - }); - - // Update treatment data with new if delta - - if (d.treatments) { - if (!d.delta) { - treatments = d.treatments; - } else { - var newTreatments = nsArrayDiff(treatments,d.treatments); - console.log('treatment data updated with', newTreatments.length, 'new records'); - treatments = treatments.concat(newTreatments); - treatments.sort(function(a, b) { - return a.x - b.x; - }); - } - } - - console.log('Total treatment data size', treatments.length); - - treatments.forEach(function (d) { - d.created_at = new Date(d.created_at); - //cache the displayBG for each treatment in DISPLAY_UNITS - d.displayBG = displayTreatmentBG(d); - }); - - updateTitle(); - if (!isInitialData) { - isInitialData = true; - initializeCharts(); - } - else { - updateChart(false); - } + } - }); + data = data.concat(MBGdata.map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), color: 'red', type: 'mbg', device: obj.device } })); - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // Alarms and Text handling - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - socket.on('connect', function () { - console.log('Client connected to server.') - }); + data.forEach(function (d) { + if (d.y < 39) + d.color = 'transparent'; + }); + // Update treatment data with new if delta - //with predicted alarms, latestSGV may still be in target so to see if the alarm - // is for a HIGH we can only check if it's >= the bottom of the target - function isAlarmForHigh() { - return latestSGV.y >= app.thresholds.bg_target_bottom; + if (d.treatments) { + if (!d.delta) { + treatments = d.treatments; + } else { + var newTreatments = nsArrayDiff(treatments,d.treatments); + console.log('treatment data updated with', newTreatments.length, 'new records'); + treatments = treatments.concat(newTreatments); + treatments.sort(function(a, b) { + return a.x - b.x; + }); } + } - //with predicted alarms, latestSGV may still be in target so to see if the alarm - // is for a LOW we can only check if it's <= the top of the target - function isAlarmForLow() { - return latestSGV.y <= app.thresholds.bg_target_top; - } + console.log('Total treatment data size', treatments.length); - socket.on('alarm', function () { - console.info('alarm received from server'); - var enabled = (isAlarmForHigh() && browserSettings.alarmHigh) || (isAlarmForLow() && browserSettings.alarmLow); - if (enabled) { - console.log('Alarm raised!'); - currentAlarmType = 'alarm'; - generateAlarm(alarmSound); - } else { - console.info('alarm was disabled locally', latestSGV.y, browserSettings); - } - brushInProgress = false; - updateChart(false); - }); - socket.on('urgent_alarm', function () { - console.info('urgent alarm received from server'); - var enabled = (isAlarmForHigh() && browserSettings.alarmUrgentHigh) || (isAlarmForLow() && browserSettings.alarmUrgentLow); - if (enabled) { - console.log('Urgent alarm raised!'); - currentAlarmType = 'urgent_alarm'; - generateAlarm(urgentAlarmSound); - } else { - console.info('urgent alarm was disabled locally', latestSGV.y, browserSettings); - } - brushInProgress = false; - updateChart(false); - }); - socket.on('clear_alarm', function () { - if (alarmInProgress) { - console.log('clearing alarm'); - stopAlarm(); - } - }); + treatments.forEach(function (d) { + d.created_at = new Date(d.created_at); + //cache the displayBG for each treatment in DISPLAY_UNITS + d.displayBG = displayTreatmentBG(d); + }); + updateTitle(); + if (!isInitialData) { + isInitialData = true; + initializeCharts(); + } + else { + updateChart(false); + } - $('#testAlarms').click(function(event) { - d3.selectAll('.audio.alarms audio').each(function () { - var audio = this; - playAlarm(audio); - setTimeout(function() { - audio.pause(); - }, 4000); - }); - event.preventDefault(); - }); + }); + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Alarms and Text handling + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + socket.on('connect', function () { + console.log('Client connected to server.') + }); + + + //with predicted alarms, latestSGV may still be in target so to see if the alarm + // is for a HIGH we can only check if it's >= the bottom of the target + function isAlarmForHigh() { + return latestSGV.y >= app.thresholds.bg_target_bottom; } - $.ajax('/api/v1/status.json', { - success: function (xhr) { - app = { name: xhr.name - , version: xhr.version - , head: xhr.head - , apiEnabled: xhr.apiEnabled - , enabledOptions: xhr.enabledOptions || '' - , thresholds: xhr.thresholds - , alarm_types: xhr.alarm_types - , units: xhr.units - , careportalEnabled: xhr.careportalEnabled - , defaults: xhr.defaults - }; - } - }).done(function() { - $('.appName').text(app.name); - $('.version').text(app.version); - $('.head').text(app.head); - if (app.apiEnabled) { - $('.serverSettings').show(); - } - $('#treatmentDrawerToggle').toggle(app.careportalEnabled); - Nightscout.plugins.init(app); - browserSettings = getBrowserSettings(browserStorage); - $('.container').toggleClass('has-minor-pills', Nightscout.plugins.hasShownType('pill-minor', browserSettings)); - init(); + //with predicted alarms, latestSGV may still be in target so to see if the alarm + // is for a LOW we can only check if it's <= the top of the target + function isAlarmForLow() { + return latestSGV.y <= app.thresholds.bg_target_top; + } + + socket.on('alarm', function () { + console.info('alarm received from server'); + var enabled = (isAlarmForHigh() && browserSettings.alarmHigh) || (isAlarmForLow() && browserSettings.alarmLow); + if (enabled) { + console.log('Alarm raised!'); + currentAlarmType = 'alarm'; + generateAlarm(alarmSound); + } else { + console.info('alarm was disabled locally', latestSGV.y, browserSettings); + } + brushInProgress = false; + updateChart(false); + }); + socket.on('urgent_alarm', function () { + console.info('urgent alarm received from server'); + var enabled = (isAlarmForHigh() && browserSettings.alarmUrgentHigh) || (isAlarmForLow() && browserSettings.alarmUrgentLow); + if (enabled) { + console.log('Urgent alarm raised!'); + currentAlarmType = 'urgent_alarm'; + generateAlarm(urgentAlarmSound); + } else { + console.info('urgent alarm was disabled locally', latestSGV.y, browserSettings); + } + brushInProgress = false; + updateChart(false); }); + socket.on('clear_alarm', function () { + if (alarmInProgress) { + console.log('clearing alarm'); + stopAlarm(); + } + }); + + + $('#testAlarms').click(function(event) { + d3.selectAll('.audio.alarms audio').each(function () { + var audio = this; + playAlarm(audio); + setTimeout(function() { + audio.pause(); + }, 4000); + }); + event.preventDefault(); + }); + } + + $.ajax('/api/v1/status.json', { + success: function (xhr) { + app = { name: xhr.name + , version: xhr.version + , head: xhr.head + , apiEnabled: xhr.apiEnabled + , enabledOptions: xhr.enabledOptions || '' + , thresholds: xhr.thresholds + , alarm_types: xhr.alarm_types + , units: xhr.units + , careportalEnabled: xhr.careportalEnabled + , defaults: xhr.defaults + }; + } + }).done(function() { + $('.appName').text(app.name); + $('.version').text(app.version); + $('.head').text(app.head); + if (app.apiEnabled) { + $('.serverSettings').show(); + } + $('#treatmentDrawerToggle').toggle(app.careportalEnabled); + Nightscout.plugins.init(app); + browserSettings = getBrowserSettings(browserStorage); + $('.container').toggleClass('has-minor-pills', Nightscout.plugins.hasShownType('pill-minor', browserSettings)); + init(); + }); })(); From 4f66f47959185c4d78a669a51a8dea39c0805b62 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 7 Jun 2015 01:30:23 +0300 Subject: [PATCH 075/937] Changed to using Iodash _.cloneDeep() --- lib/websocket.js | 55 ++---------------------------------------------- 1 file changed, 2 insertions(+), 53 deletions(-) diff --git a/lib/websocket.js b/lib/websocket.js index 079d2ba8960..e322c1eb0a5 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -1,6 +1,7 @@ 'use strict'; var ar2 = require('./plugins/ar2')(); +var _ = require('lodash'); function nsArrayDiff(oldArray, newArray) { var seen = {}; @@ -12,58 +13,6 @@ function nsArrayDiff(oldArray, newArray) { return result; } -function clone(src) { - function mixin(dest, source, copyFunc) { - var name, s, i, empty = {}; - for(name in source){ - // the (!(name in empty) || empty[name] !== s) condition avoids copying properties in "source" - // inherited from Object.prototype. For example, if dest has a custom toString() method, - // don't overwrite it with the toString() method that source inherited from Object.prototype - s = source[name]; - if(!(name in dest) || (dest[name] !== s && (!(name in empty) || empty[name] !== s))){ - dest[name] = copyFunc ? copyFunc(s) : s; - } - } - return dest; - } - - if(!src || typeof src != "object" || Object.prototype.toString.call(src) === "[object Function]"){ - // null, undefined, any non-object, or function - return src; // anything - } - if(src.nodeType && "cloneNode" in src){ - // DOM Node - return src.cloneNode(true); // Node - } - if(src instanceof Date){ - // Date - return new Date(src.getTime()); // Date - } - if(src instanceof RegExp){ - // RegExp - return new RegExp(src); // RegExp - } - var r, i, l; - if(src instanceof Array){ - // array - r = []; - for(i = 0, l = src.length; i < l; ++i){ - if(i in src){ - r.push(clone(src[i])); - } - } - // we don't clone functions for performance reasons - // }else if(d.isFunction(src)){ - // // function - // r = function(){ return src.apply(this, arguments); }; - }else{ - // generic objects - r = src.constructor ? new src.constructor() : {}; - } - return mixin(r, src, clone); - -} - function uniq(a) { var seen = {}; return a.filter(function(item) { @@ -222,7 +171,7 @@ function init (server) { } else { console.log('delta calculation indicates no new data is present'); } } - patientData = clone(d); + patientData = _.cloneDeep(d); patientData.predicted = forecast.predicted; var emitAlarmType = null; From 02c47ee8a4f75aa2aa67eab9a31ed9d37a52796c Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 6 Jun 2015 15:55:26 -0700 Subject: [PATCH 076/937] a little clean up --- lib/websocket.js | 104 ++++++++++++++++++++++------------------------- 1 file changed, 48 insertions(+), 56 deletions(-) diff --git a/lib/websocket.js b/lib/websocket.js index e322c1eb0a5..1d5e7422291 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -6,21 +6,13 @@ var _ = require('lodash'); function nsArrayDiff(oldArray, newArray) { var seen = {}; var l = oldArray.length; - for (var i = 0; i < l; i++) { seen[oldArray[i].x] = true }; + for (var i = 0; i < l; i++) { seen[oldArray[i].x] = true } var result = []; l = newArray.length; - for (var i = 0; i < l; i++) { if (!seen.hasOwnProperty(newArray[i].x)) { result.push(newArray[i]); console.log('delta data found'); } }; + for (var j = 0; j < l; j++) { if (!seen.hasOwnProperty(newArray[j].x)) { result.push(newArray[j]); console.log('delta data found'); } } return result; } -function uniq(a) { - var seen = {}; - return a.filter(function(item) { - return seen.hasOwnProperty(item.x) ? false : (seen[item.x] = true); - }); -} - - function sort (values) { values.sort(function sorter (a, b) { return a.x - b.x; @@ -34,7 +26,7 @@ function init (server) { } var FORTY_MINUTES = 2400000; - + var lastUpdated = 0; var patientData = {}; var patientDataUpdate = {}; @@ -160,7 +152,7 @@ function init (server) { if (lastSGV) { var forecast = ar2.forecast(env, ctx); - if (patientData.sgvs) { + if (patientData.sgvs) { var delta = calculateDelta(d); if (delta.delta) { patientDataUpdate = delta; @@ -173,7 +165,7 @@ function init (server) { patientData = _.cloneDeep(d); patientData.predicted = forecast.predicted; - + var emitAlarmType = null; if (env.alarm_types.indexOf('simple') > -1) { @@ -215,72 +207,72 @@ function init (server) { }; function calculateDelta(d) { - + var delta = {'delta': true}; var changesFound = false; - + // if there's no updates done so far, just return the full set - if (!patientData.sgvs) return d; - + if (!patientData.sgvs) return d; + console.log('patientData.sgvs last record time', patientData.sgvs[patientData.sgvs.length-1].x); console.log('d.sgvslast record time', d.sgvs[d.sgvs.length-1].x); - - var sgvDelta = nsArrayDiff(patientData.sgvs,d.sgvs); - - if (sgvDelta.length > 0) { + + var sgvDelta = nsArrayDiff(patientData.sgvs,d.sgvs); + + if (sgvDelta.length > 0) { console.log('sgv changes found'); - changesFound = true; - sort(sgvDelta); - delta.sgvs = sgvDelta; - }; - + changesFound = true; + sort(sgvDelta); + delta.sgvs = sgvDelta; + } + var treatmentDelta = nsArrayDiff(patientData.treatments,d.treatments); - - if (treatmentDelta.length > 0) { + + if (treatmentDelta.length > 0) { console.log('treatment changes found'); - changesFound = true; - sort(treatmentDelta); - delta.treatments = treatmentDelta; - }; + changesFound = true; + sort(treatmentDelta); + delta.treatments = treatmentDelta; + } var mbgsDelta = nsArrayDiff(patientData.mbgs,d.mbgs); - - if (mbgsDelta.length > 0) { + + if (mbgsDelta.length > 0) { console.log('mbgs changes found'); - changesFound = true; - sort(mbgsDelta); - delta.mbgs = mbgsDelta; - }; + changesFound = true; + sort(mbgsDelta); + delta.mbgs = mbgsDelta; + } var calsDelta = nsArrayDiff(patientData.cals,d.cals); - - if (calsDelta.length > 0) { + + if (calsDelta.length > 0) { console.log('cals changes found'); - changesFound = true; - sort(calsDelta); - delta.cals = calsDelta; - }; + changesFound = true; + sort(calsDelta); + delta.cals = calsDelta; + } if (JSON.stringify(patientData.devicestatus) != JSON.stringify(d.devicestatus)) { console.log('devicestatus changes found'); - changesFound = true; - delta.devicestatus = d.devicestatus; - }; + changesFound = true; + delta.devicestatus = d.devicestatus; + } if (JSON.stringify(patientData.profiles) != JSON.stringify(d.profiles)) { console.log('profile changes found'); - changesFound = true; - delta.profiles = d.profiles; - }; + changesFound = true; + delta.profiles = d.profiles; + } - if (changesFound) { - console.log('changes found'); + if (changesFound) { + console.log('changes found'); return delta; - } - return d; - + } + return d; + } - + start( ); configure( ); listeners( ); From 125ff5bc498382749f49eabb89672d1d2775aca9 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 6 Jun 2015 16:06:06 -0700 Subject: [PATCH 077/937] fix issue with cloneDeep and mongo ObjectId's --- lib/websocket.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/websocket.js b/lib/websocket.js index 1d5e7422291..729615c47d2 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -2,6 +2,7 @@ var ar2 = require('./plugins/ar2')(); var _ = require('lodash'); +var ObjectID = require('mongodb').ObjectID; function nsArrayDiff(oldArray, newArray) { var seen = {}; @@ -163,7 +164,13 @@ function init (server) { } else { console.log('delta calculation indicates no new data is present'); } } - patientData = _.cloneDeep(d); + //see https://github.com/lodash/lodash/issues/602#issuecomment-47414964 + patientData = _.cloneDeep(d, function (value) { + if (value instanceof ObjectID) { + return value.toString(); + } + }); + patientData.predicted = forecast.predicted; var emitAlarmType = null; From e78a8cb1353dedb402b38eb1e5f51831f294bfd0 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 6 Jun 2015 22:47:48 -0700 Subject: [PATCH 078/937] less debouncing --- static/js/client.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index fbb8fabfc61..4130f43f3db 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -306,7 +306,7 @@ function nsArrayDiff(oldArray, newArray) { } // clears the current user brush and resets to the current real time data - var updateBrushToNow = _.debounce(function updateBrushToNow(skipBrushing) { + function updateBrushToNow(skipBrushing) { // get current time range var dataRange = d3.extent(data, dateFn); @@ -323,7 +323,7 @@ function nsArrayDiff(oldArray, newArray) { // clear user brush tracking brushInProgress = false; } - }, DEBOUNCE_MS); + } function brushStarted() { // update the opacity of the context data points to brush extent @@ -372,7 +372,7 @@ function nsArrayDiff(oldArray, newArray) { return errorDisplay; } - var brushed = _.debounce(function brushed(skipTimer) { + function brushed(skipTimer) { if (!skipTimer) { // set a timer to reset focus chart to real-time data @@ -779,7 +779,7 @@ function nsArrayDiff(oldArray, newArray) { }); treatCircles.attr('clip-path', 'url(#clip)'); - }, DEBOUNCE_MS); + }; // called for initial update and updates for resize var updateChart = _.debounce(function updateChart(init) { @@ -1618,7 +1618,7 @@ function nsArrayDiff(oldArray, newArray) { context.append('g') .attr('class', 'y axis'); - //updateBrushToNow and updateChart are both _.debounced + //updateChart is _.debounce'd function refreshChart(updateToNow) { if (updateToNow) { updateBrushToNow(); From 6fdcf6fcc34a58f410dbf6c62fd1aa973a8da7d3 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 6 Jun 2015 23:41:04 -0700 Subject: [PATCH 079/937] stop sending unused predicted values, still a kludge but not wasting bytes --- lib/websocket.js | 3 --- static/js/client.js | 9 +++++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/lib/websocket.js b/lib/websocket.js index 729615c47d2..565886168a8 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -157,7 +157,6 @@ function init (server) { var delta = calculateDelta(d); if (delta.delta) { patientDataUpdate = delta; - patientDataUpdate.predicted = forecast.predicted; console.log('patientData full size', JSON.stringify(patientData).length,'bytes'); if (delta.sgvs) console.log('patientData update size', JSON.stringify(patientDataUpdate).length,'bytes'); emitData(); @@ -171,8 +170,6 @@ function init (server) { } }); - patientData.predicted = forecast.predicted; - var emitAlarmType = null; if (env.alarm_types.indexOf('simple') > -1) { diff --git a/static/js/client.js b/static/js/client.js index 4130f43f3db..197cd0b5a60 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1730,13 +1730,14 @@ function nsArrayDiff(oldArray, newArray) { data = data.concat(temp1, temp2); // TODO: This is a kludge to advance the time as data becomes stale by making old predictor clear (using color = 'none') - // This shouldn't have to be sent and can be fixed by using xScale.domain([x0,x1]) function with + // This shouldn't need to be generated and can be fixed by using xScale.domain([x0,x1]) function with // 2 days before now as x0 and 30 minutes from now for x1 for context plot, but this will be // required to happen when 'now' event is sent from websocket.js every minute. When fixed, // remove all 'color != 'none'' code - - if (d.predicted) { - data = data.concat(d.predicted.map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), color: 'none', type: 'server-forecast'} })); + for (var i = 1; i <= 12; i++) { + data.push({ + date: new Date(Date.now() + (i * FIVE_MINS_IN_MS)), y: 100, sgv: scaleBg(100), color: 'none', type: 'server-forecast' + }); } //Add MBG's also, pretend they are MBG's From 669f48175d03ebae78f5b352c3f1568567cc6e5e Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 7 Jun 2015 00:02:36 -0700 Subject: [PATCH 080/937] less fake padding points, and timed to last data --- static/js/client.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index 197cd0b5a60..585cb6c31d9 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1734,9 +1734,10 @@ function nsArrayDiff(oldArray, newArray) { // 2 days before now as x0 and 30 minutes from now for x1 for context plot, but this will be // required to happen when 'now' event is sent from websocket.js every minute. When fixed, // remove all 'color != 'none'' code - for (var i = 1; i <= 12; i++) { + var lastdata = data.length > 0 ? data[data.length - 1].date.getTime() : Date.now(); + for (var i = 1; i <= 8; i++) { data.push({ - date: new Date(Date.now() + (i * FIVE_MINS_IN_MS)), y: 100, sgv: scaleBg(100), color: 'none', type: 'server-forecast' + date: new Date(lastdata + (i * FIVE_MINS_IN_MS)), y: 100, sgv: scaleBg(100), color: 'none', type: 'server-forecast' }); } From 811324edfa451c4aa028367e8760fde1fcf2ee7f Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 7 Jun 2015 00:43:36 -0700 Subject: [PATCH 081/937] add new event when data is loaded and use that for updating websockets instead of tick --- lib/bootevent.js | 9 +++++++++ server.js | 9 ++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/bootevent.js b/lib/bootevent.js index f3d6a450265..cdf7d751e7b 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -34,6 +34,15 @@ function boot (env) { ctx.data = require('./data')(env, ctx); + ctx.heartbeat.on('tick', function(tick) { + console.info('tick', tick.now); + ctx.data.update(function dataUpdated () { + ctx.heartbeat.emit('data-loaded'); + }); + }); + + ctx.heartbeat.uptime( ); + next( ); }) ; diff --git a/server.js b/server.js index 134493f6124..678bb5f5c19 100644 --- a/server.js +++ b/server.js @@ -60,15 +60,10 @@ bootevent(env).boot(function booted (ctx) { /////////////////////////////////////////////////// var websocket = require('./lib/websocket')(server); - ctx.heartbeat.on('tick', function(tick) { - console.info('tick', tick.now); - ctx.data.update(function dataUpdated () { - websocket.processData(env, ctx); - }); + ctx.heartbeat.on('data-loaded', function() { + websocket.processData(env, ctx); }); - ctx.heartbeat.uptime( ); - }) ; From 9a7d3e87807343e8fb173ba9de766b6716149a67 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 7 Jun 2015 00:59:23 -0700 Subject: [PATCH 082/937] 1 less fake point so position of now doesn't shift when scrolling back --- static/js/client.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/js/client.js b/static/js/client.js index 585cb6c31d9..687b9ef0183 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1735,7 +1735,7 @@ function nsArrayDiff(oldArray, newArray) { // required to happen when 'now' event is sent from websocket.js every minute. When fixed, // remove all 'color != 'none'' code var lastdata = data.length > 0 ? data[data.length - 1].date.getTime() : Date.now(); - for (var i = 1; i <= 8; i++) { + for (var i = 1; i <= 7; i++) { data.push({ date: new Date(lastdata + (i * FIVE_MINS_IN_MS)), y: 100, sgv: scaleBg(100), color: 'none', type: 'server-forecast' }); From f52e80a0eaf66be5fa8dac6aab6c925b2e7bd993 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 7 Jun 2015 01:54:15 -0700 Subject: [PATCH 083/937] refactored websocket alarms into notifications.js --- lib/bootevent.js | 5 ++ lib/notifications.js | 124 +++++++++++++++++++++++++++++++++++++++++++ lib/websocket.js | 122 +++++------------------------------------- server.js | 8 ++- 4 files changed, 149 insertions(+), 110 deletions(-) create mode 100644 lib/notifications.js diff --git a/lib/bootevent.js b/lib/bootevent.js index cdf7d751e7b..3005a1f70ec 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -33,6 +33,7 @@ function boot (env) { ctx.heartbeat = require('./ticker')(env, ctx); ctx.data = require('./data')(env, ctx); + ctx.notifications = require('./notifications')(env, ctx); ctx.heartbeat.on('tick', function(tick) { console.info('tick', tick.now); @@ -41,6 +42,10 @@ function boot (env) { }); }); + ctx.heartbeat.on('data-loaded', function() { + ctx.notifications.processData(env, ctx); + }); + ctx.heartbeat.uptime( ); next( ); diff --git a/lib/notifications.js b/lib/notifications.js new file mode 100644 index 00000000000..e4ad3e66645 --- /dev/null +++ b/lib/notifications.js @@ -0,0 +1,124 @@ +'use strict'; + +var ar2 = require('./plugins/ar2')(); + +var THIRTY_MINUTES = 30 * 60 * 1000; + +var Alarm = function(_typeName, _threshold) { + this.typeName = _typeName; + this.silenceTime = THIRTY_MINUTES; + this.lastAckTime = 0; + this.threshold = _threshold; +}; + +// list of alarms with their thresholds +var alarms = { + 'alarm' : new Alarm('Regular', 0.05), + 'urgent_alarm': new Alarm('Urgent', 0.10) +}; + +function init (env, ctx) { + function notifications () { + return notifications; + } + + //should only be used when auto acking the alarms after going back in range or when an error corrects + //setting the silence time to 1ms so the alarm will be retriggered as soon as the condition changes + //since this wasn't ack'd by a user action + function autoAckAlarms() { + var sendClear = false; + for (var type in alarms) { + if (alarms.hasOwnProperty(type)) { + var alarm = alarms[type]; + if (alarm.lastEmitTime) { + console.info('auto acking ' + type); + notifications.ack(type, 1); + sendClear = true; + } + } + } + if (sendClear) { + ctx.heartbeat.emit('notification', {clear: true}); + console.info('emitted notification clear'); + } + } + + function emitAlarm (type) { + var alarm = alarms[type]; + if (ctx.data.lastUpdated > alarm.lastAckTime + alarm.silenceTime) { + ctx.heartbeat.emit('notification', {type: type}); + alarm.lastEmitTime = ctx.data.lastUpdated; + console.info('emitted notification:' + type); + } else { + console.log(alarm.typeName + ' alarm is silenced for ' + Math.floor((alarm.silenceTime - (ctx.data.lastUpdated - alarm.lastAckTime)) / 60000) + ' minutes more'); + } + } + + notifications.processData = function processData ( ) { + var d = ctx.data; + + console.log('running notifications.processData'); + + var lastSGV = d.sgvs.length > 0 ? d.sgvs[d.sgvs.length - 1].y : null; + + if (lastSGV) { + var forecast = ar2.forecast(env, ctx); + + var emitAlarmType = null; + + if (env.alarm_types.indexOf('simple') > -1) { + if (lastSGV > env.thresholds.bg_high) { + emitAlarmType = 'urgent_alarm'; + console.info(lastSGV + ' > ' + env.thresholds.bg_high + ' will emmit ' + emitAlarmType); + } else if (lastSGV > env.thresholds.bg_target_top) { + emitAlarmType = 'alarm'; + console.info(lastSGV + ' > ' + env.thresholds.bg_target_top + ' will emmit ' + emitAlarmType); + } else if (lastSGV < env.thresholds.bg_low) { + emitAlarmType = 'urgent_alarm'; + console.info(lastSGV + ' < ' + env.thresholds.bg_low + ' will emmit ' + emitAlarmType); + } else if (lastSGV < env.thresholds.bg_target_bottom) { + emitAlarmType = 'alarm'; + console.info(lastSGV + ' < ' + env.thresholds.bg_target_bottom + ' will emmit ' + emitAlarmType); + } + } + + if (!emitAlarmType && env.alarm_types.indexOf('predict') > -1) { + if (forecast.avgLoss > alarms['urgent_alarm'].threshold) { + emitAlarmType = 'urgent_alarm'; + console.info('Avg Loss:' + forecast.avgLoss + ' > ' + alarms['urgent_alarm'].threshold + ' will emmit ' + emitAlarmType); + } else if (forecast.avgLoss > alarms['alarm'].threshold) { + emitAlarmType = 'alarm'; + console.info('Avg Loss:' + forecast.avgLoss + ' > ' + alarms['alarm'].threshold + ' will emmit ' + emitAlarmType); + } + } + + if (d.sgvs.length > 0 && d.sgvs[d.sgvs.length - 1].y < 39) { + emitAlarmType = 'urgent_alarm'; + } + + if (emitAlarmType) { + emitAlarm(emitAlarmType); + } else { + autoAckAlarms(); + } + } + }; + + notifications.ack = function ack (type, time) { + var alarm = alarms[type]; + if (alarm) { + console.info('Got an ack for: ', alarm, 'time: ' + time); + } else { + console.warn('Got an ack for an unknown alarm time'); + return; + } + alarm.lastAckTime = new Date().getTime(); + alarm.silenceTime = time ? time : THIRTY_MINUTES; + delete alarm.lastEmitTime; + + }; + + return notifications(); +} + +module.exports = init; \ No newline at end of file diff --git a/lib/websocket.js b/lib/websocket.js index 565886168a8..941209b220b 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -1,6 +1,5 @@ 'use strict'; -var ar2 = require('./plugins/ar2')(); var _ = require('lodash'); var ObjectID = require('mongodb').ObjectID; @@ -20,14 +19,12 @@ function sort (values) { }); } -function init (server) { +function init (env, ctx, server) { function websocket ( ) { return websocket; } - var FORTY_MINUTES = 2400000; - var lastUpdated = 0; var patientData = {}; var patientDataUpdate = {}; @@ -67,13 +64,11 @@ function init (server) { io.sockets.socket(socket.id).emit('dataUpdate',patientData); io.sockets.emit('clients', ++watchers); socket.on('ack', function(alarmType, silenceTime) { - ackAlarm(alarmType, silenceTime); + ctx.notifications.ack(alarmType, silenceTime); if (alarmType == 'urgent_alarm') { //also clean normal alarm so we don't get a double alarm as BG comes back into range - ackAlarm('alarm', silenceTime); + ctx.notifications.ack('alarm', silenceTime); } - io.sockets.emit('clear_alarm', true); - console.log('alarm cleared'); }); socket.on('disconnect', function () { io.sockets.emit('clients', --watchers); @@ -81,78 +76,16 @@ function init (server) { }); } - /////////////////////////////////////////////////// - // data handling functions - /////////////////////////////////////////////////// - - var Alarm = function(_typeName, _threshold) { - this.typeName = _typeName; - this.silenceTime = FORTY_MINUTES; - this.lastAckTime = 0; - this.threshold = _threshold; - }; - -// list of alarms with their thresholds - var alarms = { - 'alarm' : new Alarm('Regular', 0.05), - 'urgent_alarm': new Alarm('Urgent', 0.10) - }; - - function ackAlarm(alarmType, silenceTime) { - var alarm = alarms[alarmType]; - if (!alarm) { - console.warn('Got an ack for an unknown alarm time'); - return; - } - alarm.lastAckTime = new Date().getTime(); - alarm.silenceTime = silenceTime ? silenceTime : FORTY_MINUTES; - delete alarm.lastEmitTime; - } - - //should only be used when auto acking the alarms after going back in range or when an error corrects - //setting the silence time to 1ms so the alarm will be retriggered as soon as the condition changes - //since this wasn't ack'd by a user action - function autoAckAlarms() { - var sendClear = false; - for (var alarmType in alarms) { - if (alarms.hasOwnProperty(alarmType)) { - var alarm = alarms[alarmType]; - if (alarm.lastEmitTime) { - console.info('auto acking ' + alarmType); - ackAlarm(alarmType, 1); - sendClear = true; - } - } - } - if (sendClear) { - io.sockets.emit('clear_alarm', true); - console.info('emitted clear_alarm to all clients'); - } - } - - function emitAlarm (alarmType) { - var alarm = alarms[alarmType]; - if (lastUpdated > alarm.lastAckTime + alarm.silenceTime) { - io.sockets.emit(alarmType); - alarm.lastEmitTime = lastUpdated; - console.info('emitted ' + alarmType + ' to all clients'); - } else { - console.log(alarm.typeName + ' alarm is silenced for ' + Math.floor((alarm.silenceTime - (lastUpdated - alarm.lastAckTime)) / 60000) + ' minutes more'); - } - } - - websocket.processData = function processData (env, ctx) { + websocket.processData = function processData ( ) { var d = ctx.data; lastUpdated = d.lastUpdated; - console.log('running websocket.loadData'); + console.log('running websocket.processData'); var lastSGV = d.sgvs.length > 0 ? d.sgvs[d.sgvs.length - 1].y : null; if (lastSGV) { - var forecast = ar2.forecast(env, ctx); - if (patientData.sgvs) { var delta = calculateDelta(d); if (delta.delta) { @@ -170,43 +103,16 @@ function init (server) { } }); - var emitAlarmType = null; - - if (env.alarm_types.indexOf('simple') > -1) { - if (lastSGV > env.thresholds.bg_high) { - emitAlarmType = 'urgent_alarm'; - console.info(lastSGV + ' > ' + env.thresholds.bg_high + ' will emmit ' + emitAlarmType); - } else if (lastSGV > env.thresholds.bg_target_top) { - emitAlarmType = 'alarm'; - console.info(lastSGV + ' > ' + env.thresholds.bg_target_top + ' will emmit ' + emitAlarmType); - } else if (lastSGV < env.thresholds.bg_low) { - emitAlarmType = 'urgent_alarm'; - console.info(lastSGV + ' < ' + env.thresholds.bg_low + ' will emmit ' + emitAlarmType); - } else if (lastSGV < env.thresholds.bg_target_bottom) { - emitAlarmType = 'alarm'; - console.info(lastSGV + ' < ' + env.thresholds.bg_target_bottom + ' will emmit ' + emitAlarmType); - } - } - - if (!emitAlarmType && env.alarm_types.indexOf('predict') > -1) { - if (forecast.avgLoss > alarms['urgent_alarm'].threshold) { - emitAlarmType = 'urgent_alarm'; - console.info('Avg Loss:' + forecast.avgLoss + ' > ' + alarms['urgent_alarm'].threshold + ' will emmit ' + emitAlarmType); - } else if (forecast.avgLoss > alarms['alarm'].threshold) { - emitAlarmType = 'alarm'; - console.info('Avg Loss:' + forecast.avgLoss + ' > ' + alarms['alarm'].threshold + ' will emmit ' + emitAlarmType); - } - } - - if (d.sgvs.length > 0 && d.sgvs[d.sgvs.length - 1].y < 39) { - emitAlarmType = 'urgent_alarm'; - } + } + }; - if (emitAlarmType) { - emitAlarm(emitAlarmType); - } else { - autoAckAlarms(); - } + websocket.emitNotification = function emitNotification (info) { + if (info.clear) { + io.sockets.emit('clear_alarm', true); + console.info('emitted clear_alarm to all clients'); + } else if (info.type) { + io.sockets.emit(info.type); + console.info('emitted ' + info.type + ' to all clients'); } }; diff --git a/server.js b/server.js index 678bb5f5c19..6a2025b8722 100644 --- a/server.js +++ b/server.js @@ -58,10 +58,14 @@ bootevent(env).boot(function booted (ctx) { /////////////////////////////////////////////////// // setup socket io for data and message transmission /////////////////////////////////////////////////// - var websocket = require('./lib/websocket')(server); + var websocket = require('./lib/websocket')(env, ctx, server); ctx.heartbeat.on('data-loaded', function() { - websocket.processData(env, ctx); + websocket.processData(); + }); + + ctx.heartbeat.on('notification', function(info) { + websocket.emitNotification(info); }); }) From d0eefc18ed15d2b1906d3726d44c3df4e82e5aa3 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 7 Jun 2015 13:02:35 +0300 Subject: [PATCH 084/937] moved timeAgo to nsUtils nsUtils is now in the bundle, mapped to Nightscout namespace cannula age now considers the point of time being visualized in the timeline small prettification on client.js --- bundle/bundle.source.js | 1 + lib/nsutils.js | 37 ++++++++++++++++++++++++- lib/plugins/cannulaage.js | 4 +-- static/js/client.js | 57 +++++++++------------------------------ 4 files changed, 51 insertions(+), 48 deletions(-) diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index c37363c2c4a..6e9beca2f1e 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -5,6 +5,7 @@ window.Nightscout = { units: require('../lib/units')(), + nsUtils: require('../lib/nsUtils')(), profile: require('../lib/profilefunctions')(), plugins: require('../lib/plugins/')().registerClientDefaults() }; diff --git a/lib/nsutils.js b/lib/nsutils.js index c96f859575b..ad2a4fc3c1b 100644 --- a/lib/nsutils.js +++ b/lib/nsutils.js @@ -6,7 +6,11 @@ function init() { return nsutils; } - nsutils.toFixed = function toFixed(value) { + var MINUTE_IN_SECS = 60 + , HOUR_IN_SECS = 3600 + , DAY_IN_SECS = 86400; + + nsutils.toFixed = function toFixed(value) { if (value === 0) { return '0'; } else { @@ -15,6 +19,37 @@ function init() { } }; + nsutils.timeAgo = function timeAgo(time) { + + var now = Date.now() + , offset = time == -1 ? -1 : (now - time) / 1000 + , parts = {}; + + if (offset < MINUTE_IN_SECS * -5) parts = { value: 'in the future' }; + else if (offset == -1) parts = { label: 'time ago' }; + else if (offset <= MINUTE_IN_SECS * 2) parts = { value: 1, label: 'min ago' }; + else if (offset < (MINUTE_IN_SECS * 60)) parts = { value: Math.round(Math.abs(offset / MINUTE_IN_SECS)), label: 'mins ago' }; + else if (offset < (HOUR_IN_SECS * 2)) parts = { value: 1, label: 'hr ago' }; + else if (offset < (HOUR_IN_SECS * 24)) parts = { value: Math.round(Math.abs(offset / HOUR_IN_SECS)), label: 'hrs ago' }; + else if (offset < DAY_IN_SECS) parts = { value: 1, label: 'day ago' }; + else if (offset <= (DAY_IN_SECS * 7)) parts = { value: Math.round(Math.abs(offset / DAY_IN_SECS)), label: 'day ago' }; + else parts = { value: 'long ago' }; + + if (offset > DAY_IN_SECS * 7) { + parts.status = 'warn'; + } else if (offset < MINUTE_IN_SECS * -5 || offset > (MINUTE_IN_SECS * browserSettings.alarmTimeAgoUrgentMins)) { + parts.status = 'urgent'; + } else if (offset > (MINUTE_IN_SECS * browserSettings.alarmTimeAgoWarnMins)) { + parts.status = 'warn'; + } else { + parts.status = 'current'; + } + + return parts; + + } + + return nsutils(); } diff --git a/lib/plugins/cannulaage.js b/lib/plugins/cannulaage.js index 636fe0d5892..a90cb7135d4 100644 --- a/lib/plugins/cannulaage.js +++ b/lib/plugins/cannulaage.js @@ -17,14 +17,14 @@ function init() { var found = false; var treatmentDate = null; var message = ''; - + for (var t in this.env.treatments) { if (this.env.treatments.hasOwnProperty(t)) { var treatment = this.env.treatments[t]; if (treatment.eventType == "Site Change") { treatmentDate = new Date(treatment.created_at); - var hours = Math.round(Math.abs(new Date() - treatmentDate) / 36e5); + var hours = Math.round(Math.abs(this.env.time - treatmentDate) / 36e5); if (!found) { found = true; diff --git a/static/js/client.js b/static/js/client.js index 687b9ef0183..c4d0cf5a167 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1,10 +1,12 @@ //TODO: clean up var app = {}, browserSettings = {}, browserStorage = $.localStorage; +var nsUtils = Nightscout.nsUtils; + function nsArrayDiff(oldArray, newArray) { var seen = {}; var l = oldArray.length; - for (var i = 0; i < l; i++) { seen[oldArray[i].x] = true }; + for (var i = 0; i < l; i++) { seen[oldArray[i].x] = true } var result = []; l = newArray.length; for (var j = 0; j < l; j++) { if (!seen.hasOwnProperty(newArray[j].x)) { result.push(newArray[j]); console.log('delta data found'); } } @@ -20,9 +22,7 @@ function nsArrayDiff(oldArray, newArray) { , UPDATE_TRANS_MS = 750 // milliseconds , ONE_MIN_IN_MS = 60000 , FIVE_MINS_IN_MS = 300000 - , SIX_MINS_IN_MS = 360000 , THREE_HOURS_MS = 3 * 60 * 60 * 1000 - , TWELVE_HOURS_MS = 12 * 60 * 60 * 1000 , TWENTY_FIVE_MINS_IN_MS = 1500000 , THIRTY_MINS_IN_MS = 1800000 , SIXTY_MINS_IN_MS = 3600000 @@ -32,11 +32,7 @@ function nsArrayDiff(oldArray, newArray) { , FORMAT_TIME_12_SCALE = '%-I %p' , FORMAT_TIME_24_SCALE = '%H' , WIDTH_SMALL_DOTS = 400 - , WIDTH_BIG_DOTS = 800 - , MINUTE_IN_SECS = 60 - , HOUR_IN_SECS = 3600 - , DAY_IN_SECS = 86400 - , WEEK_IN_SECS = 604800; + , WIDTH_BIG_DOTS = 800; var socket , isInitialData = false @@ -138,7 +134,7 @@ function nsArrayDiff(oldArray, newArray) { function updateTitle() { var time = latestSGV ? new Date(latestSGV.x).getTime() : (prevSGV ? new Date(prevSGV.x).getTime() : -1) - , ago = timeAgo(time); + , ago = nsUtils.timeAgo(time); var bg_title = browserSettings.customTitle || ''; @@ -413,10 +409,11 @@ function nsArrayDiff(oldArray, newArray) { function updateCurrentSGV(entry) { - var value = entry.y - , time = new Date(entry.x).getTime() - , ago = timeAgo(time) - , isCurrent = ago.status === 'current'; + var value, time, ago, isCurrent; + value = entry.y; + time = new Date(entry.x).getTime(); + ago = nsUtils.timeAgo(time); + isCurrent = ago.status === 'current'; if (value == 9) { currentBG.text(''); @@ -779,7 +776,7 @@ function nsArrayDiff(oldArray, newArray) { }); treatCircles.attr('clip-path', 'url(#clip)'); - }; + } // called for initial update and updates for resize var updateChart = _.debounce(function updateChart(init) { @@ -1218,36 +1215,6 @@ function nsArrayDiff(oldArray, newArray) { brushed(false); } - function timeAgo(time) { - - var now = Date.now() - , offset = time == -1 ? -1 : (now - time) / 1000 - , parts = {}; - - if (offset < MINUTE_IN_SECS * -5) parts = { value: 'in the future' }; - else if (offset == -1) parts = { label: 'time ago' }; - else if (offset <= MINUTE_IN_SECS * 2) parts = { value: 1, label: 'min ago' }; - else if (offset < (MINUTE_IN_SECS * 60)) parts = { value: Math.round(Math.abs(offset / MINUTE_IN_SECS)), label: 'mins ago' }; - else if (offset < (HOUR_IN_SECS * 2)) parts = { value: 1, label: 'hr ago' }; - else if (offset < (HOUR_IN_SECS * 24)) parts = { value: Math.round(Math.abs(offset / HOUR_IN_SECS)), label: 'hrs ago' }; - else if (offset < DAY_IN_SECS) parts = { value: 1, label: 'day ago' }; - else if (offset <= (DAY_IN_SECS * 7)) parts = { value: Math.round(Math.abs(offset / DAY_IN_SECS)), label: 'day ago' }; - else parts = { value: 'long ago' }; - - if (offset > DAY_IN_SECS * 7) { - parts.status = 'warn'; - } else if (offset < MINUTE_IN_SECS * -5 || offset > (MINUTE_IN_SECS * browserSettings.alarmTimeAgoUrgentMins)) { - parts.status = 'urgent'; - } else if (offset > (MINUTE_IN_SECS * browserSettings.alarmTimeAgoWarnMins)) { - parts.status = 'warn'; - } else { - parts.status = 'current'; - } - - return parts; - - } - function displayTreatmentBG(treatment) { function calcBGByTime(time) { @@ -1523,7 +1490,7 @@ function nsArrayDiff(oldArray, newArray) { function updateTimeAgo() { var lastEntry = $('#lastEntry') , time = latestSGV ? new Date(latestSGV.x).getTime() : -1 - , ago = timeAgo(time) + , ago = nsUtils.timeAgo(time) , retroMode = inRetroMode(); lastEntry.removeClass('current warn urgent'); From 241fcbeb849e93692b1f3b3d2686da62338ed746 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 7 Jun 2015 13:22:52 +0300 Subject: [PATCH 085/937] Clean up and remove code duplication from the data merging code upon delta --- static/js/client.js | 79 ++++++++++++++++----------------------------- 1 file changed, 28 insertions(+), 51 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index c4d0cf5a167..9faf47ed104 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1643,41 +1643,47 @@ function nsArrayDiff(oldArray, newArray) { } } + function mergeDataUpdate(isDelta, cachedDataArray, reveivedDataArray) { + + // If there was no delta data, just return the original data + if (!reveivedDataArray) return cachedDataArray; + + // If this is not a delta update, replace all data + if (!isDelta) return reveivedDataArray; + + // If this is delta, calculate the difference, merge and sort + var diff = nsArrayDiff(cachedDataArray,receivedDataArray); + return cachedDataArray.concat(diff).sort(function(a, b) { + return a.x - b.x; + }); + } + socket.on('dataUpdate', function receivedSGV(d) { if (!d) return; - // SGV + // Calculate the diff to existing data and replace as needed - if (d.sgvs) { + SGVdata = mergeDataUpdate(d.delta, SGVdata, d.sgvs); + MBGdata = mergeDataUpdate(d.delta,MBGdata, d.mbgs); + treatments = mergeDataUpdate(d.delta,treatments, d.treatments); + if (d.profiles) profile = d.profiles[0]; + if (d.cals) cal = d.cals[d.cals.length-1]; + if (d.devicestatus) devicestatusData = d.devicestatus; - if (!d.delta) { - // replace all locally stored SGV data - console.log('Replacing all local sgv records'); - SGVdata = d.sgvs; - } else { - var diff = nsArrayDiff(SGVdata,d.sgvs); - console.log('SGV data updated with', diff.length, 'new records'); - SGVdata = SGVdata.concat(diff); - } + // Do some reporting on the console + console.log('Total SGV data size', SGVdata.length); + console.log('Total treatment data size', treatments.length); - SGVdata.sort(function(a, b) { - return a.x - b.x; - }); + // Post processing after data is in + if (d.sgvs) { // change the next line so that it uses the prediction if the signal gets lost (max 1/2 hr) latestUpdateTime = Date.now(); latestSGV = SGVdata[SGVdata.length - 1]; prevSGV = SGVdata[SGVdata.length - 2]; } - console.log('Total SGV data size', SGVdata.length); - - // profile, calibration and device status - - if (d.profiles) profile = d.profiles[0]; - if (d.cals) cal = d.cals[d.cals.length-1]; - if (d.devicestatus) devicestatusData = d.devicestatus; var temp1 = [ ]; if (cal && isRawBGEnabled()) { @@ -1708,19 +1714,6 @@ function nsArrayDiff(oldArray, newArray) { }); } - //Add MBG's also, pretend they are MBG's - if (d.mbgs) { - if (!d.delta) { - // replace all locally stored MBG data - console.log('Replacing all local MBG records'); - MBGdata = d.mbgs; - } else { - var diff = nsArrayDiff(MBGdata,d.mbgs); - console.log('MBG data updated with', diff.length, 'new records'); - MBGdata = MBGdata.concat(diff); - } - } - data = data.concat(MBGdata.map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), color: 'red', type: 'mbg', device: obj.device } })); data.forEach(function (d) { @@ -1728,23 +1721,7 @@ function nsArrayDiff(oldArray, newArray) { d.color = 'transparent'; }); - // Update treatment data with new if delta - - if (d.treatments) { - if (!d.delta) { - treatments = d.treatments; - } else { - var newTreatments = nsArrayDiff(treatments,d.treatments); - console.log('treatment data updated with', newTreatments.length, 'new records'); - treatments = treatments.concat(newTreatments); - treatments.sort(function(a, b) { - return a.x - b.x; - }); - } - } - - console.log('Total treatment data size', treatments.length); - + // OPTIMIZATION: precalculate treatment location in timeline treatments.forEach(function (d) { d.created_at = new Date(d.created_at); //cache the displayBG for each treatment in DISPLAY_UNITS From f053920e28f6a76761b865999b5c8ebbdd6aa981 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 7 Jun 2015 13:24:07 +0300 Subject: [PATCH 086/937] Reformat the plugin code --- lib/plugins/cannulaage.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/plugins/cannulaage.js b/lib/plugins/cannulaage.js index a90cb7135d4..30877d05e67 100644 --- a/lib/plugins/cannulaage.js +++ b/lib/plugins/cannulaage.js @@ -17,7 +17,7 @@ function init() { var found = false; var treatmentDate = null; var message = ''; - + for (var t in this.env.treatments) { if (this.env.treatments.hasOwnProperty(t)) { var treatment = this.env.treatments[t]; @@ -32,7 +32,9 @@ function init() { } else { if (hours < age) { age = hours; - if (treatment.notes) { message = treatment.notes; } + if (treatment.notes) { + message = treatment.notes; + } } } } From 27c12fd5e6a2d4b123d10c6ca7873fa06150cedd Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 7 Jun 2015 11:41:44 -0700 Subject: [PATCH 087/937] wait an extra 5mins before going into retro mode --- static/js/client.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/js/client.js b/static/js/client.js index 9faf47ed104..5af00f2319b 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -344,7 +344,7 @@ function nsArrayDiff(oldArray, newArray) { var time = brush.extent()[1].getTime(); - return !alarmingNow() && time - THIRTY_MINS_IN_MS < now; + return !alarmingNow() && time - TWENTY_FIVE_MINS_IN_MS < now; } function errorCodeToDisplay(errorCode) { From 117f38e6deae54be657547f91eeaa5049fbc052d Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 7 Jun 2015 22:42:46 +0300 Subject: [PATCH 088/937] Socket.io upgraded to 1.3.5 --- lib/websocket.js | 28 ++++++++++------------------ package.json | 2 +- 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/lib/websocket.js b/lib/websocket.js index 941209b220b..6502dbeaf2b 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -33,7 +33,9 @@ function init (env, ctx, server) { var watchers = 0; function start ( ) { - io = require('socket.io').listen(server, { + io = require('socket.io')({ + 'transports': ['xhr-polling'], 'log level': 0 + }).listen(server, { //these only effect the socket.io.js file that is sent to the client, but better than nothing 'browser client minification': true, 'browser client etag': true, @@ -44,25 +46,16 @@ function init (env, ctx, server) { function emitData ( ) { if (patientData.cals) { console.log('running websocket.emitData', lastUpdated, patientDataUpdate.recentsgvs && patientDataUpdate.sgvdataupdate.length); - io.sockets.emit('dataUpdate', patientDataUpdate); + io.emit('dataUpdate', patientDataUpdate); } } - function configure ( ) { - // reduce logging - io.set('log level', 0); - - //TODO: make websockets support an option - io.configure(function () { - io.set('transports', ['xhr-polling']); - }); - } - function listeners ( ) { io.sockets.on('connection', function (socket) { // send all data upon new connection - io.sockets.socket(socket.id).emit('dataUpdate',patientData); - io.sockets.emit('clients', ++watchers); + socket.emit('dataUpdate',patientData); + + io.emit('clients', ++watchers); socket.on('ack', function(alarmType, silenceTime) { ctx.notifications.ack(alarmType, silenceTime); if (alarmType == 'urgent_alarm') { @@ -71,7 +64,7 @@ function init (env, ctx, server) { } }); socket.on('disconnect', function () { - io.sockets.emit('clients', --watchers); + io.emit('clients', --watchers); }); }); } @@ -108,10 +101,10 @@ function init (env, ctx, server) { websocket.emitNotification = function emitNotification (info) { if (info.clear) { - io.sockets.emit('clear_alarm', true); + io.emit('clear_alarm', true); console.info('emitted clear_alarm to all clients'); } else if (info.type) { - io.sockets.emit(info.type); + io.emit(info.type); console.info('emitted ' + info.type + ' to all clients'); } }; @@ -184,7 +177,6 @@ function init (env, ctx, server) { } start( ); - configure( ); listeners( ); return websocket(); diff --git a/package.json b/package.json index 605edb83489..f3714e54091 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "mqtt": "~0.3.11", "pushover-notifications": "0.2.0", "sgvdata": "git://github.com/ktind/sgvdata.git#wip/protobuf", - "socket.io": "^0.9.17" + "socket.io": "^1.3.5" }, "devDependencies": { "istanbul": "~0.3.5", From ff787b8a45dd39b220242e7438d1703f9cba25f4 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Mon, 8 Jun 2015 00:06:52 +0300 Subject: [PATCH 089/937] Doh, typo in received data delta processing. No idea how I didn't notice before. --- static/js/client.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index 5af00f2319b..edd5017f636 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1643,13 +1643,13 @@ function nsArrayDiff(oldArray, newArray) { } } - function mergeDataUpdate(isDelta, cachedDataArray, reveivedDataArray) { + function mergeDataUpdate(isDelta, cachedDataArray, receivedDataArray) { // If there was no delta data, just return the original data - if (!reveivedDataArray) return cachedDataArray; + if (!receivedDataArray) return cachedDataArray; // If this is not a delta update, replace all data - if (!isDelta) return reveivedDataArray; + if (!isDelta) return receivedDataArray; // If this is delta, calculate the difference, merge and sort var diff = nsArrayDiff(cachedDataArray,receivedDataArray); From 1e10ff4f0a2b6025df9e72bdd73376f655e5bf5b Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 8 Jun 2015 00:00:06 -0700 Subject: [PATCH 090/937] require is case-sensitive on Linux, some renaming --- bundle/bundle.source.js | 2 +- static/js/client.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index 6e9beca2f1e..0cfbc5ca00a 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -5,7 +5,7 @@ window.Nightscout = { units: require('../lib/units')(), - nsUtils: require('../lib/nsUtils')(), + utils: require('../lib/nsutils')(), profile: require('../lib/profilefunctions')(), plugins: require('../lib/plugins/')().registerClientDefaults() }; diff --git a/static/js/client.js b/static/js/client.js index edd5017f636..2f76a9d2bad 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1,7 +1,7 @@ //TODO: clean up var app = {}, browserSettings = {}, browserStorage = $.localStorage; -var nsUtils = Nightscout.nsUtils; +var timeAgo = Nightscout.utils.timeAgo; function nsArrayDiff(oldArray, newArray) { var seen = {}; @@ -134,7 +134,7 @@ function nsArrayDiff(oldArray, newArray) { function updateTitle() { var time = latestSGV ? new Date(latestSGV.x).getTime() : (prevSGV ? new Date(prevSGV.x).getTime() : -1) - , ago = nsUtils.timeAgo(time); + , ago = timeAgo(time); var bg_title = browserSettings.customTitle || ''; @@ -412,7 +412,7 @@ function nsArrayDiff(oldArray, newArray) { var value, time, ago, isCurrent; value = entry.y; time = new Date(entry.x).getTime(); - ago = nsUtils.timeAgo(time); + ago = timeAgo(time); isCurrent = ago.status === 'current'; if (value == 9) { @@ -1490,7 +1490,7 @@ function nsArrayDiff(oldArray, newArray) { function updateTimeAgo() { var lastEntry = $('#lastEntry') , time = latestSGV ? new Date(latestSGV.x).getTime() : -1 - , ago = nsUtils.timeAgo(time) + , ago = timeAgo(time) , retroMode = inRetroMode(); lastEntry.removeClass('current warn urgent'); From 69a09a520b51310f228683ee9bd8a4b2abc64239 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 8 Jun 2015 00:08:20 -0700 Subject: [PATCH 091/937] stop rounding cals sent via /pebble --- lib/pebble.js | 6 +++--- tests/pebble.test.js | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/pebble.js b/lib/pebble.js index 4a2910dbc52..74b918ebd98 100644 --- a/lib/pebble.js +++ b/lib/pebble.js @@ -101,9 +101,9 @@ function pebble (req, res) { results.forEach(function (element) { if (element) { calData.push({ - slope: Math.round(element.slope) - , intercept: Math.round(element.intercept) - , scale: Math.round(element.scale) + slope: element.slope + , intercept: element.intercept + , scale: element.scale }); } }); diff --git a/tests/pebble.test.js b/tests/pebble.test.js index 030be48a222..04e0d6a4900 100644 --- a/tests/pebble.test.js +++ b/tests/pebble.test.js @@ -202,8 +202,8 @@ describe('Pebble Endpoint with Raw', function ( ) { res.body.cals.length.should.equal(1); var cal = res.body.cals[0]; - cal.slope.should.equal(896); - cal.intercept.should.equal(34281); + cal.slope.toFixed(3).should.equal('895.857'); + cal.intercept.toFixed(3).should.equal('34281.069'); cal.scale.should.equal(1); done( ); }); From ebdcf596e9e29d621fe6e8b23f2063d9d14c9aad Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Mon, 8 Jun 2015 18:32:43 +0300 Subject: [PATCH 092/937] Corrected bug regarding cannula age if the timeline contained a cannula switch --- lib/plugins/cannulaage.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/plugins/cannulaage.js b/lib/plugins/cannulaage.js index 30877d05e67..20d3fc731b9 100644 --- a/lib/plugins/cannulaage.js +++ b/lib/plugins/cannulaage.js @@ -24,16 +24,18 @@ function init() { if (treatment.eventType == "Site Change") { treatmentDate = new Date(treatment.created_at); - var hours = Math.round(Math.abs(this.env.time - treatmentDate) / 36e5); + if (treatmentDate <= this.env.time) { + var hours = Math.round((this.env.time - treatmentDate) / 36e5); - if (!found) { - found = true; - age = hours; - } else { - if (hours < age) { + if (!found) { + found = true; age = hours; - if (treatment.notes) { - message = treatment.notes; + } else { + if (hours < age) { + age = hours; + if (treatment.notes) { + message = treatment.notes; + } } } } From 3e5c0b8da03722009ad475bd22f65c7c904e4179 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 8 Jun 2015 22:25:47 -0700 Subject: [PATCH 093/937] nsutils => utils --- bundle/bundle.source.js | 2 +- lib/plugins/iob.js | 4 ++-- lib/{nsutils.js => utils.js} | 10 +++++----- static/js/client.js | 10 ++++++---- 4 files changed, 14 insertions(+), 12 deletions(-) rename lib/{nsutils.js => utils.js} (91%) diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index 0cfbc5ca00a..a3780cdd42d 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -5,7 +5,7 @@ window.Nightscout = { units: require('../lib/units')(), - utils: require('../lib/nsutils')(), + utils: require('../lib/utils')(), profile: require('../lib/profilefunctions')(), plugins: require('../lib/plugins/')().registerClientDefaults() }; diff --git a/lib/plugins/iob.js b/lib/plugins/iob.js index 8202565502b..9b57b865b2d 100644 --- a/lib/plugins/iob.js +++ b/lib/plugins/iob.js @@ -2,7 +2,7 @@ var _ = require('lodash') , moment = require('moment') - , nsutils = require('../nsutils')(); + , utils = require('../utils')(); function init() { @@ -48,7 +48,7 @@ function init() { return { iob: totalIOB, - display: nsutils.toFixed(totalIOB), + display: utils.toFixed(totalIOB), activity: totalActivity, lastBolus: lastBolus }; diff --git a/lib/nsutils.js b/lib/utils.js similarity index 91% rename from lib/nsutils.js rename to lib/utils.js index ad2a4fc3c1b..e2240758c09 100644 --- a/lib/nsutils.js +++ b/lib/utils.js @@ -2,15 +2,15 @@ function init() { - function nsutils() { - return nsutils; + function utils() { + return utils; } var MINUTE_IN_SECS = 60 , HOUR_IN_SECS = 3600 , DAY_IN_SECS = 86400; - nsutils.toFixed = function toFixed(value) { + utils.toFixed = function toFixed(value) { if (value === 0) { return '0'; } else { @@ -19,7 +19,7 @@ function init() { } }; - nsutils.timeAgo = function timeAgo(time) { + utils.timeAgo = function timeAgo(time) { var now = Date.now() , offset = time == -1 ? -1 : (now - time) / 1000 @@ -50,7 +50,7 @@ function init() { } - return nsutils(); + return utils(); } module.exports = init; \ No newline at end of file diff --git a/static/js/client.js b/static/js/client.js index 2f76a9d2bad..f43d3d81a56 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -969,10 +969,12 @@ function nsArrayDiff(oldArray, newArray) { .attr('transform', 'translate(0,' + chartHeight + ')') .call(xAxis2); - // reset clip to new dimensions - clip.transition() - .attr('width', chartWidth) - .attr('height', chartHeight); + if (clip) { + // reset clip to new dimensions + clip.transition() + .attr('width', chartWidth) + .attr('height', chartHeight); + } // reset brush location context.select('.x.brush') From 709b143c2e1b10efce6776aa203c99211092e769 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 8 Jun 2015 23:28:59 -0700 Subject: [PATCH 094/937] moved delta calculation out of websockets.js --- lib/data.js | 99 +++++++++++++++++++++++++++++++++++- lib/utils.js | 11 ++-- lib/websocket.js | 129 +++++------------------------------------------ 3 files changed, 117 insertions(+), 122 deletions(-) diff --git a/lib/data.js b/lib/data.js index e158d01e168..ddecf300c0b 100644 --- a/lib/data.js +++ b/lib/data.js @@ -1,6 +1,9 @@ 'use strict'; +var _ = require('lodash'); var async = require('async'); +var utils = require('./utils')(); +var ObjectID = require('mongodb').ObjectID; function uniq(a) { var seen = {}; @@ -42,6 +45,16 @@ function init (env, ctx) { return dir2Char[direction] || '-'; } + data.clone = function clone ( ) { + return _.cloneDeep(data, function (value) { + //special handling of mongo ObjectID's + //see https://github.com/lodash/lodash/issues/602#issuecomment-47414964 + if (value instanceof ObjectID) { + return value.toString(); + } + }); + }; + data.update = function update (done) { console.log('running data.update'); @@ -58,7 +71,6 @@ function init (env, ctx) { async.parallel({ entries: function (callback) { - var now = new Date(); var q = { find: {"date": {"$gte": earliest_data}} }; ctx.entries.list(q, function (err, results) { if (!err && results) { @@ -151,6 +163,91 @@ function init (env, ctx) { }; + data.calculateDelta = function calculateDelta (lastData) { + + var delta = {'delta': true}; + var changesFound = false; + + // if there's no updates done so far, just return the full set + if (!lastData.sgvs) return data; + + console.log('lastData.sgvs last record time', lastData.sgvs[lastData.sgvs.length-1].x); + console.log('d.sgvslast record time', data.sgvs[data.sgvs.length-1].x); + + function nsArrayDiff(oldArray, newArray) { + var seen = {}; + var l = oldArray.length; + for (var i = 0; i < l; i++) { seen[oldArray[i].x] = true } + var result = []; + l = newArray.length; + for (var j = 0; j < l; j++) { if (!seen.hasOwnProperty(newArray[j].x)) { result.push(newArray[j]); console.log('delta data found'); } } + return result; + } + + function sort (values) { + values.sort(function sorter (a, b) { + return a.x - b.x; + }); + } + + var sgvDelta = nsArrayDiff(lastData.sgvs, data.sgvs); + + if (sgvDelta.length > 0) { + console.log('sgv changes found'); + changesFound = true; + sort(sgvDelta); + delta.sgvs = sgvDelta; + } + + var treatmentDelta = nsArrayDiff(lastData.treatments, data.treatments); + + if (treatmentDelta.length > 0) { + console.log('treatment changes found'); + changesFound = true; + sort(treatmentDelta); + delta.treatments = treatmentDelta; + } + + var mbgsDelta = nsArrayDiff(lastData.mbgs, data.mbgs); + + if (mbgsDelta.length > 0) { + console.log('mbgs changes found'); + changesFound = true; + sort(mbgsDelta); + delta.mbgs = mbgsDelta; + } + + var calsDelta = nsArrayDiff(lastData.cals, data.cals); + + if (calsDelta.length > 0) { + console.log('cals changes found'); + changesFound = true; + sort(calsDelta); + delta.cals = calsDelta; + } + + if (JSON.stringify(lastData.devicestatus) != JSON.stringify(data.devicestatus)) { + console.log('devicestatus changes found'); + changesFound = true; + delta.devicestatus = data.devicestatus; + } + + if (JSON.stringify(lastData.profiles) != JSON.stringify(data.profiles)) { + console.log('profile changes found'); + changesFound = true; + delta.profiles = data.profiles; + } + + if (changesFound) { + console.log('changes found'); + return delta; + } + + return data; + + }; + + return data; } diff --git a/lib/utils.js b/lib/utils.js index e2240758c09..6bc24037801 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -6,11 +6,11 @@ function init() { return utils; } - var MINUTE_IN_SECS = 60 - , HOUR_IN_SECS = 3600 - , DAY_IN_SECS = 86400; + var MINUTE_IN_SECS = 60 + , HOUR_IN_SECS = 3600 + , DAY_IN_SECS = 86400; - utils.toFixed = function toFixed(value) { + utils.toFixed = function toFixed(value) { if (value === 0) { return '0'; } else { @@ -47,8 +47,7 @@ function init() { return parts; - } - + }; return utils(); } diff --git a/lib/websocket.js b/lib/websocket.js index 6502dbeaf2b..fe09ac8dbbb 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -1,23 +1,7 @@ 'use strict'; var _ = require('lodash'); -var ObjectID = require('mongodb').ObjectID; - -function nsArrayDiff(oldArray, newArray) { - var seen = {}; - var l = oldArray.length; - for (var i = 0; i < l; i++) { seen[oldArray[i].x] = true } - var result = []; - l = newArray.length; - for (var j = 0; j < l; j++) { if (!seen.hasOwnProperty(newArray[j].x)) { result.push(newArray[j]); console.log('delta data found'); } } - return result; -} - -function sort (values) { - values.sort(function sorter (a, b) { - return a.x - b.x; - }); -} +var utils = require('./utils')(); function init (env, ctx, server) { @@ -25,12 +9,9 @@ function init (env, ctx, server) { return websocket; } - var lastUpdated = 0; - var patientData = {}; - var patientDataUpdate = {}; - var io; var watchers = 0; + var lastData = {}; function start ( ) { io = require('socket.io')({ @@ -43,18 +24,17 @@ function init (env, ctx, server) { }); } - function emitData ( ) { - if (patientData.cals) { - console.log('running websocket.emitData', lastUpdated, patientDataUpdate.recentsgvs && patientDataUpdate.sgvdataupdate.length); - io.emit('dataUpdate', patientDataUpdate); + function emitData (delta) { + if (lastData.cals) { + console.log('running websocket.emitData', ctx.data.lastUpdated, delta.recentsgvs && delta.sgvdataupdate.length); + io.emit('dataUpdate', delta); } } function listeners ( ) { io.sockets.on('connection', function (socket) { // send all data upon new connection - socket.emit('dataUpdate',patientData); - + socket.emit('dataUpdate',lastData); io.emit('clients', ++watchers); socket.on('ack', function(alarmType, silenceTime) { ctx.notifications.ack(alarmType, silenceTime); @@ -70,32 +50,18 @@ function init (env, ctx, server) { } websocket.processData = function processData ( ) { - - var d = ctx.data; - lastUpdated = d.lastUpdated; - console.log('running websocket.processData'); - - var lastSGV = d.sgvs.length > 0 ? d.sgvs[d.sgvs.length - 1].y : null; - + var lastSGV = ctx.data.sgvs.length > 0 ? ctx.data.sgvs[ctx.data.sgvs.length - 1].y : null; if (lastSGV) { - if (patientData.sgvs) { - var delta = calculateDelta(d); + if (lastData.sgvs) { + var delta = ctx.data.calculateDelta(lastData); if (delta.delta) { - patientDataUpdate = delta; - console.log('patientData full size', JSON.stringify(patientData).length,'bytes'); - if (delta.sgvs) console.log('patientData update size', JSON.stringify(patientDataUpdate).length,'bytes'); - emitData(); + console.log('lastData full size', JSON.stringify(lastData).length,'bytes'); + if (delta.sgvs) console.log('patientData update size', JSON.stringify(delta).length,'bytes'); + emitData(delta); } else { console.log('delta calculation indicates no new data is present'); } } - - //see https://github.com/lodash/lodash/issues/602#issuecomment-47414964 - patientData = _.cloneDeep(d, function (value) { - if (value instanceof ObjectID) { - return value.toString(); - } - }); - + lastData = ctx.data.clone(); } }; @@ -109,73 +75,6 @@ function init (env, ctx, server) { } }; - function calculateDelta(d) { - - var delta = {'delta': true}; - var changesFound = false; - - // if there's no updates done so far, just return the full set - if (!patientData.sgvs) return d; - - console.log('patientData.sgvs last record time', patientData.sgvs[patientData.sgvs.length-1].x); - console.log('d.sgvslast record time', d.sgvs[d.sgvs.length-1].x); - - var sgvDelta = nsArrayDiff(patientData.sgvs,d.sgvs); - - if (sgvDelta.length > 0) { - console.log('sgv changes found'); - changesFound = true; - sort(sgvDelta); - delta.sgvs = sgvDelta; - } - - var treatmentDelta = nsArrayDiff(patientData.treatments,d.treatments); - - if (treatmentDelta.length > 0) { - console.log('treatment changes found'); - changesFound = true; - sort(treatmentDelta); - delta.treatments = treatmentDelta; - } - - var mbgsDelta = nsArrayDiff(patientData.mbgs,d.mbgs); - - if (mbgsDelta.length > 0) { - console.log('mbgs changes found'); - changesFound = true; - sort(mbgsDelta); - delta.mbgs = mbgsDelta; - } - - var calsDelta = nsArrayDiff(patientData.cals,d.cals); - - if (calsDelta.length > 0) { - console.log('cals changes found'); - changesFound = true; - sort(calsDelta); - delta.cals = calsDelta; - } - - if (JSON.stringify(patientData.devicestatus) != JSON.stringify(d.devicestatus)) { - console.log('devicestatus changes found'); - changesFound = true; - delta.devicestatus = d.devicestatus; - } - - if (JSON.stringify(patientData.profiles) != JSON.stringify(d.profiles)) { - console.log('profile changes found'); - changesFound = true; - delta.profiles = d.profiles; - } - - if (changesFound) { - console.log('changes found'); - return delta; - } - return d; - - } - start( ); listeners( ); From acfe73aed9b8025d0a527f40b52cac0e746d16ab Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 9 Jun 2015 02:07:28 -0700 Subject: [PATCH 095/937] introduce sandbox (sbx) to expose state and ui to plugins also changed the way pluginBase is used, still needs some work --- bundle/bundle.source.js | 3 +- env.js | 3 + lib/plugins/boluswizardpreview.js | 42 +++++---- lib/plugins/cannulaage.js | 34 ++++--- lib/plugins/cob.js | 37 ++++---- lib/plugins/index.js | 30 +++---- lib/plugins/iob.js | 19 ++-- lib/plugins/pluginbase.js | 144 ++++++++++++++---------------- static/js/client.js | 37 +++----- 9 files changed, 175 insertions(+), 174 deletions(-) diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index a3780cdd42d..81693b6e81a 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -7,7 +7,8 @@ units: require('../lib/units')(), utils: require('../lib/utils')(), profile: require('../lib/profilefunctions')(), - plugins: require('../lib/plugins/')().registerClientDefaults() + plugins: require('../lib/plugins/')().registerClientDefaults(), + sandbox: require('../lib/sandbox')() }; console.info("Nightscout bundle ready", window.Nightscout); diff --git a/env.js b/env.js index c455e62bbba..d16ce6ef9c0 100644 --- a/env.js +++ b/env.js @@ -168,6 +168,9 @@ function config ( ) { // For pushing notifications to Pushover. env.pushover_api_token = readENV('PUSHOVER_API_TOKEN'); env.pushover_user_key = readENV('PUSHOVER_USER_KEY') || readENV('PUSHOVER_GROUP_KEY'); + if (env.pushover_api_token && env.pushover_user_key) { + env.alarm_types.push('pushover'); + } // TODO: clean up a bit // Some people prefer to use a json configuration file instead. diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index e330258b881..14b6392a802 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -1,5 +1,7 @@ 'use strict'; +var _ = require('lodash'); + function init() { function bwp() { @@ -9,41 +11,49 @@ function init() { bwp.label = 'Bolus Wizard Preview'; bwp.pluginType = 'pill-minor'; - bwp.updateVisualisation = function updateVisualisation() { + bwp.updateVisualisation = function updateVisualisation (sbx) { + + var sgv = _.last(sbx.data.sgvs); + //ug, on the client y, is unscaled, on the server we only have the unscaled sgv field + sgv = sgv && (sgv.y || sgv.sgv); - var sgv = this.env.sgv; + if (sgv == undefined || !sbx.properties.iob) return; + + var profile = sbx.data.profile; var bolusEstimate = 0.0; // TODO: MMOL -- Jason: if we assume sens is in display units, we don't need to do any conversion - sgv = this.scaleBg(sgv); + sgv = sbx.pluginBase.scaleBg(sgv, sbx); + + var iob = sbx.properties.iob.iob; - var effect = this.iob.iob * this.profile.sens; + var effect = iob * profile.sens; var outcome = sgv - effect; var delta = 0; - if (outcome > this.profile.target_high) { - delta = outcome - this.profile.target_high; - bolusEstimate = delta / this.profile.sens; + if (outcome > profile.target_high) { + delta = outcome - profile.target_high; + bolusEstimate = delta / profile.sens; } - if (outcome < this.profile.target_low) { - delta = Math.abs(outcome - this.profile.target_low); - bolusEstimate = delta / this.profile.sens * -1; + if (outcome < profile.target_low) { + delta = Math.abs(outcome - profile.target_low); + bolusEstimate = delta / profile.sens * -1; } - bolusEstimate = this.roundInsulinForDisplayFormat(bolusEstimate); - outcome = this.roundBGToDisplayFormat(outcome); - var displayIOB = this.roundInsulinForDisplayFormat(this.iob.iob); + bolusEstimate = sbx.pluginBase.roundInsulinForDisplayFormat(bolusEstimate, sbx); + outcome = sbx.pluginBase.roundBGToDisplayFormat(outcome, sbx); + var displayIOB = sbx.pluginBase.roundInsulinForDisplayFormat(iob, sbx); // display text var info = [ {label: 'Insulin on Board', value: displayIOB + 'U'} - , {label: 'Expected effect', value: '-' + this.roundBGToDisplayFormat(effect) + ' ' + this.getBGUnits()} - , {label: 'Expected outcome', value: outcome + ' ' + this.getBGUnits()} + , {label: 'Expected effect', value: '-' + sbx.pluginBase.roundBGToDisplayFormat(effect, sbx) + ' ' + sbx.pluginBase.getBGUnits(sbx)} + , {label: 'Expected outcome', value: outcome + ' ' + sbx.pluginBase.getBGUnits(sbx)} ]; - this.updatePillText(bolusEstimate + 'U', 'BWP', info); + sbx.pluginBase.updatePillText(bwp, bolusEstimate + 'U', 'BWP', info); }; diff --git a/lib/plugins/cannulaage.js b/lib/plugins/cannulaage.js index 30877d05e67..1d6e0c2400a 100644 --- a/lib/plugins/cannulaage.js +++ b/lib/plugins/cannulaage.js @@ -1,5 +1,6 @@ 'use strict'; +var _ = require('lodash'); var moment = require('moment'); function init() { @@ -11,40 +12,35 @@ function init() { cage.label = 'Cannula Age'; cage.pluginType = 'pill-minor'; - cage.updateVisualisation = function updateVisualisation() { - + cage.updateVisualisation = function updateVisualisation (sbx) { var age = 0; var found = false; var treatmentDate = null; var message = ''; - for (var t in this.env.treatments) { - if (this.env.treatments.hasOwnProperty(t)) { - var treatment = this.env.treatments[t]; - - if (treatment.eventType == "Site Change") { - treatmentDate = new Date(treatment.created_at); - var hours = Math.round(Math.abs(this.env.time - treatmentDate) / 36e5); + _.forEach(sbx.data.treatments, function eachTreatment (treatment) { + if (treatment.eventType == "Site Change") { + treatmentDate = new Date(treatment.created_at); + var hours = Math.round(Math.abs(sbx.time - treatmentDate) / 36e5); - if (!found) { - found = true; + if (!found) { + found = true; + age = hours; + } else { + if (hours < age) { age = hours; - } else { - if (hours < age) { - age = hours; - if (treatment.notes) { - message = treatment.notes; - } + if (treatment.notes) { + message = treatment.notes; } } } } - } + }); var info = [{label: 'Inserted:', value: moment(treatmentDate).format('lll')}]; if (message != '') info.push({label: 'Notes:', value: message}); - this.updatePillText(age + 'h', 'CAGE', info); + sbx.pluginBase.updatePillText(cage, age + 'h', 'CAGE', info); }; diff --git a/lib/plugins/cob.js b/lib/plugins/cob.js index b7aad7fc673..eab827c483c 100644 --- a/lib/plugins/cob.js +++ b/lib/plugins/cob.js @@ -13,14 +13,17 @@ function init() { cob.label = 'Carbs-on-Board'; cob.pluginType = 'pill-minor'; - cob.getData = function getData() { - return cob.cobTotal(this.env.treatments, this.env.time); + + cob.setProperties = function setProperties(sbx) { + sbx.offerProperty('cob', function setCOB ( ) { + return cob.cobTotal(sbx.data.treatments, sbx.data.profile, sbx.time); + }); }; - cob.cobTotal = function cobTotal(treatments, time) { + cob.cobTotal = function cobTotal(treatments, profile, time) { var liverSensRatio = 1; - var sens = this.profile.sens; - var carbratio = this.profile.carbratio; + var sens = profile.sens; + var carbratio = profile.carbratio; var totalCOB = 0; var lastCarbs = null; if (!treatments) return {}; @@ -30,12 +33,12 @@ function init() { var isDecaying = 0; var lastDecayedBy = new Date('1/1/1970'); - var carbs_hr = this.profile.carbs_hr; + var carbs_hr = profile.carbs_hr; _.forEach(treatments, function eachTreatment(treatment) { if (treatment.carbs && treatment.created_at < time) { lastCarbs = treatment; - var cCalc = cob.cobCalc(treatment, lastDecayedBy, time); + var cCalc = cob.cobCalc(treatment, profile, lastDecayedBy, time); var decaysin_hr = (cCalc.decayedBy - time) / 1000 / 60 / 60; if (decaysin_hr > -10) { var actStart = iob.calcTotal(treatments, lastDecayedBy).activity; @@ -90,9 +93,9 @@ function init() { } }; - cob.cobCalc = function cobCalc(treatment, lastDecayedBy, time) { + cob.cobCalc = function cobCalc(treatment, profile, lastDecayedBy, time) { - var carbs_hr = this.profile.carbs_hr; + var carbs_hr = profile.carbs_hr; var delay = 20; var carbs_min = carbs_hr / 60; var isDecaying = 0; @@ -130,17 +133,21 @@ function init() { } }; - cob.updateVisualisation = function updateVisualisation() { - var displayCob = Math.round(this.env.cob.cob * 10) / 10; + cob.updateVisualisation = function updateVisualisation(sbx) { + + var prop = sbx.properties.cob.cob; + if (prop == undefined) return; + + var displayCob = Math.round(prop * 10) / 10; var info = null; - if (this.env.cob.lastCarbs) { - var when = moment(new Date(this.env.cob.lastCarbs.created_at)).format('lll'); - var amount = this.env.cob.lastCarbs.carbs + 'g'; + if (prop.lastCarbs) { + var when = moment(new Date(prop.lastCarbs.created_at)).format('lll'); + var amount = prop.lastCarbs.carbs + 'g'; info = [{label: 'Last Carbs', value: amount + ' @ ' + when }] } - this.updatePillText(displayCob + " g", 'COB', info); + sbx.pluginBase.updatePillText(sbx, displayCob + " g", 'COB', info); }; return cob(); diff --git a/lib/plugins/index.js b/lib/plugins/index.js index 29f3c4e082d..62905cc1a71 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -1,7 +1,6 @@ 'use strict'; -var _ = require('lodash') - , PluginBase = require('./pluginbase')(); // Define any shared functionality in this class +var _ = require('lodash'); function init() { @@ -12,6 +11,8 @@ function init() { return plugins; } + plugins.base = require('./pluginbase'); + plugins.registerServerDefaults = function registerServerDefaults() { plugins.register([ require('./pushnotify')() ]); return plugins; @@ -29,7 +30,6 @@ function init() { plugins.register = function register(all) { _.forEach(all, function eachPlugin(plugin) { - _.extend(plugin, PluginBase); allPlugins.push(plugin); }); }; @@ -59,31 +59,31 @@ function init() { _.forEach(enabledPlugins, f); }; - plugins.shownPlugins = function(clientSettings) { + plugins.shownPlugins = function(sbx) { return _.filter(enabledPlugins, function filterPlugins(plugin) { - return clientSettings && clientSettings.showPlugins && clientSettings.showPlugins.indexOf(plugin.name) > -1; + return sbx && sbx.showPlugins && sbx.showPlugins.indexOf(plugin.name) > -1; }); }; - plugins.eachShownPlugins = function eachShownPlugins(clientSettings, f) { - _.forEach(plugins.shownPlugins(clientSettings), f); + plugins.eachShownPlugins = function eachShownPlugins(sbx, f) { + _.forEach(plugins.shownPlugins(sbx), f); }; - plugins.hasShownType = function hasShownType(pluginType, clientSettings) { - return _.find(plugins.shownPlugins(clientSettings), function findWithType(plugin) { + plugins.hasShownType = function hasShownType(pluginType, sbx) { + return _.find(plugins.shownPlugins(sbx), function findWithType(plugin) { return plugin.pluginType == pluginType; }) != undefined; }; - plugins.setEnvs = function setEnvs(env) { - plugins.eachEnabledPlugin(function eachPlugin(plugin) { - plugin.setEnv(env); + plugins.setProperties = function setProperties(sbx) { + plugins.eachEnabledPlugin( function eachPlugin (plugin) { + plugin.setProperties && plugin.setProperties(sbx); }); }; - plugins.updateVisualisations = function updateVisualisations(clientSettings) { - plugins.eachShownPlugins(clientSettings, function eachPlugin(plugin) { - plugin.updateVisualisation && plugin.updateVisualisation(); + plugins.updateVisualisations = function updateVisualisations(sbx) { + plugins.eachShownPlugins(sbx, function eachPlugin(plugin) { + plugin.updateVisualisation && plugin.updateVisualisation(sbx); }); }; diff --git a/lib/plugins/iob.js b/lib/plugins/iob.js index 9b57b865b2d..30748e51cfd 100644 --- a/lib/plugins/iob.js +++ b/lib/plugins/iob.js @@ -13,8 +13,10 @@ function init() { iob.label = 'Insulin-on-Board'; iob.pluginType = 'pill-major'; - iob.getData = function getData() { - return iob.calcTotal(this.env.treatments, this.env.profile, this.env.time); + iob.setProperties = function setProperties(sbx) { + sbx.offerProperty('iob', function setIOB ( ) { + return iob.calcTotal(sbx.data.treatments, sbx.data.profile, sbx.time); + }); }; iob.calcTotal = function calcTotal(treatments, profile, time) { @@ -86,16 +88,19 @@ function init() { }; - iob.updateVisualisation = function updateVisualisation() { + iob.updateVisualisation = function updateVisualisation(sbx) { var info = null; - if (this.iob.lastBolus) { - var when = moment(new Date(this.iob.lastBolus.created_at)).format('lll'); - var amount = this.roundInsulinForDisplayFormat(Number(this.iob.lastBolus.insulin)) + 'U'; + var prop = sbx.properties.iob; + + if (prop && prop.lastBolus) { + var when = moment(new Date(prop.lastBolus.created_at)).format('lll'); + var amount = sbx.pluginBase.roundInsulinForDisplayFormat(Number(prop.lastBolus.insulin), sbx) + 'U'; info = [{label: 'Last Bolus', value: amount + ' @ ' + when }] } - this.updatePillText(this.roundInsulinForDisplayFormat(this.iob.display) + 'U', 'IOB', info, true); + sbx.pluginBase.updatePillText(iob, sbx.pluginBase.roundInsulinForDisplayFormat(prop.display, sbx) + 'U', 'IOB', info); + }; return iob(); diff --git a/lib/plugins/pluginbase.js b/lib/plugins/pluginbase.js index abedce5ffb1..09c342399c7 100644 --- a/lib/plugins/pluginbase.js +++ b/lib/plugins/pluginbase.js @@ -2,101 +2,91 @@ var _ = require('lodash'); -function setEnv(env) { - this.profile = env.profile; - this.majorPills = env.majorPills; - this.minorPills = env.minorPills; - this.iob = env.iob; - - // TODO: clean! - this.env = env; -} +function init (majorPills, minorPills, tooltip) { -function updatePillText(updatedText, label, info, major) { + function pluginBase ( ) { + return pluginBase; + } - var self = this; + pluginBase.updatePillText = function updatePillText (plugin, updatedText, label, info) { - var pillName = "span.pill." + this.name; + var pillName = "span.pill." + plugin.name; - var container = this.pluginType == 'pill-major' ? this.majorPills : this.minorPills; + var container = plugin.pluginType == 'pill-major' ? majorPills : minorPills; - var pill = container.find(pillName); + var pill = container.find(pillName); - if (!pill || pill.length == 0) { - pill = $(''); - container.append(pill); - } + if (!pill || pill.length == 0) { + pill = $(''); + container.append(pill); + } - pill.find('em').text(updatedText); + pill.find('em').text(updatedText); - if (info) { + if (info) { - var html = _.map(info, function mapInfo(i) { - return '' + i.label + ' ' + i.value; - }).join('
    \n'); + var html = _.map(info, function mapInfo (i) { + return '' + i.label + ' ' + i.value; + }).join('
    \n'); - pill.mouseover(function pillMouseover(event) { - self.env.tooltip.transition().duration(200).style('opacity', .9); - self.env.tooltip.html(html) - .style('left', (event.pageX) + 'px') - .style('top', (event.pageY + 15) + 'px'); - }); + pill.mouseover(function pillMouseover (event) { + tooltip.transition().duration(200).style('opacity', .9); + tooltip.html(html) + .style('left', (event.pageX) + 'px') + .style('top', (event.pageY + 15) + 'px'); + }); - pill.mouseout(function pillMouseout() { - self.env.tooltip.transition() - .duration(200) - .style('opacity', 0); - }); - } -} + pill.mouseout(function pillMouseout ( ) { + tooltip.transition() + .duration(200) + .style('opacity', 0); + }); + } + }; -function roundInsulinForDisplayFormat(iob, roundingStyle) { + pluginBase.roundInsulinForDisplayFormat = function roundInsulinForDisplayFormat (iob, sbx) { - if (iob == 0) return 0; + if (iob == 0) return 0; - if (roundingStyle === undefined) roundingStyle = 'generic'; - - if (roundingStyle == 'medtronic') { - var denominator = 0.1; - var digits = 1; - if (iob > 0.5 && iob < 1) { denominator = 0.05; digits = 2;} - if (iob <= 0.5) { denominator = 0.025; digits = 3;} - return (Math.floor(iob / denominator) * denominator).toFixed(digits); - } - - return (Math.floor(iob / 0.01) * 0.01).toFixed(2); - -} + if (sbx.properties.roundingStyle == 'medtronic') { + var denominator = 0.1; + var digits = 1; + if (iob > 0.5 && iob < 1) { + denominator = 0.05; + digits = 2; + } + if (iob <= 0.5) { + denominator = 0.025; + digits = 3; + } + return (Math.floor(iob / denominator) * denominator).toFixed(digits); + } -function getBGUnits() { - if (browserSettings.units == 'mmol') return 'mmol/L'; - return "mg/dl"; -} + return (Math.floor(iob / 0.01) * 0.01).toFixed(2); -function roundBGToDisplayFormat(bg) { - if (browserSettings.units == 'mmol') { - return Math.round(bg * 10) / 10; - } - return Math.round(bg); -} + }; -function scaleBg(bg) { - if (browserSettings.units == 'mmol') { - return Nightscout.units.mgdlToMMOL(bg); - } else { - return bg; - } -} + pluginBase.getBGUnits = function getBGUnits (sbx) { + if (sbx.units == 'mmol') return 'mmol/L'; + return "mg/dl"; + }; + + pluginBase.roundBGToDisplayFormat = function roundBGToDisplayFormat (bg, sbx) { + if (sbx.units == 'mmol') { + return Math.round(bg * 10) / 10; + } + return Math.round(bg); + }; -function PluginBase() { - return { - setEnv: setEnv, - scaleBg: scaleBg, - updatePillText: updatePillText, - roundBGToDisplayFormat: roundBGToDisplayFormat, - roundInsulinForDisplayFormat: roundInsulinForDisplayFormat, - getBGUnits: getBGUnits + pluginBase.scaleBg = function scaleBg (bg, sbx) { + if (sbx.units == 'mmol') { + return Nightscout.units.mgdlToMMOL(bg); + } else { + return bg; + } }; + + return pluginBase(); } -module.exports = PluginBase; +module.exports = init; diff --git a/static/js/client.js b/static/js/client.js index f43d3d81a56..97b950ed57b 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -476,32 +476,21 @@ function nsArrayDiff(oldArray, newArray) { } - function updatePlugins(sgv, time) { - var env = {}; - env.profile = profile; - env.majorPills = majorPills; - env.minorPills = minorPills; - env.sgv = Number(sgv); - env.treatments = treatments; - env.time = time; - env.tooltip = tooltip; - - //all enabled plugins get a chance to add data, even if they aren't shown - Nightscout.plugins.eachEnabledPlugin(function updateEachPlugin(plugin) { - // Update the env through data provider plugins - plugin.setEnv(env); - - // check if the plugin implements processing data - if (plugin.getData) { - env[plugin.name] = plugin.getData(); - } + function updatePlugins(sgvs, time) { + + var pluginBase = Nightscout.plugins.base(majorPills, minorPills, tooltip); + + var sbx = Nightscout.sandbox.clientInit(app, browserSettings, time, pluginBase, { + sgvs: sgvs + , treatments: treatments + , profile: profile }); - // update data for all the plugins, before updating visualisations - Nightscout.plugins.setEnvs(env); + //all enabled plugins get a chance to set properties, even if they aren't shown + Nightscout.plugins.setProperties(sbx); //only shown plugins get a chance to update visualisations - Nightscout.plugins.updateVisualisations(browserSettings); + Nightscout.plugins.updateVisualisations(sbx); } // predict for retrospective data @@ -552,7 +541,7 @@ function nsArrayDiff(oldArray, newArray) { bgButton.removeClass('urgent warning inrange'); } - updatePlugins(focusPoint && focusPoint.y, retroTime); + updatePlugins(nowData, retroTime); $('#currentTime') .text(formatTime(retroTime, true)) @@ -587,7 +576,7 @@ function nsArrayDiff(oldArray, newArray) { updateBGDelta(prevSGV, latestSGV); - updatePlugins(latestSGV.y, nowDate); + updatePlugins(nowData, nowDate); currentDirection.html(latestSGV.y < 39 ? '✖' : latestSGV.direction); } From 86c57bcf59dee127f7217f922d59f75abc1f202a Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 9 Jun 2015 02:11:27 -0700 Subject: [PATCH 096/937] fixed some cob tests --- tests/cob.test.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/cob.test.js b/tests/cob.test.js index d2c2dddb121..db2cc9fbef3 100644 --- a/tests/cob.test.js +++ b/tests/cob.test.js @@ -5,7 +5,7 @@ var should = require('should'); describe('COB', function ( ) { var cob = require('../lib/plugins/cob')(); - cob.profile = { + var profile = { sens: 95 , carbratio: 18 , carbs_hr: 30 @@ -24,9 +24,9 @@ describe('COB', function ( ) { } ]; - var after100 = cob.cobTotal(treatments, new Date("2015-05-29T02:03:49.827Z")); - var before10 = cob.cobTotal(treatments, new Date("2015-05-29T03:45:10.670Z")); - var after10 = cob.cobTotal(treatments, new Date("2015-05-29T03:45:11.670Z")); + var after100 = cob.cobTotal(treatments, profile, new Date("2015-05-29T02:03:49.827Z")); + var before10 = cob.cobTotal(treatments, profile, new Date("2015-05-29T03:45:10.670Z")); + var after10 = cob.cobTotal(treatments, profile, new Date("2015-05-29T03:45:11.670Z")); console.info('>>>>after100:', after100); console.info('>>>>before10:', before10); @@ -52,11 +52,11 @@ describe('COB', function ( ) { var later3 = new Date("2015-05-29T05:50:00.174Z"); var later4 = new Date("2015-05-29T06:50:00.174Z"); - var result1 = cob.cobTotal(treatments, rightAfterCorrection); - var result2 = cob.cobTotal(treatments, later1); - var result3 = cob.cobTotal(treatments, later2); - var result4 = cob.cobTotal(treatments, later3); - var result5 = cob.cobTotal(treatments, later4); + var result1 = cob.cobTotal(treatments, profile, rightAfterCorrection); + var result2 = cob.cobTotal(treatments, profile, later1); + var result3 = cob.cobTotal(treatments, profile, later2); + var result4 = cob.cobTotal(treatments, profile, later3); + var result5 = cob.cobTotal(treatments, profile, later4); result1.cob.should.equal(8); result2.cob.should.equal(6); From 055e6ad1066d07aac34e37f83dd30fe353f646c4 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 9 Jun 2015 02:12:15 -0700 Subject: [PATCH 097/937] almost lost the sandbox --- lib/sandbox.js | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 lib/sandbox.js diff --git a/lib/sandbox.js b/lib/sandbox.js new file mode 100644 index 00000000000..af15c413a42 --- /dev/null +++ b/lib/sandbox.js @@ -0,0 +1,80 @@ +var utils = require('./utils'); + +function init ( ) { + var sbx = {}; + + function init() { + sbx.properties = []; + } + + /** + * Initialize the sandbox using server state + * + * @param env - .js + * @param ctx - created from bootevent + * @returns {{sbx}} + */ + sbx.serverInit = function serverInit(env, ctx) { + init(); + + sbx.time = Date.now(); + sbx.units = env.DISPLAY_UNITS; + sbx.defaults = env.defaults; + sbx.thresholds = env.thresholds; + sbx.alarm_types = env.alarm_types; + sbx.data = ctx.data.clone(); + + //Plugins will expect the right profile based on time + sbx.data.profile = data.profiles.length > 0 ? data.profiles[0] : undefined; + delete sbx.data.profiles; + + sbx.properties = []; + + return sbx; + }; + + /** + * Initialize the sandbox using client state + * + * @param app - app settings + * @param clientSettings - specific settings from the client, starting with the defaults + * @param time - could be a retro time + * @param pluginBase - used by visualization plugins to update the UI + * @param data - svgs, treatments, profile, etc + * @returns {{sbx}} + */ + sbx.clientInit = function clientInit(app, clientSettings, time, pluginBase, data) { + init(); + + sbx.units = clientSettings.units; + sbx.defaults = clientSettings; //TODO: strip out extra stuff + sbx.thresholds = app.thresholds; + sbx.alarm_types = clientSettings.alarm_types; + sbx.showPlugins = clientSettings.showPlugins; + sbx.time = time; + sbx.data = data; + sbx.pluginBase = pluginBase; + + return sbx; + }; + + /** + * Properties are immutable, first plugin to set it wins, plugins should be in the correct order + * + * @param name + * @param setter + */ + sbx.offerProperty = function offerProperty(name, setter) { + if (!sbx.properties.hasOwnProperty(name)) { + var value = setter(); + if (value) { + sbx.properties[name] = value; + } + } + }; + + return sbx; +} + +module.exports = init; + From bcd6b27ac576d0c74964df31d1b53ff4b5f7dd1e Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 9 Jun 2015 08:24:34 -0700 Subject: [PATCH 098/937] ctx.bus --- lib/bootevent.js | 10 +++++----- lib/{ticker.js => bus.js} | 13 ++++++++----- lib/notifications.js | 4 ++-- server.js | 4 ++-- 4 files changed, 17 insertions(+), 14 deletions(-) rename lib/{ticker.js => bus.js} (87%) diff --git a/lib/bootevent.js b/lib/bootevent.js index 3005a1f70ec..34b63e81f26 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -30,23 +30,23 @@ function boot (env) { store.ensureIndexes(ctx.devicestatus( ), ctx.devicestatus.indexedFields); store.ensureIndexes(ctx.profile( ), ctx.profile.indexedFields); - ctx.heartbeat = require('./ticker')(env, ctx); + ctx.bus = require('./bus')(env, ctx); ctx.data = require('./data')(env, ctx); ctx.notifications = require('./notifications')(env, ctx); - ctx.heartbeat.on('tick', function(tick) { + ctx.bus.on('tick', function(tick) { console.info('tick', tick.now); ctx.data.update(function dataUpdated () { - ctx.heartbeat.emit('data-loaded'); + ctx.bus.emit('data-loaded'); }); }); - ctx.heartbeat.on('data-loaded', function() { + ctx.bus.on('data-loaded', function() { ctx.notifications.processData(env, ctx); }); - ctx.heartbeat.uptime( ); + ctx.bus.uptime( ); next( ); }) diff --git a/lib/ticker.js b/lib/bus.js similarity index 87% rename from lib/ticker.js rename to lib/bus.js index 0be1293f297..687f31cb6ee 100644 --- a/lib/ticker.js +++ b/lib/bus.js @@ -1,12 +1,13 @@ - -var es = require('event-stream'); var Stream = require('stream'); -function heartbeat (env, ctx) { +function init (env, ctx) { var beats = 0; var started = new Date( ); var id; var interval = env.HEARTBEAT || 20000; + + var stream = new Stream; + function ictus ( ) { var tick = { now: new Date( ) @@ -18,18 +19,20 @@ function heartbeat (env, ctx) { }; return tick; } + function repeat ( ) { stream.emit('tick', ictus( )); } + function ender ( ) { if (id) cancelInterval(id); stream.emit('end'); } - var stream = new Stream; + stream.readable = true; stream.uptime = repeat; id = setInterval(repeat, interval); return stream; } -module.exports = heartbeat; +module.exports = init; diff --git a/lib/notifications.js b/lib/notifications.js index e4ad3e66645..739e65e9d0e 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -38,7 +38,7 @@ function init (env, ctx) { } } if (sendClear) { - ctx.heartbeat.emit('notification', {clear: true}); + ctx.bus.emit('notification', {clear: true}); console.info('emitted notification clear'); } } @@ -46,7 +46,7 @@ function init (env, ctx) { function emitAlarm (type) { var alarm = alarms[type]; if (ctx.data.lastUpdated > alarm.lastAckTime + alarm.silenceTime) { - ctx.heartbeat.emit('notification', {type: type}); + ctx.bus.emit('notification', {type: type}); alarm.lastEmitTime = ctx.data.lastUpdated; console.info('emitted notification:' + type); } else { diff --git a/server.js b/server.js index 6a2025b8722..1cf8bc1c8b8 100644 --- a/server.js +++ b/server.js @@ -60,11 +60,11 @@ bootevent(env).boot(function booted (ctx) { /////////////////////////////////////////////////// var websocket = require('./lib/websocket')(env, ctx, server); - ctx.heartbeat.on('data-loaded', function() { + ctx.bus.on('data-loaded', function() { websocket.processData(); }); - ctx.heartbeat.on('notification', function(info) { + ctx.bus.on('notification', function(info) { websocket.emitNotification(info); }); From 6637d3f9d292479f2e44f75ce18b278d1fcc9f48 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 9 Jun 2015 13:07:25 -0700 Subject: [PATCH 099/937] added support for plugins to request and snooze notifications --- env.js | 11 ++- lib/bootevent.js | 6 +- lib/notifications.js | 120 +++++++++++++++--------------- lib/plugins/ar2.js | 50 ++++++++++++- lib/plugins/boluswizardpreview.js | 81 ++++++++++++-------- lib/plugins/index.js | 23 ++++-- lib/plugins/iob.js | 4 +- lib/plugins/pluginbase.js | 42 ----------- lib/plugins/simplealarms.js | 60 +++++++++++++++ lib/sandbox.js | 69 ++++++++++++++++- lib/websocket.js | 22 +++--- 11 files changed, 334 insertions(+), 154 deletions(-) create mode 100644 lib/plugins/simplealarms.js diff --git a/env.js b/env.js index d16ce6ef9c0..38eeda38200 100644 --- a/env.js +++ b/env.js @@ -165,11 +165,20 @@ function config ( ) { var thresholdsSet = readIntENV('BG_HIGH') || readIntENV('BG_TARGET_TOP') || readIntENV('BG_TARGET_BOTTOM') || readIntENV('BG_LOW'); env.alarm_types = readENV('ALARM_TYPES') || (thresholdsSet ? "simple" : "predict"); + //TODO: maybe get rid of ALARM_TYPES and only use enable? + if (env.alarm_types.indexOf('simple') > -1) { + env.enable = 'simplealarms ' + env.enable; + } + if (env.alarm_types.indexOf('predict') > -1) { + env.enable = 'ar2 ' + env.enable; + } + // For pushing notifications to Pushover. + //TODO: handle PUSHOVER_ as generic plugin props env.pushover_api_token = readENV('PUSHOVER_API_TOKEN'); env.pushover_user_key = readENV('PUSHOVER_USER_KEY') || readENV('PUSHOVER_GROUP_KEY'); if (env.pushover_api_token && env.pushover_user_key) { - env.alarm_types.push('pushover'); + env.enable = env.enable + ' pushover'; } // TODO: clean up a bit diff --git a/lib/bootevent.js b/lib/bootevent.js index 34b63e81f26..1bbae95a72f 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -43,7 +43,11 @@ function boot (env) { }); ctx.bus.on('data-loaded', function() { - ctx.notifications.processData(env, ctx); + var sbx = require('./sandbox')().serverInit(env, ctx); + ctx.plugins.setProperties(sbx); + ctx.notifications.initRequests(); + ctx.plugins.checkNotifications(sbx); + ctx.notifications.process(env, ctx); }); ctx.bus.uptime( ); diff --git a/lib/notifications.js b/lib/notifications.js index 739e65e9d0e..3d4c1a31703 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -1,20 +1,19 @@ 'use strict'; -var ar2 = require('./plugins/ar2')(); +var _ = require('lodash'); var THIRTY_MINUTES = 30 * 60 * 1000; -var Alarm = function(_typeName, _threshold) { - this.typeName = _typeName; +var Alarm = function(label) { + this.label = label; this.silenceTime = THIRTY_MINUTES; this.lastAckTime = 0; - this.threshold = _threshold; }; // list of alarms with their thresholds var alarms = { - 'alarm' : new Alarm('Regular', 0.05), - 'urgent_alarm': new Alarm('Urgent', 0.10) + 1 : new Alarm('Warn'), + 2: new Alarm('Urgent') }; function init (env, ctx) { @@ -26,89 +25,88 @@ function init (env, ctx) { //setting the silence time to 1ms so the alarm will be retriggered as soon as the condition changes //since this wasn't ack'd by a user action function autoAckAlarms() { + var sendClear = false; - for (var type in alarms) { - if (alarms.hasOwnProperty(type)) { - var alarm = alarms[type]; - if (alarm.lastEmitTime) { - console.info('auto acking ' + type); - notifications.ack(type, 1); - sendClear = true; - } + + for (var level = 1; level <=2; level++) { + var alarm = alarms[level]; + if (alarm.lastEmitTime) { + console.info('auto acking ' + alarm.level); + notifications.ack(alarm.level, 1); + sendClear = true; } } + if (sendClear) { ctx.bus.emit('notification', {clear: true}); console.info('emitted notification clear'); } } - function emitAlarm (type) { - var alarm = alarms[type]; + function emitNotification (notify) { + var alarm = alarms[notify.level]; if (ctx.data.lastUpdated > alarm.lastAckTime + alarm.silenceTime) { - ctx.bus.emit('notification', {type: type}); + ctx.bus.emit('notification', notify); alarm.lastEmitTime = ctx.data.lastUpdated; - console.info('emitted notification:' + type); + console.info('emitted notification:', notify); } else { - console.log(alarm.typeName + ' alarm is silenced for ' + Math.floor((alarm.silenceTime - (ctx.data.lastUpdated - alarm.lastAckTime)) / 60000) + ' minutes more'); + console.log(alarm.label + ' alarm is silenced for ' + Math.floor((alarm.silenceTime - (ctx.data.lastUpdated - alarm.lastAckTime)) / 60000) + ' minutes more'); } } - notifications.processData = function processData ( ) { - var d = ctx.data; + var requests = {}; - console.log('running notifications.processData'); + notifications.initRequests = function initRequests ( ) { + requests = { notifies: [] , snoozes: []}; + }; - var lastSGV = d.sgvs.length > 0 ? d.sgvs[d.sgvs.length - 1].y : null; + notifications.initRequests(); - if (lastSGV) { - var forecast = ar2.forecast(env, ctx); + function findHighestNotify ( ) { + return _.find(requests.notifies, {level: 'urgent'}) || _.find(requests.notifies, {level: 'warn'}) || _.first(requests.notifies); + } - var emitAlarmType = null; + function findLongestSnooze ( ) { + if (_.isEmpty(requests.snoozes)) return null; - if (env.alarm_types.indexOf('simple') > -1) { - if (lastSGV > env.thresholds.bg_high) { - emitAlarmType = 'urgent_alarm'; - console.info(lastSGV + ' > ' + env.thresholds.bg_high + ' will emmit ' + emitAlarmType); - } else if (lastSGV > env.thresholds.bg_target_top) { - emitAlarmType = 'alarm'; - console.info(lastSGV + ' > ' + env.thresholds.bg_target_top + ' will emmit ' + emitAlarmType); - } else if (lastSGV < env.thresholds.bg_low) { - emitAlarmType = 'urgent_alarm'; - console.info(lastSGV + ' < ' + env.thresholds.bg_low + ' will emmit ' + emitAlarmType); - } else if (lastSGV < env.thresholds.bg_target_bottom) { - emitAlarmType = 'alarm'; - console.info(lastSGV + ' < ' + env.thresholds.bg_target_bottom + ' will emmit ' + emitAlarmType); - } - } + var groups = _.groupBy(requests.snoozes, 'level'); + var firstKey = _.first(_.keys(groups)); + var longest = firstKey && _.last(groups[firstKey].sort()); - if (!emitAlarmType && env.alarm_types.indexOf('predict') > -1) { - if (forecast.avgLoss > alarms['urgent_alarm'].threshold) { - emitAlarmType = 'urgent_alarm'; - console.info('Avg Loss:' + forecast.avgLoss + ' > ' + alarms['urgent_alarm'].threshold + ' will emmit ' + emitAlarmType); - } else if (forecast.avgLoss > alarms['alarm'].threshold) { - emitAlarmType = 'alarm'; - console.info('Avg Loss:' + forecast.avgLoss + ' > ' + alarms['alarm'].threshold + ' will emmit ' + emitAlarmType); - } - } + return longest; + } - if (d.sgvs.length > 0 && d.sgvs[d.sgvs.length - 1].y < 39) { - emitAlarmType = 'urgent_alarm'; - } + notifications.requestNotify = function requestNotify (notify) { + requests.notifies.push(notify); + }; + + notifications.requestSnooze = function requestSnooze (snooze) { + requests.snoozes.push(snooze); + }; + + notifications.process = function process ( ) { + var highestNotify = findHighestNotify(); + var longestSnooze = findLongestSnooze(); - if (emitAlarmType) { - emitAlarm(emitAlarmType); + if (longestSnooze) { + if (highestNotify && highestNotify.level > longestSnooze.level) { + console.log('notifications.process, ignoring snooze: ', longestSnooze, ' because notify: ', highestNotify); } else { - autoAckAlarms(); + console.log('notifications.process, snoozing because: ', longestSnooze); + notifications.ack(longestSnooze.level, longestSnooze.mills) } } - }; - notifications.ack = function ack (type, time) { - var alarm = alarms[type]; - if (alarm) { - console.info('Got an ack for: ', alarm, 'time: ' + time); + if (highestNotify) { + emitNotification(highestNotify); } else { + autoAckAlarms(); + } + }; + + notifications.ack = function ack (level, time) { + var alarm = alarms[level]; + if (!alarm) { console.warn('Got an ack for an unknown alarm time'); return; } diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index f35b0074b2c..6cb62472ce5 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -1,5 +1,7 @@ 'use strict'; +var _ = require('lodash'); + function init() { function ar2() { @@ -9,13 +11,57 @@ function init() { ar2.label = 'AR2'; ar2.pluginType = 'forecast'; + var WARN_THRESHOLD = 0.05; + var URGENT_THRESHOLD = 0.10; + var ONE_HOUR = 3600000; var ONE_MINUTE = 60000; var FIVE_MINUTES = 300000; - ar2.forecast = function forecast(env, ctx) { + ar2.checkNotifications = function checkNotifications(sbx) { + var forecast = ar2.forecast(sbx.data.sgvs); + + var trigger = false + , level = 0 + , levelLabel = ''; + + if (forecast.avgLoss > URGENT_THRESHOLD) { + trigger = true; + level = 2; + levelLabel = 'Urgent'; + } else if (forecast.avgLoss > WARN_THRESHOLD) { + trigger = true; + level = 1; + levelLabel = 'Warning'; + } + + if (trigger) { + var lastPredicted = _.last(forecast.predicted); + var rangeLabel = ''; + + if (lastPredicted > sbx.thresholds.bg_target_top) { + rangeLabel = 'HIGH'; + } else if (lastPredicted > sbx.thresholds.bg_target_top) { + rangeLabel = 'LOW'; + } + + var display = [levelLabel, rangeLabel, 'predicted - ', forecast.predicted[2], ' in 15mins'].join(' '); + + console.info(display); + + sbx.notifications.requestNotify({ + level: level + , display: display + , debug: { + forecast: forecast + , thresholds: sbx.thresholds + } + }); + } + }; + + ar2.forecast = function forecast(sgvs) { - var sgvs = ctx.data.sgvs; var lastIndex = sgvs.length - 1; var result = { diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 14b6392a802..9ca95f7033b 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -2,6 +2,8 @@ var _ = require('lodash'); +var FIFTEEN_MINS = 15 * 60 * 1000; + function init() { function bwp() { @@ -11,50 +13,69 @@ function init() { bwp.label = 'Bolus Wizard Preview'; bwp.pluginType = 'pill-minor'; + bwp.checkNotifications = function checkNotifications(sbx) { + var results = bwp.calc(sbx); + if (results.bolusEstimate < .25) { + sbx.notifications.requestSnooze({ + level: 2 + , mills: FIFTEEN_MINS + , debug: 'BWP: ' + results.bolusEstimateDisplay + 'U' + }) + } + }; + + bwp.updateVisualisation = function updateVisualisation (sbx) { - var sgv = _.last(sbx.data.sgvs); - //ug, on the client y, is unscaled, on the server we only have the unscaled sgv field - sgv = sgv && (sgv.y || sgv.sgv); + var results = bwp.calc(sbx); - if (sgv == undefined || !sbx.properties.iob) return; - - var profile = sbx.data.profile; + // display text + var info = [ + {label: 'Insulin on Board', value: results.displayIOB + 'U'} + , {label: 'Expected effect', value: '-' + results.effectDisplay + ' ' + sbx.units} + , {label: 'Expected outcome', value: results.outcomeDisplay + ' ' + sbx.units} + ]; + + sbx.pluginBase.updatePillText(bwp, results.bolusEstimateDisplay + 'U', 'BWP', info); - var bolusEstimate = 0.0; + }; - // TODO: MMOL -- Jason: if we assume sens is in display units, we don't need to do any conversion - sgv = sbx.pluginBase.scaleBg(sgv, sbx); + bwp.calc = function calc (sbx) { - var iob = sbx.properties.iob.iob; + var results = { + effect: 0 + , outcome: 0 + , bolusEstimate: 0.0 + }; - var effect = iob * profile.sens; - var outcome = sgv - effect; + var sgv = sbx.scaleBg(sbx.data.lastSGV()); + + if (sgv == undefined || !sbx.properties.iob) return; + + var profile = sbx.data.profile; + + var iob = results.iob = sbx.properties.iob.iob; + + results.effect = iob * profile.sens; + results.outcome = sgv - results.effect; var delta = 0; - if (outcome > profile.target_high) { - delta = outcome - profile.target_high; - bolusEstimate = delta / profile.sens; + if (results.outcome > profile.target_high) { + delta = results.outcome - profile.target_high; + results.bolusEstimate = delta / profile.sens; } - if (outcome < profile.target_low) { - delta = Math.abs(outcome - profile.target_low); - bolusEstimate = delta / profile.sens * -1; + if (results.outcome < profile.target_low) { + delta = Math.abs(results.outcome - profile.target_low); + results.bolusEstimate = delta / profile.sens * -1; } - bolusEstimate = sbx.pluginBase.roundInsulinForDisplayFormat(bolusEstimate, sbx); - outcome = sbx.pluginBase.roundBGToDisplayFormat(outcome, sbx); - var displayIOB = sbx.pluginBase.roundInsulinForDisplayFormat(iob, sbx); - - // display text - var info = [ - {label: 'Insulin on Board', value: displayIOB + 'U'} - , {label: 'Expected effect', value: '-' + sbx.pluginBase.roundBGToDisplayFormat(effect, sbx) + ' ' + sbx.pluginBase.getBGUnits(sbx)} - , {label: 'Expected outcome', value: outcome + ' ' + sbx.pluginBase.getBGUnits(sbx)} - ]; - - sbx.pluginBase.updatePillText(bwp, bolusEstimate + 'U', 'BWP', info); + results.bolusEstimateDisplay = sbx.roundInsulinForDisplayFormat(results.bolusEstimate); + results.outcomeDisplay = sbx.roundBGToDisplayFormat(results.outcome); + results.displayIOB = sbx.roundInsulinForDisplayFormat(results.iob); + results.effectDisplay = sbx.roundBGToDisplayFormat(results.effect); + return results; }; return bwp(); diff --git a/lib/plugins/index.js b/lib/plugins/index.js index 62905cc1a71..d4d61b81869 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -14,16 +14,23 @@ function init() { plugins.base = require('./pluginbase'); plugins.registerServerDefaults = function registerServerDefaults() { - plugins.register([ require('./pushnotify')() ]); + plugins.register([ + require('./ar2')() + , require('./simplealarms')() + , require('./pushnotify')() + , require('./iob')() + , require('./cob')() + , require('./boluswizardpreview')() + ]); return plugins; }; plugins.registerClientDefaults = function registerClientDefaults() { plugins.register([ - require('./iob')(), - require('./cob')(), - require('./boluswizardpreview')(), - require('./cannulaage')() + require('./iob')() + , require('./cob')() + , require('./boluswizardpreview')() + , require('./cannulaage')() ]); return plugins; }; @@ -81,6 +88,12 @@ function init() { }); }; + plugins.checkNotifications = function checkNotifications(sbx) { + plugins.eachEnabledPlugin( function eachPlugin (plugin) { + plugin.checkNotifications && plugin.checkNotifications(sbx); + }); + }; + plugins.updateVisualisations = function updateVisualisations(sbx) { plugins.eachShownPlugins(sbx, function eachPlugin(plugin) { plugin.updateVisualisation && plugin.updateVisualisation(sbx); diff --git a/lib/plugins/iob.js b/lib/plugins/iob.js index 30748e51cfd..806107f61c2 100644 --- a/lib/plugins/iob.js +++ b/lib/plugins/iob.js @@ -95,11 +95,11 @@ function init() { if (prop && prop.lastBolus) { var when = moment(new Date(prop.lastBolus.created_at)).format('lll'); - var amount = sbx.pluginBase.roundInsulinForDisplayFormat(Number(prop.lastBolus.insulin), sbx) + 'U'; + var amount = sbx.roundInsulinForDisplayFormat(Number(prop.lastBolus.insulin)) + 'U'; info = [{label: 'Last Bolus', value: amount + ' @ ' + when }] } - sbx.pluginBase.updatePillText(iob, sbx.pluginBase.roundInsulinForDisplayFormat(prop.display, sbx) + 'U', 'IOB', info); + sbx.pluginBase.updatePillText(iob, sbx.roundInsulinForDisplayFormat(prop.display) + 'U', 'IOB', info); }; diff --git a/lib/plugins/pluginbase.js b/lib/plugins/pluginbase.js index 09c342399c7..9ef44913970 100644 --- a/lib/plugins/pluginbase.js +++ b/lib/plugins/pluginbase.js @@ -44,48 +44,6 @@ function init (majorPills, minorPills, tooltip) { } }; - pluginBase.roundInsulinForDisplayFormat = function roundInsulinForDisplayFormat (iob, sbx) { - - if (iob == 0) return 0; - - if (sbx.properties.roundingStyle == 'medtronic') { - var denominator = 0.1; - var digits = 1; - if (iob > 0.5 && iob < 1) { - denominator = 0.05; - digits = 2; - } - if (iob <= 0.5) { - denominator = 0.025; - digits = 3; - } - return (Math.floor(iob / denominator) * denominator).toFixed(digits); - } - - return (Math.floor(iob / 0.01) * 0.01).toFixed(2); - - }; - - pluginBase.getBGUnits = function getBGUnits (sbx) { - if (sbx.units == 'mmol') return 'mmol/L'; - return "mg/dl"; - }; - - pluginBase.roundBGToDisplayFormat = function roundBGToDisplayFormat (bg, sbx) { - if (sbx.units == 'mmol') { - return Math.round(bg * 10) / 10; - } - return Math.round(bg); - }; - - pluginBase.scaleBg = function scaleBg (bg, sbx) { - if (sbx.units == 'mmol') { - return Nightscout.units.mgdlToMMOL(bg); - } else { - return bg; - } - }; - return pluginBase(); } diff --git a/lib/plugins/simplealarms.js b/lib/plugins/simplealarms.js new file mode 100644 index 00000000000..78b42f6b398 --- /dev/null +++ b/lib/plugins/simplealarms.js @@ -0,0 +1,60 @@ +'use strict'; + +var _ = require('lodash'); + +function init() { + + function simplealarms() { + return simplealarms; + } + + simplealarms.label = 'Simple Alarms'; + simplealarms.pluginType = 'notification'; + + simplealarms.checkNotifications = function checkNotifications(sbx) { + var lastSGV = sbx.data.lastSGV() + , trigger = false + , level = 0 + , label = '' + ; + + if (lastSGV) { + if (lastSGV > sbx.thresholds.bg_high) { + trigger = true; + level = 2; + label = 'Urgent HIGH:'; + console.info(label + (lastSGV + ' > ' + sbx.thresholds.bg_high)); + } else if (lastSGV > sbx.thresholds.bg_target_top) { + trigger = true; + level = 1; + label = 'High warning:'; + console.info(label + (lastSGV + ' > ' + sbx.thresholds.bg_target_top)); + } else if (lastSGV < sbx.thresholds.bg_low) { + trigger = true; + level = 2; + label = 'Urgent LOW:'; + console.info(label + (lastSGV + ' < ' + sbx.thresholds.bg_low)); + } else if (lastSGV < sbx.thresholds.bg_target_bottom) { + trigger = true; + level = 1; + label = 'Low warning:'; + console.info(label + (lastSGV + ' < ' + sbx.thresholds.bg_target_bottom)); + } + + if (trigger) { + sbx.notifications.requestNotify({ + level: level + , display: [label, lastSGV].join(' ') + , debug: { + lastSGV: lastSGV, thresholds: sbx.thresholds + } + }); + } + } + }; + + return simplealarms(); + +} + +module.exports = init; \ No newline at end of file diff --git a/lib/sandbox.js b/lib/sandbox.js index af15c413a42..a15031f43b9 100644 --- a/lib/sandbox.js +++ b/lib/sandbox.js @@ -1,3 +1,7 @@ +'use strict'; + +var _ = require('lodash'); +var units = require('./units'); var utils = require('./utils'); function init ( ) { @@ -5,6 +9,19 @@ function init ( ) { function init() { sbx.properties = []; + sbx.scaleBg = scaleBg; + sbx.roundInsulinForDisplayFormat = roundInsulinForDisplayFormat; + sbx.roundBGToDisplayFormat = roundBGToDisplayFormat; + } + + function extend () { + sbx.unitsLabel = unitsLabel(); + sbx.data = sbx.data || {}; + sbx.data.lastSGV = function lastSGV () { + var last = _.last(sbx.data.sgvs); + //ug, on the client y, is unscaled, on the server we only have the unscaled sgv field + return last && (last.y || last.sgv); + }; } /** @@ -24,12 +41,17 @@ function init ( ) { sbx.alarm_types = env.alarm_types; sbx.data = ctx.data.clone(); + //don't expose all of notifications, ctx.notifications will decide what to do after all plugins chime in + sbx.notifications = _.pick(ctx.notifications, ['requestNotify', 'requestSnooze', 'requestClear']); + //Plugins will expect the right profile based on time - sbx.data.profile = data.profiles.length > 0 ? data.profiles[0] : undefined; + sbx.data.profile = _.first(ctx.data.profiles); delete sbx.data.profiles; sbx.properties = []; + extend(); + return sbx; }; @@ -55,6 +77,8 @@ function init ( ) { sbx.data = data; sbx.pluginBase = pluginBase; + extend(); + return sbx; }; @@ -73,6 +97,49 @@ function init ( ) { } }; + function scaleBg (bg) { + if (sbx.units == 'mmol' && bg) { + return units.mgdlToMMOL(bg); + } else { + return bg; + } + } + + function roundInsulinForDisplayFormat (insulin) { + + if (insulin == 0) return '0'; + + if (sbx.properties.roundingStyle == 'medtronic') { + var denominator = 0.1; + var digits = 1; + if (insulin > 0.5 && iob < 1) { + denominator = 0.05; + digits = 2; + } + if (insulin <= 0.5) { + denominator = 0.025; + digits = 3; + } + return (Math.floor(insulin / denominator) * denominator).toFixed(digits); + } + + return (Math.floor(insulin / 0.01) * 0.01).toFixed(2); + + } + + function unitsLabel ( ) { + if (sbx.units == 'mmol') return 'mmol/L'; + return "mg/dl"; + } + + function roundBGToDisplayFormat (bg) { + if (sbx.units == 'mmol') { + return Math.round(bg * 10) / 10; + } + return Math.round(bg); + } + + return sbx; } diff --git a/lib/websocket.js b/lib/websocket.js index fe09ac8dbbb..0d6dbababc7 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -37,10 +37,11 @@ function init (env, ctx, server) { socket.emit('dataUpdate',lastData); io.emit('clients', ++watchers); socket.on('ack', function(alarmType, silenceTime) { - ctx.notifications.ack(alarmType, silenceTime); - if (alarmType == 'urgent_alarm') { + var level = alarmType == 'urgent_alarm' ? 2 : 1; + ctx.notifications.ack(level, silenceTime); + if (level == 2) { //also clean normal alarm so we don't get a double alarm as BG comes back into range - ctx.notifications.ack('alarm', silenceTime); + ctx.notifications.ack(1, silenceTime); } }); socket.on('disconnect', function () { @@ -65,13 +66,16 @@ function init (env, ctx, server) { } }; - websocket.emitNotification = function emitNotification (info) { - if (info.clear) { + websocket.emitNotification = function emitNotification (notify) { + if (notify.clear) { io.emit('clear_alarm', true); - console.info('emitted clear_alarm to all clients'); - } else if (info.type) { - io.emit(info.type); - console.info('emitted ' + info.type + ' to all clients'); + console.notify('emitted clear_alarm to all clients'); + } else if (notify.level == 1) { + io.emit('alarm', notify); + console.info('emitted alarm to all clients'); + } else if (notify.level == 2) { + io.emit('urgent_alarm', notify); + console.info('emitted urgent_alarm to all clients'); } }; From 36bbf8d7da9a6c5d1b39ce6322b36f23026069d4 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 9 Jun 2015 14:13:54 -0700 Subject: [PATCH 100/937] cleanup/fix --- lib/plugins/ar2.js | 2 +- lib/sandbox.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 6cb62472ce5..35e633516e7 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -56,7 +56,7 @@ function init() { forecast: forecast , thresholds: sbx.thresholds } - }); + }); } }; diff --git a/lib/sandbox.js b/lib/sandbox.js index a15031f43b9..b5cb8acf067 100644 --- a/lib/sandbox.js +++ b/lib/sandbox.js @@ -1,7 +1,7 @@ 'use strict'; var _ = require('lodash'); -var units = require('./units'); +var units = require('./units')(); var utils = require('./utils'); function init ( ) { From cb1ced609f27cee34b288ffacb18182e509fa83d Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 9 Jun 2015 14:16:30 -0700 Subject: [PATCH 101/937] also trigger alarms from BWP --- lib/plugins/boluswizardpreview.js | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 9ca95f7033b..13c114ea32d 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -15,12 +15,27 @@ function init() { bwp.checkNotifications = function checkNotifications(sbx) { var results = bwp.calc(sbx); - if (results.bolusEstimate < .25) { + + //TODO: not sure where these will come from yet + var snoozeBWP = sbx.properties.snoozeBWP || 0.10; + var warnBWP = sbx.properties.warnBWP || 0.35; + var urgentBWP = sbx.properties.urgentBWP || 0.75; + + if (results.bolusEstimate < snoozeBWP) { sbx.notifications.requestSnooze({ level: 2 , mills: FIFTEEN_MINS - , debug: 'BWP: ' + results.bolusEstimateDisplay + 'U' + , debug: results }) + } else if (results.bolusEstimate > warnBWP) { + var level = results.bolusEstimate > urgentBWP ? 2 : 1; + var levelLabel = results.bolusEstimate > urgentBWP ? 'Urgent': 'Warning'; + var display = [levelLabel, 'Check BG, time to bolus?'].join(' '); + sbx.notifications.requestNotify({ + level: level + , display: display + , debug: results + }); } }; From e1a05c26eb4f41805831992de8d88522d2ade847 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 9 Jun 2015 14:19:19 -0700 Subject: [PATCH 102/937] if an urgent alarm is acked, also ack warns --- lib/notifications.js | 4 ++++ lib/websocket.js | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/notifications.js b/lib/notifications.js index 3d4c1a31703..ecd5c003cb0 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -114,6 +114,10 @@ function init (env, ctx) { alarm.silenceTime = time ? time : THIRTY_MINUTES; delete alarm.lastEmitTime; + if (level == 2) { + notifications.ack(1, time); + } + }; return notifications(); diff --git a/lib/websocket.js b/lib/websocket.js index 0d6dbababc7..d06964a3ef2 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -39,10 +39,6 @@ function init (env, ctx, server) { socket.on('ack', function(alarmType, silenceTime) { var level = alarmType == 'urgent_alarm' ? 2 : 1; ctx.notifications.ack(level, silenceTime); - if (level == 2) { - //also clean normal alarm so we don't get a double alarm as BG comes back into range - ctx.notifications.ack(1, silenceTime); - } }); socket.on('disconnect', function () { io.emit('clients', --watchers); From 65d24a86068e33d32c59d6bc510a7da9f99b5153 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 9 Jun 2015 14:38:49 -0700 Subject: [PATCH 103/937] add lastSVG to results/debug --- lib/plugins/boluswizardpreview.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 13c114ea32d..541390cf0be 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -65,6 +65,8 @@ function init() { var sgv = sbx.scaleBg(sbx.data.lastSGV()); + results.lastSGV = sgv; + if (sgv == undefined || !sbx.properties.iob) return; var profile = sbx.data.profile; From 48fad6870d93de0b1e4fa5570cc9ab8a0341f2c2 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 9 Jun 2015 16:23:20 -0700 Subject: [PATCH 104/937] a few more steps to get pushover hooked in the new way --- lib/bootevent.js | 23 ++++- lib/entries.js | 6 +- lib/plugins/index.js | 1 - lib/plugins/simplealarms.js | 16 +++- lib/{plugins => }/pushnotify.js | 145 ++++++++++++++++++++------------ lib/treatments.js | 7 +- lib/websocket.js | 4 +- server.js | 4 +- 8 files changed, 132 insertions(+), 74 deletions(-) rename lib/{plugins => }/pushnotify.js (60%) diff --git a/lib/bootevent.js b/lib/bootevent.js index 1bbae95a72f..c8e2947a1b5 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -29,25 +29,39 @@ function boot (env) { store.ensureIndexes(ctx.treatments( ), ctx.treatments.indexedFields); store.ensureIndexes(ctx.devicestatus( ), ctx.devicestatus.indexedFields); store.ensureIndexes(ctx.profile( ), ctx.profile.indexedFields); - + ctx.bus = require('./bus')(env, ctx); ctx.data = require('./data')(env, ctx); ctx.notifications = require('./notifications')(env, ctx); - ctx.bus.on('tick', function(tick) { - console.info('tick', tick.now); + function updateData ( ) { ctx.data.update(function dataUpdated () { ctx.bus.emit('data-loaded'); }); + } + + ctx.bus.on('tick', function timedReloadData (tick) { + console.info('tick', tick.now); + updateData(); }); - ctx.bus.on('data-loaded', function() { + ctx.bus.on('data-received', function forceReloadData ( ) { + console.info('got data-received event, reloading now'); + updateData(); + }); + + ctx.bus.on('data-loaded', function updatePlugins ( ) { var sbx = require('./sandbox')().serverInit(env, ctx); ctx.plugins.setProperties(sbx); ctx.notifications.initRequests(); ctx.plugins.checkNotifications(sbx); ctx.notifications.process(env, ctx); + ctx.bus.emit('data-processed'); + }); + + ctx.bus.on('notification', function(info) { + websocket.emitNotification(info); }); ctx.bus.uptime( ); @@ -58,4 +72,5 @@ function boot (env) { return proc; } + module.exports = boot; diff --git a/lib/entries.js b/lib/entries.js index fe57606e6e9..e271272ff3c 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -116,13 +116,11 @@ function storage(env, ctx) { collection.update(query, doc, {upsert: true}, function (err, created) { firstErr = firstErr || err; if (++totalCreated === numDocs) { + //TODO: this is triggering a read from Mongo, we can do better + ctx.bus.emit('data-received'); fn(firstErr, docs); } }); - - ctx.plugins.eachEnabledPlugin(function eachEnabled(plugin) { - if (plugin.processEntry) plugin.processEntry(doc, ctx, env); - }); }); }); } diff --git a/lib/plugins/index.js b/lib/plugins/index.js index d4d61b81869..5242f27f7a2 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -17,7 +17,6 @@ function init() { plugins.register([ require('./ar2')() , require('./simplealarms')() - , require('./pushnotify')() , require('./iob')() , require('./cob')() , require('./boluswizardpreview')() diff --git a/lib/plugins/simplealarms.js b/lib/plugins/simplealarms.js index 78b42f6b398..860df1d583d 100644 --- a/lib/plugins/simplealarms.js +++ b/lib/plugins/simplealarms.js @@ -12,10 +12,12 @@ function init() { simplealarms.pluginType = 'notification'; simplealarms.checkNotifications = function checkNotifications(sbx) { - var lastSGV = sbx.data.lastSGV() + var lastSGV = sbx.scaleBg(sbx.data.lastSGV()) + , lastSGVEntry = _.last(sbx.data.sgvs) , trigger = false , level = 0 , label = '' + , pushoverSound = null ; if (lastSGV) { @@ -23,28 +25,40 @@ function init() { trigger = true; level = 2; label = 'Urgent HIGH:'; + pushoverSound = 'persistent'; console.info(label + (lastSGV + ' > ' + sbx.thresholds.bg_high)); } else if (lastSGV > sbx.thresholds.bg_target_top) { trigger = true; level = 1; label = 'High warning:'; + pushoverSound = 'climb'; console.info(label + (lastSGV + ' > ' + sbx.thresholds.bg_target_top)); } else if (lastSGV < sbx.thresholds.bg_low) { trigger = true; level = 2; label = 'Urgent LOW:'; + pushoverSound = 'persistent'; console.info(label + (lastSGV + ' < ' + sbx.thresholds.bg_low)); } else if (lastSGV < sbx.thresholds.bg_target_bottom) { trigger = true; level = 1; label = 'Low warning:'; + pushoverSound = 'falling'; console.info(label + (lastSGV + ' < ' + sbx.thresholds.bg_target_bottom)); + } else if (sbx.thresholds.bg_magic && lastSVG == sbx.thresholds.bg_magic && lastSGVEntry.direction == 'Flat') { + trigger = true; + level = o; + label = 'Perfect:'; + pushoverSound = 'magic'; } + + if (trigger) { sbx.notifications.requestNotify({ level: level , display: [label, lastSGV].join(' ') + , pushoverSound: pushoverSound , debug: { lastSGV: lastSGV, thresholds: sbx.thresholds } diff --git a/lib/plugins/pushnotify.js b/lib/pushnotify.js similarity index 60% rename from lib/plugins/pushnotify.js rename to lib/pushnotify.js index c938af66130..9b6f8a95431 100644 --- a/lib/plugins/pushnotify.js +++ b/lib/pushnotify.js @@ -1,16 +1,17 @@ 'use strict'; -var units = require('../units')(); +var _ = require('lodash'); +var units = require('units')(); -function init() { +function init(env, ctx) { // declare local constants for time differences var TIME_10_MINS = 10 * 60 * 1000, TIME_15_MINS = 15 * 60 * 1000, TIME_30_MINS = TIME_15_MINS * 2; - //the uploader may the last MBG multiple times, make sure we get a single notification - var lastMBGDate = 0; + var lastSentMBG = null; + var lastSentTreatment = null; //simple SGV Alert throttling //TODO: single snooze for websockets and push (when we add push callbacks) @@ -22,42 +23,69 @@ function init() { return pushnotify; } - pushnotify.label = 'Push Notify'; - pushnotify.pluginType = 'server-process'; + pushnotify.emitNotification = function emitNotification (notify) { + + if (!ctx.pushover) return; + + var msg = { + expire: TIME_15_MINS, + message: notify.message, + title: notify.display, + sound: notify.pushoverSound || 'gamelan', + timestamp: new Date( ), + priority: notify.level, + retry: 30 + }; + + ctx.pushover.send( msg, function( err, result ) { + console.info('pushnotify.emitNotification', err, result); + }); + - pushnotify.processEntry = function processEntry(entry, ctx, env) { - if (entry.type && entry.date && ctx.pushover) { - if (entry.type == 'mbg' || entry.type == 'meter') { - sendMBGPushover(entry, ctx); - } else if (entry.type == 'sgv') { - sendSGVPushover(entry, ctx); - } - } }; - pushnotify.processTreatment = function processTreatment(treatment, eventTime, preBolusCarbs, ctx, env) { + pushnotify.update = function update( ) { + sendMBG(); + sendTreatment() + }; - if (!ctx.pushover) return; + function sendTreatment ( ) { + + var lastTreatment = _.last(ctx.data.treatments); + if (!lastTreatment) return; + + var ago = new Date().getTime() - new Date(lastTreatment.created_at).getTime(); + + if (JSON.stringify(lastMBG) == JSON.stringify(lastSentMBG) || ago > TIME_10_MINS) { + return; + } //since we don't know the time zone on the device viewing the push message //we can only show the amount of adjustment - var timeAdjustment = calcTimeAdjustment(eventTime); + //TODO: need to store time extra info to figure out if treatment was added in past/future + //var timeAdjustment = calcTimeAdjustment(eventTime); + + var text = (lastTreatment.glucose ? 'BG: ' + lastTreatment.glucose + ' (' + lastTreatment.glucoseType + ')' : '') + + (lastTreatment.carbs ? '\nCarbs: ' + lastTreatment.carbs : '') + - var text = (treatment.glucose ? 'BG: ' + treatment.glucose + ' (' + treatment.glucoseType + ')' : '') + - (treatment.carbs ? '\nCarbs: ' + treatment.carbs : '') + - (preBolusCarbs ? '\nCarbs: ' + preBolusCarbs + ' (in ' + treatment.preBolus + ' minutes)' : '')+ - (treatment.insulin ? '\nInsulin: ' + treatment.insulin : '')+ - (treatment.enteredBy ? '\nEntered By: ' + treatment.enteredBy : '') + - (timeAdjustment ? '\nEvent Time: ' + timeAdjustment : '') + - (treatment.notes ? '\nNotes: ' + treatment.notes : ''); + //TODO: find a better way to connect split treatments + //(preBolusCarbs ? '\nCarbs: ' + preBolusCarbs + ' (in ' + treatment.preBolus + ' minutes)' : '')+ + + (lastTreatment.insulin ? '\nInsulin: ' + lastTreatment.insulin : '')+ + (lastTreatment.enteredBy ? '\nEntered By: ' + lastTreatment.enteredBy : '') + + + //TODO: find a better way to store timeAdjustment + //(timeAdjustment ? '\nEvent Time: ' + timeAdjustment : '') + + + (lastTreatment.notes ? '\nNotes: ' + lastTreatment.notes : ''); var msg = { - expire: 14400, // 4 hours + expire: TIME_10_MINS, message: text, - title: treatment.eventType, + title: lastTreatment.eventType, sound: 'gamelan', timestamp: new Date( ), - priority: (treatment.eventType == 'Note' ? -1 : 0), + priority: (lastTreatment.eventType == 'Note' ? -1 : 0), retry: 30 }; @@ -65,39 +93,46 @@ function init() { console.log(result); }); - }; + lastSentTreatment = lastTreatment; + } - function sendMBGPushover(entry, ctx) { - - if (entry.mbg && entry.type == 'mbg' && entry.date != lastMBGDate) { - var offset = new Date().getTime() - entry.date; - if (offset > TIME_10_MINS) { - console.info('No MBG Pushover, offset: ' + offset + ' too big, doc.date: ' + entry.date + ', now: ' + new Date().getTime()); - } else { - var mbg = entry.mbg; - if (env.DISPLAY_UNITS == 'mmol') { - mbg = units.mgdlToMMOL(mbg); - } - var msg = { - expire: 14400, // 4 hours - message: '\nMeter BG: ' + mbg, - title: 'Calibration', - sound: 'magic', - timestamp: new Date(entry.date), - priority: 0, - retry: 30 - }; - - ctx.pushover.send(msg, function (err, result) { - console.log(result); - }); - } - lastMBGDate = entry.date; + + function sendMBG( ) { + + var lastMBG = _.last(ctx.data.mbgs); + if (!lastMBG) return; + + var ago = new Date().getTime() - lastMBG.date; + + if (JSON.stringify(lastMBG) == JSON.stringify(lastSentMBG) || ago > TIME_10_MINS) { + return; } + + var mbg = lastMBG.mbg; + if (env.DISPLAY_UNITS == 'mmol') { + mbg = units.mgdlToMMOL(mbg); + } + var msg = { + expire: TIME_30_MINS, + message: '\nMeter BG: ' + mbg, + title: 'Calibration', + sound: 'magic', + timestamp: new Date(entry.date), + priority: 0, + retry: 30 + }; + + ctx.pushover.send(msg, function (err, result) { + console.log(result); + }); + + lastSentMBG = lastMBG; + } - function sendSGVPushover(entry, ctx) { + //TODO: move some of this to simplealarms + function old(entry, ctx) { if (!entry.sgv || entry.type != 'sgv') { return; diff --git a/lib/treatments.js b/lib/treatments.js index c0859a07e5c..8e92128dcc8 100644 --- a/lib/treatments.js +++ b/lib/treatments.js @@ -51,11 +51,8 @@ function storage (env, ctx) { }); } - //TODO: call plugins with processTreatments - ctx.plugins.eachEnabledPlugin(function eachEnabled(plugin) { - if (plugin.processTreatment) plugin.processTreatment(obj, eventTime, preBolusCarbs, ctx, env) - }); - + //TODO: this is triggering a read from Mongo, we can do better + ctx.bus.emit('data-received'); }); } diff --git a/lib/websocket.js b/lib/websocket.js index d06964a3ef2..d6e6880861b 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -46,8 +46,8 @@ function init (env, ctx, server) { }); } - websocket.processData = function processData ( ) { - console.log('running websocket.processData'); + websocket.update = function update ( ) { + console.log('running websocket.update'); var lastSGV = ctx.data.sgvs.length > 0 ? ctx.data.sgvs[ctx.data.sgvs.length - 1].y : null; if (lastSGV) { if (lastData.sgvs) { diff --git a/server.js b/server.js index 1cf8bc1c8b8..8dcecfce491 100644 --- a/server.js +++ b/server.js @@ -60,8 +60,8 @@ bootevent(env).boot(function booted (ctx) { /////////////////////////////////////////////////// var websocket = require('./lib/websocket')(env, ctx, server); - ctx.bus.on('data-loaded', function() { - websocket.processData(); + ctx.bus.on('data-processed', function() { + websocket.update(); }); ctx.bus.on('notification', function(info) { From eaa7faa3e1a6bfda66fd09e447d766fb15eded72 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 9 Jun 2015 17:38:22 -0700 Subject: [PATCH 105/937] pushnotify, not websockets --- lib/bootevent.js | 5 ++--- lib/pushnotify.js | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/bootevent.js b/lib/bootevent.js index c8e2947a1b5..17aa02d5af1 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -18,6 +18,7 @@ function boot (env) { /////////////////////////////////////////////////// ctx.plugins = require('./plugins')().registerServerDefaults().init(env); ctx.pushover = require('./pushover')(env); + ctx.pushnotify = require('./pushnotify')(env, ctx); ctx.entries = require('./entries')(env, ctx); ctx.treatments = require('./treatments')(env, ctx); ctx.devicestatus = require('./devicestatus')(env.devicestatus_collection, ctx); @@ -60,9 +61,7 @@ function boot (env) { ctx.bus.emit('data-processed'); }); - ctx.bus.on('notification', function(info) { - websocket.emitNotification(info); - }); + ctx.bus.on('notification', ctx.pushnotify.emitNotification); ctx.bus.uptime( ); diff --git a/lib/pushnotify.js b/lib/pushnotify.js index 9b6f8a95431..779b9e5fb7a 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -1,7 +1,7 @@ 'use strict'; var _ = require('lodash'); -var units = require('units')(); +var units = require('./units')(); function init(env, ctx) { From b6cf0b3a616138223f3d6ce290a70b2bccb08f8a Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 9 Jun 2015 18:08:02 -0700 Subject: [PATCH 106/937] some more little bugs --- lib/plugins/ar2.js | 6 +++--- lib/plugins/boluswizardpreview.js | 4 +++- lib/websocket.js | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 35e633516e7..64a9cf8c076 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -39,13 +39,13 @@ function init() { var lastPredicted = _.last(forecast.predicted); var rangeLabel = ''; - if (lastPredicted > sbx.thresholds.bg_target_top) { + if (lastPredicted > sbx.thresholds.bg_target_bottom) { rangeLabel = 'HIGH'; - } else if (lastPredicted > sbx.thresholds.bg_target_top) { + } else if (lastPredicted < sbx.thresholds.bg_target_top) { rangeLabel = 'LOW'; } - var display = [levelLabel, rangeLabel, 'predicted - ', forecast.predicted[2], ' in 15mins'].join(' '); + var display = [levelLabel, rangeLabel, 'predicted', sbx.scaleBg(forecast.predicted[2].y), 'in 15mins'].join(' '); console.info(display); diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 541390cf0be..428e576ecd2 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -16,12 +16,14 @@ function init() { bwp.checkNotifications = function checkNotifications(sbx) { var results = bwp.calc(sbx); + if (results.lastSGV < sbx.data.profile.target_high) return; + //TODO: not sure where these will come from yet var snoozeBWP = sbx.properties.snoozeBWP || 0.10; var warnBWP = sbx.properties.warnBWP || 0.35; var urgentBWP = sbx.properties.urgentBWP || 0.75; - if (results.bolusEstimate < snoozeBWP) { + if (results.lastSGV > sbx.thresholds.bg_target_top && results.bolusEstimate < snoozeBWP) { sbx.notifications.requestSnooze({ level: 2 , mills: FIFTEEN_MINS diff --git a/lib/websocket.js b/lib/websocket.js index d6e6880861b..a2028656755 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -65,7 +65,7 @@ function init (env, ctx, server) { websocket.emitNotification = function emitNotification (notify) { if (notify.clear) { io.emit('clear_alarm', true); - console.notify('emitted clear_alarm to all clients'); + console.info('emitted clear_alarm to all clients'); } else if (notify.level == 1) { io.emit('alarm', notify); console.info('emitted alarm to all clients'); From 2316c4ee1c1fa98c1d415ed04a8b2f946a11d89b Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 9 Jun 2015 18:19:39 -0700 Subject: [PATCH 107/937] add title/message for all notifications --- lib/plugins/ar2.js | 6 ++++-- lib/plugins/boluswizardpreview.js | 7 ++++--- lib/plugins/simplealarms.js | 16 +++++++++------- lib/pushnotify.js | 2 +- 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 64a9cf8c076..c1d0305a574 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -45,13 +45,15 @@ function init() { rangeLabel = 'LOW'; } - var display = [levelLabel, rangeLabel, 'predicted', sbx.scaleBg(forecast.predicted[2].y), 'in 15mins'].join(' '); + var title = [levelLabel, rangeLabel, 'predicted'].join(' '); + var message = [sbx.scaleBg(forecast.predicted[2].y), 'in 15mins'].join(' '); console.info(display); sbx.notifications.requestNotify({ level: level - , display: display + , title: title + , message: message , debug: { forecast: forecast , thresholds: sbx.thresholds diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 428e576ecd2..77d7042cbf4 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -31,11 +31,12 @@ function init() { }) } else if (results.bolusEstimate > warnBWP) { var level = results.bolusEstimate > urgentBWP ? 2 : 1; - var levelLabel = results.bolusEstimate > urgentBWP ? 'Urgent': 'Warning'; - var display = [levelLabel, 'Check BG, time to bolus?'].join(' '); + var levelLabel = results.bolusEstimate > urgentBWP ? 'Urgent' : 'Warning'; + var message = [levelLabel, results.lastSGV, sbx.unitsLabel].join(' '); sbx.notifications.requestNotify({ level: level - , display: display + , title: 'Check BG, time to bolus?' + , message: message , debug: results }); } diff --git a/lib/plugins/simplealarms.js b/lib/plugins/simplealarms.js index 860df1d583d..5c39cdcbf4c 100644 --- a/lib/plugins/simplealarms.js +++ b/lib/plugins/simplealarms.js @@ -16,7 +16,8 @@ function init() { , lastSGVEntry = _.last(sbx.data.sgvs) , trigger = false , level = 0 - , label = '' + , title = '' + , message = '' , pushoverSound = null ; @@ -24,31 +25,31 @@ function init() { if (lastSGV > sbx.thresholds.bg_high) { trigger = true; level = 2; - label = 'Urgent HIGH:'; + title = 'Urgent HIGH'; pushoverSound = 'persistent'; console.info(label + (lastSGV + ' > ' + sbx.thresholds.bg_high)); } else if (lastSGV > sbx.thresholds.bg_target_top) { trigger = true; level = 1; - label = 'High warning:'; + title = 'High warning'; pushoverSound = 'climb'; console.info(label + (lastSGV + ' > ' + sbx.thresholds.bg_target_top)); } else if (lastSGV < sbx.thresholds.bg_low) { trigger = true; level = 2; - label = 'Urgent LOW:'; + title = 'Urgent LOW'; pushoverSound = 'persistent'; console.info(label + (lastSGV + ' < ' + sbx.thresholds.bg_low)); } else if (lastSGV < sbx.thresholds.bg_target_bottom) { trigger = true; level = 1; - label = 'Low warning:'; + title = 'Low warning'; pushoverSound = 'falling'; console.info(label + (lastSGV + ' < ' + sbx.thresholds.bg_target_bottom)); } else if (sbx.thresholds.bg_magic && lastSVG == sbx.thresholds.bg_magic && lastSGVEntry.direction == 'Flat') { trigger = true; level = o; - label = 'Perfect:'; + title = 'Perfect'; pushoverSound = 'magic'; } @@ -57,7 +58,8 @@ function init() { if (trigger) { sbx.notifications.requestNotify({ level: level - , display: [label, lastSGV].join(' ') + , title: title + , message: [lastSGV, sbx.unitsLabel].join(' ') , pushoverSound: pushoverSound , debug: { lastSGV: lastSGV, thresholds: sbx.thresholds diff --git a/lib/pushnotify.js b/lib/pushnotify.js index 779b9e5fb7a..2857326759e 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -29,8 +29,8 @@ function init(env, ctx) { var msg = { expire: TIME_15_MINS, + title: notify.title, message: notify.message, - title: notify.display, sound: notify.pushoverSound || 'gamelan', timestamp: new Date( ), priority: notify.level, From 184ba224c67b567e177142a928392a47e32b208c Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 9 Jun 2015 18:28:12 -0700 Subject: [PATCH 108/937] starting to work, simple bg pushover messages getting through --- lib/plugins/simplealarms.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/plugins/simplealarms.js b/lib/plugins/simplealarms.js index 5c39cdcbf4c..870e7e8c3cb 100644 --- a/lib/plugins/simplealarms.js +++ b/lib/plugins/simplealarms.js @@ -17,7 +17,6 @@ function init() { , trigger = false , level = 0 , title = '' - , message = '' , pushoverSound = null ; @@ -27,25 +26,25 @@ function init() { level = 2; title = 'Urgent HIGH'; pushoverSound = 'persistent'; - console.info(label + (lastSGV + ' > ' + sbx.thresholds.bg_high)); + console.info(title + ': ' + (lastSGV + ' > ' + sbx.thresholds.bg_high)); } else if (lastSGV > sbx.thresholds.bg_target_top) { trigger = true; level = 1; title = 'High warning'; pushoverSound = 'climb'; - console.info(label + (lastSGV + ' > ' + sbx.thresholds.bg_target_top)); + console.info(title + ': ' + (lastSGV + ' > ' + sbx.thresholds.bg_target_top)); } else if (lastSGV < sbx.thresholds.bg_low) { trigger = true; level = 2; title = 'Urgent LOW'; pushoverSound = 'persistent'; - console.info(label + (lastSGV + ' < ' + sbx.thresholds.bg_low)); + console.info(title + ': ' + (lastSGV + ' < ' + sbx.thresholds.bg_low)); } else if (lastSGV < sbx.thresholds.bg_target_bottom) { trigger = true; level = 1; title = 'Low warning'; pushoverSound = 'falling'; - console.info(label + (lastSGV + ' < ' + sbx.thresholds.bg_target_bottom)); + console.info(title + ': ' + (lastSGV + ' < ' + sbx.thresholds.bg_target_bottom)); } else if (sbx.thresholds.bg_magic && lastSVG == sbx.thresholds.bg_magic && lastSGVEntry.direction == 'Flat') { trigger = true; level = o; From 6db5844ac31d6f651bfad38bf726ce2de153b5e7 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 9 Jun 2015 22:13:29 -0700 Subject: [PATCH 109/937] fix pushover expire time; always return a result when calcing bwp --- lib/plugins/boluswizardpreview.js | 2 +- lib/pushnotify.js | 25 +++++++++++++------------ 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 77d7042cbf4..3074bcebf57 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -70,7 +70,7 @@ function init() { results.lastSGV = sgv; - if (sgv == undefined || !sbx.properties.iob) return; + if (sgv == undefined || !sbx.properties.iob) return results; var profile = sbx.data.profile; diff --git a/lib/pushnotify.js b/lib/pushnotify.js index 2857326759e..5e0a17c653b 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -6,9 +6,10 @@ var units = require('./units')(); function init(env, ctx) { // declare local constants for time differences - var TIME_10_MINS = 10 * 60 * 1000, - TIME_15_MINS = 15 * 60 * 1000, - TIME_30_MINS = TIME_15_MINS * 2; + var TIME_10_MINS_MS = 10 * 60 * 1000, + TIME_15_MINS_S = 15 * 60, + TIME_15_MINS_MS = TIME_15_MINS_S * 1000, + TIME_30_MINS_MS = TIME_15_MINS_MS * 2; var lastSentMBG = null; var lastSentTreatment = null; @@ -28,7 +29,7 @@ function init(env, ctx) { if (!ctx.pushover) return; var msg = { - expire: TIME_15_MINS, + expire: TIME_15_MINS_S, title: notify.title, message: notify.message, sound: notify.pushoverSound || 'gamelan', @@ -56,7 +57,7 @@ function init(env, ctx) { var ago = new Date().getTime() - new Date(lastTreatment.created_at).getTime(); - if (JSON.stringify(lastMBG) == JSON.stringify(lastSentMBG) || ago > TIME_10_MINS) { + if (JSON.stringify(lastTreatment) == JSON.stringify(lastSentTreatment) || ago > TIME_10_MINS_MS) { return; } @@ -80,7 +81,7 @@ function init(env, ctx) { (lastTreatment.notes ? '\nNotes: ' + lastTreatment.notes : ''); var msg = { - expire: TIME_10_MINS, + expire: TIME_15_MINS_S, message: text, title: lastTreatment.eventType, sound: 'gamelan', @@ -105,7 +106,7 @@ function init(env, ctx) { var ago = new Date().getTime() - lastMBG.date; - if (JSON.stringify(lastMBG) == JSON.stringify(lastSentMBG) || ago > TIME_10_MINS) { + if (JSON.stringify(lastMBG) == JSON.stringify(lastSentMBG) || ago > TIME_10_MINS_MS) { return; } @@ -114,7 +115,7 @@ function init(env, ctx) { mbg = units.mgdlToMMOL(mbg); } var msg = { - expire: TIME_30_MINS, + expire: TIME_15_MINS_S, message: '\nMeter BG: ' + mbg, title: 'Calibration', sound: 'magic', @@ -141,7 +142,7 @@ function init(env, ctx) { var now = new Date().getTime(), offset = new Date().getTime() - entry.date; - if (offset > TIME_10_MINS || entry.date == lastSGVDate) { + if (offset > TIME_10_MINS_MS || entry.date == lastSGVDate) { console.info('No SVG Pushover, offset: ' + offset + ' too big, doc.date: ' + entry.date + ', now: ' + new Date().getTime() + ', lastSGVDate: ' + lastSGVDate); return; } @@ -163,7 +164,7 @@ function init(env, ctx) { // set vibration pattern; alert value; 0 nothing, 1 normal, 2 low, 3 high if (entry.sgv < 39) { - if (sinceLastAlert > TIME_30_MINS) { + if (sinceLastAlert > TIME_30_MINS_MS) { title = 'CGM Error'; priority = 1; sound = 'persistent'; @@ -188,11 +189,11 @@ function init(env, ctx) { title = 'Double Up'; priority = 1; sound = 'climb'; - } else if (entry.sgv > env.thresholds.bg_target_top && sinceLastAlert > TIME_30_MINS) { + } else if (entry.sgv > env.thresholds.bg_target_top && sinceLastAlert > TIME_30_MINS_MS) { title = 'High'; priority = 1; sound = 'climb'; - } else if (entry.sgv > env.thresholds.bg_high && sinceLastAlert > TIME_30_MINS) { + } else if (entry.sgv > env.thresholds.bg_high && sinceLastAlert > TIME_30_MINS_MS) { title = 'Urgent High'; priority = 1; sound = 'persistent'; From a7a65469bd7d8530a40cc1f38157e60ec62a8a44 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 9 Jun 2015 22:41:34 -0700 Subject: [PATCH 110/937] ug --- lib/plugins/ar2.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index c1d0305a574..391f128518c 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -48,8 +48,6 @@ function init() { var title = [levelLabel, rangeLabel, 'predicted'].join(' '); var message = [sbx.scaleBg(forecast.predicted[2].y), 'in 15mins'].join(' '); - console.info(display); - sbx.notifications.requestNotify({ level: level , title: title From da3b56425a3d2b6411a11fe7d693c818fcd1f465 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 9 Jun 2015 23:34:52 -0700 Subject: [PATCH 111/937] less pushing, better messages --- lib/bootevent.js | 1 - lib/plugins/ar2.js | 29 ++++++++++++++++++++++++----- lib/pushnotify.js | 17 +++++++++++------ 3 files changed, 35 insertions(+), 12 deletions(-) diff --git a/lib/bootevent.js b/lib/bootevent.js index 17aa02d5af1..9755fd5a5e6 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -17,7 +17,6 @@ function boot (env) { // api and json object variables /////////////////////////////////////////////////// ctx.plugins = require('./plugins')().registerServerDefaults().init(env); - ctx.pushover = require('./pushover')(env); ctx.pushnotify = require('./pushnotify')(env, ctx); ctx.entries = require('./entries')(env, ctx); ctx.treatments = require('./treatments')(env, ctx); diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 391f128518c..6539b195280 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -23,12 +23,15 @@ function init() { var trigger = false , level = 0 - , levelLabel = ''; + , levelLabel = '' + , pushoverSound = null + ; if (forecast.avgLoss > URGENT_THRESHOLD) { trigger = true; level = 2; levelLabel = 'Urgent'; + pushoverSound = 'persistent'; } else if (forecast.avgLoss > WARN_THRESHOLD) { trigger = true; level = 1; @@ -36,22 +39,38 @@ function init() { } if (trigger) { - var lastPredicted = _.last(forecast.predicted); + + var predicted = _.map(forecast.predicted, function(p) { return sbx.scaleBg(p.y) } ); + + var first = _.first(predicted); + var last = _.last(predicted); + var avg = _.sum(predicted) / predicted.length; + + var max = _.max([first, last, avg]); + var min = _.max([first, last, avg]); + var rangeLabel = ''; - if (lastPredicted > sbx.thresholds.bg_target_bottom) { + if (max > sbx.thresholds.bg_target_top) { rangeLabel = 'HIGH'; - } else if (lastPredicted < sbx.thresholds.bg_target_top) { + if (!pushoverSound) pushoverSound = 'climb' + } else if (min < sbx.thresholds.bg_target_bottom) { rangeLabel = 'LOW'; + if (!pushoverSound) pushoverSound = 'falling' + } else { + rangeLabel = ''; } - var title = [levelLabel, rangeLabel, 'predicted'].join(' '); + var title = [levelLabel, rangeLabel, 'predicted'].join(' ').replace(' ', ' '); var message = [sbx.scaleBg(forecast.predicted[2].y), 'in 15mins'].join(' '); + forecast.predicted = _.map(forecast.predicted, function(p) { return sbx.scaleBg(p.y) } ).join(', '); + sbx.notifications.requestNotify({ level: level , title: title , message: message + , pushoverSound: pushoverSound , debug: { forecast: forecast , thresholds: sbx.thresholds diff --git a/lib/pushnotify.js b/lib/pushnotify.js index 5e0a17c653b..a743777dba4 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -5,6 +5,8 @@ var units = require('./units')(); function init(env, ctx) { + var pushover = require('./pushover')(env); + // declare local constants for time differences var TIME_10_MINS_MS = 10 * 60 * 1000, TIME_15_MINS_S = 15 * 60, @@ -24,9 +26,12 @@ function init(env, ctx) { return pushnotify; } + var lastEmit = {}; + pushnotify.emitNotification = function emitNotification (notify) { - if (!ctx.pushover) return; + //make this smarter, for now send alerts every 15mins till cleared + if (lastEmit[notify.level] && lastEmit[notify.level] > Date.now() - TIME_15_MINS_MS) return; var msg = { expire: TIME_15_MINS_S, @@ -38,11 +43,11 @@ function init(env, ctx) { retry: 30 }; - ctx.pushover.send( msg, function( err, result ) { + pushover.send( msg, function( err, result ) { console.info('pushnotify.emitNotification', err, result); }); - + lastEmit[notify.level] = Date.now(); }; pushnotify.update = function update( ) { @@ -90,7 +95,7 @@ function init(env, ctx) { retry: 30 }; - ctx.pushover.send( msg, function( err, result ) { + pushover.send( msg, function( err, result ) { console.log(result); }); @@ -124,7 +129,7 @@ function init(env, ctx) { retry: 30 }; - ctx.pushover.send(msg, function (err, result) { + pushover.send(msg, function (err, result) { console.log(result); }); @@ -212,7 +217,7 @@ function init(env, ctx) { retry: 30 }; - ctx.pushover.send(msg, function (err, result) { + pushover.send(msg, function (err, result) { console.log(result); }); } From 46935cf20b4d97f954436929047c43bd02dba17a Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 9 Jun 2015 23:42:12 -0700 Subject: [PATCH 112/937] include units in message --- lib/plugins/ar2.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 6539b195280..9588868f0a4 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -62,7 +62,7 @@ function init() { } var title = [levelLabel, rangeLabel, 'predicted'].join(' ').replace(' ', ' '); - var message = [sbx.scaleBg(forecast.predicted[2].y), 'in 15mins'].join(' '); + var message = [predicted[2], sbx.unitsLabel, 'in 15mins'].join(' '); forecast.predicted = _.map(forecast.predicted, function(p) { return sbx.scaleBg(p.y) } ).join(', '); From ca3b20b26cb6d59012d9dfc1d16ab7d9a938bcb5 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 10 Jun 2015 00:52:59 -0700 Subject: [PATCH 113/937] got mbg calibrations and treatment notifications working --- lib/bootevent.js | 3 ++- lib/pushnotify.js | 16 ++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/bootevent.js b/lib/bootevent.js index 9755fd5a5e6..e0ecfc2ee34 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -56,7 +56,8 @@ function boot (env) { ctx.plugins.setProperties(sbx); ctx.notifications.initRequests(); ctx.plugins.checkNotifications(sbx); - ctx.notifications.process(env, ctx); + ctx.pushnotify.process(sbx); + ctx.notifications.process(sbx); ctx.bus.emit('data-processed'); }); diff --git a/lib/pushnotify.js b/lib/pushnotify.js index a743777dba4..205df593ada 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -50,12 +50,12 @@ function init(env, ctx) { lastEmit[notify.level] = Date.now(); }; - pushnotify.update = function update( ) { - sendMBG(); - sendTreatment() + pushnotify.process = function process(sbx) { + sendMBG(sbx); + sendTreatment(sbx) }; - function sendTreatment ( ) { + function sendTreatment (sbx) { var lastTreatment = _.last(ctx.data.treatments); if (!lastTreatment) return; @@ -104,7 +104,7 @@ function init(env, ctx) { } - function sendMBG( ) { + function sendMBG(sbx) { var lastMBG = _.last(ctx.data.mbgs); if (!lastMBG) return; @@ -115,16 +115,16 @@ function init(env, ctx) { return; } - var mbg = lastMBG.mbg; + var mbg = lastMBG.y; if (env.DISPLAY_UNITS == 'mmol') { mbg = units.mgdlToMMOL(mbg); } var msg = { expire: TIME_15_MINS_S, - message: '\nMeter BG: ' + mbg, + message: '\nMeter BG: ' + mbg + ' ' + sbx.unitsLabel, title: 'Calibration', sound: 'magic', - timestamp: new Date(entry.date), + timestamp: new Date(lastMBG.date), priority: 0, retry: 30 }; From 7446ef6c3a94d63435e32521fe9fe90757785e99 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Wed, 10 Jun 2015 22:52:20 +0300 Subject: [PATCH 114/937] removed code duplication from delta calculation --- lib/data.js | 127 ++++++++++++++++++++++++---------------------------- 1 file changed, 59 insertions(+), 68 deletions(-) diff --git a/lib/data.js b/lib/data.js index ddecf300c0b..a130c25991c 100644 --- a/lib/data.js +++ b/lib/data.js @@ -7,12 +7,12 @@ var ObjectID = require('mongodb').ObjectID; function uniq(a) { var seen = {}; - return a.filter(function(item) { + return a.filter(function (item) { return seen.hasOwnProperty(item.x) ? false : (seen[item.x] = true); }); } -function init (env, ctx) { +function init(env, ctx) { var data = { sgvs: [] @@ -45,7 +45,7 @@ function init (env, ctx) { return dir2Char[direction] || '-'; } - data.clone = function clone ( ) { + data.clone = function clone() { return _.cloneDeep(data, function (value) { //special handling of mongo ObjectID's //see https://github.com/lodash/lodash/issues/602#issuecomment-47414964 @@ -55,7 +55,7 @@ function init (env, ctx) { }); }; - data.update = function update (done) { + data.update = function update(done) { console.log('running data.update'); data.lastUpdated = Date.now(); @@ -63,15 +63,15 @@ function init (env, ctx) { var earliest_data = data.lastUpdated - TWO_DAYS; var treatment_earliest_data = data.lastUpdated - (ONE_DAY * 8); - function sort (values) { - values.sort(function sorter (a, b) { + function sort(values) { + values.sort(function sorter(a, b) { return a.x - b.x; }); } async.parallel({ entries: function (callback) { - var q = { find: {"date": {"$gte": earliest_data}} }; + var q = {find: {"date": {"$gte": earliest_data}}}; ctx.entries.list(q, function (err, results) { if (!err && results) { var mbgs = []; @@ -99,7 +99,7 @@ function init (env, ctx) { }) }, cal: function (callback) { //FIXME: date $gte????? - var cq = { count: 1, find: {"type": "cal"} }; + var cq = {count: 1, find: {"type": "cal"}}; ctx.entries.list(cq, function (err, results) { if (!err && results) { var cals = []; @@ -115,7 +115,7 @@ function init (env, ctx) { callback(); }); }, treatments: function (callback) { - var tq = { find: {"created_at": {"$gte": new Date(treatment_earliest_data).toISOString()}} }; + var tq = {find: {"created_at": {"$gte": new Date(treatment_earliest_data).toISOString()}}}; ctx.treatments.list(tq, function (err, results) { if (!err && results) { var treatments = []; @@ -126,7 +126,7 @@ function init (env, ctx) { }); //FIXME: sort in mongo - treatments.sort(function(a, b) { + treatments.sort(function (a, b) { return a.x - b.x; }); @@ -163,7 +163,7 @@ function init (env, ctx) { }; - data.calculateDelta = function calculateDelta (lastData) { + data.calculateDelta = function calculateDelta(lastData) { var delta = {'delta': true}; var changesFound = false; @@ -171,80 +171,71 @@ function init (env, ctx) { // if there's no updates done so far, just return the full set if (!lastData.sgvs) return data; - console.log('lastData.sgvs last record time', lastData.sgvs[lastData.sgvs.length-1].x); - console.log('d.sgvslast record time', data.sgvs[data.sgvs.length-1].x); - function nsArrayDiff(oldArray, newArray) { var seen = {}; var l = oldArray.length; - for (var i = 0; i < l; i++) { seen[oldArray[i].x] = true } + for (var i = 0; i < l; i++) { + seen[oldArray[i].x] = true + } var result = []; l = newArray.length; - for (var j = 0; j < l; j++) { if (!seen.hasOwnProperty(newArray[j].x)) { result.push(newArray[j]); console.log('delta data found'); } } + for (var j = 0; j < l; j++) { + if (!seen.hasOwnProperty(newArray[j].x)) { + result.push(newArray[j]); + } + } return result; } - function sort (values) { - values.sort(function sorter (a, b) { + function sort(values) { + values.sort(function sorter(a, b) { return a.x - b.x; }); } - var sgvDelta = nsArrayDiff(lastData.sgvs, data.sgvs); - - if (sgvDelta.length > 0) { - console.log('sgv changes found'); - changesFound = true; - sort(sgvDelta); - delta.sgvs = sgvDelta; - } - - var treatmentDelta = nsArrayDiff(lastData.treatments, data.treatments); - - if (treatmentDelta.length > 0) { - console.log('treatment changes found'); - changesFound = true; - sort(treatmentDelta); - delta.treatments = treatmentDelta; - } - - var mbgsDelta = nsArrayDiff(lastData.mbgs, data.mbgs); - - if (mbgsDelta.length > 0) { - console.log('mbgs changes found'); - changesFound = true; - sort(mbgsDelta); - delta.mbgs = mbgsDelta; - } - - var calsDelta = nsArrayDiff(lastData.cals, data.cals); - - if (calsDelta.length > 0) { - console.log('cals changes found'); - changesFound = true; - sort(calsDelta); - delta.cals = calsDelta; - } - - if (JSON.stringify(lastData.devicestatus) != JSON.stringify(data.devicestatus)) { - console.log('devicestatus changes found'); - changesFound = true; - delta.devicestatus = data.devicestatus; - } - - if (JSON.stringify(lastData.profiles) != JSON.stringify(data.profiles)) { - console.log('profile changes found'); - changesFound = true; - delta.profiles = data.profiles; + delta.lastUpdated = data.lastUpdated; + + // array compression + var compressibleArrays = ['sgvs', 'treatments', 'mbgs', 'cals']; + + for (var array in compressibleArrays) { + var a = compressibleArrays[array]; + if (data.hasOwnProperty(a)) { + + // if previous data doesn't have the property (first time delta?), just assign data over + if (!lastData.hasOwnProperty(a)) { + delta[a] = data[a]; + changesFound = true; + continue; + } + + // Calculate delta and assign delta over if changes were found + var deltaData = nsArrayDiff(lastData[a], data[a]); + if (deltaData.length > 0) { + console.log('delta changes found on', a); + changesFound = true; + sort(deltaData); + delta[a] = deltaData; + } + } } - if (changesFound) { - console.log('changes found'); - return delta; + // objects + var skippableObjects = ['profiles', 'devicestatus']; + + for (var object in skippableObjects) { + var o = skippableObjects[object]; + if (data.hasOwnProperty(o)) { + if (JSON.stringify(data[o]) != JSON.stringify(lastData[o])) { + console.log('delta changes found on', o); + changesFound = true; + delta[o] = data[o]; + } + } } + if (changesFound) return delta; return data; - }; From f6b33e86da2df3c81bd081c2ef5d75401525148e Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 11 Jun 2015 08:28:11 -0700 Subject: [PATCH 115/937] don't blow up if pushover isn't configured --- lib/pushnotify.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/pushnotify.js b/lib/pushnotify.js index 205df593ada..33abf10b91d 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -29,6 +29,7 @@ function init(env, ctx) { var lastEmit = {}; pushnotify.emitNotification = function emitNotification (notify) { + if (!pushover) return; //make this smarter, for now send alerts every 15mins till cleared if (lastEmit[notify.level] && lastEmit[notify.level] > Date.now() - TIME_15_MINS_MS) return; @@ -51,6 +52,7 @@ function init(env, ctx) { }; pushnotify.process = function process(sbx) { + if (!pushover) return; sendMBG(sbx); sendTreatment(sbx) }; From 144ed5ad0acf1b4040d38123a8580f7442376b91 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 11 Jun 2015 17:42:20 -0700 Subject: [PATCH 116/937] date on mbg at time of pushnotify processing is in the x field, need to clean that up --- lib/pushnotify.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/pushnotify.js b/lib/pushnotify.js index 33abf10b91d..ac850b6072d 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -111,7 +111,8 @@ function init(env, ctx) { var lastMBG = _.last(ctx.data.mbgs); if (!lastMBG) return; - var ago = new Date().getTime() - lastMBG.date; + //TODO: figure out why date is x here, clean up data model + var ago = new Date().getTime() - lastMBG.x; if (JSON.stringify(lastMBG) == JSON.stringify(lastSentMBG) || ago > TIME_10_MINS_MS) { return; From c9711b86040c6410c6915732af175968f9346dc0 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 11 Jun 2015 18:26:19 -0700 Subject: [PATCH 117/937] don't blow up if the target_high or target_low fields aren't set --- lib/plugins/boluswizardpreview.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 3074bcebf57..ac9dd496ee4 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -74,6 +74,10 @@ function init() { var profile = sbx.data.profile; + if (!profile.target_high || !profile.target_low) { + console.warn('For the BolusWizardPreview plugin to function your treatment profile must hava a both target_high and target_low fields'); + } + var iob = results.iob = sbx.properties.iob.iob; results.effect = iob * profile.sens; From fe3c3d2ed3ca32a8dccfa0162f0127fcd8c02468 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 11 Jun 2015 18:33:24 -0700 Subject: [PATCH 118/937] also make sure there is a treatment profile --- lib/plugins/boluswizardpreview.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index ac9dd496ee4..2e58dccfdc4 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -74,8 +74,14 @@ function init() { var profile = sbx.data.profile; + if (!profile) { + console.warn('For the BolusWizardPreview plugin to function you need a treatment profile'); + return results; + } + if (!profile.target_high || !profile.target_low) { console.warn('For the BolusWizardPreview plugin to function your treatment profile must hava a both target_high and target_low fields'); + return results; } var iob = results.iob = sbx.properties.iob.iob; From 6f14bc2ecc6bb683ad76fcd54d66870d2cc0b81a Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 11 Jun 2015 21:39:32 -0700 Subject: [PATCH 119/937] include IOB in prediction message if available --- lib/plugins/ar2.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 9588868f0a4..17cfeeafc85 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -50,7 +50,6 @@ function init() { var min = _.max([first, last, avg]); var rangeLabel = ''; - if (max > sbx.thresholds.bg_target_top) { rangeLabel = 'HIGH'; if (!pushoverSound) pushoverSound = 'climb' @@ -62,7 +61,17 @@ function init() { } var title = [levelLabel, rangeLabel, 'predicted'].join(' ').replace(' ', ' '); - var message = [predicted[2], sbx.unitsLabel, 'in 15mins'].join(' '); + var lines = [ + ['Now', sbx.data.lastSGV(), sbx.unitsLabel].join(' ') + , ['15m', predicted[2], sbx.unitsLabel].join(' ') + ]; + + var iob = sbx.properties.iob && sbx.properties.iob.display; + if (iob) { + lines.unshift(['\nIOB:', iob, 'U'].join(' ')); + } + + var message = lines.join('\n'); forecast.predicted = _.map(forecast.predicted, function(p) { return sbx.scaleBg(p.y) } ).join(', '); From 8a16d147092651177ddfa822811d31a222ad6833 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 12 Jun 2015 14:32:28 -0700 Subject: [PATCH 120/937] factored MBG/Calibratoin and Treatment notificaions into a new plugin; auto snooze for 10m after any treatment --- env.js | 5 +- lib/bootevent.js | 1 - lib/notifications.js | 22 ++- lib/plugins/boluswizardpreview.js | 10 +- lib/plugins/cob.js | 11 ++ lib/plugins/index.js | 1 + lib/plugins/treatmentnotify.js | 86 ++++++++++ lib/pushnotify.js | 254 ++++++------------------------ lib/sandbox.js | 14 +- 9 files changed, 182 insertions(+), 222 deletions(-) create mode 100644 lib/plugins/treatmentnotify.js diff --git a/env.js b/env.js index 38eeda38200..3be410ef33c 100644 --- a/env.js +++ b/env.js @@ -178,9 +178,12 @@ function config ( ) { env.pushover_api_token = readENV('PUSHOVER_API_TOKEN'); env.pushover_user_key = readENV('PUSHOVER_USER_KEY') || readENV('PUSHOVER_GROUP_KEY'); if (env.pushover_api_token && env.pushover_user_key) { - env.enable = env.enable + ' pushover'; + env.enable += ' pushover'; + //TODO: after config changes are documented this shouldn't be auto enabled + env.enable += ' treatmentnotify'; } + // TODO: clean up a bit // Some people prefer to use a json configuration file instead. // This allows a provided json config to override environment variables diff --git a/lib/bootevent.js b/lib/bootevent.js index e0ecfc2ee34..e4af413f07c 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -56,7 +56,6 @@ function boot (env) { ctx.plugins.setProperties(sbx); ctx.notifications.initRequests(); ctx.plugins.checkNotifications(sbx); - ctx.pushnotify.process(sbx); ctx.notifications.process(sbx); ctx.bus.emit('data-processed'); }); diff --git a/lib/notifications.js b/lib/notifications.js index ecd5c003cb0..6680b690e29 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -12,8 +12,9 @@ var Alarm = function(label) { // list of alarms with their thresholds var alarms = { - 1 : new Alarm('Warn'), - 2: new Alarm('Urgent') + 0: new Alarm('Info') + , 1: new Alarm('Warn') + , 2: new Alarm('Urgent') }; function init (env, ctx) { @@ -21,6 +22,23 @@ function init (env, ctx) { return notifications; } + notifications.levels = { + URGENT: 2 + , WARN: 1 + , INFO: 0 + }; + + notifications.levels.toString = function levelToString(level) { + switch (level) { + case 2: + return 'Urgent'; + case 1: + return 'Warn'; + case 0: + return 'Info'; + } + }; + //should only be used when auto acking the alarms after going back in range or when an error corrects //setting the silence time to 1ms so the alarm will be retriggered as soon as the condition changes //since this wasn't ack'd by a user action diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 2e58dccfdc4..f60c7f0480e 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -13,7 +13,7 @@ function init() { bwp.label = 'Bolus Wizard Preview'; bwp.pluginType = 'pill-minor'; - bwp.checkNotifications = function checkNotifications(sbx) { + bwp.checkNotifications = function checkNotifications (sbx) { var results = bwp.calc(sbx); if (results.lastSGV < sbx.data.profile.target_high) return; @@ -25,13 +25,13 @@ function init() { if (results.lastSGV > sbx.thresholds.bg_target_top && results.bolusEstimate < snoozeBWP) { sbx.notifications.requestSnooze({ - level: 2 + level: sbx.notifications.levels.URGENT , mills: FIFTEEN_MINS , debug: results }) } else if (results.bolusEstimate > warnBWP) { - var level = results.bolusEstimate > urgentBWP ? 2 : 1; - var levelLabel = results.bolusEstimate > urgentBWP ? 'Urgent' : 'Warning'; + var level = results.bolusEstimate > urgentBWP ? sbx.notifications.levels.URGENT : sbx.notifications.levels.WARN; + var levelLabel = sbx.notifications.levels.toString(level); var message = [levelLabel, results.lastSGV, sbx.unitsLabel].join(' '); sbx.notifications.requestNotify({ level: level @@ -80,7 +80,7 @@ function init() { } if (!profile.target_high || !profile.target_low) { - console.warn('For the BolusWizardPreview plugin to function your treatment profile must hava a both target_high and target_low fields'); + console.warn('For the BolusWizardPreview plugin to function your treatment profile must have both target_high and target_low fields'); return results; } diff --git a/lib/plugins/cob.js b/lib/plugins/cob.js index eab827c483c..7ce5e71619e 100644 --- a/lib/plugins/cob.js +++ b/lib/plugins/cob.js @@ -21,6 +21,17 @@ function init() { }; cob.cobTotal = function cobTotal(treatments, profile, time) { + + if (!profile) { + console.warn('For the COB plugin to function you need a treatment profile'); + return {}; + } + + if (!profile.sens || !profile.carbratio) { + console.warn('For the CPB plugin to function your treatment profile must have both sens and carbratio fields'); + return {}; + } + var liverSensRatio = 1; var sens = profile.sens; var carbratio = profile.carbratio; diff --git a/lib/plugins/index.js b/lib/plugins/index.js index 5242f27f7a2..72953881e72 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -20,6 +20,7 @@ function init() { , require('./iob')() , require('./cob')() , require('./boluswizardpreview')() + , require('./treatmentnotify')() ]); return plugins; }; diff --git a/lib/plugins/treatmentnotify.js b/lib/plugins/treatmentnotify.js new file mode 100644 index 00000000000..ad5829cb041 --- /dev/null +++ b/lib/plugins/treatmentnotify.js @@ -0,0 +1,86 @@ +'use strict'; + +var _ = require('lodash'); + +function init() { + + var TIME_10_MINS_MS = 10 * 60 * 1000; + + function treatmentnotify() { + return treatmentnotify; + } + + treatmentnotify.label = 'Treatment Notifications'; + treatmentnotify.pluginType = 'notification'; + + treatmentnotify.checkNotifications = function checkNotifications (sbx) { + + var now = Date.now(); + + var lastMBG = _.last(sbx.data.mbgs); + var lastTreatment = _.last(sbx.data.treatments); + + //TODO: figure out why date is x here #CleanUpDataModel + var mbgAgo = (lastMBG && lastMBG.x < now) ? now - lastMBG.x : 0; + var mbgCurrent = mbgAgo != 0 && mbgAgo < TIME_10_MINS_MS; + + var lastTreatmentTime = lastTreatment ? new Date(lastTreatment.created_at).getTime() : 0; + var treatmentAgo = (lastTreatmentTime && lastTreatmentTime < now) ? now - lastTreatmentTime : 0; + var treatmentCurrent = treatmentAgo != 0 && treatmentAgo < TIME_10_MINS_MS; + + if (mbgCurrent || treatmentCurrent) { + //first auto snooze any alarms + sbx.notifications.requestSnooze({ + level: sbx.notifications.levels.URGENT + , mills: TIME_10_MINS_MS + //, debug: results + }); + + //and add some info notifications + //the notification providers (push, websockets, etc) are responsible to not sending the same notifications repeatedly + if (mbgCurrent) requestMBGNotify(lastMBG, sbx); + if (treatmentCurrent) requestTreatmentNotify(lastTreatment, sbx); + } + }; + + function requestMBGNotify (lastMBG, sbx) { + sbx.notifications.requestNotify({ + level: sbx.notifications.levels.INFO + , title: 'Calibration' + //TODO: figure out why mbg is y here #CleanUpDataModel + , message: '\nMeter BG: ' + sbx.scaleBg(lastMBG.y) + ' ' + sbx.unitsLabel + , pushoverSound: 'magic' + //, debug: results + }); + } + + function requestTreatmentNotify (lastTreatment, sbx) { + var message = (lastTreatment.glucose ? 'BG: ' + lastTreatment.glucose + ' (' + lastTreatment.glucoseType + ')' : '') + + (lastTreatment.carbs ? '\nCarbs: ' + lastTreatment.carbs + 'g' : '') + + + //TODO: find a better way to connect split treatments + //(preBolusCarbs ? '\nCarbs: ' + preBolusCarbs + ' (in ' + treatment.preBolus + ' minutes)' : '')+ + + (lastTreatment.insulin ? '\nInsulin: ' + sbx.roundInsulinForDisplayFormat(lastTreatment.insulin) + 'U' : '')+ + (lastTreatment.enteredBy ? '\nEntered By: ' + lastTreatment.enteredBy : '') + + + //TODO: find a better way to store timeAdjustment + //(timeAdjustment ? '\nEvent Time: ' + timeAdjustment : '') + + + (lastTreatment.notes ? '\nNotes: ' + lastTreatment.notes : ''); + + + sbx.notifications.requestNotify({ + level: sbx.notifications.levels.INFO + , title: lastTreatment.eventType + , message: message +// , debug: results + }); + + } + + return treatmentnotify(); + +} + +module.exports = init; \ No newline at end of file diff --git a/lib/pushnotify.js b/lib/pushnotify.js index ac850b6072d..c63cc9d1b48 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -1,6 +1,7 @@ 'use strict'; var _ = require('lodash'); +var crypto = require('crypto'); var units = require('./units')(); function init(env, ctx) { @@ -8,31 +9,21 @@ function init(env, ctx) { var pushover = require('./pushover')(env); // declare local constants for time differences - var TIME_10_MINS_MS = 10 * 60 * 1000, - TIME_15_MINS_S = 15 * 60, - TIME_15_MINS_MS = TIME_15_MINS_S * 1000, - TIME_30_MINS_MS = TIME_15_MINS_MS * 2; - - var lastSentMBG = null; - var lastSentTreatment = null; - - //simple SGV Alert throttling - //TODO: single snooze for websockets and push (when we add push callbacks) - var lastAlert = 0; - var lastSGVDate = 0; - + var TIME_5_MINS_MS = 5 * 60 * 1000 + ,TIME_15_MINS_S = 15 * 60 + , TIME_15_MINS_MS = TIME_15_MINS_S * 1000 + ; function pushnotify() { return pushnotify; } - var lastEmit = {}; + var recentlySent = {}; pushnotify.emitNotification = function emitNotification (notify) { if (!pushover) return; - //make this smarter, for now send alerts every 15mins till cleared - if (lastEmit[notify.level] && lastEmit[notify.level] > Date.now() - TIME_15_MINS_MS) return; + if (isDuplicate(notify)) return; var msg = { expire: TIME_15_MINS_S, @@ -44,216 +35,67 @@ function init(env, ctx) { retry: 30 }; - pushover.send( msg, function( err, result ) { - console.info('pushnotify.emitNotification', err, result); + pushover.send(msg, function(err, result) { + updateRecentlySent(err, notify); + if (err) { + console.error('unable to send pushover notification', err); + } else { + console.info('sent pushover notification: ', msg, 'result: ', result); + } }); - lastEmit[notify.level] = Date.now(); - }; - - pushnotify.process = function process(sbx) { - if (!pushover) return; - sendMBG(sbx); - sendTreatment(sbx) }; - function sendTreatment (sbx) { - - var lastTreatment = _.last(ctx.data.treatments); - if (!lastTreatment) return; - - var ago = new Date().getTime() - new Date(lastTreatment.created_at).getTime(); - - if (JSON.stringify(lastTreatment) == JSON.stringify(lastSentTreatment) || ago > TIME_10_MINS_MS) { - return; - } - - //since we don't know the time zone on the device viewing the push message - //we can only show the amount of adjustment - //TODO: need to store time extra info to figure out if treatment was added in past/future - //var timeAdjustment = calcTimeAdjustment(eventTime); - - var text = (lastTreatment.glucose ? 'BG: ' + lastTreatment.glucose + ' (' + lastTreatment.glucoseType + ')' : '') + - (lastTreatment.carbs ? '\nCarbs: ' + lastTreatment.carbs : '') + - - //TODO: find a better way to connect split treatments - //(preBolusCarbs ? '\nCarbs: ' + preBolusCarbs + ' (in ' + treatment.preBolus + ' minutes)' : '')+ + function isDuplicate(notify) { + var byLevel = sentByLevel(notify); + var hash = notifyToHash(notify); - (lastTreatment.insulin ? '\nInsulin: ' + lastTreatment.insulin : '')+ - (lastTreatment.enteredBy ? '\nEntered By: ' + lastTreatment.enteredBy : '') + - - //TODO: find a better way to store timeAdjustment - //(timeAdjustment ? '\nEvent Time: ' + timeAdjustment : '') + - - (lastTreatment.notes ? '\nNotes: ' + lastTreatment.notes : ''); - - var msg = { - expire: TIME_15_MINS_S, - message: text, - title: lastTreatment.eventType, - sound: 'gamelan', - timestamp: new Date( ), - priority: (lastTreatment.eventType == 'Note' ? -1 : 0), - retry: 30 - }; - - pushover.send( msg, function( err, result ) { - console.log(result); + var found = _.find(byLevel, function findByHash(sent) { + return sent.hash = hash; }); - lastSentTreatment = lastTreatment; - - } - - - function sendMBG(sbx) { - - var lastMBG = _.last(ctx.data.mbgs); - if (!lastMBG) return; - - //TODO: figure out why date is x here, clean up data model - var ago = new Date().getTime() - lastMBG.x; - - if (JSON.stringify(lastMBG) == JSON.stringify(lastSentMBG) || ago > TIME_10_MINS_MS) { - return; + if (found) { + console.info('Found duplicate notification that was sent recently using hash: ', hash, 'of notify: ', notify); + return true; + } else { + console.info('No duplicate notification found, using hash: ', hash, 'of notify: ', notify); + return false; } - var mbg = lastMBG.y; - if (env.DISPLAY_UNITS == 'mmol') { - mbg = units.mgdlToMMOL(mbg); - } - var msg = { - expire: TIME_15_MINS_S, - message: '\nMeter BG: ' + mbg + ' ' + sbx.unitsLabel, - title: 'Calibration', - sound: 'magic', - timestamp: new Date(lastMBG.date), - priority: 0, - retry: 30 - }; + } - pushover.send(msg, function (err, result) { - console.log(result); + function updateRecentlySent(err, notify) { + sentByLevel(notify).push({ + time: Date.now() + , err: err + , hash: notifyToHash(notify) }); - - lastSentMBG = lastMBG; - } - //TODO: move some of this to simplealarms - function old(entry, ctx) { - - if (!entry.sgv || entry.type != 'sgv') { - return; - } - - var now = new Date().getTime(), - offset = new Date().getTime() - entry.date; - - if (offset > TIME_10_MINS_MS || entry.date == lastSGVDate) { - console.info('No SVG Pushover, offset: ' + offset + ' too big, doc.date: ' + entry.date + ', now: ' + new Date().getTime() + ', lastSGVDate: ' + lastSGVDate); - return; + function sentByLevel(notify) { + var byLevel = recentlySent[notify.level]; + if (!byLevel) { + byLevel = []; } - // initialize message data - var sinceLastAlert = now - lastAlert, - title = 'CGM Alert', - priority = 0, - sound = null, - readingtime = entry.date, - readago = now - readingtime; + var now = Date.now(); - console.info('now: ' + now); - console.info('doc.sgv: ' + entry.sgv); - console.info('doc.direction: ' + entry.direction); - console.info('doc.date: ' + entry.date); - console.info('readingtime: ' + readingtime); - console.info('readago: ' + readago); - - // set vibration pattern; alert value; 0 nothing, 1 normal, 2 low, 3 high - if (entry.sgv < 39) { - if (sinceLastAlert > TIME_30_MINS_MS) { - title = 'CGM Error'; - priority = 1; - sound = 'persistent'; - } - } else if (entry.sgv < env.thresholds.bg_low && sinceLastAlert > TIME_15_MINS) { - title = 'Urgent Low'; - priority = 2; - sound = 'persistent'; - } else if (entry.sgv < env.thresholds.bg_target_bottom && sinceLastAlert > TIME_15_MINS) { - title = 'Low'; - priority = 1; - sound = 'falling'; - } else if (entry.sgv < 120 && entry.direction == 'DoubleDown') { - title = 'Double Down'; - priority = 1; - sound = 'falling'; - } else if (entry.sgv == 100 && entry.direction == 'Flat' && sinceLastAlert > TIME_15_MINS) { //Perfect Score - a good time to take a picture :) - title = 'Perfect'; - priority = 0; - sound = 'cashregister'; - } else if (entry.sgv > 120 && entry.direction == 'DoubleUp' && sinceLastAlert > TIME_15_MINS) { - title = 'Double Up'; - priority = 1; - sound = 'climb'; - } else if (entry.sgv > env.thresholds.bg_target_top && sinceLastAlert > TIME_30_MINS_MS) { - title = 'High'; - priority = 1; - sound = 'climb'; - } else if (entry.sgv > env.thresholds.bg_high && sinceLastAlert > TIME_30_MINS_MS) { - title = 'Urgent High'; - priority = 1; - sound = 'persistent'; - } - - if (sound != null) { - lastAlert = now; - - var msg = { - expire: 14400, // 4 hours - message: 'BG NOW: ' + entry.sgv, - title: title, - sound: sound, - timestamp: new Date(entry.date), - priority: priority, - retry: 30 - }; - - pushover.send(msg, function (err, result) { - console.log(result); - }); - } + byLevel = _.filter(byLevel, function isRecent(sent) { + //consider errors stale sooner than successful sends + var staleAfter = sent.err ? TIME_5_MINS_MS : TIME_15_MINS_MS; + return now - sent.time < staleAfter; + }); + recentlySent[notify.level] = byLevel; - lastSGVDate = entry.date; + return byLevel; } - function calcTimeAdjustment(eventTime) { - - if (!eventTime) return null; - - var now = (new Date()).getTime(), - other = eventTime.getTime(), - past = other < now, - offset = Math.abs(now - other); - - var MINUTE = 60 * 1000, - HOUR = 3600 * 1000; - - var parts = {}; - - if (offset <= MINUTE) - return 'now'; - else if (offset < (HOUR * 2)) - parts = { value: Math.round(Math.abs(offset / MINUTE)), label: 'mins' }; - else - parts = { value: Math.round(Math.abs(offset / HOUR)), label: 'hrs' }; - - if (past) - return parts.value + ' ' + parts.label + ' ago'; - else - return 'in ' + parts.value + ' ' + parts.label; + function notifyToHash(notify) { + var hash = crypto.createHash('sha1'); + var info = JSON.stringify(_.pick(notify, ['title', 'message'])); + hash.update(info); + return hash.digest('hex'); } return pushnotify(); diff --git a/lib/sandbox.js b/lib/sandbox.js index b5cb8acf067..2c4c6bb9127 100644 --- a/lib/sandbox.js +++ b/lib/sandbox.js @@ -7,7 +7,7 @@ var utils = require('./utils'); function init ( ) { var sbx = {}; - function init() { + function reset () { sbx.properties = []; sbx.scaleBg = scaleBg; sbx.roundInsulinForDisplayFormat = roundInsulinForDisplayFormat; @@ -31,8 +31,8 @@ function init ( ) { * @param ctx - created from bootevent * @returns {{sbx}} */ - sbx.serverInit = function serverInit(env, ctx) { - init(); + sbx.serverInit = function serverInit (env, ctx) { + reset(); sbx.time = Date.now(); sbx.units = env.DISPLAY_UNITS; @@ -42,7 +42,7 @@ function init ( ) { sbx.data = ctx.data.clone(); //don't expose all of notifications, ctx.notifications will decide what to do after all plugins chime in - sbx.notifications = _.pick(ctx.notifications, ['requestNotify', 'requestSnooze', 'requestClear']); + sbx.notifications = _.pick(ctx.notifications, ['levels', 'requestNotify', 'requestSnooze', 'requestClear']); //Plugins will expect the right profile based on time sbx.data.profile = _.first(ctx.data.profiles); @@ -65,8 +65,8 @@ function init ( ) { * @param data - svgs, treatments, profile, etc * @returns {{sbx}} */ - sbx.clientInit = function clientInit(app, clientSettings, time, pluginBase, data) { - init(); + sbx.clientInit = function clientInit (app, clientSettings, time, pluginBase, data) { + reset(); sbx.units = clientSettings.units; sbx.defaults = clientSettings; //TODO: strip out extra stuff @@ -88,7 +88,7 @@ function init ( ) { * @param name * @param setter */ - sbx.offerProperty = function offerProperty(name, setter) { + sbx.offerProperty = function offerProperty (name, setter) { if (!sbx.properties.hasOwnProperty(name)) { var value = setter(); if (value) { From a9e5e10341d4859444ad28137956db46bbd324c6 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 12 Jun 2015 16:16:35 -0700 Subject: [PATCH 121/937] make sure infos are always sent, only alarms (warn/urgent) get snoozed --- lib/notifications.js | 30 ++++++++++++++++++++++-------- lib/plugins/treatmentnotify.js | 24 +++++++++++++++--------- lib/websocket.js | 8 ++++++-- 3 files changed, 43 insertions(+), 19 deletions(-) diff --git a/lib/notifications.js b/lib/notifications.js index 6680b690e29..0dce5a503fb 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -22,12 +22,14 @@ function init (env, ctx) { return notifications; } - notifications.levels = { + var levels = { URGENT: 2 , WARN: 1 , INFO: 0 }; + notifications.levels = levels; + notifications.levels.toString = function levelToString(level) { switch (level) { case 2: @@ -80,8 +82,16 @@ function init (env, ctx) { notifications.initRequests(); - function findHighestNotify ( ) { - return _.find(requests.notifies, {level: 'urgent'}) || _.find(requests.notifies, {level: 'warn'}) || _.first(requests.notifies); + /** + * Find the first URGENT or first WARN + * @returns a notification or undefined + */ + function findHighestAlarm ( ) { + return _.find(requests.notifies, {level: levels.URGENT}) || _.find(requests.notifies, {level: levels.WARN}); + } + + function findInfos ( ) { + return _.filter(requests.notifies, {level: levels.INFO}); } function findLongestSnooze ( ) { @@ -103,23 +113,27 @@ function init (env, ctx) { }; notifications.process = function process ( ) { - var highestNotify = findHighestNotify(); + var highestAlarm = findHighestAlarm(); var longestSnooze = findLongestSnooze(); if (longestSnooze) { - if (highestNotify && highestNotify.level > longestSnooze.level) { - console.log('notifications.process, ignoring snooze: ', longestSnooze, ' because notify: ', highestNotify); + if (highestAlarm && highestAlarm.level > longestSnooze.level) { + console.log('notifications.process, ignoring snooze: ', longestSnooze, ' because notify: ', highestAlarm); } else { console.log('notifications.process, snoozing because: ', longestSnooze); notifications.ack(longestSnooze.level, longestSnooze.mills) } } - if (highestNotify) { - emitNotification(highestNotify); + if (highestAlarm) { + emitNotification(highestAlarm); } else { autoAckAlarms(); } + + findInfos().forEach(function eachInfo (info) { + emitNotification(info); + }) }; notifications.ack = function ack (level, time) { diff --git a/lib/plugins/treatmentnotify.js b/lib/plugins/treatmentnotify.js index ad5829cb041..0cfd79fd86b 100644 --- a/lib/plugins/treatmentnotify.js +++ b/lib/plugins/treatmentnotify.js @@ -21,7 +21,8 @@ function init() { var lastTreatment = _.last(sbx.data.treatments); //TODO: figure out why date is x here #CleanUpDataModel - var mbgAgo = (lastMBG && lastMBG.x < now) ? now - lastMBG.x : 0; + var lastMBGTime = lastMBG ? lastMBG.x : 0; + var mbgAgo = (lastMBGTime && lastMBGTime < now) ? now - lastMBGTime : 0; var mbgCurrent = mbgAgo != 0 && mbgAgo < TIME_10_MINS_MS; var lastTreatmentTime = lastTreatment ? new Date(lastTreatment.created_at).getTime() : 0; @@ -29,13 +30,7 @@ function init() { var treatmentCurrent = treatmentAgo != 0 && treatmentAgo < TIME_10_MINS_MS; if (mbgCurrent || treatmentCurrent) { - //first auto snooze any alarms - sbx.notifications.requestSnooze({ - level: sbx.notifications.levels.URGENT - , mills: TIME_10_MINS_MS - //, debug: results - }); - + autoSnoozeAlarms(lastMBGTime, lastTreatmentTime); //and add some info notifications //the notification providers (push, websockets, etc) are responsible to not sending the same notifications repeatedly if (mbgCurrent) requestMBGNotify(lastMBG, sbx); @@ -43,10 +38,21 @@ function init() { } }; + /** + * auto snooze any alarms by max(treatment, mbg) time + 10m + */ + function autoSnoozeAlarms(lastMBGTime, lastTreatmentTime) { + sbx.notifications.requestSnooze({ + level: sbx.notifications.levels.URGENT + , mills: Math.max(lastMBGTime, lastTreatmentTime) + TIME_10_MINS_MS + //, debug: results + }); + } + function requestMBGNotify (lastMBG, sbx) { sbx.notifications.requestNotify({ level: sbx.notifications.levels.INFO - , title: 'Calibration' + , title: 'Calibration' //assume all MGBs are calibrations for now //TODO: figure out why mbg is y here #CleanUpDataModel , message: '\nMeter BG: ' + sbx.scaleBg(lastMBG.y) + ' ' + sbx.unitsLabel , pushoverSound: 'magic' diff --git a/lib/websocket.js b/lib/websocket.js index a2028656755..ef6b0c83896 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -13,6 +13,8 @@ function init (env, ctx, server) { var watchers = 0; var lastData = {}; + var levels = ctx.notifications.levels; + function start ( ) { io = require('socket.io')({ 'transports': ['xhr-polling'], 'log level': 0 @@ -66,10 +68,12 @@ function init (env, ctx, server) { if (notify.clear) { io.emit('clear_alarm', true); console.info('emitted clear_alarm to all clients'); - } else if (notify.level == 1) { + } else if (notify.level == levels.INFO) { + //client doesn't know how to display info notifications yet, ignoring + } else if (notify.level == levels.WARN) { io.emit('alarm', notify); console.info('emitted alarm to all clients'); - } else if (notify.level == 2) { + } else if (notify.level == levels.URGENT) { io.emit('urgent_alarm', notify); console.info('emitted urgent_alarm to all clients'); } From 1731fdc18316b087323f4f9690b94e8345995d70 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 12 Jun 2015 17:38:14 -0700 Subject: [PATCH 122/937] init tests for notifications --- lib/notifications.js | 18 ++++++------- tests/notifications.test.js | 51 +++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 9 deletions(-) create mode 100644 tests/notifications.test.js diff --git a/lib/notifications.js b/lib/notifications.js index 0dce5a503fb..f4bfce3b55f 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -86,15 +86,15 @@ function init (env, ctx) { * Find the first URGENT or first WARN * @returns a notification or undefined */ - function findHighestAlarm ( ) { + notifications.findHighestAlarm = function findHighestAlarm ( ) { return _.find(requests.notifies, {level: levels.URGENT}) || _.find(requests.notifies, {level: levels.WARN}); - } + }; - function findInfos ( ) { + notifications.findInfos = function findInfos ( ) { return _.filter(requests.notifies, {level: levels.INFO}); - } + }; - function findLongestSnooze ( ) { + notifications.findLongestSnooze = function findLongestSnooze ( ) { if (_.isEmpty(requests.snoozes)) return null; var groups = _.groupBy(requests.snoozes, 'level'); @@ -102,7 +102,7 @@ function init (env, ctx) { var longest = firstKey && _.last(groups[firstKey].sort()); return longest; - } + }; notifications.requestNotify = function requestNotify (notify) { requests.notifies.push(notify); @@ -113,8 +113,8 @@ function init (env, ctx) { }; notifications.process = function process ( ) { - var highestAlarm = findHighestAlarm(); - var longestSnooze = findLongestSnooze(); + var highestAlarm = notifications.findHighestAlarm(); + var longestSnooze = notifications.findLongestSnooze(); if (longestSnooze) { if (highestAlarm && highestAlarm.level > longestSnooze.level) { @@ -131,7 +131,7 @@ function init (env, ctx) { autoAckAlarms(); } - findInfos().forEach(function eachInfo (info) { + notifications.findInfos().forEach(function eachInfo (info) { emitNotification(info); }) }; diff --git a/tests/notifications.test.js b/tests/notifications.test.js new file mode 100644 index 00000000000..1ed633f968c --- /dev/null +++ b/tests/notifications.test.js @@ -0,0 +1,51 @@ +var should = require('should'); +var Stream = require('stream'); + +describe('units', function ( ) { + + var env = {}; + var ctx = { + bus: new Stream + , data: { + lastUpdated: Date.now() + } + }; + + var notifications = require('../lib/notifications')(env, ctx); + + it('initAndReInit', function (done) { + notifications.initRequests(); + var notify = { + title: 'test' + , message: 'testing' + , level: notifications.levels.WARN + }; + notifications.requestNotify(notify); + notifications.findHighestAlarm().should.equal(notify); + notifications.initRequests(); + should.not.exist(notifications.findHighestAlarm()); + done(); + }); + + + it('emitANotification', function (done) { + + //if notification doesn't get called test will time out + ctx.bus.on('notification', function callback ( ) { + done(); + }); + + notifications.initRequests(); + var notify = { + title: 'test' + , message: 'testing' + , level: notifications.levels.WARN + }; + notifications.requestNotify(notify); + notifications.findHighestAlarm().should.equal(notify); + notifications.process(); + + }); + + +}); From 0097176ccb1bf1b799e67789232555296e20d4e0 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 12 Jun 2015 18:05:02 -0700 Subject: [PATCH 123/937] forgot to pass the sbx --- lib/plugins/treatmentnotify.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/plugins/treatmentnotify.js b/lib/plugins/treatmentnotify.js index 0cfd79fd86b..56cd819d4c3 100644 --- a/lib/plugins/treatmentnotify.js +++ b/lib/plugins/treatmentnotify.js @@ -30,7 +30,7 @@ function init() { var treatmentCurrent = treatmentAgo != 0 && treatmentAgo < TIME_10_MINS_MS; if (mbgCurrent || treatmentCurrent) { - autoSnoozeAlarms(lastMBGTime, lastTreatmentTime); + autoSnoozeAlarms(lastMBGTime, lastTreatmentTime, sbx); //and add some info notifications //the notification providers (push, websockets, etc) are responsible to not sending the same notifications repeatedly if (mbgCurrent) requestMBGNotify(lastMBG, sbx); @@ -41,7 +41,7 @@ function init() { /** * auto snooze any alarms by max(treatment, mbg) time + 10m */ - function autoSnoozeAlarms(lastMBGTime, lastTreatmentTime) { + function autoSnoozeAlarms(lastMBGTime, lastTreatmentTime, sbx) { sbx.notifications.requestSnooze({ level: sbx.notifications.levels.URGENT , mills: Math.max(lastMBGTime, lastTreatmentTime) + TIME_10_MINS_MS From 3fe4a4bf2c953a2cfd4e719f7a86d6df4ada30eb Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 12 Jun 2015 18:27:39 -0700 Subject: [PATCH 124/937] more notification tests (and fixes for the bugs they found) --- lib/notifications.js | 34 ++++++++------- tests/notifications.test.js | 83 ++++++++++++++++++++++++++++++------- 2 files changed, 86 insertions(+), 31 deletions(-) diff --git a/lib/notifications.js b/lib/notifications.js index f4bfce3b55f..1de7c5c9a40 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -94,14 +94,22 @@ function init (env, ctx) { return _.filter(requests.notifies, {level: levels.INFO}); }; - notifications.findLongestSnooze = function findLongestSnooze ( ) { - if (_.isEmpty(requests.snoozes)) return null; + notifications.snoozedBy = function snoozedBy (notify) { + if (_.isEmpty(requests.snoozes)) return false; - var groups = _.groupBy(requests.snoozes, 'level'); - var firstKey = _.first(_.keys(groups)); - var longest = firstKey && _.last(groups[firstKey].sort()); + var byLevel = _.filter(requests.snoozes, function checkSnooze (snooze) { + return snooze.level >= notify.level; + }); + var sorted = _.sortBy(byLevel, 'mills'); + var longest = _.last(sorted); - return longest; + var alarm = alarms[notify.level]; + + if (longest && Date.now() + longest.mills > alarm.lastAckTime + alarm.silenceTime) { + return longest; + } else { + return null; + } }; notifications.requestNotify = function requestNotify (notify) { @@ -114,15 +122,11 @@ function init (env, ctx) { notifications.process = function process ( ) { var highestAlarm = notifications.findHighestAlarm(); - var longestSnooze = notifications.findLongestSnooze(); - - if (longestSnooze) { - if (highestAlarm && highestAlarm.level > longestSnooze.level) { - console.log('notifications.process, ignoring snooze: ', longestSnooze, ' because notify: ', highestAlarm); - } else { - console.log('notifications.process, snoozing because: ', longestSnooze); - notifications.ack(longestSnooze.level, longestSnooze.mills) - } + + var snoozedBy = notifications.snoozedBy(highestAlarm); + if (snoozedBy) { + console.log('snoozing: ', highestAlarm, ' with: ', snoozedBy); + notifications.ack(snoozedBy.level, snoozedBy.mills) } if (highestAlarm) { diff --git a/tests/notifications.test.js b/tests/notifications.test.js index 1ed633f968c..ba65ed8ef51 100644 --- a/tests/notifications.test.js +++ b/tests/notifications.test.js @@ -1,7 +1,7 @@ var should = require('should'); var Stream = require('stream'); -describe('units', function ( ) { +describe('notifications', function ( ) { var env = {}; var ctx = { @@ -13,15 +13,37 @@ describe('units', function ( ) { var notifications = require('../lib/notifications')(env, ctx); + var exampleWarn = { + title: 'test' + , message: 'testing' + , level: notifications.levels.WARN + }; + + var exampleUrgent = { + title: 'test' + , message: 'testing' + , level: notifications.levels.URGENT + }; + + var exampleSnooze = { + level: notifications.levels.WARN + , mills: 1000 + }; + + var exampleSnoozeNone = { + level: notifications.levels.WARN + , mills: -1000 + }; + + var exampleSnoozeUrgent = { + level: notifications.levels.URGENT + , mills: 1000 + }; + it('initAndReInit', function (done) { notifications.initRequests(); - var notify = { - title: 'test' - , message: 'testing' - , level: notifications.levels.WARN - }; - notifications.requestNotify(notify); - notifications.findHighestAlarm().should.equal(notify); + notifications.requestNotify(exampleWarn); + notifications.findHighestAlarm().should.equal(exampleWarn); notifications.initRequests(); should.not.exist(notifications.findHighestAlarm()); done(); @@ -29,23 +51,52 @@ describe('units', function ( ) { it('emitANotification', function (done) { - //if notification doesn't get called test will time out ctx.bus.on('notification', function callback ( ) { done(); }); notifications.initRequests(); - var notify = { - title: 'test' - , message: 'testing' - , level: notifications.levels.WARN - }; - notifications.requestNotify(notify); - notifications.findHighestAlarm().should.equal(notify); + notifications.requestNotify(exampleWarn); + notifications.findHighestAlarm().should.equal(exampleWarn); notifications.process(); + }); + it('Can be snoozed', function (done) { + notifications.initRequests(); + notifications.requestNotify(exampleWarn); + notifications.requestSnooze(exampleSnooze); + notifications.snoozedBy(exampleWarn).should.equal(exampleSnooze); + + done(); }); + it('Can be snoozed by last snooze', function (done) { + notifications.initRequests(); + notifications.requestNotify(exampleWarn); + notifications.requestSnooze(exampleSnoozeNone); + notifications.requestSnooze(exampleSnooze); + notifications.snoozedBy(exampleWarn).should.equal(exampleSnooze); + + done(); + }); + + it('Urgent alarms can\'t be snoozed by warn', function (done) { + notifications.initRequests(); + notifications.requestNotify(exampleUrgent); + notifications.requestSnooze(exampleSnooze); + should.not.exist(notifications.snoozedBy(exampleUrgent)); + + done(); + }); + + it('Warnings can be snoozed by urgent', function (done) { + notifications.initRequests(); + notifications.requestNotify(exampleWarn); + notifications.requestSnooze(exampleSnoozeUrgent); + notifications.snoozedBy(exampleWarn).should.equal(exampleSnoozeUrgent); + + done(); + }); }); From 10cbae52ff6b1e1eaea4b6743839a7937659144a Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 13 Jun 2015 09:10:26 -0700 Subject: [PATCH 125/937] don't allow requests for incomplete notifies or snoozes --- lib/notifications.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/notifications.js b/lib/notifications.js index 1de7c5c9a40..5a37fe8fb74 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -113,10 +113,18 @@ function init (env, ctx) { }; notifications.requestNotify = function requestNotify (notify) { + if (!notify.level || !notify.title || !notify.message) { + console.error(new Error('Unable to request notification, since the notify isn\'t complete: ' + JSON.stringify(notify))); + return; + } requests.notifies.push(notify); }; notifications.requestSnooze = function requestSnooze (snooze) { + if (!snooze.level || !snooze.mills) { + console.error(new Error('Unable to request snooze, since the snooze isn\'t complete: ' + JSON.stringify(notify))); + return; + } requests.snoozes.push(snooze); }; From f90cf78cae4c68c9a014e3d60b64f8c5ce2d9bd9 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 13 Jun 2015 09:13:31 -0700 Subject: [PATCH 126/937] when trying to find the position on the timeline for a treatment, don't consider sgvs < 40 --- static/js/client.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/js/client.js b/static/js/client.js index 97b950ed57b..4f7e7fdcf5d 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1210,7 +1210,7 @@ function nsArrayDiff(oldArray, newArray) { function calcBGByTime(time) { var withBGs = _.filter(data, function(d) { - return d.y && d.type == 'sgv'; + return d.y > 39 && d.type == 'sgv'; }); var beforeTreatment = _.findLast(withBGs, function (d) { From 7d71341c8caaadb9d53bac15117dc0f491e8c0ac Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 13 Jun 2015 09:26:51 -0700 Subject: [PATCH 127/937] need to make sure there is an alarm, before checking if it is snoozed --- lib/notifications.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/notifications.js b/lib/notifications.js index 5a37fe8fb74..48b0f5fd159 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -131,13 +131,13 @@ function init (env, ctx) { notifications.process = function process ( ) { var highestAlarm = notifications.findHighestAlarm(); - var snoozedBy = notifications.snoozedBy(highestAlarm); - if (snoozedBy) { - console.log('snoozing: ', highestAlarm, ' with: ', snoozedBy); - notifications.ack(snoozedBy.level, snoozedBy.mills) - } - if (highestAlarm) { + var snoozedBy = notifications.snoozedBy(highestAlarm); + if (snoozedBy) { + console.log('snoozing: ', highestAlarm, ' with: ', snoozedBy); + notifications.ack(snoozedBy.level, snoozedBy.mills) + } + emitNotification(highestAlarm); } else { autoAckAlarms(); From 5f611f2ccdf647aa8dc31c61d46c0fc2517f260c Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 13 Jun 2015 10:33:45 -0700 Subject: [PATCH 128/937] tests for simplealarms plugin --- tests/simplealarms.test.js | 69 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 tests/simplealarms.test.js diff --git a/tests/simplealarms.test.js b/tests/simplealarms.test.js new file mode 100644 index 00000000000..c4f47384181 --- /dev/null +++ b/tests/simplealarms.test.js @@ -0,0 +1,69 @@ +var should = require('should'); + +describe('simplealarms', function ( ) { + + var simplealarms = require('../lib/plugins/simplealarms')(); + + var env = require('../env')(); + var ctx = {}; + ctx.data = require('../lib/data')(env, ctx); + ctx.notifications = require('../lib/notifications')(env, ctx); + + + it('Not trigger an alarm when in range', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{sgv: 100}]; + + var sbx = require('../lib/sandbox')().serverInit(env, ctx); + simplealarms.checkNotifications(sbx); + should.not.exist(ctx.notifications.findHighestAlarm()); + + done(); + }); + + it('should trigger a warning when above target', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{sgv: 181}]; + + var sbx = require('../lib/sandbox')().serverInit(env, ctx); + simplealarms.checkNotifications(sbx); + ctx.notifications.findHighestAlarm().level.should.equal(ctx.notifications.levels.WARN); + + done(); + }); + + it('should trigger a urgent alarm when really high', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{sgv: 400}]; + + var sbx = require('../lib/sandbox')().serverInit(env, ctx); + simplealarms.checkNotifications(sbx); + ctx.notifications.findHighestAlarm().level.should.equal(ctx.notifications.levels.URGENT); + + done(); + }); + + it('should trigger a warning when below target', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{sgv: 70}]; + + var sbx = require('../lib/sandbox')().serverInit(env, ctx); + simplealarms.checkNotifications(sbx); + ctx.notifications.findHighestAlarm().level.should.equal(ctx.notifications.levels.WARN); + + done(); + }); + + it('should trigger a urgent alarm when really low', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{sgv: 40}]; + + var sbx = require('../lib/sandbox')().serverInit(env, ctx); + simplealarms.checkNotifications(sbx); + ctx.notifications.findHighestAlarm().level.should.equal(ctx.notifications.levels.URGENT); + + done(); + }); + + +}); \ No newline at end of file From a2ac5fa61744853db2b5bd9039c1981a06efc06b Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 13 Jun 2015 11:07:32 -0700 Subject: [PATCH 129/937] just use the snooze time, don't add it to the last treatment time --- lib/plugins/treatmentnotify.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/plugins/treatmentnotify.js b/lib/plugins/treatmentnotify.js index 56cd819d4c3..5e54e79a564 100644 --- a/lib/plugins/treatmentnotify.js +++ b/lib/plugins/treatmentnotify.js @@ -30,7 +30,7 @@ function init() { var treatmentCurrent = treatmentAgo != 0 && treatmentAgo < TIME_10_MINS_MS; if (mbgCurrent || treatmentCurrent) { - autoSnoozeAlarms(lastMBGTime, lastTreatmentTime, sbx); + autoSnoozeAlarms(sbx); //and add some info notifications //the notification providers (push, websockets, etc) are responsible to not sending the same notifications repeatedly if (mbgCurrent) requestMBGNotify(lastMBG, sbx); @@ -38,13 +38,10 @@ function init() { } }; - /** - * auto snooze any alarms by max(treatment, mbg) time + 10m - */ - function autoSnoozeAlarms(lastMBGTime, lastTreatmentTime, sbx) { + function autoSnoozeAlarms(sbx) { sbx.notifications.requestSnooze({ level: sbx.notifications.levels.URGENT - , mills: Math.max(lastMBGTime, lastTreatmentTime) + TIME_10_MINS_MS + , mills: TIME_10_MINS_MS //, debug: results }); } From 826254052bf2df75d7dd0220ad6b2a32f420d37d Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 13 Jun 2015 12:09:31 -0700 Subject: [PATCH 130/937] better checking for required info in the BWP plugin --- lib/plugins/boluswizardpreview.js | 47 +++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index f60c7f0480e..fcc4eb34d32 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -13,7 +13,37 @@ function init() { bwp.label = 'Bolus Wizard Preview'; bwp.pluginType = 'pill-minor'; + function hasRequiredInfo (sbx) { + if (!sbx.data.profile) { + console.warn('For the BolusWizardPreview plugin to function you need a treatment profile'); + return false; + } + + if (!sbx.data.profile.target_high || !sbx.data.profile.target_low) { + console.warn('For the BolusWizardPreview plugin to function your treatment profile must have both target_high and target_low fields'); + return false; + } + + if (!sbx.properties.iob) { + console.warn('For the BolusWizardPreview plugin to function the IOB plugin must also be enabled'); + return false; + } + + if (!sbx.data.lastSGV()) { + console.warn('For the BolusWizardPreview plugin to function there needs to be a current SGV'); + return false; + } + + return true; + + } + bwp.checkNotifications = function checkNotifications (sbx) { + + if (!hasRequiredInfo(sbx)) { + return; + } + var results = bwp.calc(sbx); if (results.lastSGV < sbx.data.profile.target_high) return; @@ -45,6 +75,10 @@ function init() { bwp.updateVisualisation = function updateVisualisation (sbx) { + if (!hasRequiredInfo(sbx)) { + return; + } + var results = bwp.calc(sbx); // display text @@ -70,20 +104,11 @@ function init() { results.lastSGV = sgv; - if (sgv == undefined || !sbx.properties.iob) return results; - - var profile = sbx.data.profile; - - if (!profile) { - console.warn('For the BolusWizardPreview plugin to function you need a treatment profile'); - return results; - } - - if (!profile.target_high || !profile.target_low) { - console.warn('For the BolusWizardPreview plugin to function your treatment profile must have both target_high and target_low fields'); + if (!hasRequiredInfo(sbx)) { return results; } + var profile = sbx.data.profile; var iob = results.iob = sbx.properties.iob.iob; results.effect = iob * profile.sens; From b25485cf4f2ed472676dd2fa3387d71ad15086f2 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 13 Jun 2015 12:38:02 -0700 Subject: [PATCH 131/937] added basic tests for AR2, and fixed old off-by-1 but that was requiring 3 points when only 2 are needed --- lib/plugins/ar2.js | 2 +- tests/ar2.test.js | 72 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 tests/ar2.test.js diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 17cfeeafc85..690ade39323 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -97,7 +97,7 @@ function init() { , avgLoss: 0 }; - if (lastIndex > 1) { + if (lastIndex > 0) { // predict using AR model var lastValidReadingTime = sgvs[lastIndex].x; var elapsedMins = (sgvs[lastIndex].x - sgvs[lastIndex - 1].x) / ONE_MINUTE; diff --git a/tests/ar2.test.js b/tests/ar2.test.js new file mode 100644 index 00000000000..a84f8febf66 --- /dev/null +++ b/tests/ar2.test.js @@ -0,0 +1,72 @@ +var should = require('should'); + +describe('ar2', function ( ) { + + var ar2 = require('../lib/plugins/ar2')(); + + var env = require('../env')(); + var ctx = {}; + ctx.data = require('../lib/data')(env, ctx); + ctx.notifications = require('../lib/notifications')(env, ctx); + + var now = Date.now(); + var before = now - (5 * 60 * 1000); + + + it('Not trigger an alarm when in range', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{y: 100, x: before}, {y: 105, x: now}]; + + var sbx = require('../lib/sandbox')().serverInit(env, ctx); + ar2.checkNotifications(sbx); + should.not.exist(ctx.notifications.findHighestAlarm()); + + done(); + }); + + it('should trigger a warning when going above target', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{y: 150, x: before}, {y: 170, x: now}]; + + var sbx = require('../lib/sandbox')().serverInit(env, ctx); + ar2.checkNotifications(sbx); + ctx.notifications.findHighestAlarm().level.should.equal(ctx.notifications.levels.WARN); + + done(); + }); + + it('should trigger a urgent alarm when going high fast', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{y: 140, x: before}, {y: 200, x: now}]; + + var sbx = require('../lib/sandbox')().serverInit(env, ctx); + ar2.checkNotifications(sbx); + ctx.notifications.findHighestAlarm().level.should.equal(ctx.notifications.levels.URGENT); + + done(); + }); + + it('should trigger a warning when below target', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{y: 90, x: before}, {y: 80, x: now}]; + + var sbx = require('../lib/sandbox')().serverInit(env, ctx); + ar2.checkNotifications(sbx); + ctx.notifications.findHighestAlarm().level.should.equal(ctx.notifications.levels.WARN); + + done(); + }); + + it('should trigger a urgent alarm when low fast', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{y: 120, x: before}, {y: 80, x: now}]; + + var sbx = require('../lib/sandbox')().serverInit(env, ctx); + ar2.checkNotifications(sbx); + ctx.notifications.findHighestAlarm().level.should.equal(ctx.notifications.levels.URGENT); + + done(); + }); + + +}); \ No newline at end of file From e19f2e2496d5c14229f63471cd04b664d7a00a2f Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 13 Jun 2015 14:42:06 -0700 Subject: [PATCH 132/937] rename snooze.mills to snooze.lengthMills --- lib/notifications.js | 8 ++++---- lib/plugins/boluswizardpreview.js | 2 +- lib/plugins/treatmentnotify.js | 2 +- tests/notifications.test.js | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/notifications.js b/lib/notifications.js index 48b0f5fd159..d95607543a8 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -105,7 +105,7 @@ function init (env, ctx) { var alarm = alarms[notify.level]; - if (longest && Date.now() + longest.mills > alarm.lastAckTime + alarm.silenceTime) { + if (longest && Date.now() + longest.lengthMills > alarm.lastAckTime + alarm.silenceTime) { return longest; } else { return null; @@ -121,8 +121,8 @@ function init (env, ctx) { }; notifications.requestSnooze = function requestSnooze (snooze) { - if (!snooze.level || !snooze.mills) { - console.error(new Error('Unable to request snooze, since the snooze isn\'t complete: ' + JSON.stringify(notify))); + if (!snooze.level || !snooze.lengthMills) { + console.error(new Error('Unable to request snooze, since the snooze isn\'t complete: ' + JSON.stringify(snooze))); return; } requests.snoozes.push(snooze); @@ -135,7 +135,7 @@ function init (env, ctx) { var snoozedBy = notifications.snoozedBy(highestAlarm); if (snoozedBy) { console.log('snoozing: ', highestAlarm, ' with: ', snoozedBy); - notifications.ack(snoozedBy.level, snoozedBy.mills) + notifications.ack(snoozedBy.level, snoozedBy.lengthMills) } emitNotification(highestAlarm); diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index fcc4eb34d32..20161fa9a06 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -56,7 +56,7 @@ function init() { if (results.lastSGV > sbx.thresholds.bg_target_top && results.bolusEstimate < snoozeBWP) { sbx.notifications.requestSnooze({ level: sbx.notifications.levels.URGENT - , mills: FIFTEEN_MINS + , lengthMills: FIFTEEN_MINS , debug: results }) } else if (results.bolusEstimate > warnBWP) { diff --git a/lib/plugins/treatmentnotify.js b/lib/plugins/treatmentnotify.js index 5e54e79a564..cb8f7d81420 100644 --- a/lib/plugins/treatmentnotify.js +++ b/lib/plugins/treatmentnotify.js @@ -41,7 +41,7 @@ function init() { function autoSnoozeAlarms(sbx) { sbx.notifications.requestSnooze({ level: sbx.notifications.levels.URGENT - , mills: TIME_10_MINS_MS + , lengthMills: TIME_10_MINS_MS //, debug: results }); } diff --git a/tests/notifications.test.js b/tests/notifications.test.js index ba65ed8ef51..3e5e05a9261 100644 --- a/tests/notifications.test.js +++ b/tests/notifications.test.js @@ -27,17 +27,17 @@ describe('notifications', function ( ) { var exampleSnooze = { level: notifications.levels.WARN - , mills: 1000 + , lengthMills: 1000 }; var exampleSnoozeNone = { level: notifications.levels.WARN - , mills: -1000 + , lengthMills: 1 }; var exampleSnoozeUrgent = { level: notifications.levels.URGENT - , mills: 1000 + , lengthMills: 1000 }; it('initAndReInit', function (done) { From 41b14503ae8bfa0ae9323d4501e09fa807367d21 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 13 Jun 2015 19:14:01 -0700 Subject: [PATCH 133/937] try adding codecov.io --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index ec7e4f21041..c7fa740dcbb 100644 --- a/Makefile +++ b/Makefile @@ -33,6 +33,9 @@ report: test -f ${ANALYZED} && \ (npm install coveralls && cat ${ANALYZED} | \ ./node_modules/.bin/coveralls) || echo "NO COVERAGE" + test -f ${ANALYZED} && \ + (npm install codecov.io && cat ${ANALYZED} | \ + ./node_modules/codecov.io/bin/codecov.io.js) || echo "NO COVERAGE" test: ${MONGO_SETTINGS} ${MOCHA} -R tap ${TESTS} From 4aeee25b92143761c4e9dc9091d03da7b4bb41c3 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 13 Jun 2015 19:14:01 -0700 Subject: [PATCH 134/937] try adding codecov.io --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index ec7e4f21041..c7fa740dcbb 100644 --- a/Makefile +++ b/Makefile @@ -33,6 +33,9 @@ report: test -f ${ANALYZED} && \ (npm install coveralls && cat ${ANALYZED} | \ ./node_modules/.bin/coveralls) || echo "NO COVERAGE" + test -f ${ANALYZED} && \ + (npm install codecov.io && cat ${ANALYZED} | \ + ./node_modules/codecov.io/bin/codecov.io.js) || echo "NO COVERAGE" test: ${MONGO_SETTINGS} ${MOCHA} -R tap ${TESTS} From 169a46129d6e71972046df4df416b401d6dec907 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 13 Jun 2015 20:17:35 -0700 Subject: [PATCH 135/937] some BWP plugin tests --- lib/plugins/boluswizardpreview.js | 4 +- tests/boluswizardpreview.test.js | 66 +++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 tests/boluswizardpreview.test.js diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 20161fa9a06..26089ded5f7 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -19,8 +19,8 @@ function init() { return false; } - if (!sbx.data.profile.target_high || !sbx.data.profile.target_low) { - console.warn('For the BolusWizardPreview plugin to function your treatment profile must have both target_high and target_low fields'); + if (!sbx.data.profile.sens || !sbx.data.profile.target_high || !sbx.data.profile.target_low) { + console.warn('For the BolusWizardPreview plugin to function your treatment profile must have both sens, target_high, and target_low fields'); return false; } diff --git a/tests/boluswizardpreview.test.js b/tests/boluswizardpreview.test.js new file mode 100644 index 00000000000..5280d19e3ea --- /dev/null +++ b/tests/boluswizardpreview.test.js @@ -0,0 +1,66 @@ +var should = require('should'); + +describe('boluswizardpreview', function ( ) { + + var boluswizardpreview = require('../lib/plugins/boluswizardpreview')(); + + var env = require('../env')(); + var ctx = {}; + ctx.data = require('../lib/data')(env, ctx); + ctx.notifications = require('../lib/notifications')(env, ctx); + + var profile = { + sens: 90 + , target_high: 120 + , target_low: 100 + }; + + it('Not trigger an alarm when in range', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{sgv: 100}]; + ctx.data.profiles = [profile]; + + var sbx = require('../lib/sandbox')().serverInit(env, ctx); + sbx.offerProperty('iob', function () { + return {iob: 0} + }); + + boluswizardpreview.checkNotifications(sbx); + should.not.exist(ctx.notifications.findHighestAlarm()); + + done(); + }); + + it('trigger a warning when going out of range', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{sgv: 180}]; + ctx.data.profiles = [profile]; + + var sbx = require('../lib/sandbox')().serverInit(env, ctx); + sbx.offerProperty('iob', function () { + return {iob: 0} + }); + + boluswizardpreview.checkNotifications(sbx); + ctx.notifications.findHighestAlarm().level.should.equal(ctx.notifications.levels.WARN); + + done(); + }); + + it('trigger an urgent alarms when going too high', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{sgv: 300}]; + ctx.data.profiles = [profile]; + + var sbx = require('../lib/sandbox')().serverInit(env, ctx); + sbx.offerProperty('iob', function () { + return {iob: 0} + }); + + boluswizardpreview.checkNotifications(sbx); + ctx.notifications.findHighestAlarm().level.should.equal(ctx.notifications.levels.URGENT); + + done(); + }); + +}); \ No newline at end of file From 05e567b76404e1439219d8071b142483555f34dc Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 13 Jun 2015 20:37:33 -0700 Subject: [PATCH 136/937] added some tests for utils; made timeAgo take clientSettings as a param instead of getting lucky that browserSetting is global on the client --- lib/utils.js | 6 +++--- static/js/client.js | 6 +++--- tests/utils.test.js | 22 ++++++++++++++++++++++ 3 files changed, 28 insertions(+), 6 deletions(-) create mode 100644 tests/utils.test.js diff --git a/lib/utils.js b/lib/utils.js index 6bc24037801..35761d80042 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -19,7 +19,7 @@ function init() { } }; - utils.timeAgo = function timeAgo(time) { + utils.timeAgo = function timeAgo(time, clientSettings) { var now = Date.now() , offset = time == -1 ? -1 : (now - time) / 1000 @@ -37,9 +37,9 @@ function init() { if (offset > DAY_IN_SECS * 7) { parts.status = 'warn'; - } else if (offset < MINUTE_IN_SECS * -5 || offset > (MINUTE_IN_SECS * browserSettings.alarmTimeAgoUrgentMins)) { + } else if (offset < MINUTE_IN_SECS * -5 || offset > (MINUTE_IN_SECS * clientSettings.alarmTimeAgoUrgentMins)) { parts.status = 'urgent'; - } else if (offset > (MINUTE_IN_SECS * browserSettings.alarmTimeAgoWarnMins)) { + } else if (offset > (MINUTE_IN_SECS * clientSettings.alarmTimeAgoWarnMins)) { parts.status = 'warn'; } else { parts.status = 'current'; diff --git a/static/js/client.js b/static/js/client.js index 4f7e7fdcf5d..1006e6f8acf 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -134,7 +134,7 @@ function nsArrayDiff(oldArray, newArray) { function updateTitle() { var time = latestSGV ? new Date(latestSGV.x).getTime() : (prevSGV ? new Date(prevSGV.x).getTime() : -1) - , ago = timeAgo(time); + , ago = timeAgo(time, browserSettings); var bg_title = browserSettings.customTitle || ''; @@ -412,7 +412,7 @@ function nsArrayDiff(oldArray, newArray) { var value, time, ago, isCurrent; value = entry.y; time = new Date(entry.x).getTime(); - ago = timeAgo(time); + ago = timeAgo(time, browserSettings); isCurrent = ago.status === 'current'; if (value == 9) { @@ -1481,7 +1481,7 @@ function nsArrayDiff(oldArray, newArray) { function updateTimeAgo() { var lastEntry = $('#lastEntry') , time = latestSGV ? new Date(latestSGV.x).getTime() : -1 - , ago = timeAgo(time) + , ago = timeAgo(time, browserSettings) , retroMode = inRetroMode(); lastEntry.removeClass('current warn urgent'); diff --git a/tests/utils.test.js b/tests/utils.test.js new file mode 100644 index 00000000000..3d6873d7ff5 --- /dev/null +++ b/tests/utils.test.js @@ -0,0 +1,22 @@ +var should = require('should'); + +describe('utils', function ( ) { + var utils = require('../lib/utils')(); + + var clientSettings = { + alarmTimeAgoUrgentMins: 30 + , alarmTimeAgoWarnMins: 15 + }; + + it('shod format numbers', function () { + utils.toFixed(5.499999999).should.equal('5.50') + }); + + it('show format recent times to 1 minute', function () { + var result = utils.timeAgo(Date.now() - 30000, clientSettings); + result.value.should.equal(1); + result.label.should.equal('min ago'); + result.status.should.equal('current'); + }); + +}); From 1ded4d4b36a974c2ce1771ae611b002d6c709c28 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 13 Jun 2015 22:12:04 -0700 Subject: [PATCH 137/937] another notification test; and bug fix for infos --- lib/notifications.js | 2 +- tests/notifications.test.js | 27 ++++++++++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/lib/notifications.js b/lib/notifications.js index d95607543a8..a5992b8d084 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -113,7 +113,7 @@ function init (env, ctx) { }; notifications.requestNotify = function requestNotify (notify) { - if (!notify.level || !notify.title || !notify.message) { + if (!notify.hasOwnProperty('level') || !notify.title || !notify.message) { console.error(new Error('Unable to request notification, since the notify isn\'t complete: ' + JSON.stringify(notify))); return; } diff --git a/tests/notifications.test.js b/tests/notifications.test.js index 3e5e05a9261..215b4a0b709 100644 --- a/tests/notifications.test.js +++ b/tests/notifications.test.js @@ -13,6 +13,12 @@ describe('notifications', function ( ) { var notifications = require('../lib/notifications')(env, ctx); + var exampleInfo = { + title: 'test' + , message: 'testing' + , level: notifications.levels.INFO + }; + var exampleWarn = { title: 'test' , message: 'testing' @@ -50,7 +56,9 @@ describe('notifications', function ( ) { }); - it('emitANotification', function (done) { + it('emitAWarning', function (done) { + //start fresh to we don't pick up other notifications + ctx.bus = new Stream; //if notification doesn't get called test will time out ctx.bus.on('notification', function callback ( ) { done(); @@ -62,6 +70,23 @@ describe('notifications', function ( ) { notifications.process(); }); + it('emitAnInfo', function (done) { + //start fresh to we don't pick up other notifications + ctx.bus = new Stream; + //if notification doesn't get called test will time out + ctx.bus.on('notification', function callback (notify) { + if (!notify.clear) { + done(); + } + }); + + notifications.initRequests(); + notifications.requestNotify(exampleInfo); + should.not.exist(notifications.findHighestAlarm()); + + notifications.process(); + }); + it('Can be snoozed', function (done) { notifications.initRequests(); notifications.requestNotify(exampleWarn); From afa31a6a69ac547cee93654155e9a6518fac4cf4 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 13 Jun 2015 22:33:04 -0700 Subject: [PATCH 138/937] sandbox init tests --- tests/sandbox.test.js | 49 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 tests/sandbox.test.js diff --git a/tests/sandbox.test.js b/tests/sandbox.test.js new file mode 100644 index 00000000000..60ee9b91e93 --- /dev/null +++ b/tests/sandbox.test.js @@ -0,0 +1,49 @@ +var should = require('should'); + +describe('sandbox', function ( ) { + var sandbox = require('../lib/sandbox')(); + + it('init on client', function (done) { + var app = { + thresholds:{ + bg_high: 260 + , bg_target_top: 180 + , bg_target_bottom: 80 + , bg_low: 55 + } + }; + + var clientSettings = { + units: 'mg/dl' + }; + + var pluginBase = {}; + var data = {sgvs: [{sgv: 100}]}; + + var sbx = sandbox.clientInit(app, clientSettings, Date.now(), pluginBase, data); + + sbx.pluginBase.should.equal(pluginBase); + sbx.data.should.equal(data); + sbx.data.lastSGV().should.equal(100); + + done(); + }); + + it('init on server', function (done) { + var env = require('../env')(); + var ctx = {}; + ctx.data = require('../lib/data')(env, ctx); + ctx.data.sgvs = [{sgv: 100}]; + ctx.notifications = require('../lib/notifications')(env, ctx); + + var sbx = sandbox.serverInit(env, ctx); + + should.exist(sbx.notifications.requestNotify); + should.not.exist(sbx.notifications.process); + should.not.exist(sbx.notifications.ack); + sbx.data.lastSGV().should.equal(100); + + done(); + }); + +}); From 2696d8d984d581a0202578365ccb4dadbcfe04a5 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 13 Jun 2015 22:53:48 -0700 Subject: [PATCH 139/937] treatmentnotify tests --- tests/treatmentnotify.test.js | 74 +++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 tests/treatmentnotify.test.js diff --git a/tests/treatmentnotify.test.js b/tests/treatmentnotify.test.js new file mode 100644 index 00000000000..0f6a41fe696 --- /dev/null +++ b/tests/treatmentnotify.test.js @@ -0,0 +1,74 @@ +var _ = require('lodash'); +var should = require('should'); + +describe('treatmentnotify', function ( ) { + + var treatmentnotify = require('../lib/plugins/treatmentnotify')(); + + var env = require('../env')(); + var ctx = {}; + ctx.data = require('../lib/data')(env, ctx); + ctx.notifications = require('../lib/notifications')(env, ctx); + + it('Request a snooze for a recent treatment and request an info notify', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{sgv: 100}]; + ctx.data.treatments = [{eventType: 'BG Check', glucose: '100', created_at: (new Date()).toISOString()}]; + + var sbx = require('../lib/sandbox')().serverInit(env, ctx); + treatmentnotify.checkNotifications(sbx); + should.not.exist(ctx.notifications.findHighestAlarm()); + should.exist(ctx.notifications.snoozedBy({level: ctx.notifications.levels.URGENT})); + + _.first(ctx.notifications.findInfos()).level.should.equal(ctx.notifications.levels.INFO); + + done(); + }); + + it('Not Request a snooze for an older treatment and not request an info notification', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{sgv: 100}]; + ctx.data.treatments = [{created_at: (new Date(Date.now() - (15 * 60 * 1000))).toISOString()}]; + + var sbx = require('../lib/sandbox')().serverInit(env, ctx); + treatmentnotify.checkNotifications(sbx); + should.not.exist(ctx.notifications.findHighestAlarm()); + should.exist(ctx.notifications.snoozedBy({level: ctx.notifications.levels.URGENT})); + + should.not.exist(_.first(ctx.notifications.findInfos())); + + done(); + }); + + it('Request a snooze for a recent calibration and request an info notify', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{sgv: 100}]; + ctx.data.mbgs = [{y: '100', x: Date.now()}]; + + var sbx = require('../lib/sandbox')().serverInit(env, ctx); + treatmentnotify.checkNotifications(sbx); + should.not.exist(ctx.notifications.findHighestAlarm()); + should.exist(ctx.notifications.snoozedBy({level: ctx.notifications.levels.URGENT})); + + _.first(ctx.notifications.findInfos()).level.should.equal(ctx.notifications.levels.INFO); + + done(); + }); + + it('Not Request a snooze for an older calibration treatment and not request an info notification', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{sgv: 100}]; + ctx.data.mbgs = [{y: '100', x: Date.now() - (15 * 60 * 1000)}]; + + var sbx = require('../lib/sandbox')().serverInit(env, ctx); + treatmentnotify.checkNotifications(sbx); + should.not.exist(ctx.notifications.findHighestAlarm()); + should.exist(ctx.notifications.snoozedBy({level: ctx.notifications.levels.URGENT})); + + should.not.exist(_.first(ctx.notifications.findInfos())); + + done(); + }); + + +}); \ No newline at end of file From 2a575d232606930738dcf5bbab7d85cfb00728fe Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 13 Jun 2015 23:09:10 -0700 Subject: [PATCH 140/937] = now is ok --- lib/plugins/treatmentnotify.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/plugins/treatmentnotify.js b/lib/plugins/treatmentnotify.js index cb8f7d81420..2984401ad3f 100644 --- a/lib/plugins/treatmentnotify.js +++ b/lib/plugins/treatmentnotify.js @@ -22,12 +22,12 @@ function init() { //TODO: figure out why date is x here #CleanUpDataModel var lastMBGTime = lastMBG ? lastMBG.x : 0; - var mbgAgo = (lastMBGTime && lastMBGTime < now) ? now - lastMBGTime : 0; - var mbgCurrent = mbgAgo != 0 && mbgAgo < TIME_10_MINS_MS; + var mbgAgo = (lastMBGTime && lastMBGTime <= now) ? now - lastMBGTime : -1; + var mbgCurrent = mbgAgo != -1 && mbgAgo < TIME_10_MINS_MS; var lastTreatmentTime = lastTreatment ? new Date(lastTreatment.created_at).getTime() : 0; - var treatmentAgo = (lastTreatmentTime && lastTreatmentTime < now) ? now - lastTreatmentTime : 0; - var treatmentCurrent = treatmentAgo != 0 && treatmentAgo < TIME_10_MINS_MS; + var treatmentAgo = (lastTreatmentTime && lastTreatmentTime <= now) ? now - lastTreatmentTime : -1; + var treatmentCurrent = treatmentAgo != -1 && treatmentAgo < TIME_10_MINS_MS; if (mbgCurrent || treatmentCurrent) { autoSnoozeAlarms(sbx); @@ -47,6 +47,7 @@ function init() { } function requestMBGNotify (lastMBG, sbx) { + console.info('requestMBGNotify for', lastMBG); sbx.notifications.requestNotify({ level: sbx.notifications.levels.INFO , title: 'Calibration' //assume all MGBs are calibrations for now From 890cb714774482ace09fd6ddff4b45df04ed8e04 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 13 Jun 2015 23:27:34 -0700 Subject: [PATCH 141/937] added cannula age test --- lib/plugins/cannulaage.js | 2 +- tests/cannulaage.test.js | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 tests/cannulaage.test.js diff --git a/lib/plugins/cannulaage.js b/lib/plugins/cannulaage.js index 1d6e0c2400a..41fd6c75344 100644 --- a/lib/plugins/cannulaage.js +++ b/lib/plugins/cannulaage.js @@ -19,7 +19,7 @@ function init() { var message = ''; _.forEach(sbx.data.treatments, function eachTreatment (treatment) { - if (treatment.eventType == "Site Change") { + if (treatment.eventType == 'Site Change') { treatmentDate = new Date(treatment.created_at); var hours = Math.round(Math.abs(sbx.time - treatmentDate) / 36e5); diff --git a/tests/cannulaage.test.js b/tests/cannulaage.test.js new file mode 100644 index 00000000000..2db06434073 --- /dev/null +++ b/tests/cannulaage.test.js @@ -0,0 +1,29 @@ +var should = require('should'); + +describe('cage', function ( ) { + var cage = require('../lib/plugins/cannulaage')(); + var sandbox = require('../lib/sandbox')(); + + it('set a pill to the current cannula age', function (done) { + + var app = {}; + var clientSettings = {}; + + var data = { + treatments: [{eventType: 'Site Change', created_at: (new Date(Date.now() - 24 * 60 * 60000)).toISOString()}] + }; + + var pluginBase = { + updatePillText: function mockedUpdatePillText (plugin, updatedText, label, info) { + updatedText.should.equal('24h'); + done(); + } + }; + + var sbx = sandbox.clientInit(app, clientSettings, Date.now(), pluginBase, data); + cage.updateVisualisation(sbx); + + }); + + +}); From 5d287443c279ed239bd86e0c00bb7280247166db Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 13 Jun 2015 23:27:47 -0700 Subject: [PATCH 142/937] clean up --- tests/utils.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/utils.test.js b/tests/utils.test.js index 3d6873d7ff5..0b79d107ade 100644 --- a/tests/utils.test.js +++ b/tests/utils.test.js @@ -8,7 +8,7 @@ describe('utils', function ( ) { , alarmTimeAgoWarnMins: 15 }; - it('shod format numbers', function () { + it('format numbers', function () { utils.toFixed(5.499999999).should.equal('5.50') }); From 36a9675dadab70fb75fecb2a635f6710d5743bd0 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 14 Jun 2015 12:00:40 +0300 Subject: [PATCH 143/937] Small structure change to data to make testing deltas easier. Test for delta calculation. --- lib/data.js | 24 +++++++++++--------- tests/data.test.js | 55 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 10 deletions(-) create mode 100644 tests/data.test.js diff --git a/lib/data.js b/lib/data.js index a130c25991c..8a557ff8029 100644 --- a/lib/data.js +++ b/lib/data.js @@ -164,12 +164,16 @@ function init(env, ctx) { }; data.calculateDelta = function calculateDelta(lastData) { + return data.calculateDeltaBetweenDatasets(lastData,data); + } + + data.calculateDeltaBetweenDatasets = function calculateDeltaBetweenDatasets(oldData,newData) { var delta = {'delta': true}; var changesFound = false; // if there's no updates done so far, just return the full set - if (!lastData.sgvs) return data; + if (!oldData.sgvs) return newData; function nsArrayDiff(oldArray, newArray) { var seen = {}; @@ -193,24 +197,24 @@ function init(env, ctx) { }); } - delta.lastUpdated = data.lastUpdated; + delta.lastUpdated = newData.lastUpdated; // array compression var compressibleArrays = ['sgvs', 'treatments', 'mbgs', 'cals']; for (var array in compressibleArrays) { var a = compressibleArrays[array]; - if (data.hasOwnProperty(a)) { + if (newData.hasOwnProperty(a)) { // if previous data doesn't have the property (first time delta?), just assign data over - if (!lastData.hasOwnProperty(a)) { - delta[a] = data[a]; + if (!oldData.hasOwnProperty(a)) { + delta[a] = newData[a]; changesFound = true; continue; } // Calculate delta and assign delta over if changes were found - var deltaData = nsArrayDiff(lastData[a], data[a]); + var deltaData = nsArrayDiff(oldData[a], newData[a]); if (deltaData.length > 0) { console.log('delta changes found on', a); changesFound = true; @@ -225,17 +229,17 @@ function init(env, ctx) { for (var object in skippableObjects) { var o = skippableObjects[object]; - if (data.hasOwnProperty(o)) { - if (JSON.stringify(data[o]) != JSON.stringify(lastData[o])) { + if (newData.hasOwnProperty(o)) { + if (JSON.stringify(newData[o]) != JSON.stringify(oldData[o])) { console.log('delta changes found on', o); changesFound = true; - delta[o] = data[o]; + delta[o] = newData[o]; } } } if (changesFound) return delta; - return data; + return newData; }; diff --git a/tests/data.test.js b/tests/data.test.js new file mode 100644 index 00000000000..574db0f8d8e --- /dev/null +++ b/tests/data.test.js @@ -0,0 +1,55 @@ +var should = require('should'); + +describe('Data', function ( ) { + + var env = require('../env')(); + var ctx = {}; + data = require('../lib/data')(env, ctx); +// console.log(data); + + it('should return original data if there are no changes', function() { + data.sgvs = [{sgv: 100, x:100},{sgv: 100, x:99}]; + var delta = data.calculateDeltaBetweenDatasets(data,data); + delta.should.equal(data); + }); + + it('adding one sgv record should return delta with one sgv', function() { + data.sgvs = [{sgv: 100, x:100},{sgv: 100, x:99}]; + var newData = data.clone(); + newData.sgvs = [{sgv: 100, x:101},{sgv: 100, x:100},{sgv: 100, x:99}]; + var delta = data.calculateDeltaBetweenDatasets(data,newData); + delta.delta.should.equal(true); + delta.sgvs.length.should.equal(1); + }); + + it('changes to treatments, mbgs and cals should be calculated even if sgvs is not changed', function() { + data.sgvs = [{sgv: 100, x:100},{sgv: 100, x:99}]; + data.treatments = [{sgv: 100, x:100},{sgv: 100, x:99}]; + data.mbgs = [{sgv: 100, x:100},{sgv: 100, x:99}]; + data.cals = [{sgv: 100, x:100},{sgv: 100, x:99}]; + var newData = data.clone(); + newData.sgvs = [{sgv: 100, x:100},{sgv: 100, x:99}]; + newData.treatments = [{sgv: 100, x:101},{sgv: 100, x:100},{sgv: 100, x:99}]; + newData.mbgs = [{sgv: 100, x:101},{sgv: 100, x:100},{sgv: 100, x:99}]; + newData.cals = [{sgv: 100, x:101},{sgv: 100, x:100},{sgv: 100, x:99}]; + var delta = data.calculateDeltaBetweenDatasets(data,newData); + delta.delta.should.equal(true); + delta.treatments.length.should.equal(1); + delta.mbgs.length.should.equal(1); + delta.cals.length.should.equal(1); + }); + + it('delta should include profile and devicestatus object if changed', function() { + data.sgvs = [{sgv: 100, x:100},{sgv: 100, x:99}]; + data.profiles = {foo:true}; + data.devicestatus = {foo:true}; + var newData = data.clone(); + newData.sgvs = [{sgv: 100, x:101},{sgv: 100, x:100},{sgv: 100, x:99}]; + newData.profiles = {bar:true}; + newData.devicestatus = {bar:true}; + var delta = data.calculateDeltaBetweenDatasets(data,newData); + delta.profiles.bar.should.equal(true); + delta.devicestatus.bar.should.equal(true); + }); + +}); \ No newline at end of file From 8c029922a3687f6a89127913617c59e45c02d5f3 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 14 Jun 2015 16:10:07 +0300 Subject: [PATCH 144/937] Fix threshold calculation with ar2 prediction, when using mmol/L --- lib/plugins/ar2.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 690ade39323..dd05c878c07 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -50,10 +50,10 @@ function init() { var min = _.max([first, last, avg]); var rangeLabel = ''; - if (max > sbx.thresholds.bg_target_top) { + if (max > sbx.scaleBg(sbx.thresholds.bg_target_top)) { rangeLabel = 'HIGH'; if (!pushoverSound) pushoverSound = 'climb' - } else if (min < sbx.thresholds.bg_target_bottom) { + } else if (min < sbx.scaleBg(sbx.thresholds.bg_target_bottom)) { rangeLabel = 'LOW'; if (!pushoverSound) pushoverSound = 'falling' } else { @@ -62,7 +62,7 @@ function init() { var title = [levelLabel, rangeLabel, 'predicted'].join(' ').replace(' ', ' '); var lines = [ - ['Now', sbx.data.lastSGV(), sbx.unitsLabel].join(' ') + ['Now', sbx.scaleBg(sbx.data.lastSGV()), sbx.unitsLabel].join(' ') , ['15m', predicted[2], sbx.unitsLabel].join(' ') ]; From 6461de61976b919916bed960eeb3c1e4c44e634b Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 14 Jun 2015 16:23:28 +0300 Subject: [PATCH 145/937] Fix Simple Alarms threshold calculation when on mmol --- lib/plugins/simplealarms.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/plugins/simplealarms.js b/lib/plugins/simplealarms.js index 870e7e8c3cb..d6634f69824 100644 --- a/lib/plugins/simplealarms.js +++ b/lib/plugins/simplealarms.js @@ -21,30 +21,30 @@ function init() { ; if (lastSGV) { - if (lastSGV > sbx.thresholds.bg_high) { + if (lastSGV > sbx.scaleBg(sbx.thresholds.bg_high)) { trigger = true; level = 2; title = 'Urgent HIGH'; pushoverSound = 'persistent'; - console.info(title + ': ' + (lastSGV + ' > ' + sbx.thresholds.bg_high)); + console.info(title + ': ' + (lastSGV + ' > ' + sbx.scaleBg(sbx.thresholds.bg_high))); } else if (lastSGV > sbx.thresholds.bg_target_top) { trigger = true; level = 1; title = 'High warning'; pushoverSound = 'climb'; - console.info(title + ': ' + (lastSGV + ' > ' + sbx.thresholds.bg_target_top)); + console.info(title + ': ' + (lastSGV + ' > ' + sbx.scaleBg(sbx.thresholds.bg_target_top))); } else if (lastSGV < sbx.thresholds.bg_low) { trigger = true; level = 2; title = 'Urgent LOW'; pushoverSound = 'persistent'; - console.info(title + ': ' + (lastSGV + ' < ' + sbx.thresholds.bg_low)); + console.info(title + ': ' + (lastSGV + ' < ' + sbx.scaleBg(sbx.thresholds.bg_low))); } else if (lastSGV < sbx.thresholds.bg_target_bottom) { trigger = true; level = 1; title = 'Low warning'; pushoverSound = 'falling'; - console.info(title + ': ' + (lastSGV + ' < ' + sbx.thresholds.bg_target_bottom)); + console.info(title + ': ' + (lastSGV + ' < ' + sbx.scaleBg(sbx.thresholds.bg_target_bottom))); } else if (sbx.thresholds.bg_magic && lastSVG == sbx.thresholds.bg_magic && lastSGVEntry.direction == 'Flat') { trigger = true; level = o; From 4eaaeffb27360cb4aab2147fca5e02ab10d5378a Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 14 Jun 2015 17:13:32 +0300 Subject: [PATCH 146/937] Ensure sbx returns numbers when scaling BGs as this function is used before comparing BGs, which subsequently fails if the unit conversion returns a value converted with toFixed() --- lib/sandbox.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/sandbox.js b/lib/sandbox.js index 2c4c6bb9127..a724bb6de72 100644 --- a/lib/sandbox.js +++ b/lib/sandbox.js @@ -99,9 +99,9 @@ function init ( ) { function scaleBg (bg) { if (sbx.units == 'mmol' && bg) { - return units.mgdlToMMOL(bg); + return Number(units.mgdlToMMOL(bg)); } else { - return bg; + return Number(bg); } } From 0ddcc4bf828cbdc3eca8c01497419967d4e8039e Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 14 Jun 2015 17:19:12 +0300 Subject: [PATCH 147/937] Had missed a couple thresholds in Simple Alarms. (Need to configure test env to use the same profile as production) --- lib/plugins/simplealarms.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/plugins/simplealarms.js b/lib/plugins/simplealarms.js index d6634f69824..30b4e34563d 100644 --- a/lib/plugins/simplealarms.js +++ b/lib/plugins/simplealarms.js @@ -27,19 +27,19 @@ function init() { title = 'Urgent HIGH'; pushoverSound = 'persistent'; console.info(title + ': ' + (lastSGV + ' > ' + sbx.scaleBg(sbx.thresholds.bg_high))); - } else if (lastSGV > sbx.thresholds.bg_target_top) { + } else if (lastSGV > sbx.scaleBg(sbx.thresholds.bg_target_top)) { trigger = true; level = 1; title = 'High warning'; pushoverSound = 'climb'; console.info(title + ': ' + (lastSGV + ' > ' + sbx.scaleBg(sbx.thresholds.bg_target_top))); - } else if (lastSGV < sbx.thresholds.bg_low) { + } else if (lastSGV < sbx.scaleBg(sbx.thresholds.bg_low)) { trigger = true; level = 2; title = 'Urgent LOW'; pushoverSound = 'persistent'; console.info(title + ': ' + (lastSGV + ' < ' + sbx.scaleBg(sbx.thresholds.bg_low))); - } else if (lastSGV < sbx.thresholds.bg_target_bottom) { + } else if (lastSGV < sbx.scaleBg(sbx.thresholds.bg_target_bottom)) { trigger = true; level = 1; title = 'Low warning'; From c329a3019912cd1634a14f4eeb9e58734fa122d3 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 14 Jun 2015 09:55:31 -0700 Subject: [PATCH 148/937] add placeholder point the same way as the old websocket.js did --- static/js/client.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index 1006e6f8acf..5a18f2dbb42 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1698,10 +1698,11 @@ function nsArrayDiff(oldArray, newArray) { // 2 days before now as x0 and 30 minutes from now for x1 for context plot, but this will be // required to happen when 'now' event is sent from websocket.js every minute. When fixed, // remove all 'color != 'none'' code - var lastdata = data.length > 0 ? data[data.length - 1].date.getTime() : Date.now(); - for (var i = 1; i <= 7; i++) { + var lastTime = data.length > 0 ? data[data.length - 1].date.getTime() : Date.now(); + var n = Math.ceil(12 * (1 / 2 + (now - lastTime) / SIXTY_MINS_IN_MS)) + 1; + for (var i = 1; i <= n; i++) { data.push({ - date: new Date(lastdata + (i * FIVE_MINS_IN_MS)), y: 100, sgv: scaleBg(100), color: 'none', type: 'server-forecast' + date: new Date(lastTime + (i * FIVE_MINS_IN_MS)), y: 100, sgv: scaleBg(100), color: 'none', type: 'server-forecast' }); } From 45d182b7d8c6cdf1679911316f451f7f04676c6d Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 14 Jun 2015 11:18:20 -0700 Subject: [PATCH 149/937] mqtt tests and fixes for the sgv/sensor merge --- lib/mqtt.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/lib/mqtt.js b/lib/mqtt.js index a89d5663386..daa4a86be6b 100644 --- a/lib/mqtt.js +++ b/lib/mqtt.js @@ -112,24 +112,27 @@ function sgvSensorMerge(packet) { for (var i = 1; i <= smallerLength; i++) { var sgv = sgvs[sgvsLength - i]; var sensor = sensors[sensorsLength - i]; - if (Math.abs(sgv.date - sensor.date) < 10000) { + if (sgv && sensor && Math.abs(sgv.date - sensor.date) < 10000) { //timestamps are close so merge sgv.filtered = sensor.filtered; sgv.unfiltered = sensor.unfiltered; sgv.rssi = sensor.rssi; merged.push(sgv); } else { + console.info('mismatch or missing, sgv: ', sgv, ' sensor: ', sensor); //timestamps aren't close enough so add both - merged.push(sgv); + if (sgv) merged.push(sgv); //but the sensor will become and sgv now - sensor.type = 'sgv'; - merged.push(sensor); + if (sensor) { + sensor.type = 'sgv'; + merged.push(sensor); + } } } //any extra sgvs? if (sgvsLength > smallerLength) { - for (var j = sgvsLength - smallerLength; j < sgvsLength; j++) { + for (var j = 0; j < sgvsLength - smallerLength; j++) { var extraSGV = sgvs[j]; merged.push(extraSGV); } @@ -137,7 +140,7 @@ function sgvSensorMerge(packet) { //any extra sensors? if (sensorsLength > smallerLength) { - for (var k = sensorsLength - smallerLength; k < sensorsLength; k++) { + for (var k = 0; k < sensorsLength - smallerLength; k++) { var extraSensor = sensors[k]; //from now on we consider it a sgv extraSensor.type = 'sgv'; @@ -250,4 +253,8 @@ function configure(env, ctx) { client.every = every; return client; } + +//expose for tests that don't need to connect +configure.sgvSensorMerge = sgvSensorMerge; + module.exports = configure; From b87f2f41df960d01ccf68314c83a4b4a2cb73d70 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 14 Jun 2015 11:24:22 -0700 Subject: [PATCH 150/937] mqtt tests that didn't get added before --- tests/mqtt.test.js | 102 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 tests/mqtt.test.js diff --git a/tests/mqtt.test.js b/tests/mqtt.test.js new file mode 100644 index 00000000000..546978221a7 --- /dev/null +++ b/tests/mqtt.test.js @@ -0,0 +1,102 @@ +var should = require('should'); + +var FIVE_MINS = 5 * 60 * 1000; + +describe('mqtt', function ( ) { + + var mqtt = require('../lib/mqtt'); + + var now = Date.now() + , prev1 = now - FIVE_MINS + , prev2 = prev1 - FIVE_MINS + ; + + it('setup env correctly', function (done) { + process.env.MONGO="mongodb://localhost/test_db"; + process.env.MONGO_COLLECTION="test_sgvs"; + process.env.MQTT_MONITOR = 'mqtt://user:password@m10.cloudmqtt.com:12345'; + var env = require('../env')(); + env.mqtt_client_id.should.equal('fSjoHx8buyCtAc474tg8Dt3'); + done(); + }); + + it('merge sgvs and sensor records that match up', function (done) { + var packet = { + sgv: [ + {sgv_mgdl: 110, trend: 4, date: prev2} + , {sgv_mgdl: 105, trend: 4, date: prev1} + , {sgv_mgdl: 100, trend: 4, date: now} + ] + , sensor: [ + {filtered: 99999, unfiltered: 99999, rssi: 200, date: prev2} + , {filtered: 99999, unfiltered: 99999, rssi: 200, date: prev1} + , {filtered: 99999, unfiltered: 99999, rssi: 200, date: now} + ] + }; + + var merged = mqtt.sgvSensorMerge(packet); + + merged.length.should.equal(packet.sgv.length); + + merged.filter(function (sgv) { + return sgv.filtered && sgv.unfiltered && sgv.rssi; + }).length.should.equal(packet.sgv.length); + + done(); + + }); + + it('merge sgvs and sensor records that match up, and get the sgvs that don\'t match', function (done) { + var packet = { + sgv: [ + {sgv_mgdl: 110, trend: 4, date: prev2} + , {sgv_mgdl: 105, trend: 4, date: prev1} + , {sgv_mgdl: 100, trend: 4, date: now} + ] + , sensor: [ + {filtered: 99999, unfiltered: 99999, rssi: 200, date: now} + ] + }; + + var merged = mqtt.sgvSensorMerge(packet); + + merged.length.should.equal(packet.sgv.length); + + var withBoth = merged.filter(function (sgv) { + return sgv.sgv && sgv.filtered && sgv.unfiltered && sgv.rssi; + }); + + withBoth.length.should.equal(1); + + done(); + + }); + + it('merge sgvs and sensor records that match up, and get the sensors that don\'t match', function (done) { + var packet = { + sgv: [ + {sgv_mgdl: 100, trend: 4, date: now} + ] + , sensor: [ + {filtered: 99999, unfiltered: 99999, rssi: 200, date: prev2} + , {filtered: 99999, unfiltered: 99999, rssi: 200, date: prev1} + , {filtered: 99999, unfiltered: 99999, rssi: 200, date: now} + ] + }; + + var merged = mqtt.sgvSensorMerge(packet); + + merged.length.should.equal(packet.sensor.length); + + var withBoth = merged.filter(function (sgv) { + return sgv.sgv && sgv.filtered && sgv.unfiltered && sgv.rssi; + }); + + withBoth.length.should.equal(1); + + done(); + + }); + + +}); From aa5576904fd3617cfe7a83158133f2f643de2446 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 14 Jun 2015 13:35:01 -0700 Subject: [PATCH 151/937] 1 more mqtt test --- tests/mqtt.test.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/mqtt.test.js b/tests/mqtt.test.js index 546978221a7..76d4c3c6911 100644 --- a/tests/mqtt.test.js +++ b/tests/mqtt.test.js @@ -20,6 +20,23 @@ describe('mqtt', function ( ) { done(); }); + it('handle a download with only sgvs', function (done) { + var packet = { + sgv: [ + {sgv_mgdl: 110, trend: 4, date: prev2} + , {sgv_mgdl: 105, trend: 4, date: prev1} + , {sgv_mgdl: 100, trend: 4, date: now} + ] + }; + + var merged = mqtt.sgvSensorMerge(packet); + + merged.length.should.equal(packet.sgv.length); + + done(); + + }); + it('merge sgvs and sensor records that match up', function (done) { var packet = { sgv: [ From 49b280e43b07f26cae903e7f5390c6ce887bf36c Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 14 Jun 2015 19:15:52 -0700 Subject: [PATCH 152/937] make sure ar2 and simple alarms don't trigger on error codes --- lib/plugins/ar2.js | 2 +- lib/plugins/simplealarms.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index dd05c878c07..591db518720 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -97,7 +97,7 @@ function init() { , avgLoss: 0 }; - if (lastIndex > 0) { + if (lastIndex > 0 && sgvs[lastIndex].y > 39 && sgvs[lastIndex - 1].y > 39) { // predict using AR model var lastValidReadingTime = sgvs[lastIndex].x; var elapsedMins = (sgvs[lastIndex].x - sgvs[lastIndex - 1].x) / ONE_MINUTE; diff --git a/lib/plugins/simplealarms.js b/lib/plugins/simplealarms.js index 30b4e34563d..2966f80be1a 100644 --- a/lib/plugins/simplealarms.js +++ b/lib/plugins/simplealarms.js @@ -20,7 +20,7 @@ function init() { , pushoverSound = null ; - if (lastSGV) { + if (lastSGV && lastSGVEntry && lastSGVEntry.y > 39) { if (lastSGV > sbx.scaleBg(sbx.thresholds.bg_high)) { trigger = true; level = 2; From d361b1b999fc5fe7250452500589b16d3af08ca4 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 14 Jun 2015 19:16:30 -0700 Subject: [PATCH 153/937] plugin to trigger notifications based on cgm error codes --- env.js | 1 + lib/notifications.js | 34 ++++++++++----- lib/plugins/errorcodes.js | 90 +++++++++++++++++++++++++++++++++++++++ lib/plugins/index.js | 1 + tests/errorcodes.test.js | 60 ++++++++++++++++++++++++++ 5 files changed, 176 insertions(+), 10 deletions(-) create mode 100644 lib/plugins/errorcodes.js create mode 100644 tests/errorcodes.test.js diff --git a/env.js b/env.js index 3be410ef33c..f730f14a7f4 100644 --- a/env.js +++ b/env.js @@ -182,6 +182,7 @@ function config ( ) { //TODO: after config changes are documented this shouldn't be auto enabled env.enable += ' treatmentnotify'; } + env.enable += ' errorcodes'; // TODO: clean up a bit diff --git a/lib/notifications.js b/lib/notifications.js index a5992b8d084..6cf58d11a99 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -11,21 +11,21 @@ var Alarm = function(label) { }; // list of alarms with their thresholds -var alarms = { - 0: new Alarm('Info') - , 1: new Alarm('Warn') - , 2: new Alarm('Urgent') -}; +var alarms = {}; function init (env, ctx) { function notifications () { return notifications; } + //aligned with https://pushover.net/api#priority var levels = { URGENT: 2 , WARN: 1 , INFO: 0 + , LOW: -1 + , LOWEST: -2 + , NONE: -3 }; notifications.levels = levels; @@ -38,9 +38,21 @@ function init (env, ctx) { return 'Warn'; case 0: return 'Info'; + case -1: + return 'Low'; + case -2: + return 'Lowest'; } }; + function getAlarm (level) { + var alarm = alarms[level]; + if (!alarm) { + alarm = new Alarm(levels.toString(level)); + alarms[level] = alarm; + } + return alarm; + } //should only be used when auto acking the alarms after going back in range or when an error corrects //setting the silence time to 1ms so the alarm will be retriggered as soon as the condition changes //since this wasn't ack'd by a user action @@ -49,7 +61,7 @@ function init (env, ctx) { var sendClear = false; for (var level = 1; level <=2; level++) { - var alarm = alarms[level]; + var alarm = getAlarm(level); if (alarm.lastEmitTime) { console.info('auto acking ' + alarm.level); notifications.ack(alarm.level, 1); @@ -64,7 +76,7 @@ function init (env, ctx) { } function emitNotification (notify) { - var alarm = alarms[notify.level]; + var alarm = getAlarm(notify.level); if (ctx.data.lastUpdated > alarm.lastAckTime + alarm.silenceTime) { ctx.bus.emit('notification', notify); alarm.lastEmitTime = ctx.data.lastUpdated; @@ -91,7 +103,9 @@ function init (env, ctx) { }; notifications.findInfos = function findInfos ( ) { - return _.filter(requests.notifies, {level: levels.INFO}); + return _.filter(requests.notifies, function (notify) { + return notify.level <= levels.INFO; + }); }; notifications.snoozedBy = function snoozedBy (notify) { @@ -103,7 +117,7 @@ function init (env, ctx) { var sorted = _.sortBy(byLevel, 'mills'); var longest = _.last(sorted); - var alarm = alarms[notify.level]; + var alarm = getAlarm(notify.level); if (longest && Date.now() + longest.lengthMills > alarm.lastAckTime + alarm.silenceTime) { return longest; @@ -149,7 +163,7 @@ function init (env, ctx) { }; notifications.ack = function ack (level, time) { - var alarm = alarms[level]; + var alarm = getAlarm(level); if (!alarm) { console.warn('Got an ack for an unknown alarm time'); return; diff --git a/lib/plugins/errorcodes.js b/lib/plugins/errorcodes.js new file mode 100644 index 00000000000..5f27bd486a4 --- /dev/null +++ b/lib/plugins/errorcodes.js @@ -0,0 +1,90 @@ +'use strict'; + +var _ = require('lodash'); + +function init() { + + var TIME_10_MINS_MS = 10 * 60 * 1000; + + function errorcodes() { + return errorcodes; + } + + errorcodes.label = 'Dexcom Error Codes'; + errorcodes.pluginType = 'notification'; + + errorcodes.checkNotifications = function checkNotifications (sbx) { + var now = Date.now(); + var lastSGV = _.last(sbx.data.sgvs); + + if (lastSGV && now - lastSGV.x < TIME_10_MINS_MS && lastSGV.y < 40) { + + var errorDisplay; + var pushoverSound = null; + var notifyLevel = sbx.notifications.levels.LOW; + + switch (parseInt(lastSGV.y)) { + case 0: //None + errorDisplay = '??0'; + break; + case 1: //SENSOR_NOT_ACTIVE + errorDisplay = '?SN'; + break; + case 2: //MINIMAL_DEVIATION + errorDisplay = '??2'; + break; + case 3: //NO_ANTENNA + errorDisplay = '?NA'; + break; + case 5: //SENSOR_NOT_CALIBRATED + errorDisplay = '?NC'; + break; + case 6: //COUNTS_DEVIATION + errorDisplay = '?CD'; + break; + case 7: //? + errorDisplay = '??7'; + break; + case 8: //? + errorDisplay = '??8'; + break; + case 9: //ABSOLUTE_DEVIATION + errorDisplay = '?HG'; + pushoverSound = 'persistent'; + notifyLevel = sbx.notifications.levels.URGENT; + break; + case 10: //POWER_DEVIATION + errorDisplay = '???'; + pushoverSound = 'persistent'; + notifyLevel = sbx.notifications.levels.URGENT; + break; + case 12: //BAD_RF + errorDisplay = '?RF'; + break; + default: + notifyLevel = sbx.notifications.levels.LOWEST; + errorDisplay = '?' + parseInt(lastSVG.y) + '?'; + break; + } + + if (notifyLevel > sbx.notifications.levels.NONE) { + sbx.notifications.requestNotify({ + level: notifyLevel + , title: 'CGM Error Code' + , message: errorDisplay + , pushoverSound: pushoverSound + , debug: { + lastSGV: lastSGV + } + }); + } + + } + }; + + + return errorcodes(); + +} + +module.exports = init; \ No newline at end of file diff --git a/lib/plugins/index.js b/lib/plugins/index.js index 72953881e72..171ae16446e 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -17,6 +17,7 @@ function init() { plugins.register([ require('./ar2')() , require('./simplealarms')() + , require('./errorcodes')() , require('./iob')() , require('./cob')() , require('./boluswizardpreview')() diff --git a/tests/errorcodes.test.js b/tests/errorcodes.test.js new file mode 100644 index 00000000000..75c2f880aff --- /dev/null +++ b/tests/errorcodes.test.js @@ -0,0 +1,60 @@ +var _ = require('lodash'); +var should = require('should'); + +describe('errorcodes', function ( ) { + + var errorcodes = require('../lib/plugins/errorcodes')(); + + var now = Date.now(); + var env = require('../env')(); + var ctx = {}; + ctx.data = require('../lib/data')(env, ctx); + ctx.notifications = require('../lib/notifications')(env, ctx); + + + it('Not trigger an alarm when in range', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{y: 100, x: now}]; + + var sbx = require('../lib/sandbox')().serverInit(env, ctx); + errorcodes.checkNotifications(sbx); + should.not.exist(ctx.notifications.findHighestAlarm()); + + done(); + }); + + it('should trigger a urgent alarm when ???', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{y: 10, x: now}]; + + var sbx = require('../lib/sandbox')().serverInit(env, ctx); + errorcodes.checkNotifications(sbx); + ctx.notifications.findHighestAlarm().level.should.equal(ctx.notifications.levels.URGENT); + + done(); + }); + + it('should trigger a urgent alarm when hourglass', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{y: 9, x: now}]; + + var sbx = require('../lib/sandbox')().serverInit(env, ctx); + errorcodes.checkNotifications(sbx); + ctx.notifications.findHighestAlarm().level.should.equal(ctx.notifications.levels.URGENT); + + done(); + }); + + it('should trigger a low notification when needing calibration', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{y: 5, x: now}]; + + var sbx = require('../lib/sandbox')().serverInit(env, ctx); + errorcodes.checkNotifications(sbx); + should.not.exist(ctx.notifications.findHighestAlarm()); + _.first(ctx.notifications.findInfos()).level.should.equal(ctx.notifications.levels.LOW); + + done(); + }); + +}); \ No newline at end of file From 719dfdfc1ff25a69da543424432616551c1c3900 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 14 Jun 2015 19:21:05 -0700 Subject: [PATCH 154/937] use the ugly y field instead of sgv for now --- tests/simplealarms.test.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/simplealarms.test.js b/tests/simplealarms.test.js index c4f47384181..ab7a3f941a6 100644 --- a/tests/simplealarms.test.js +++ b/tests/simplealarms.test.js @@ -12,7 +12,7 @@ describe('simplealarms', function ( ) { it('Not trigger an alarm when in range', function (done) { ctx.notifications.initRequests(); - ctx.data.sgvs = [{sgv: 100}]; + ctx.data.sgvs = [{y: 100}]; var sbx = require('../lib/sandbox')().serverInit(env, ctx); simplealarms.checkNotifications(sbx); @@ -23,7 +23,7 @@ describe('simplealarms', function ( ) { it('should trigger a warning when above target', function (done) { ctx.notifications.initRequests(); - ctx.data.sgvs = [{sgv: 181}]; + ctx.data.sgvs = [{y: 181}]; var sbx = require('../lib/sandbox')().serverInit(env, ctx); simplealarms.checkNotifications(sbx); @@ -34,7 +34,7 @@ describe('simplealarms', function ( ) { it('should trigger a urgent alarm when really high', function (done) { ctx.notifications.initRequests(); - ctx.data.sgvs = [{sgv: 400}]; + ctx.data.sgvs = [{y: 400}]; var sbx = require('../lib/sandbox')().serverInit(env, ctx); simplealarms.checkNotifications(sbx); @@ -45,7 +45,7 @@ describe('simplealarms', function ( ) { it('should trigger a warning when below target', function (done) { ctx.notifications.initRequests(); - ctx.data.sgvs = [{sgv: 70}]; + ctx.data.sgvs = [{y: 70}]; var sbx = require('../lib/sandbox')().serverInit(env, ctx); simplealarms.checkNotifications(sbx); @@ -56,7 +56,7 @@ describe('simplealarms', function ( ) { it('should trigger a urgent alarm when really low', function (done) { ctx.notifications.initRequests(); - ctx.data.sgvs = [{sgv: 40}]; + ctx.data.sgvs = [{y: 40}]; var sbx = require('../lib/sandbox')().serverInit(env, ctx); simplealarms.checkNotifications(sbx); From 2eef14648ba5f290bbedbcb7f20de7c0606d9870 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 14 Jun 2015 19:38:05 -0700 Subject: [PATCH 155/937] more errorcode tests --- lib/plugins/errorcodes.js | 4 ++-- tests/errorcodes.test.js | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/plugins/errorcodes.js b/lib/plugins/errorcodes.js index 5f27bd486a4..ddba4d56aad 100644 --- a/lib/plugins/errorcodes.js +++ b/lib/plugins/errorcodes.js @@ -49,7 +49,7 @@ function init() { errorDisplay = '??8'; break; case 9: //ABSOLUTE_DEVIATION - errorDisplay = '?HG'; + errorDisplay = '?AD'; pushoverSound = 'persistent'; notifyLevel = sbx.notifications.levels.URGENT; break; @@ -63,7 +63,7 @@ function init() { break; default: notifyLevel = sbx.notifications.levels.LOWEST; - errorDisplay = '?' + parseInt(lastSVG.y) + '?'; + errorDisplay = '?' + parseInt(lastSGV.y) + '?'; break; } diff --git a/tests/errorcodes.test.js b/tests/errorcodes.test.js index 75c2f880aff..2624d4344e1 100644 --- a/tests/errorcodes.test.js +++ b/tests/errorcodes.test.js @@ -57,4 +57,18 @@ describe('errorcodes', function ( ) { done(); }); + it('should trigger a low notification when code < 9', function (done) { + + for (var i = 0; i < 9; i++) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{y: i, x: now}]; + + var sbx = require('../lib/sandbox')().serverInit(env, ctx); + errorcodes.checkNotifications(sbx); + should.not.exist(ctx.notifications.findHighestAlarm()); + _.first(ctx.notifications.findInfos()).level.should.be.lessThan(ctx.notifications.levels.WARN); + } + done(); + }); + }); \ No newline at end of file From e495bdc94d3ea94000f5ff1bea09476b3c710656 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 16 Jun 2015 22:32:43 -0700 Subject: [PATCH 156/937] use node-cache to keep track of recently sent pushovers; prevent some more dupe and false alarms --- lib/notifications.js | 2 +- lib/plugins/ar2.js | 27 ++++++++----- lib/plugins/boluswizardpreview.js | 8 ++++ lib/plugins/errorcodes.js | 5 ++- lib/plugins/simplealarms.js | 5 ++- lib/plugins/treatmentnotify.js | 2 + lib/pushnotify.js | 67 ++++++++----------------------- package.json | 1 + tests/notifications.test.js | 5 +++ tests/simplealarms.test.js | 12 +++--- 10 files changed, 65 insertions(+), 69 deletions(-) diff --git a/lib/notifications.js b/lib/notifications.js index 6cf58d11a99..4d078242786 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -127,7 +127,7 @@ function init (env, ctx) { }; notifications.requestNotify = function requestNotify (notify) { - if (!notify.hasOwnProperty('level') || !notify.title || !notify.message) { + if (!notify.hasOwnProperty('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 591db518720..15ad1a20c3b 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -17,25 +17,31 @@ function init() { var ONE_HOUR = 3600000; var ONE_MINUTE = 60000; var FIVE_MINUTES = 300000; + var TEN_MINUTES = 600000; + ar2.checkNotifications = function checkNotifications(sbx) { - var forecast = ar2.forecast(sbx.data.sgvs); var trigger = false + , lastSGVEntry = _.last(sbx.data.sgvs) + , forecast = null , level = 0 , levelLabel = '' , pushoverSound = null ; - if (forecast.avgLoss > URGENT_THRESHOLD) { - trigger = true; - level = 2; - levelLabel = 'Urgent'; - pushoverSound = 'persistent'; - } else if (forecast.avgLoss > WARN_THRESHOLD) { - trigger = true; - level = 1; - levelLabel = 'Warning'; + if (lastSGVEntry && Date.now() - lastSGVEntry.x < TEN_MINUTES) { + forecast = ar2.forecast(sbx.data.sgvs); + if (forecast.avgLoss > URGENT_THRESHOLD) { + trigger = true; + level = 2; + levelLabel = 'Urgent'; + pushoverSound = 'persistent'; + } else if (forecast.avgLoss > WARN_THRESHOLD) { + trigger = true; + level = 1; + levelLabel = 'Warning'; + } } if (trigger) { @@ -80,6 +86,7 @@ function init() { , title: title , message: message , pushoverSound: pushoverSound + , plugin: ar2 , debug: { forecast: forecast , thresholds: sbx.thresholds diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 26089ded5f7..97d9a4cd3b8 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -62,11 +62,19 @@ function init() { } else if (results.bolusEstimate > warnBWP) { var level = results.bolusEstimate > urgentBWP ? sbx.notifications.levels.URGENT : sbx.notifications.levels.WARN; var levelLabel = sbx.notifications.levels.toString(level); + var sound = level == sbx.notifications.levels.URGENT ? 'updown' : 'bike'; var message = [levelLabel, results.lastSGV, sbx.unitsLabel].join(' '); + var iob = sbx.properties.iob && sbx.properties.iob.display; + if (iob) { + message += ['\nIOB:', iob, 'U'].join(' '); + } + sbx.notifications.requestNotify({ level: level , title: 'Check BG, time to bolus?' , message: message + , pushoverSound: sound + , plugin: bwp , debug: results }); } diff --git a/lib/plugins/errorcodes.js b/lib/plugins/errorcodes.js index ddba4d56aad..2462dd2a5de 100644 --- a/lib/plugins/errorcodes.js +++ b/lib/plugins/errorcodes.js @@ -50,12 +50,12 @@ function init() { break; case 9: //ABSOLUTE_DEVIATION errorDisplay = '?AD'; - pushoverSound = 'persistent'; + pushoverSound = 'alien'; notifyLevel = sbx.notifications.levels.URGENT; break; case 10: //POWER_DEVIATION errorDisplay = '???'; - pushoverSound = 'persistent'; + pushoverSound = 'alien'; notifyLevel = sbx.notifications.levels.URGENT; break; case 12: //BAD_RF @@ -72,6 +72,7 @@ function init() { level: notifyLevel , title: 'CGM Error Code' , message: errorDisplay + , plugin: errorcodes , pushoverSound: pushoverSound , debug: { lastSGV: lastSGV diff --git a/lib/plugins/simplealarms.js b/lib/plugins/simplealarms.js index 2966f80be1a..8db1aea089d 100644 --- a/lib/plugins/simplealarms.js +++ b/lib/plugins/simplealarms.js @@ -8,6 +8,8 @@ function init() { return simplealarms; } + var TIME_10_MINS_MS = 10 * 60 * 1000; + simplealarms.label = 'Simple Alarms'; simplealarms.pluginType = 'notification'; @@ -20,7 +22,7 @@ function init() { , pushoverSound = null ; - if (lastSGV && lastSGVEntry && lastSGVEntry.y > 39) { + if (lastSGV && lastSGVEntry && lastSGVEntry.y > 39 && Date.now() - lastSGVEntry.x < TIME_10_MINS_MS) { if (lastSGV > sbx.scaleBg(sbx.thresholds.bg_high)) { trigger = true; level = 2; @@ -59,6 +61,7 @@ function init() { level: level , title: title , message: [lastSGV, sbx.unitsLabel].join(' ') + , plugin: simplealarms , pushoverSound: pushoverSound , debug: { lastSGV: lastSGV, thresholds: sbx.thresholds diff --git a/lib/plugins/treatmentnotify.js b/lib/plugins/treatmentnotify.js index 2984401ad3f..72b57fdb3e6 100644 --- a/lib/plugins/treatmentnotify.js +++ b/lib/plugins/treatmentnotify.js @@ -53,6 +53,7 @@ function init() { , title: 'Calibration' //assume all MGBs are calibrations for now //TODO: figure out why mbg is y here #CleanUpDataModel , message: '\nMeter BG: ' + sbx.scaleBg(lastMBG.y) + ' ' + sbx.unitsLabel + , plugin: treatmentnotify , pushoverSound: 'magic' //, debug: results }); @@ -78,6 +79,7 @@ function init() { level: sbx.notifications.levels.INFO , title: lastTreatment.eventType , message: message + , plugin: treatmentnotify // , debug: results }); diff --git a/lib/pushnotify.js b/lib/pushnotify.js index c63cc9d1b48..462c31b81ea 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -3,14 +3,14 @@ var _ = require('lodash'); var crypto = require('crypto'); var units = require('./units')(); +var NodeCache = require( "node-cache" ); function init(env, ctx) { var pushover = require('./pushover')(env); // declare local constants for time differences - var TIME_5_MINS_MS = 5 * 60 * 1000 - ,TIME_15_MINS_S = 15 * 60 + var TIME_15_MINS_S = 15 * 60 , TIME_15_MINS_MS = TIME_15_MINS_S * 1000 ; @@ -18,12 +18,24 @@ function init(env, ctx) { return pushnotify; } - var recentlySent = {}; + var recentlySent = new NodeCache({ stdTTL: TIME_15_MINS_MS, checkperiod: 20 }); pushnotify.emitNotification = function emitNotification (notify) { if (!pushover) return; - if (isDuplicate(notify)) return; + var key = null; + if (notify.level >= ctx.notifications.levels.WARN) { + //for WARN and higher use the plugin name and notification level so that louder alarms aren't triggered too often + key = notify.plugin.name + '_' + notify.level; + } else { + //INFO and lower notifications should be sent as long as they are different + key = notifyToHash(notify); + } + + if (recentlySent.get(key)) { + console.info('notify: ' + key + ' has ALREADY been sent'); + return; + } var msg = { expire: TIME_15_MINS_S, @@ -36,61 +48,16 @@ function init(env, ctx) { }; pushover.send(msg, function(err, result) { - updateRecentlySent(err, notify); if (err) { console.error('unable to send pushover notification', err); } else { + recentlySent.set(key, notify); console.info('sent pushover notification: ', msg, 'result: ', result); } }); }; - function isDuplicate(notify) { - var byLevel = sentByLevel(notify); - var hash = notifyToHash(notify); - - var found = _.find(byLevel, function findByHash(sent) { - return sent.hash = hash; - }); - - if (found) { - console.info('Found duplicate notification that was sent recently using hash: ', hash, 'of notify: ', notify); - return true; - } else { - console.info('No duplicate notification found, using hash: ', hash, 'of notify: ', notify); - return false; - } - - } - - function updateRecentlySent(err, notify) { - sentByLevel(notify).push({ - time: Date.now() - , err: err - , hash: notifyToHash(notify) - }); - } - - function sentByLevel(notify) { - var byLevel = recentlySent[notify.level]; - if (!byLevel) { - byLevel = []; - } - - var now = Date.now(); - - byLevel = _.filter(byLevel, function isRecent(sent) { - //consider errors stale sooner than successful sends - var staleAfter = sent.err ? TIME_5_MINS_MS : TIME_15_MINS_MS; - return now - sent.time < staleAfter; - }); - - recentlySent[notify.level] = byLevel; - - return byLevel; - } - function notifyToHash(notify) { var hash = crypto.createHash('sha1'); var info = JSON.stringify(_.pick(notify, ['title', 'message'])); diff --git a/package.json b/package.json index f3714e54091..649081ba62c 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "mongodb": "^1.4.7", "moment": "2.8.1", "mqtt": "~0.3.11", + "node-cache": "^3.0.0", "pushover-notifications": "0.2.0", "sgvdata": "git://github.com/ktind/sgvdata.git#wip/protobuf", "socket.io": "^1.3.5" diff --git a/tests/notifications.test.js b/tests/notifications.test.js index 215b4a0b709..df386cbf098 100644 --- a/tests/notifications.test.js +++ b/tests/notifications.test.js @@ -13,22 +13,27 @@ describe('notifications', function ( ) { var notifications = require('../lib/notifications')(env, ctx); + var examplePlugin = function examplePlugin () {}; + var exampleInfo = { title: 'test' , message: 'testing' , level: notifications.levels.INFO + , plugin: examplePlugin }; var exampleWarn = { title: 'test' , message: 'testing' , level: notifications.levels.WARN + , plugin: examplePlugin }; var exampleUrgent = { title: 'test' , message: 'testing' , level: notifications.levels.URGENT + , plugin: examplePlugin }; var exampleSnooze = { diff --git a/tests/simplealarms.test.js b/tests/simplealarms.test.js index ab7a3f941a6..1aac1ea2043 100644 --- a/tests/simplealarms.test.js +++ b/tests/simplealarms.test.js @@ -9,10 +9,12 @@ describe('simplealarms', function ( ) { ctx.data = require('../lib/data')(env, ctx); ctx.notifications = require('../lib/notifications')(env, ctx); + var now = Date.now(); + it('Not trigger an alarm when in range', function (done) { ctx.notifications.initRequests(); - ctx.data.sgvs = [{y: 100}]; + ctx.data.sgvs = [{x: now, y: 100}]; var sbx = require('../lib/sandbox')().serverInit(env, ctx); simplealarms.checkNotifications(sbx); @@ -23,7 +25,7 @@ describe('simplealarms', function ( ) { it('should trigger a warning when above target', function (done) { ctx.notifications.initRequests(); - ctx.data.sgvs = [{y: 181}]; + ctx.data.sgvs = [{x: now, y: 181}]; var sbx = require('../lib/sandbox')().serverInit(env, ctx); simplealarms.checkNotifications(sbx); @@ -34,7 +36,7 @@ describe('simplealarms', function ( ) { it('should trigger a urgent alarm when really high', function (done) { ctx.notifications.initRequests(); - ctx.data.sgvs = [{y: 400}]; + ctx.data.sgvs = [{x: now, y: 400}]; var sbx = require('../lib/sandbox')().serverInit(env, ctx); simplealarms.checkNotifications(sbx); @@ -45,7 +47,7 @@ describe('simplealarms', function ( ) { it('should trigger a warning when below target', function (done) { ctx.notifications.initRequests(); - ctx.data.sgvs = [{y: 70}]; + ctx.data.sgvs = [{x: now, y: 70}]; var sbx = require('../lib/sandbox')().serverInit(env, ctx); simplealarms.checkNotifications(sbx); @@ -56,7 +58,7 @@ describe('simplealarms', function ( ) { it('should trigger a urgent alarm when really low', function (done) { ctx.notifications.initRequests(); - ctx.data.sgvs = [{y: 40}]; + ctx.data.sgvs = [{x: now, y: 40}]; var sbx = require('../lib/sandbox')().serverInit(env, ctx); simplealarms.checkNotifications(sbx); From a04a436a0192ee02e4d0d9e540d25d3d1270659b Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 16 Jun 2015 23:22:47 -0700 Subject: [PATCH 157/937] don't store date strings in memory --- lib/data.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/data.js b/lib/data.js index 8a557ff8029..166d8ce7785 100644 --- a/lib/data.js +++ b/lib/data.js @@ -80,11 +80,11 @@ function init(env, ctx) { if (element) { if (element.mbg) { mbgs.push({ - y: element.mbg, x: element.date, d: element.dateString, device: element.device + y: element.mbg, x: element.date, device: element.device }); } else if (element.sgv) { sgvs.push({ - y: element.sgv, x: element.date, d: element.dateString, device: element.device, direction: directionToChar(element.direction), filtered: element.filtered, unfiltered: element.unfiltered, noise: element.noise, rssi: element.rssi + y: element.sgv, x: element.date, device: element.device, direction: directionToChar(element.direction), filtered: element.filtered, unfiltered: element.unfiltered, noise: element.noise, rssi: element.rssi }); } } @@ -106,7 +106,7 @@ function init(env, ctx) { results.forEach(function (element) { if (element) { cals.push({ - x: element.date, d: element.dateString, scale: element.scale, intercept: element.intercept, slope: element.slope + x: element.date, scale: element.scale, intercept: element.intercept, slope: element.slope }); } }); @@ -120,14 +120,13 @@ function init(env, ctx) { if (!err && results) { var treatments = []; treatments = results.map(function (treatment) { - var timestamp = new Date(treatment.timestamp || treatment.created_at); - treatment.x = timestamp.getTime(); + treatment.created_at = new Date(treatment.created_at).getTime(); return treatment; }); //FIXME: sort in mongo treatments.sort(function (a, b) { - return a.x - b.x; + return a.created_at - b.created_at; }); data.treatments = treatments; From 2a9cb9559f68a1dd1862104288e80ca1a936964c Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 17 Jun 2015 01:02:35 -0700 Subject: [PATCH 158/937] also pull extended setting from the env based on what plugin have been enabled any env var that is prefixed with _ will be added to a new env.enableExt field, and will be made available to the individual plugin Example ENABLE=bwp BWP_WARN=.45 BWP_URGENT=.75 ==> {warn: .45, urgent: .75} will be available to BWP plugin, but not others --- env.js | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/env.js b/env.js index f730f14a7f4..dcaec2fa5fb 100644 --- a/env.js +++ b/env.js @@ -1,6 +1,7 @@ 'use strict'; var env = { }; +var _ = require('lodash'); var crypto = require('crypto'); var consts = require('./lib/constants'); var fs = require('fs'); @@ -184,6 +185,8 @@ function config ( ) { } env.enable += ' errorcodes'; + env.enableExt = findExtendedSettings(env.enable, process.env); + // TODO: clean up a bit // Some people prefer to use a json configuration file instead. @@ -215,4 +218,26 @@ function readENV(varName, defaultValue) { return value != null ? value : defaultValue; } +function findExtendedSettings (enables, envs) { + var extended = {}; + enables.split(' ').forEach(function eachEnable(enable) { + if (_.trim(enable)) { + _.forIn(envs, function (value, key) { + if (_.startsWith(key, enable.toUpperCase() + '_') || _.startsWith(key, enable.toLowerCase() + '_')) { + var split = key.indexOf('_'); + if (split > -1 && split <= key.length) { + var exts = extended[enable] || {}; + extended[enable] = exts; + var ext = _.kebabCase(key.substring(split + 1).toLowerCase()); + exts[ext] = value; + } + } + }); + } + }); + + console.info('Extended Settings: ', extended); + return extended; +} + module.exports = config; From 4b8a6de683405b09808ef92c47124b26bea30910 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 17 Jun 2015 15:15:55 -0700 Subject: [PATCH 159/937] clean up --- env.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/env.js b/env.js index dcaec2fa5fb..9b77a67878e 100644 --- a/env.js +++ b/env.js @@ -222,7 +222,7 @@ function findExtendedSettings (enables, envs) { var extended = {}; enables.split(' ').forEach(function eachEnable(enable) { if (_.trim(enable)) { - _.forIn(envs, function (value, key) { + _.forIn(envs, function eachEnvPair (value, key) { if (_.startsWith(key, enable.toUpperCase() + '_') || _.startsWith(key, enable.toLowerCase() + '_')) { var split = key.indexOf('_'); if (split > -1 && split <= key.length) { From 5c07f165e604dc43392294daa0c28734d19edc14 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 17 Jun 2015 15:16:13 -0700 Subject: [PATCH 160/937] try harder to prevent dupes --- lib/pushnotify.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/pushnotify.js b/lib/pushnotify.js index 462c31b81ea..d1adf75164c 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -47,11 +47,14 @@ function init(env, ctx) { retry: 30 }; + //add the key to the cache before sending, but with a short TTL + recentlySent.set(key, notify, 30); pushover.send(msg, function(err, result) { if (err) { console.error('unable to send pushover notification', err); } else { - recentlySent.set(key, notify); + //after successfully sent, increase the TTL + recentlySent.ttl(key, TIME_15_MINS_S); console.info('sent pushover notification: ', msg, 'result: ', result); } }); From b6c2959bb201cc5f6d36ae4f0dbabf9d8bfc61f4 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 17 Jun 2015 15:41:46 -0700 Subject: [PATCH 161/937] BWP should only send notifications when there's a current sgv --- lib/plugins/boluswizardpreview.js | 6 +++++- tests/boluswizardpreview.test.js | 8 +++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 97d9a4cd3b8..0e8aad21e93 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -2,6 +2,8 @@ var _ = require('lodash'); + +var TEN_MINS = 10 * 60 * 1000; var FIFTEEN_MINS = 15 * 60 * 1000; function init() { @@ -29,7 +31,9 @@ function init() { return false; } - if (!sbx.data.lastSGV()) { + var lastSGVEntry = _.last(sbx.data.sgvs); + + if (!lastSGVEntry || lastSGVEntry.y < 40 || Date.now() - lastSGVEntry.x > TEN_MINS) { console.warn('For the BolusWizardPreview plugin to function there needs to be a current SGV'); return false; } diff --git a/tests/boluswizardpreview.test.js b/tests/boluswizardpreview.test.js index 5280d19e3ea..7338e15c98c 100644 --- a/tests/boluswizardpreview.test.js +++ b/tests/boluswizardpreview.test.js @@ -9,6 +9,8 @@ describe('boluswizardpreview', function ( ) { ctx.data = require('../lib/data')(env, ctx); ctx.notifications = require('../lib/notifications')(env, ctx); + var now = Date.now(); + var profile = { sens: 90 , target_high: 120 @@ -17,7 +19,7 @@ describe('boluswizardpreview', function ( ) { it('Not trigger an alarm when in range', function (done) { ctx.notifications.initRequests(); - ctx.data.sgvs = [{sgv: 100}]; + ctx.data.sgvs = [{x: now, y: 100}]; ctx.data.profiles = [profile]; var sbx = require('../lib/sandbox')().serverInit(env, ctx); @@ -33,7 +35,7 @@ describe('boluswizardpreview', function ( ) { it('trigger a warning when going out of range', function (done) { ctx.notifications.initRequests(); - ctx.data.sgvs = [{sgv: 180}]; + ctx.data.sgvs = [{x: now, y: 180}]; ctx.data.profiles = [profile]; var sbx = require('../lib/sandbox')().serverInit(env, ctx); @@ -49,7 +51,7 @@ describe('boluswizardpreview', function ( ) { it('trigger an urgent alarms when going too high', function (done) { ctx.notifications.initRequests(); - ctx.data.sgvs = [{sgv: 300}]; + ctx.data.sgvs = [{x: now, y: 300}]; ctx.data.profiles = [profile]; var sbx = require('../lib/sandbox')().serverInit(env, ctx); From 3936ecaed3b4b24f5370fb8df45d294f0311646a Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 17 Jun 2015 18:10:26 -0700 Subject: [PATCH 162/937] expose extended plugin settings via the sandox --- env.js | 2 +- lib/api/index.js | 1 + lib/api/status.js | 1 + lib/plugins/boluswizardpreview.js | 7 ++--- lib/plugins/index.js | 48 +++++++++++++++++++------------ lib/sandbox.js | 18 ++++++++++++ static/js/client.js | 1 + 7 files changed, 55 insertions(+), 23 deletions(-) diff --git a/env.js b/env.js index 9b77a67878e..daa9e41a638 100644 --- a/env.js +++ b/env.js @@ -185,7 +185,7 @@ function config ( ) { } env.enable += ' errorcodes'; - env.enableExt = findExtendedSettings(env.enable, process.env); + env.extendedSettings = findExtendedSettings(env.enable, process.env); // TODO: clean up a bit diff --git a/lib/api/index.js b/lib/api/index.js index a53840fd5e7..0732e797847 100644 --- a/lib/api/index.js +++ b/lib/api/index.js @@ -25,6 +25,7 @@ function create (env, ctx) { if (env.enable) { app.enabledOptions = env.enable || ''; + app.extendedClientSettings = ctx.plugins && ctx.plugins.extendedClientSettings ? ctx.plugins.extendedClientSettings(env.extendedSettings) : {}; env.enable.toLowerCase().split(' ').forEach(function (value) { var enable = value.trim(); console.info("enabling feature:", enable); diff --git a/lib/api/status.js b/lib/api/status.js index ec0b4452a3a..eca642ce0ab 100644 --- a/lib/api/status.js +++ b/lib/api/status.js @@ -14,6 +14,7 @@ function configure (app, wares) { , apiEnabled: app.enabled('api') , careportalEnabled: app.enabled('api') && app.enabled('careportal') , enabledOptions: app.enabledOptions + , extendedSettings: app.extendedClientSettings , defaults: app.defaults , units: app.get('units') , head: wares.get_head( ) diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 0e8aad21e93..78d08fc558b 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -52,10 +52,9 @@ function init() { if (results.lastSGV < sbx.data.profile.target_high) return; - //TODO: not sure where these will come from yet - var snoozeBWP = sbx.properties.snoozeBWP || 0.10; - var warnBWP = sbx.properties.warnBWP || 0.35; - var urgentBWP = sbx.properties.urgentBWP || 0.75; + var snoozeBWP = Number(sbx.extendedSettings.snooze) || 0.10; + var warnBWP = Number(sbx.extendedSettings.warn) || 0.50; + var urgentBWP = Number(sbx.extendedSettings.urgent) || 1.00; if (results.lastSGV > sbx.thresholds.bg_target_top && results.bolusEstimate < snoozeBWP) { sbx.notifications.requestSnooze({ diff --git a/lib/plugins/index.js b/lib/plugins/index.js index 171ae16446e..5a7c3ce27c9 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -13,26 +13,30 @@ function init() { plugins.base = require('./pluginbase'); + var clientDefaultPlugins = [ + require('./iob')() + , require('./cob')() + , require('./boluswizardpreview')() + , require('./cannulaage')() + ]; + + var serverDefaultPlugins = [ + require('./ar2')() + , require('./simplealarms')() + , require('./errorcodes')() + , require('./iob')() + , require('./cob')() + , require('./boluswizardpreview')() + , require('./treatmentnotify')() + ]; + plugins.registerServerDefaults = function registerServerDefaults() { - plugins.register([ - require('./ar2')() - , require('./simplealarms')() - , require('./errorcodes')() - , require('./iob')() - , require('./cob')() - , require('./boluswizardpreview')() - , require('./treatmentnotify')() - ]); + plugins.register(serverDefaultPlugins); return plugins; }; plugins.registerClientDefaults = function registerClientDefaults() { - plugins.register([ - require('./iob')() - , require('./cob')() - , require('./boluswizardpreview')() - , require('./cannulaage')() - ]); + plugins.register(clientDefaultPlugins); return plugins; }; @@ -85,19 +89,19 @@ function init() { plugins.setProperties = function setProperties(sbx) { plugins.eachEnabledPlugin( function eachPlugin (plugin) { - plugin.setProperties && plugin.setProperties(sbx); + plugin.setProperties && plugin.setProperties(sbx.withExtendedSettings(plugin)); }); }; plugins.checkNotifications = function checkNotifications(sbx) { plugins.eachEnabledPlugin( function eachPlugin (plugin) { - plugin.checkNotifications && plugin.checkNotifications(sbx); + plugin.checkNotifications && plugin.checkNotifications(sbx.withExtendedSettings(plugin)); }); }; plugins.updateVisualisations = function updateVisualisations(sbx) { plugins.eachShownPlugins(sbx, function eachPlugin(plugin) { - plugin.updateVisualisation && plugin.updateVisualisation(sbx); + plugin.updateVisualisation && plugin.updateVisualisation(sbx.withExtendedSettings(plugin)); }); }; @@ -107,6 +111,14 @@ function init() { }).join(' '); }; + plugins.extendedClientSettings = function extendedClientSettings (allExtendedSettings) { + var clientSettings = {}; + _.forEach(clientDefaultPlugins, function eachClientPlugin (plugin) { + clientSettings[plugin.name] = allExtendedSettings[plugin.name]; + }); + return clientSettings; + }; + return plugins(); } diff --git a/lib/sandbox.js b/lib/sandbox.js index a724bb6de72..74315083cf8 100644 --- a/lib/sandbox.js +++ b/lib/sandbox.js @@ -22,6 +22,15 @@ function init ( ) { //ug, on the client y, is unscaled, on the server we only have the unscaled sgv field return last && (last.y || last.sgv); }; + + //default to prevent adding checks everywhere + sbx.extendedSettings = {empty: true}; + } + + function withExtendedSettings(plugin, allExtendedSettings, sbx) { + var sbx2 = _.extend({}, sbx); + sbx2.extendedSettings = allExtendedSettings[plugin.name] || {}; + return sbx2; } /** @@ -50,6 +59,10 @@ function init ( ) { sbx.properties = []; + sbx.withExtendedSettings = function getPluginExtendedSettingsOnly (plugin) { + return withExtendedSettings(plugin, env.extendedSettings, sbx); + }; + extend(); return sbx; @@ -77,6 +90,11 @@ function init ( ) { sbx.data = data; sbx.pluginBase = pluginBase; + sbx.extendedSettings = {empty: true}; + sbx.withExtendedSettings = function getPluginExtendedSettingsOnly (plugin) { + return withExtendedSettings(plugin, app.extendedSettings, sbx); + }; + extend(); return sbx; diff --git a/static/js/client.js b/static/js/client.js index 5a18f2dbb42..8249f75251d 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1804,6 +1804,7 @@ function nsArrayDiff(oldArray, newArray) { , head: xhr.head , apiEnabled: xhr.apiEnabled , enabledOptions: xhr.enabledOptions || '' + , extendedSettings: xhr.extendedSettings , thresholds: xhr.thresholds , alarm_types: xhr.alarm_types , units: xhr.units From cebd9a8a3793f649813880136d2d8fe8a9a66e9b Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 17 Jun 2015 23:55:10 -0700 Subject: [PATCH 163/937] added support for pushover callbacks to snooze system wide alarms --- README.md | 1 + env.js | 3 +- lib/api/index.js | 1 + lib/api/notifications-api.js | 27 +++++++++++++++ lib/notifications.js | 34 ++++++++++++++++++- lib/pushnotify.js | 45 ++++++++++++++++++++----- static/result/index.html | 64 ++++++++++++++++++++++++++++++++++++ 7 files changed, 165 insertions(+), 10 deletions(-) create mode 100644 lib/api/notifications-api.js create mode 100644 static/result/index.html diff --git a/README.md b/README.md index 136fd3cede1..2bf40e19279 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,7 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * `BG_TARGET_BOTTOM` (`80`) - must be set using mg/dl units; the bottom of the target range, also used to draw the line on the chart * `BG_LOW` (`55`) - must be set using mg/dl units; the low BG outside the target range that is considered urgent * `ALARM_TYPES` (`simple` if any `BG_`* ENV's are set, otherwise `predict`) - currently 2 alarm types are supported, and can be used independently or combined. The `simple` alarm type only compares the current BG to `BG_` thresholds above, the `predict` alarm type uses highly tuned formula that forecasts where the BG is going based on it's trend. `predict` **DOES NOT** currently use any of the `BG_`* ENV's + * `BASE_URL` - Used for building links to your sites api, ie pushover callbacks * `PUSHOVER_API_TOKEN` - Used to enable pushover notifications for Care Portal treatments, this token is specific to the application you create from in [Pushover](https://pushover.net/) * `PUSHOVER_USER_KEY` - Your Pushover user key, can be found in the top left of the [Pushover](https://pushover.net/) site diff --git a/env.js b/env.js index daa9e41a638..a458a908c03 100644 --- a/env.js +++ b/env.js @@ -187,13 +187,14 @@ function config ( ) { env.extendedSettings = findExtendedSettings(env.enable, process.env); + env.baseUrl = readENV('BASE_URL'); // TODO: clean up a bit // Some people prefer to use a json configuration file instead. // This allows a provided json config to override environment variables var DB = require('./database_configuration.json'), DB_URL = DB.url ? DB.url : env.mongo, - DB_COLLECTION = DB.collection ? DB.collection : env.mongo_collection + DB_COLLECTION = DB.collection ? DB.collection : env.mongo_collection; env.mongo = DB_URL; env.mongo_collection = DB_COLLECTION; env.static_files = readENV('NIGHTSCOUT_STATIC_FILES', __dirname + '/static/'); diff --git a/lib/api/index.js b/lib/api/index.js index 0732e797847..6e28946db46 100644 --- a/lib/api/index.js +++ b/lib/api/index.js @@ -51,6 +51,7 @@ function create (env, ctx) { app.use('/', require('./treatments/')(app, wares, ctx)); app.use('/', require('./profile/')(app, wares, ctx)); app.use('/', require('./devicestatus/')(app, wares, ctx)); + app.use('/', require('./notifications-api')(app, wares, ctx)); // Status app.use('/', require('./status')(app, wares)); diff --git a/lib/api/notifications-api.js b/lib/api/notifications-api.js new file mode 100644 index 00000000000..5e502bc24c4 --- /dev/null +++ b/lib/api/notifications-api.js @@ -0,0 +1,27 @@ +'use strict'; + +function configure (app, wares, ctx) { + var express = require('express'), + notifications = express.Router( ) + ; + + notifications.get('/notifications/snooze', function (req, res) { + console.info('GOT web notification snooze', req.query); + var result = ctx.notifications.secureAck( + req.query.level + , req.query.lengthMills + , req.query.t + , req.query.sig + ); + res.redirect(302, '/result/?ok=' + result); + }); + + notifications.post('/notifications/pushovercallback', function (req, res) { + console.info('GOT Pushover callback', req.body); + var result = ctx.pushnotify.ack(req.body); + res.redirect(302, '/result/?ok=' + result); + }); + + return notifications; +} +module.exports = configure; diff --git a/lib/notifications.js b/lib/notifications.js index 4d078242786..0e08165b1cd 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -1,6 +1,7 @@ 'use strict'; var _ = require('lodash'); +var crypto = require('crypto'); var THIRTY_MINUTES = 30 * 60 * 1000; @@ -162,7 +163,7 @@ function init (env, ctx) { }) }; - notifications.ack = function ack (level, time) { + notifications.ack = function ack (level, time, sendClear) { var alarm = getAlarm(level); if (!alarm) { console.warn('Got an ack for an unknown alarm time'); @@ -176,6 +177,37 @@ function init (env, ctx) { notifications.ack(1, time); } + if (sendClear) { + ctx.bus.emit('notification', {clear: true}); + } + + }; + + notifications.secureAck = function secureAck (level, lenghtMills, t, sig) { + var expected = notifications.sign(level, lenghtMills, t); + + if (expected && expected == sig) { + notifications.ack(level, lenghtMills, true); + return true + } else { + console.info(expected, ' != ', sig); + return false; + } + }; + + notifications.sign = function sign (level, lenghtMills, t) { + + console.info('trying to sign with', level, lenghtMills, t); + if (env.api_secret) { + var shasum = crypto.createHash('sha1'); + shasum.update(level.toString()); + shasum.update(lenghtMills.toString()); + shasum.update(t.toString()); + return shasum.digest('base64'); + } else { + return false; + } + }; return notifications(); diff --git a/lib/pushnotify.js b/lib/pushnotify.js index d1adf75164c..48784c18779 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -12,16 +12,19 @@ function init(env, ctx) { // declare local constants for time differences var TIME_15_MINS_S = 15 * 60 , TIME_15_MINS_MS = TIME_15_MINS_S * 1000 + , TIME_30_MINS_MS = 30 * 60 * 1000 ; function pushnotify() { return pushnotify; } + var receipts = new NodeCache({ stdTTL: TIME_15_MINS_MS, checkperiod: 120 }); var recentlySent = new NodeCache({ stdTTL: TIME_15_MINS_MS, checkperiod: 20 }); pushnotify.emitNotification = function emitNotification (notify) { if (!pushover) return; + if (notify.clear) return; var key = null; if (notify.level >= ctx.notifications.levels.WARN) { @@ -38,29 +41,55 @@ function init(env, ctx) { } var msg = { - expire: TIME_15_MINS_S, - title: notify.title, - message: notify.message, - sound: notify.pushoverSound || 'gamelan', - timestamp: new Date( ), - priority: notify.level, - retry: 30 + expire: TIME_15_MINS_S + , title: notify.title + , message: notify.message + , sound: notify.pushoverSound || 'gamelan' + , timestamp: new Date() + , priority: notify.level }; + if (notify.level == ctx.notifications.levels.URGENT) { + msg.retry = 120; + if (env.baseUrl) { + msg.callback = env.baseUrl + '/api/v1/notifications/pushovercallback'; + } + } else if (notify.level == ctx.notifications.levels.WARN && env.baseUrl) { + var now = Date.now(); + var sig = ctx.notifications.sign(1, TIME_30_MINS_MS, Date.now()); + if (sig) { + msg.url_title = 'Snooze for 30 minutes'; + msg.url = env.baseUrl + '/api/v1/notifications/snooze?level=1&lengthMills=' + TIME_30_MINS_MS + '&t=' + now + '&sig=' + sig; + } + } + //add the key to the cache before sending, but with a short TTL recentlySent.set(key, notify, 30); pushover.send(msg, function(err, result) { if (err) { console.error('unable to send pushover notification', err); } else { + //result comes back as a string here, so fix it + result = JSON.parse(result); + console.info('sent pushover notification: ', msg, 'result: ', result); //after successfully sent, increase the TTL recentlySent.ttl(key, TIME_15_MINS_S); - console.info('sent pushover notification: ', msg, 'result: ', result); + //also hold on to the receipt/notify mapping + receipts.set(result.receipt, notify); } }); }; + pushnotify.ack = function ack (response) { + var notify = receipts.get(response.receipt); + console.info('push ack, response: ', response, ', notify: ', notify); + if (notify) { + ctx.notifications.ack(notify.level, TIME_30_MINS_MS, true) + } + return !!notify; + }; + function notifyToHash(notify) { var hash = crypto.createHash('sha1'); var info = JSON.stringify(_.pick(notify, ['title', 'message'])); diff --git a/static/result/index.html b/static/result/index.html new file mode 100644 index 00000000000..b0f5969aca0 --- /dev/null +++ b/static/result/index.html @@ -0,0 +1,64 @@ + + + + + Notifications + + + + + + + + + + +

    Success

    + +

    + You can close this page this page now. +

    + + + + + + + + + + From c95503c6518a4f77b6133db67c6f06d752dad187 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 18 Jun 2015 00:27:18 -0700 Subject: [PATCH 164/937] only emergency alarms get a receipt, use hex digest so we don't have to wory about url encoding --- lib/notifications.js | 4 ++-- lib/pushnotify.js | 9 +++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/notifications.js b/lib/notifications.js index 0e08165b1cd..1b219a0dc52 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -197,13 +197,13 @@ function init (env, ctx) { notifications.sign = function sign (level, lenghtMills, t) { - console.info('trying to sign with', level, lenghtMills, t); if (env.api_secret) { var shasum = crypto.createHash('sha1'); + shasum.update(env.api_secret); shasum.update(level.toString()); shasum.update(lenghtMills.toString()); shasum.update(t.toString()); - return shasum.digest('base64'); + return shasum.digest('hex'); } else { return false; } diff --git a/lib/pushnotify.js b/lib/pushnotify.js index 48784c18779..2de184fac8d 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -74,14 +74,19 @@ function init(env, ctx) { console.info('sent pushover notification: ', msg, 'result: ', result); //after successfully sent, increase the TTL recentlySent.ttl(key, TIME_15_MINS_S); - //also hold on to the receipt/notify mapping - receipts.set(result.receipt, notify); + + if (result.receipt) { + //if this was an emergency alarm, also hold on to the receipt/notify mapping, for later acking + receipts.set(result.receipt, notify); + } } }); }; pushnotify.ack = function ack (response) { + if (!response.receipt) return false; + var notify = receipts.get(response.receipt); console.info('push ack, response: ', response, ', notify: ', notify); if (notify) { From 980c45ea29a39d09509091969b40f43ea672f5d9 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 18 Jun 2015 00:38:11 -0700 Subject: [PATCH 165/937] don't log the extended settings, they may include keys like pushover --- env.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/env.js b/env.js index a458a908c03..9575d754818 100644 --- a/env.js +++ b/env.js @@ -236,8 +236,6 @@ function findExtendedSettings (enables, envs) { }); } }); - - console.info('Extended Settings: ', extended); return extended; } From c0588fa11d1ee92ac9fd79f9ee5d72cb942c333b Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 18 Jun 2015 18:37:06 -0700 Subject: [PATCH 166/937] add back the x field to treatments so they can be used like normal entries --- lib/data.js | 5 +++-- tests/data.test.js | 9 +++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/data.js b/lib/data.js index 166d8ce7785..cc7025a4d25 100644 --- a/lib/data.js +++ b/lib/data.js @@ -118,9 +118,10 @@ function init(env, ctx) { var tq = {find: {"created_at": {"$gte": new Date(treatment_earliest_data).toISOString()}}}; ctx.treatments.list(tq, function (err, results) { if (!err && results) { - var treatments = []; - treatments = results.map(function (treatment) { + var treatments = results.map(function (treatment) { treatment.created_at = new Date(treatment.created_at).getTime(); + //TODO: #CleanUpDataModel, some code expects x everywhere + treatment.x = treatment.created_at; return treatment; }); diff --git a/tests/data.test.js b/tests/data.test.js index 574db0f8d8e..27370f1fc93 100644 --- a/tests/data.test.js +++ b/tests/data.test.js @@ -22,6 +22,15 @@ describe('Data', function ( ) { delta.sgvs.length.should.equal(1); }); + it('adding one treatment record should return delta with one treatment', function() { + data.treatments = [{sgv: 100, x:100},{sgv: 100, x:99}]; + var newData = data.clone(); + newData.treatments = [{sgv: 100, x:100},{sgv: 100, x:99},{sgv: 100, x:98}]; + var delta = data.calculateDeltaBetweenDatasets(data,newData); + delta.delta.should.equal(true); + delta.treatments.length.should.equal(1); + }); + it('changes to treatments, mbgs and cals should be calculated even if sgvs is not changed', function() { data.sgvs = [{sgv: 100, x:100},{sgv: 100, x:99}]; data.treatments = [{sgv: 100, x:100},{sgv: 100, x:99}]; From 5f22b97477ba0209b9fde28aa920ca1932f8d7d7 Mon Sep 17 00:00:00 2001 From: Paul Andrel Date: Fri, 19 Jun 2015 12:33:22 -0400 Subject: [PATCH 167/937] Some notes on configuration changes and variables needed with wip/push-notify --- README.md | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2bf40e19279..7f2faa52a78 100644 --- a/README.md +++ b/README.md @@ -99,10 +99,11 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * `BG_TARGET_BOTTOM` (`80`) - must be set using mg/dl units; the bottom of the target range, also used to draw the line on the chart * `BG_LOW` (`55`) - must be set using mg/dl units; the low BG outside the target range that is considered urgent * `ALARM_TYPES` (`simple` if any `BG_`* ENV's are set, otherwise `predict`) - currently 2 alarm types are supported, and can be used independently or combined. The `simple` alarm type only compares the current BG to `BG_` thresholds above, the `predict` alarm type uses highly tuned formula that forecasts where the BG is going based on it's trend. `predict` **DOES NOT** currently use any of the `BG_`* ENV's - * `BASE_URL` - Used for building links to your sites api, ie pushover callbacks + * `BASE_URL` - Used for building links to your sites api, ie pushover callbacks, usually the URL of your Nightscout site you may want https instead of http * `PUSHOVER_API_TOKEN` - Used to enable pushover notifications for Care Portal treatments, this token is specific to the application you create from in [Pushover](https://pushover.net/) * `PUSHOVER_USER_KEY` - Your Pushover user key, can be found in the top left of the [Pushover](https://pushover.net/) site - + * `PUSHOVER_GROUP_KEY` - If you wish to send to a Pushover delivery group instead of just to a user, Specify a group key in this variable, See the [Pushover](https://pushover.net) site to set up a delivery group and get a group key. + #### Core @@ -132,7 +133,21 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * `ALARM_TIMEAGO_URGENT_MINS` (`30`) - minutes since the last reading to trigger a urgent alarm * `SHOW_PLUGINS` - enabled plugins that should have their visualisations shown, defaults to all enabled - +#### A note on BWP/Bolus wizard preview + * If your ENABLE variable has bwp enabled, and you don't have a profile set up in mongo your Nightscout deployment + likely won't run cause it couldn't find some profile values that bwp is looking for + * To provide the profile information you will have to add a document to the profile collection in you mongo database with the following information + ```json +{ + "carbratio": 7.5, // Insulin to carb ratio + "carbs_hr": 30, // see here [IOB-COB site](http://www.nightscout.info/wiki/labs/the-nightscout-iob-cob-website) + "dia": 4, // Duration of insulin action used for calculating IOB remaining in iob/bwp + "sens": 35, // Insulin Sensitivity Factor how much one unit lowers your blood glucose + "target_low": 95, // Bottom number for your target range for BWP + "target_high": 120 // Top number for your target range for BWP +} + ``` + ## Setting environment variables Easy to emulate on the commandline: From db71b6e3bddd8e34b5db2bf394609a170962d3dc Mon Sep 17 00:00:00 2001 From: Paul Andrel Date: Fri, 19 Jun 2015 12:37:51 -0400 Subject: [PATCH 168/937] Additional cleanup and stuff. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7f2faa52a78..57f96b77b72 100644 --- a/README.md +++ b/README.md @@ -137,7 +137,7 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * If your ENABLE variable has bwp enabled, and you don't have a profile set up in mongo your Nightscout deployment likely won't run cause it couldn't find some profile values that bwp is looking for * To provide the profile information you will have to add a document to the profile collection in you mongo database with the following information - ```json + ```bash { "carbratio": 7.5, // Insulin to carb ratio "carbs_hr": 30, // see here [IOB-COB site](http://www.nightscout.info/wiki/labs/the-nightscout-iob-cob-website) From 17e8689d1a173574f0bf67af5071db97ad4eee3f Mon Sep 17 00:00:00 2001 From: Paul Andrel Date: Fri, 19 Jun 2015 12:39:42 -0400 Subject: [PATCH 169/937] Fix a typo... --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 57f96b77b72..03c794d5217 100644 --- a/README.md +++ b/README.md @@ -137,7 +137,7 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * If your ENABLE variable has bwp enabled, and you don't have a profile set up in mongo your Nightscout deployment likely won't run cause it couldn't find some profile values that bwp is looking for * To provide the profile information you will have to add a document to the profile collection in you mongo database with the following information - ```bash +```bash { "carbratio": 7.5, // Insulin to carb ratio "carbs_hr": 30, // see here [IOB-COB site](http://www.nightscout.info/wiki/labs/the-nightscout-iob-cob-website) @@ -146,7 +146,7 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. "target_low": 95, // Bottom number for your target range for BWP "target_high": 120 // Top number for your target range for BWP } - ``` +``` ## Setting environment variables Easy to emulate on the commandline: From 9f2008d6c0daf6abe730555ee37c2885ca974abb Mon Sep 17 00:00:00 2001 From: Paul Andrel Date: Fri, 19 Jun 2015 12:41:00 -0400 Subject: [PATCH 170/937] Cleanup --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 03c794d5217..1b3684bb7cc 100644 --- a/README.md +++ b/README.md @@ -137,7 +137,7 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * If your ENABLE variable has bwp enabled, and you don't have a profile set up in mongo your Nightscout deployment likely won't run cause it couldn't find some profile values that bwp is looking for * To provide the profile information you will have to add a document to the profile collection in you mongo database with the following information -```bash +```json { "carbratio": 7.5, // Insulin to carb ratio "carbs_hr": 30, // see here [IOB-COB site](http://www.nightscout.info/wiki/labs/the-nightscout-iob-cob-website) From aac13639ec5dd9c33aaefabe8e534a8c4d9b2c61 Mon Sep 17 00:00:00 2001 From: Paul Andrel Date: Fri, 19 Jun 2015 12:58:49 -0400 Subject: [PATCH 171/937] A bit more mentioned on BWP/IOB profile info --- README.md | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 1b3684bb7cc..55835dcaea0 100644 --- a/README.md +++ b/README.md @@ -136,17 +136,24 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. #### A note on BWP/Bolus wizard preview * If your ENABLE variable has bwp enabled, and you don't have a profile set up in mongo your Nightscout deployment likely won't run cause it couldn't find some profile values that bwp is looking for - * To provide the profile information you will have to add a document to the profile collection in you mongo database with the following information + * To provide the profile information you will have to add a document to the profile collection in you mongo database with the following information, + Data below is provided as an example please ensure you change it to fit. ```json { - "carbratio": 7.5, // Insulin to carb ratio - "carbs_hr": 30, // see here [IOB-COB site](http://www.nightscout.info/wiki/labs/the-nightscout-iob-cob-website) - "dia": 4, // Duration of insulin action used for calculating IOB remaining in iob/bwp - "sens": 35, // Insulin Sensitivity Factor how much one unit lowers your blood glucose - "target_low": 95, // Bottom number for your target range for BWP - "target_high": 120 // Top number for your target range for BWP + "carbratio": 7.5, + "carbs_hr": 30, + "dia": 4, + "sens": 35, + "target_low": 95, + "target_high": 120 } ``` + * The ```carbratio``` value should be the insulin to carb ratio used for BWP. + The ```dia``` value should be the duration of insulin action you want IOB/BWP to use in calculating how much insulin is left active. + The ```sens``` value should be the Insulin Sensitivity Factor used by BWP, How much one unit of insulin will normally lower blood glucose. + The ```target_low``` value should be the low number of the target zone you want BWP calculations to aim for. + The ```target_high``` value should be the high number of the target zone you want BWP calculations to aim for. + Additional information can be found [here](http://www.nightscout.info/wiki/labs/the-nightscout-iob-cob-website) ## Setting environment variables Easy to emulate on the commandline: From e31e539639fecccd683c45f807daef5c1c767761 Mon Sep 17 00:00:00 2001 From: Paul Andrel Date: Fri, 19 Jun 2015 13:00:36 -0400 Subject: [PATCH 172/937] Finishing touches --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 55835dcaea0..d990735d1e6 100644 --- a/README.md +++ b/README.md @@ -149,11 +149,11 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. } ``` * The ```carbratio``` value should be the insulin to carb ratio used for BWP. - The ```dia``` value should be the duration of insulin action you want IOB/BWP to use in calculating how much insulin is left active. - The ```sens``` value should be the Insulin Sensitivity Factor used by BWP, How much one unit of insulin will normally lower blood glucose. - The ```target_low``` value should be the low number of the target zone you want BWP calculations to aim for. - The ```target_high``` value should be the high number of the target zone you want BWP calculations to aim for. - Additional information can be found [here](http://www.nightscout.info/wiki/labs/the-nightscout-iob-cob-website) + * The ```dia``` value should be the duration of insulin action you want IOB/BWP to use in calculating how much insulin is left active. + * The ```sens``` value should be the Insulin Sensitivity Factor used by BWP, How much one unit of insulin will normally lower blood glucose. + * The ```target_low``` value should be the low number of the target zone you want BWP calculations to aim for. + * The ```target_high``` value should be the high number of the target zone you want BWP calculations to aim for. + * Additional information can be found [here](http://www.nightscout.info/wiki/labs/the-nightscout-iob-cob-website) ## Setting environment variables Easy to emulate on the commandline: From 3f9dc24b9d35b4de5801f8725d6855a942e28c00 Mon Sep 17 00:00:00 2001 From: Paul Andrel Date: Fri, 19 Jun 2015 15:03:08 -0400 Subject: [PATCH 173/937] Instead of noting the PUSHOVER_GROUP_KEY added a note that a group key could be used instead of a user key --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index d990735d1e6..35e456103d7 100644 --- a/README.md +++ b/README.md @@ -101,8 +101,7 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * `ALARM_TYPES` (`simple` if any `BG_`* ENV's are set, otherwise `predict`) - currently 2 alarm types are supported, and can be used independently or combined. The `simple` alarm type only compares the current BG to `BG_` thresholds above, the `predict` alarm type uses highly tuned formula that forecasts where the BG is going based on it's trend. `predict` **DOES NOT** currently use any of the `BG_`* ENV's * `BASE_URL` - Used for building links to your sites api, ie pushover callbacks, usually the URL of your Nightscout site you may want https instead of http * `PUSHOVER_API_TOKEN` - Used to enable pushover notifications for Care Portal treatments, this token is specific to the application you create from in [Pushover](https://pushover.net/) - * `PUSHOVER_USER_KEY` - Your Pushover user key, can be found in the top left of the [Pushover](https://pushover.net/) site - * `PUSHOVER_GROUP_KEY` - If you wish to send to a Pushover delivery group instead of just to a user, Specify a group key in this variable, See the [Pushover](https://pushover.net) site to set up a delivery group and get a group key. + * `PUSHOVER_USER_KEY` - Your Pushover user key, can be found in the top left of the [Pushover](https://pushover.net/) site, this can also be a pushover delivery group key to send to a group rather than just a single user. #### Core From 387e4e33e9698b677931b823337b74ebb7ec8ee2 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 19 Jun 2015 16:50:19 -0700 Subject: [PATCH 174/937] make sure the placeholder points are added when the now line moves --- static/js/client.js | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index 8249f75251d..4c0f0bc1c38 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -301,6 +301,21 @@ function nsArrayDiff(oldArray, newArray) { } } + function addPlaceholderPoints () { + // TODO: This is a kludge to advance the time as data becomes stale by making old predictor clear (using color = 'none') + // This shouldn't need to be generated and can be fixed by using xScale.domain([x0,x1]) function with + // 2 days before now as x0 and 30 minutes from now for x1 for context plot, but this will be + // required to happen when 'now' event is sent from websocket.js every minute. When fixed, + // remove all 'color != 'none'' code + var lastTime = data.length > 0 ? data[data.length - 1].date.getTime() : Date.now(); + var n = Math.ceil(12 * (1 / 2 + (now - lastTime) / SIXTY_MINS_IN_MS)) + 1; + for (var i = 1; i <= n; i++) { + data.push({ + date: new Date(lastTime + (i * FIVE_MINS_IN_MS)), y: 100, sgv: scaleBg(100), color: 'none', type: 'server-forecast' + }); + } + } + // clears the current user brush and resets to the current real time data function updateBrushToNow(skipBrushing) { @@ -313,6 +328,8 @@ function nsArrayDiff(oldArray, newArray) { .duration(UPDATE_TRANS_MS) .call(brush.extent([new Date(dataRange[1].getTime() - foucusRangeMS), dataRange[1]])); + addPlaceholderPoints(); + if (!skipBrushing) { brushed(true); @@ -1693,18 +1710,7 @@ function nsArrayDiff(oldArray, newArray) { data = []; data = data.concat(temp1, temp2); - // TODO: This is a kludge to advance the time as data becomes stale by making old predictor clear (using color = 'none') - // This shouldn't need to be generated and can be fixed by using xScale.domain([x0,x1]) function with - // 2 days before now as x0 and 30 minutes from now for x1 for context plot, but this will be - // required to happen when 'now' event is sent from websocket.js every minute. When fixed, - // remove all 'color != 'none'' code - var lastTime = data.length > 0 ? data[data.length - 1].date.getTime() : Date.now(); - var n = Math.ceil(12 * (1 / 2 + (now - lastTime) / SIXTY_MINS_IN_MS)) + 1; - for (var i = 1; i <= n; i++) { - data.push({ - date: new Date(lastTime + (i * FIVE_MINS_IN_MS)), y: 100, sgv: scaleBg(100), color: 'none', type: 'server-forecast' - }); - } + addPlaceholderPoints(); data = data.concat(MBGdata.map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), color: 'red', type: 'mbg', device: obj.device } })); From 73b88b2ac7d31fd0977cd760bf62ad721834897c Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 19 Jun 2015 18:35:41 -0700 Subject: [PATCH 175/937] update the README with information about the plugins, treatment profile, and pushover --- README.md | 72 ++++++++++++++++++++++++++++--- env.js | 2 +- lib/plugins/boluswizardpreview.js | 4 +- lib/plugins/treatmentnotify.js | 3 +- 4 files changed, 71 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 2bf40e19279..fe822be8bcd 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -cgm-remote-monitor (a.k.a. Nightscout) +Nightscout Web Monitor (a.k.a. cgm-remote-monitor) ====================================== [![Build Status][build-img]][build-url] @@ -19,6 +19,8 @@ and blood glucose values are predicted 0.5 hours ahead using an autoregressive second order model. Alarms are generated for high and low values, which can be cleared by any watcher of the data. +#[#WeAreNotWaiting](https://twitter.com/hashtag/WeAreNotWaiting) and [this](https://vimeo.com/109767890) is why. + Community maintained fork of the [original cgm-remote-monitor][original]. @@ -92,7 +94,7 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. #### Features/Labs - * `ENABLE` - Used to enable optional features, expects a space delimited list such as: `careportal rawbg iob` + * `ENABLE` - Used to enable optional features, expects a space delimited list such as: `careportal rawbg iob`, see [plugins](#plugins) below * `API_SECRET` - A secret passphrase that must be at least 12 characters long, required to enable `POST` and `PUT`; also required for the Care Portal * `BG_HIGH` (`260`) - must be set using mg/dl units; the high BG outside the target range that is considered urgent * `BG_TARGET_TOP` (`180`) - must be set using mg/dl units; the top of the target range, also used to draw the line on the chart @@ -100,8 +102,8 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * `BG_LOW` (`55`) - must be set using mg/dl units; the low BG outside the target range that is considered urgent * `ALARM_TYPES` (`simple` if any `BG_`* ENV's are set, otherwise `predict`) - currently 2 alarm types are supported, and can be used independently or combined. The `simple` alarm type only compares the current BG to `BG_` thresholds above, the `predict` alarm type uses highly tuned formula that forecasts where the BG is going based on it's trend. `predict` **DOES NOT** currently use any of the `BG_`* ENV's * `BASE_URL` - Used for building links to your sites api, ie pushover callbacks - * `PUSHOVER_API_TOKEN` - Used to enable pushover notifications for Care Portal treatments, this token is specific to the application you create from in [Pushover](https://pushover.net/) - * `PUSHOVER_USER_KEY` - Your Pushover user key, can be found in the top left of the [Pushover](https://pushover.net/) site + * `PUSHOVER_API_TOKEN` - Used to enable pushover notifications for Care Portal treatments, this token is specific to the application you create from in [Pushover](https://pushover.net/), ***[additional pushover information](#pushover)*** below. + * `PUSHOVER_USER_KEY` - Your Pushover user *(or group)* key, can be found in the top left of the [Pushover](https://pushover.net/) site #### Core @@ -115,7 +117,7 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * `SSL_CERT` - Path to your ssl cert file, so that ssl(https) can be enabled directly in node.js * `SSL_CA` - Path to your ssl ca file, so that ssl(https) can be enabled directly in node.js - + #### Predefined values for your browser settings (optional) * `TIME_FORMAT` (`12`)- possible values `12` or `24` * `NIGHT_MODE` (`off`) - possible values `on` or `off` @@ -131,8 +133,64 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * `ALARM_TIMEAGO_URGENT` (`on`) - possible values `on` or `off` * `ALARM_TIMEAGO_URGENT_MINS` (`30`) - minutes since the last reading to trigger a urgent alarm * `SHOW_PLUGINS` - enabled plugins that should have their visualisations shown, defaults to all enabled - - + +### Plugins + + Plugins are used extend the way information is displayed, how notifications are sent, alarms are triggered, and more. + + The built-in/example plugins that are available by default are listed below. The plugins may still need to be `ENABLE`'d. + + **Built-in/Example Plugins:** + + * `iob` (Insulin-on-Board) - Adds the IOB pill visualization in the client and calculates values that used by other plugins. Uses treatments with insulin doses and the `dia` and `sens` fields from the [treatment profile](#treatment-profile). + + * `cob` (Carbs-on-Board) - Adds the COB pill visualization in the client and calculates values that used by other plugins. Uses treatments with carb doses and the `carbs_hr`, `carbratio`, and `sens` fields from the [treatment profile](#treatment-profile). + + * `bwp` (Bolus Wizard Preview) ***Example only*** - Calculates the bolus amount when above your target, generates alarms when you should consider checking and bolusing, and snoozes alarms when there is enough IOB to cover a high BG. Uses the results of the `iob` plugin and `sens`, `target_high`, and `target_low` fields from the [treatment profile](#treatment-profile). Defaults that can be adjusted with [extended setting](#extended-settings) + * `BWP_WARN` (`0.50`) - If `BWP` is > `BWP_WARN` a warning alarm will be triggered. + + * `BWP_URGENT` (`1.00`) - If `BWP` is > `BWP_URGENT` an urgent alarm will be triggered. + + * `BWP_SNOOZE_MINS` (`10`) - minutes to snooze when there is enough IOB to cover a high BG. + + * `BWP_SNOOZE` - (`0.10`) If BG is higher then the `target_high` and `BWP` < `BWP_SNOOZE` alarms will be snoozed for `BWP_SNOOZE_MINS`. + + * `cage` (Cannula Age) - Calculates the number of hours since the last `Site Change` treatment that was recorded. + + * `ar2` ([Forcasting using AR2 algorithm](https://github.com/nightscout/nightscout.github.io/wiki/Forecasting)) - Generates alarms based on forecasted values. **Enabled by default.** + + * `simplealarms` (Simple BG Alarms) - Uses `BG_HIGH`, `BG_TARGET_TOP`, `BG_TARGET_BOTTOM`, `BG_LOW` settings to generate alarms. + + * `errorcodes` (CGM Error Codes) - Generates alarms for CGM codes `9` (hourglass) and `10` (???). **Enabled by default.** + + * `treatmentnotify` (Treatment Notifications) - Generates notifications when a treatment has been entered and snoozes alarms minutes after a treatment. Default snooze is 10 minutes, and can be set using the `TREATMENTNOTIFY_SNOOZE_MINS` [extended setting](#extended-settings). + +#### 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. + + Plugins only have access to their own extended settings, all the extended settings of client plugins will be sent to the browser. + +### Treatment Profile + Some of the [plugins](#plugins) make use of a treatment profile that is stored in Mongo. To use those plugins there should only be a single doc in the `profile` collection with the following fields: + + * `dia` (Insulin duration) - defaults to 3 hours + * `carbs_hr` ([Carbs per hour](http://diyps.org/2014/05/29/determining-your-carbohydrate-absorption-rate-diyps-lessons-learned/)) + * `carbratio` - grams per unit of insulin + * `sens` (Insulin sensitivity) field from the treatment profile + * `target_high` - Upper target for correction boluses + * `target_low` - Lower target for correction boluses + +### Pushover + In addition to the normal web based alarms, there is also support for [Pushover](https://pushover.net/) based alarms and notifications. + + To get started install the Pushover application on your iOS or Android device and create an account. + + Using that account login to [Pushover](https://pushover.net/), in the top left you’ll see your User Key, you’ll need this plus an application API Token/Key to complete this setup. + + You’ll need to [Create a Pushover Application](https://pushover.net/apps/build). You only need to set the Application name, you can ignore all the other settings, but setting an Icon is a nice touch. Maybe you'd like to use [this one](https://raw.githubusercontent.com/nightscout/cgm-remote-monitor/master/static/images/large.png) + + Pushover is configured using the `PUSHOVER_API_TOKEN`, `PUSHOVER_USER_KEY`, `BASE_URL`, and `API_SECRET` environment variables. For acknowledgment callbacks to work `BASE_URL` and `API_SECRET` must be set and `BASE_URL` must be publicly accessible. For testing/devlopment try [localtunnel](http://localtunnel.me/). + ## Setting environment variables Easy to emulate on the commandline: diff --git a/env.js b/env.js index 9575d754818..86d6e8424e4 100644 --- a/env.js +++ b/env.js @@ -229,7 +229,7 @@ function findExtendedSettings (enables, envs) { if (split > -1 && split <= key.length) { var exts = extended[enable] || {}; extended[enable] = exts; - var ext = _.kebabCase(key.substring(split + 1).toLowerCase()); + var ext = _.camelCase(key.substring(split + 1).toLowerCase()); exts[ext] = value; } } diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 78d08fc558b..a519713c584 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -56,10 +56,12 @@ function init() { var warnBWP = Number(sbx.extendedSettings.warn) || 0.50; var urgentBWP = Number(sbx.extendedSettings.urgent) || 1.00; + var snoozeLength = (sbx.extendedSettings.snoozeMins && Number(sbx.extendedSettings.snoozeMins) * 60 * 1000) || TEN_MINS; + if (results.lastSGV > sbx.thresholds.bg_target_top && results.bolusEstimate < snoozeBWP) { sbx.notifications.requestSnooze({ level: sbx.notifications.levels.URGENT - , lengthMills: FIFTEEN_MINS + , lengthMills: snoozeLength , debug: results }) } else if (results.bolusEstimate > warnBWP) { diff --git a/lib/plugins/treatmentnotify.js b/lib/plugins/treatmentnotify.js index 72b57fdb3e6..255aa7d9f26 100644 --- a/lib/plugins/treatmentnotify.js +++ b/lib/plugins/treatmentnotify.js @@ -39,9 +39,10 @@ function init() { }; function autoSnoozeAlarms(sbx) { + var snoozeLength = (sbx.extendedSettings.snoozeMins && Number(sbx.extendedSettings.snoozeMins) * 60 * 1000) || TIME_10_MINS_MS; sbx.notifications.requestSnooze({ level: sbx.notifications.levels.URGENT - , lengthMills: TIME_10_MINS_MS + , lengthMills: snoozeLength //, debug: results }); } From ba40c9dd4bd418d09013e2cc7c82f62fe78ed55a Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 19 Jun 2015 18:43:41 -0700 Subject: [PATCH 176/937] more readme updates --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index fe822be8bcd..a9171f795b7 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ and blood glucose values are predicted 0.5 hours ahead using an autoregressive second order model. Alarms are generated for high and low values, which can be cleared by any watcher of the data. -#[#WeAreNotWaiting](https://twitter.com/hashtag/WeAreNotWaiting) and [this](https://vimeo.com/109767890) is why. +#[#WeAreNotWaiting](https://twitter.com/hashtag/wearenotwaiting?src=hash&vertical=default&f=images) and [this](https://vimeo.com/109767890) is why. Community maintained fork of the [original cgm-remote-monitor][original]. @@ -132,13 +132,13 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * `ALARM_TIMEAGO_WARN_MINS` (`15`) - minutes since the last reading to trigger a warning * `ALARM_TIMEAGO_URGENT` (`on`) - possible values `on` or `off` * `ALARM_TIMEAGO_URGENT_MINS` (`30`) - minutes since the last reading to trigger a urgent alarm - * `SHOW_PLUGINS` - enabled plugins that should have their visualisations shown, defaults to all enabled + * `SHOW_PLUGINS` - enabled plugins that should have their visualizations shown, defaults to all enabled ### Plugins Plugins are used extend the way information is displayed, how notifications are sent, alarms are triggered, and more. - The built-in/example plugins that are available by default are listed below. The plugins may still need to be `ENABLE`'d. + The built-in/example plugins that are available by default are listed below. The plugins may still need to be enabled by adding the to the `ENABLE` environment variable. **Built-in/Example Plugins:** @@ -174,8 +174,8 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. Some of the [plugins](#plugins) make use of a treatment profile that is stored in Mongo. To use those plugins there should only be a single doc in the `profile` collection with the following fields: * `dia` (Insulin duration) - defaults to 3 hours - * `carbs_hr` ([Carbs per hour](http://diyps.org/2014/05/29/determining-your-carbohydrate-absorption-rate-diyps-lessons-learned/)) - * `carbratio` - grams per unit of insulin + * `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 * `sens` (Insulin sensitivity) field from the treatment profile * `target_high` - Upper target for correction boluses * `target_low` - Lower target for correction boluses From c9f412680f8591680f757f3c52d5d82132057aaf Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 19 Jun 2015 19:45:42 -0700 Subject: [PATCH 177/937] more readme fix ups... --- README.md | 26 ++------------------------ 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 7027a4969fd..e84888adb52 100644 --- a/README.md +++ b/README.md @@ -194,29 +194,7 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * `target_high` - Upper target for correction boluses * `target_low` - Lower target for correction boluses - Additional information can be found [here](http://www.nightscout.info/wiki/labs/the-nightscout-iob-cob-website) - -#### A note on BWP/Bolus wizard preview - * If your ENABLE variable has bwp enabled, and you don't have a profile set up in mongo your Nightscout deployment - likely won't run cause it couldn't find some profile values that bwp is looking for - * To provide the profile information you will have to add a document to the profile collection in you mongo database with the following information, - Data below is provided as an example please ensure you change it to fit. -```json -{ - "carbratio": 7.5, - "carbs_hr": 30, - "dia": 4, - "sens": 35, - "target_low": 95, - "target_high": 120 -} -``` - * The ```carbratio``` value should be the insulin to carb ratio used for BWP. - * The ```dia``` value should be the duration of insulin action you want IOB/BWP to use in calculating how much insulin is left active. - * The ```sens``` value should be the Insulin Sensitivity Factor used by BWP, How much one unit of insulin will normally lower blood glucose. - * The ```target_low``` value should be the low number of the target zone you want BWP calculations to aim for. - * The ```target_high``` value should be the high number of the target zone you want BWP calculations to aim for. - * Additional information can be found [here](http://www.nightscout.info/wiki/labs/the-nightscout-iob-cob-website) + Additional information can be found [here](http://www.nightscout.info/wiki/labs/the-nightscout-iob-cob-website). ### Pushover In addition to the normal web based alarms, there is also support for [Pushover](https://pushover.net/) based alarms and notifications. @@ -225,7 +203,7 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. Using that account login to [Pushover](https://pushover.net/), in the top left you’ll see your User Key, you’ll need this plus an application API Token/Key to complete this setup. - You’ll need to [Create a Pushover Application](https://pushover.net/apps/build). You only need to set the Application name, you can ignore all the other settings, but setting an Icon is a nice touch. Maybe you'd like to use [this one](https://raw.githubusercontent.com/nightscout/cgm-remote-monitor/master/static/images/large.png) + You’ll need to [Create a Pushover Application](https://pushover.net/apps/build). You only need to set the Application name, you can ignore all the other settings, but setting an Icon is a nice touch. Maybe you'd like to use [this one](https://raw.githubusercontent.com/nightscout/cgm-remote-monitor/master/static/images/large.png)? Pushover is configured using the `PUSHOVER_API_TOKEN`, `PUSHOVER_USER_KEY`, `BASE_URL`, and `API_SECRET` environment variables. For acknowledgment callbacks to work `BASE_URL` and `API_SECRET` must be set and `BASE_URL` must be publicly accessible. For testing/devlopment try [localtunnel](http://localtunnel.me/). From 2a4434defbca8ebac7a99b4bafaf4fac2cd6d58d Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sat, 20 Jun 2015 10:56:38 +0300 Subject: [PATCH 178/937] Stronger language the BWP plugin --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e84888adb52..ba3933ec3f3 100644 --- a/README.md +++ b/README.md @@ -147,7 +147,7 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * `cob` (Carbs-on-Board) - Adds the COB pill visualization in the client and calculates values that used by other plugins. Uses treatments with carb doses and the `carbs_hr`, `carbratio`, and `sens` fields from the [treatment profile](#treatment-profile). - * `bwp` (Bolus Wizard Preview) ***Example only*** - Calculates the bolus amount when above your target, generates alarms when you should consider checking and bolusing, and snoozes alarms when there is enough IOB to cover a high BG. Uses the results of the `iob` plugin and `sens`, `target_high`, and `target_low` fields from the [treatment profile](#treatment-profile). Defaults that can be adjusted with [extended setting](#extended-settings) + * `bwp` (Bolus Wizard Preview) - This plugin in intended for the purpose of automatically snoozing alarms when the CGM indicates high blood sugar but there is also insulin on board (IOB) and secondly, alerting to user that it might be beneficial to measure the blood sugar using a glucometer and dosing insulin as calculated by the pump or instructed by trained medicare professionals. ***The values provided by the plugin are provided as a reference based on CGM data and insulin sensitivity you have configured, and are not intended to be used as a reference for bolus calculation.*** The plugin calculates the bolus amount when above your target, generates alarms when you should consider checking and bolusing, and snoozes alarms when there is enough IOB to cover a high BG. Uses the results of the `iob` plugin and `sens`, `target_high`, and `target_low` fields from the [treatment profile](#treatment-profile). Defaults that can be adjusted with [extended setting](#extended-settings) * `BWP_WARN` (`0.50`) - If `BWP` is > `BWP_WARN` a warning alarm will be triggered. * `BWP_URGENT` (`1.00`) - If `BWP` is > `BWP_URGENT` an urgent alarm will be triggered. From 735a6f1abdeffa8faf8df558c58b4abc1a1279f8 Mon Sep 17 00:00:00 2001 From: Fokko Driesprong Date: Sat, 20 Jun 2015 20:09:32 +0200 Subject: [PATCH 179/937] Added a file which enables to test the REST api by inserting data based on the sine (just like populate.js but using the REST api). --- testing/populate.js | 135 +++++++-------------------------------- testing/populate_rest.js | 41 ++++++++++++ 2 files changed, 63 insertions(+), 113 deletions(-) create mode 100644 testing/populate_rest.js diff --git a/testing/populate.js b/testing/populate.js index f4fff19ae6e..67d133d7599 100644 --- a/testing/populate.js +++ b/testing/populate.js @@ -1,125 +1,34 @@ 'use strict'; -/////////////////////////////////////////////////// -// This script is intended to be run as a cron job -// every n-minutes or whatever the equiv is on windows -// -// Author: John A. [euclidjda](https://github.com/euclidjda) -// Source: https://gist.github.com/euclidjda/4ae207a89921f21382a9 -/////////////////////////////////////////////////// -/////////////////////////////////////////////////// -// DB Connection setup and utils -/////////////////////////////////////////////////// +var mongodb = require('mongodb'); +var software = require('./../package.json'); +var env = require('./../env')(); -var mongodb = require('mongodb'); -var software = require('./package.json'); -var env = require('./env')( ); +var util = require('./helpers/util'); main(); -function main( ) { - +function main() { var MongoClient = mongodb.MongoClient; - - MongoClient.connect(env.mongo, function connected (err, db) { - - console.log("Connected to mongo, ERROR: %j", err); - if (err) { throw err; } - populate_collection( db ); - + MongoClient.connect(env.mongo, function connected(err, db) { + + console.log("Connecting to mongo..."); + if (err) { + console.log("Error occurred: ", err); + throw err; + } + populate_collection(db); }); - -} - -function populate_collection( db ) { - - //console.log( 'mongo = ' + env.mongo ); - //console.log( 'collection = ' + env.mongo_collection ); - - var cgm_collection = db.collection( env.mongo_collection ); - - var new_cgm_record = get_cgm_record(); - - cgm_collection.insert( new_cgm_record, function(err,created) { - - // TODO: Error checking - process.exit( 0 ); - - } ); - - -} - -function get_cgm_record( ) { - - var dateobj = new Date(); - var datemil = dateobj.getTime(); - var datesec = datemil / 1000; - var datestr = getDateString( dateobj ); - - // We put the time in a range from -1 to +1 for every thiry minute period - var range = (datesec % 1800)/900 - 1.0; - - // The we push through a COS function and scale between 40 and 400 (so it is like a bg level) - var sgv = Math.floor(360*(Math.cos( 10.0 * range / 3.14 ) / 2 + 0.5)) + 40; - var dir = range > 0.0 ? "FortyFiveDown" : "FortyFiveUp"; - - console.log( 'Writing Record: '); - console.log( 'sgv = ' + sgv ); - console.log( 'date = ' + datemil ); - console.log( 'dir = ' + dir ); - console.log( 'str = ' + datestr ); - - var mondo_db = null; - var doc = { 'device' :'dexcom' , - 'date' : datemil , - 'sgv' : sgv , - 'direction' : dir , - 'dateString' : datestr }; - - - return doc; } -function getDateString( d ) { - - // How I wish js had strftime. This would be one line of code! - - var month = d.getMonth(); - var day = d.getDay(); - var year = d.getFullYear(); - - if (month < 10 ) month = '0'+month; - if (day < 10 ) day = '0'+day; - - var hour = d.getHours(); - var min = d.getMinutes(); - var sec = d.getSeconds(); - - var ampm = 'PM'; - - if (hour < 12) - { - ampm = "AM"; - } - else - { - ampm = "PM"; - } - - if (hour == 0) - { - hour = 12; - } - if (hour > 12) - { - hour = hour - 12; - } - - if (hour < 10) hour = '0' + hour; - if (min < 10) min = '0' + min; - if (sec < 10) sec = '0' + sec; - - return month + '/' + day + '/' + year + ' ' + hour + ':' + min + ':' + sec + ' ' + ampm; +function populate_collection(db) { + var cgm_collection = db.collection(env.mongo_collection); + var new_cgm_record = util.get_cgm_record(); + cgm_collection.insert(new_cgm_record, function (err, created) { + if (err) { + throw err; + } + process.exit(0); + }); } diff --git a/testing/populate_rest.js b/testing/populate_rest.js new file mode 100644 index 00000000000..fc8e614d7c9 --- /dev/null +++ b/testing/populate_rest.js @@ -0,0 +1,41 @@ +'use strict'; + +var software = require('./../package.json'); +var env = require('./../env')(); +var http = require('http'); +var util = require('./util'); + +main(); + +function main() { + send_entry_rest(); +} + +function send_entry_rest() { + var new_cgm_record = util.get_cgm_record(); + var new_cgm_record_string = JSON.stringify(new_cgm_record); + + var options = { + host: 'localhost', + port: env.PORT, + path: '/api/v1/entries/', + method: 'POST', + headers: { + 'api-secret' : env.api_secret, + 'Content-Type': 'application/json', + 'Content-Length': new_cgm_record_string.length + } + }; + + var req = http.request(options, function(res) { + console.log("Ok: ", res.statusCode); + }); + + req.on('error', function(e) { + console.error('error'); + console.error(e); + }); + + req.write(new_cgm_record_string); + req.end(); +} \ No newline at end of file From 4697a739bda805e33aa5a588a46a36a0b350f89e Mon Sep 17 00:00:00 2001 From: Fokko Driesprong Date: Sat, 20 Jun 2015 20:11:45 +0200 Subject: [PATCH 180/937] Modified the assertions in the tests a bit, if the .res is not an array, it doest nog have a .length property and throws an unreadable exception. --- tests/api.entries.test.js | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/tests/api.entries.test.js b/tests/api.entries.test.js index b65ec22000f..72c5a76fb2e 100644 --- a/tests/api.entries.test.js +++ b/tests/api.entries.test.js @@ -37,9 +37,8 @@ describe('Entries REST api', function ( ) { .get('/entries.json?count=' + count) .expect(200) .end(function (err, res) { - // console.log('body', res.body); - res.body.length.should.equal(count); - done( ); + res.body.should.be.instanceof(Array).and.have.lengthOf(count); + done(); }); }); @@ -49,8 +48,7 @@ describe('Entries REST api', function ( ) { .get('/entries.json') .expect(200) .end(function (err, res) { - // console.log('body', res.body); - res.body.length.should.equal(defaultCount); + res.body.should.be.instanceof(Array).and.have.lengthOf(defaultCount); done( ); }); }); @@ -60,9 +58,8 @@ describe('Entries REST api', function ( ) { .get('/entries/current.json') .expect(200) .end(function (err, res) { - res.body.length.should.equal(1); - done( ); - // console.log('err', err, 'res', res); + res.body.should.be.instanceof(Array).and.have.lengthOf(1); + done(); }); }); @@ -74,7 +71,7 @@ describe('Entries REST api', function ( ) { .get('/entries/'+currentId+'.json') .expect(200) .end(function (err, res) { - res.body.length.should.equal(1); + res.body.should.be.instanceof(Array).and.have.lengthOf(1); res.body[0]._id.should.equal(currentId); done( ); }); @@ -88,10 +85,8 @@ describe('Entries REST api', function ( ) { .send(load('json')) .expect(201) .end(function (err, res) { - // console.log(res.body); - res.body.length.should.equal(30); - done( ); - // console.log('err', err, 'res', res); + res.body.should.be.instanceof(Array).and.have.lengthOf(30); + done(); }); }); }); From 13e93dff7c200093d3067be8ff30fc5552c17e7d Mon Sep 17 00:00:00 2001 From: Fokko Driesprong Date: Sat, 20 Jun 2015 20:13:40 +0200 Subject: [PATCH 181/937] Added test for storage itself. --- tests/storage.test.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 tests/storage.test.js diff --git a/tests/storage.test.js b/tests/storage.test.js new file mode 100644 index 00000000000..ff9ff06171c --- /dev/null +++ b/tests/storage.test.js @@ -0,0 +1,28 @@ +'use strict'; + +var request = require('supertest'); +var should = require('should'); + +// TODO: It would be better to have something like the dependency injection pattern for testing the datastore +describe('DATABASE_ABSTRACTION', function ( ) { + var env = require('../env')( ); + + it('Should able to connect to the database', function (done) { + require('../lib/storage')(env, function ( err ) { + should(err).be.exactly(null); + done(); + }); + }); + + it('Should throw an error when connecting to the database with an invalid connection string', function (done) { + (function() { + return require('../lib/storage')(env, function (err) { + }, { + connectionUrl: 'this is invalid' + }); + }).should.throw('URL must be in the format mongodb://user:pass@host:port/dbname'); + + done(); + }); +}); + From d018e9d3f2c4dd58c3f973cfc1e625a9a4827a20 Mon Sep 17 00:00:00 2001 From: Fokko Driesprong Date: Sat, 20 Jun 2015 20:14:45 +0200 Subject: [PATCH 182/937] Oops, forgot to pull the dev-branch. --- testing/util.js | 63 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 testing/util.js diff --git a/testing/util.js b/testing/util.js new file mode 100644 index 00000000000..b434fc627a3 --- /dev/null +++ b/testing/util.js @@ -0,0 +1,63 @@ +'use strict'; + +exports.get_cgm_record = function() { + var dateobj = new Date(); + var datemil = dateobj.getTime(); + var datesec = datemil / 1000; + var datestr = getDateString(dateobj); + + // We put the time in a range from -1 to +1 for every thiry minute period + var range = (datesec % 1800) / 900 - 1.0; + + // The we push through a COS function and scale between 40 and 400 (so it is like a bg level) + var sgv = Math.floor(360 * (Math.cos(10.0 * range / 3.14) / 2 + 0.5)) + 40; + var dir = range > 0.0 ? "FortyFiveDown" : "FortyFiveUp"; + + console.log('Writing Record: '); + console.log('sgv = ' + sgv); + console.log('date = ' + datemil); + console.log('dir = ' + dir); + console.log('str = ' + datestr); + + return { + 'device': 'dexcom', + 'date': datemil, + 'sgv': sgv, + 'direction': dir, + 'dateString': datestr + }; +} + +function getDateString(d) { + + // How I wish js had strftime. This would be one line of code! + + var month = d.getMonth(); + var day = d.getDay(); + var year = d.getFullYear(); + + if (month < 10) month = '0' + month; + if (day < 10) day = '0' + day; + + var hour = d.getHours(); + var min = d.getMinutes(); + var sec = d.getSeconds(); + + var ampm = 'PM'; + if (hour < 12) { + ampm = "AM"; + } + + if (hour == 0) { + hour = 12; + } + if (hour > 12) { + hour = hour - 12; + } + + if (hour < 10) hour = '0' + hour; + if (min < 10) min = '0' + min; + if (sec < 10) sec = '0' + sec; + + return month + '/' + day + '/' + year + ' ' + hour + ':' + min + ':' + sec + ' ' + ampm; +} \ No newline at end of file From 6db10fcdf68916e97b1a8f5aa057697a3c5e1487 Mon Sep 17 00:00:00 2001 From: Fokko Driesprong Date: Sat, 20 Jun 2015 20:17:00 +0200 Subject: [PATCH 183/937] Not really relevant. --- tests/storage.test.js | 28 ---------------------------- 1 file changed, 28 deletions(-) delete mode 100644 tests/storage.test.js diff --git a/tests/storage.test.js b/tests/storage.test.js deleted file mode 100644 index ff9ff06171c..00000000000 --- a/tests/storage.test.js +++ /dev/null @@ -1,28 +0,0 @@ -'use strict'; - -var request = require('supertest'); -var should = require('should'); - -// TODO: It would be better to have something like the dependency injection pattern for testing the datastore -describe('DATABASE_ABSTRACTION', function ( ) { - var env = require('../env')( ); - - it('Should able to connect to the database', function (done) { - require('../lib/storage')(env, function ( err ) { - should(err).be.exactly(null); - done(); - }); - }); - - it('Should throw an error when connecting to the database with an invalid connection string', function (done) { - (function() { - return require('../lib/storage')(env, function (err) { - }, { - connectionUrl: 'this is invalid' - }); - }).should.throw('URL must be in the format mongodb://user:pass@host:port/dbname'); - - done(); - }); -}); - From 7d149bafbda729e46e2d87160c43c193e23819f7 Mon Sep 17 00:00:00 2001 From: Fokko Driesprong Date: Sat, 20 Jun 2015 20:59:08 +0200 Subject: [PATCH 184/937] Correct MongoDB initalization, and removed the Bootsequence plugin as it did not provide any benefits anymore. --- lib/bootevent.js | 110 ++++++++++++++++++-------------------- lib/storage.js | 10 ++-- package.json | 3 +- server.js | 9 +--- tests/api.entries.test.js | 3 +- tests/api.status.test.js | 7 +-- tests/security.test.js | 3 +- 7 files changed, 64 insertions(+), 81 deletions(-) diff --git a/lib/bootevent.js b/lib/bootevent.js index e4af413f07c..abe6fd91659 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -1,74 +1,68 @@ -var bootevent = require('bootevent'); +function boot(env, booted) { + require('./storage')(env, function ready(err, store) { -function boot (env) { - var store = require('./storage')(env); - var proc = bootevent( ) - .acquire(function db (ctx, next) { - // initialize db connections - store( function ready ( ) { - console.log('storage system ready'); - ctx.store = store; - next( ); - }); - }) - .acquire(function data (ctx, next) { - /////////////////////////////////////////////////// - // api and json object variables - /////////////////////////////////////////////////// - ctx.plugins = require('./plugins')().registerServerDefaults().init(env); - ctx.pushnotify = require('./pushnotify')(env, ctx); - ctx.entries = require('./entries')(env, ctx); - ctx.treatments = require('./treatments')(env, ctx); - ctx.devicestatus = require('./devicestatus')(env.devicestatus_collection, ctx); - ctx.profile = require('./profile')(env.profile_collection, ctx); - ctx.pebble = require('./pebble')(env, ctx); + if(err) + console.warn('Could not succesfully connect to MongoDB.') - console.info("Ensuring indexes"); - store.ensureIndexes(ctx.entries( ), ctx.entries.indexedFields); - store.ensureIndexes(ctx.treatments( ), ctx.treatments.indexedFields); - store.ensureIndexes(ctx.devicestatus( ), ctx.devicestatus.indexedFields); - store.ensureIndexes(ctx.profile( ), ctx.profile.indexedFields); + console.log('Storage system ready'); + var ctx = { + store: store + }; - ctx.bus = require('./bus')(env, ctx); + /////////////////////////////////////////////////// + // api and json object variables + /////////////////////////////////////////////////// + ctx.plugins = require('./plugins')().registerServerDefaults().init(env); + ctx.pushnotify = require('./pushnotify')(env, ctx); + ctx.entries = require('./entries')(env, ctx); + ctx.treatments = require('./treatments')(env, ctx); + ctx.devicestatus = require('./devicestatus')(env.devicestatus_collection, ctx); + ctx.profile = require('./profile')(env.profile_collection, ctx); + ctx.pebble = require('./pebble')(env, ctx); - ctx.data = require('./data')(env, ctx); - ctx.notifications = require('./notifications')(env, ctx); + console.info("Ensuring indexes"); + store.ensureIndexes(ctx.entries(), ctx.entries.indexedFields); + store.ensureIndexes(ctx.treatments(), ctx.treatments.indexedFields); + store.ensureIndexes(ctx.devicestatus(), ctx.devicestatus.indexedFields); + store.ensureIndexes(ctx.profile(), ctx.profile.indexedFields); - function updateData ( ) { - ctx.data.update(function dataUpdated () { - ctx.bus.emit('data-loaded'); - }); - } + ctx.bus = require('./bus')(env, ctx); - ctx.bus.on('tick', function timedReloadData (tick) { - console.info('tick', tick.now); - updateData(); - }); + ctx.data = require('./data')(env, ctx); + ctx.notifications = require('./notifications')(env, ctx); - ctx.bus.on('data-received', function forceReloadData ( ) { - console.info('got data-received event, reloading now'); - updateData(); - }); + function updateData() { + ctx.data.update(function dataUpdated() { + ctx.bus.emit('data-loaded'); + }); + } - ctx.bus.on('data-loaded', function updatePlugins ( ) { - var sbx = require('./sandbox')().serverInit(env, ctx); - ctx.plugins.setProperties(sbx); - ctx.notifications.initRequests(); - ctx.plugins.checkNotifications(sbx); - ctx.notifications.process(sbx); - ctx.bus.emit('data-processed'); - }); + ctx.bus.on('tick', function timedReloadData(tick) { + console.info('tick', tick.now); + updateData(); + }); - ctx.bus.on('notification', ctx.pushnotify.emitNotification); + ctx.bus.on('data-received', function forceReloadData() { + console.info('got data-received event, reloading now'); + updateData(); + }); + + ctx.bus.on('data-loaded', function updatePlugins() { + var sbx = require('./sandbox')().serverInit(env, ctx); + ctx.plugins.setProperties(sbx); + ctx.notifications.initRequests(); + ctx.plugins.checkNotifications(sbx); + ctx.notifications.process(sbx); + ctx.bus.emit('data-processed'); + }); - ctx.bus.uptime( ); + ctx.bus.on('notification', ctx.pushnotify.emitNotification); - next( ); - }) - ; - return proc; + ctx.bus.uptime(); + booted(ctx); + }); } module.exports = boot; diff --git a/lib/storage.js b/lib/storage.js index 0f8c50ba3fa..2d600dd2801 100644 --- a/lib/storage.js +++ b/lib/storage.js @@ -7,20 +7,20 @@ function init (env, cb) { var my = { }; function maybe_connect (cb) { if (my.db) { - console.log("Reusing mongo connection"); + console.log("Reusing MongoDB connection handler"); if (cb && cb.call) { cb(null, mongo); } return; } if (!env.mongo) { - throw new Error("Mongo string is missing"); + throw new Error("MongoDB connection string is missing"); } - console.log("Connecting to mongo"); + console.log("Setting up new connection to MongoDB"); MongoClient.connect(env.mongo, function connected (err, db) { if (err) { - console.log("Error connecting to mongo, ERROR: %j", err); + console.log("Error connecting to MongoDB, ERROR: %j", err); throw err; } else { - console.log("Connected to mongo"); + console.log("Successfully established a connected to mongo"); } my.db = db; mongo.pool.db = my.db = db; diff --git a/package.json b/package.json index 649081ba62c..ff62a4d44b1 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,6 @@ "dependencies": { "async": "^0.9.0", "body-parser": "^1.4.3", - "bootevent": "0.0.1", "bower": "^1.3.8", "browserify-express": "^0.1.4", "compression": "^1.4.2", @@ -56,7 +55,7 @@ "git-rev": "git://github.com/bewest/git-rev.git", "lodash": "^3.9.1", "long": "~2.2.3", - "mongodb": "^1.4.7", + "mongodb": "2.0.34", "moment": "2.8.1", "mqtt": "~0.3.11", "node-cache": "^3.0.0", diff --git a/server.js b/server.js index 8dcecfce491..bb3c1cc61c8 100644 --- a/server.js +++ b/server.js @@ -43,8 +43,7 @@ function create (app) { return transport.createServer(app); } -var bootevent = require('./lib/bootevent'); -bootevent(env).boot(function booted (ctx) { +require('./lib/bootevent')(env, function booted (ctx) { var app = require('./app')(env, ctx); var server = create(app).listen(PORT); console.log('listening', PORT); @@ -67,8 +66,4 @@ bootevent(env).boot(function booted (ctx) { ctx.bus.on('notification', function(info) { websocket.emitNotification(info); }); - -}) -; - -/////////////////////////////////////////////////// +}); \ No newline at end of file diff --git a/tests/api.entries.test.js b/tests/api.entries.test.js index 064283950d2..5985188f17a 100644 --- a/tests/api.entries.test.js +++ b/tests/api.entries.test.js @@ -12,8 +12,7 @@ describe('Entries REST api', function ( ) { this.app = require('express')( ); this.app.enable('api'); var self = this; - var bootevent = require('../lib/bootevent'); - bootevent(env).boot(function booted (ctx) { + require('../lib/bootevent')(env, function booted (ctx) { self.app.use('/', entries(self.app, self.wares, ctx)); self.archive = require('../lib/entries')(env, ctx); self.archive.create(load('json'), done); diff --git a/tests/api.status.test.js b/tests/api.status.test.js index 88a9bc23b9a..a8f5c1cf58a 100644 --- a/tests/api.status.test.js +++ b/tests/api.status.test.js @@ -9,16 +9,13 @@ describe('Status REST api', function ( ) { env.enable = "careportal rawbg"; env.api_secret = 'this is my long pass phrase'; this.wares = require('../lib/middleware/')(env); - var store = require('../lib/storage')(env); this.app = require('express')( ); this.app.enable('api'); var self = this; - var bootevent = require('../lib/bootevent'); - bootevent(env).boot(function booted (ctx) { - self.app.use('/api', api(env, ctx.entries)); + require('../lib/bootevent')(env, function booted (ctx) { + self.app.use('/api', api(env, ctx.entries)); done(); }); - }); it('should be a module', function ( ) { diff --git a/tests/security.test.js b/tests/security.test.js index 8852aba855b..8154c505c16 100644 --- a/tests/security.test.js +++ b/tests/security.test.js @@ -10,8 +10,7 @@ describe('API_SECRET', function ( ) { var scope = this; function setup_app (env, fn) { - var bootevent = require('../lib/bootevent'); - bootevent(env).boot(function booted (ctx) { + require('../lib/bootevent')(env, function booted (ctx) { var wares = require('../lib/middleware/')(env); ctx.app = api(env, wares, ctx); scope.app = ctx.app; From 8e87e10a8473c50566f929b9ebdd2b378ede2336 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 20 Jun 2015 19:51:56 -0700 Subject: [PATCH 185/937] clean up bootevent, create more phases --- lib/bootevent.js | 137 +++++++++++++++++++++++++++-------------------- 1 file changed, 79 insertions(+), 58 deletions(-) diff --git a/lib/bootevent.js b/lib/bootevent.js index e4af413f07c..f65c039251e 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -1,72 +1,93 @@ +'use strict'; var bootevent = require('bootevent'); function boot (env) { - var store = require('./storage')(env); - var proc = bootevent( ) - .acquire(function db (ctx, next) { - // initialize db connections - store( function ready ( ) { - console.log('storage system ready'); - ctx.store = store; - next( ); - }); - }) - .acquire(function data (ctx, next) { - /////////////////////////////////////////////////// - // api and json object variables - /////////////////////////////////////////////////// - ctx.plugins = require('./plugins')().registerServerDefaults().init(env); - ctx.pushnotify = require('./pushnotify')(env, ctx); - ctx.entries = require('./entries')(env, ctx); - ctx.treatments = require('./treatments')(env, ctx); - ctx.devicestatus = require('./devicestatus')(env.devicestatus_collection, ctx); - ctx.profile = require('./profile')(env.profile_collection, ctx); - ctx.pebble = require('./pebble')(env, ctx); - - console.info("Ensuring indexes"); - store.ensureIndexes(ctx.entries( ), ctx.entries.indexedFields); - store.ensureIndexes(ctx.treatments( ), ctx.treatments.indexedFields); - store.ensureIndexes(ctx.devicestatus( ), ctx.devicestatus.indexedFields); - store.ensureIndexes(ctx.profile( ), ctx.profile.indexedFields); - - ctx.bus = require('./bus')(env, ctx); - - ctx.data = require('./data')(env, ctx); - ctx.notifications = require('./notifications')(env, ctx); - - function updateData ( ) { - ctx.data.update(function dataUpdated () { - ctx.bus.emit('data-loaded'); - }); - } - - ctx.bus.on('tick', function timedReloadData (tick) { - console.info('tick', tick.now); - updateData(); - }); - ctx.bus.on('data-received', function forceReloadData ( ) { - console.info('got data-received event, reloading now'); - updateData(); - }); + function setupMongo (ctx, next) { + var store = require('./storage')(env); + // initialize db connections + store( function ready ( ) { + console.log('storage system ready'); + ctx.store = store; + + next( ); + }); + } + + function setupInternals (ctx, next) { + /////////////////////////////////////////////////// + // api and json object variables + /////////////////////////////////////////////////// + ctx.plugins = require('./plugins')().registerServerDefaults().init(env); + ctx.pushnotify = require('./pushnotify')(env, ctx); + ctx.entries = require('./entries')(env, ctx); + ctx.treatments = require('./treatments')(env, ctx); + ctx.devicestatus = require('./devicestatus')(env.devicestatus_collection, ctx); + ctx.profile = require('./profile')(env.profile_collection, ctx); + ctx.pebble = require('./pebble')(env, ctx); + ctx.bus = require('./bus')(env, ctx); + ctx.data = require('./data')(env, ctx); + ctx.notifications = require('./notifications')(env, ctx); + + next( ); + } + + function ensureIndexes (ctx, next) { + console.info("Ensuring indexes"); + ctx.store.ensureIndexes(ctx.entries( ), ctx.entries.indexedFields); + ctx.store.ensureIndexes(ctx.treatments( ), ctx.treatments.indexedFields); + ctx.store.ensureIndexes(ctx.devicestatus( ), ctx.devicestatus.indexedFields); + ctx.store.ensureIndexes(ctx.profile( ), ctx.profile.indexedFields); - ctx.bus.on('data-loaded', function updatePlugins ( ) { - var sbx = require('./sandbox')().serverInit(env, ctx); - ctx.plugins.setProperties(sbx); - ctx.notifications.initRequests(); - ctx.plugins.checkNotifications(sbx); - ctx.notifications.process(sbx); - ctx.bus.emit('data-processed'); + next( ); + } + + function setupListeners (ctx, next) { + function updateData ( ) { + ctx.data.update(function dataUpdated () { + ctx.bus.emit('data-loaded'); }); + } - ctx.bus.on('notification', ctx.pushnotify.emitNotification); + ctx.bus.on('tick', function timedReloadData (tick) { + console.info('tick', tick.now); + updateData(); + }); - ctx.bus.uptime( ); + ctx.bus.on('data-received', function forceReloadData ( ) { + console.info('got data-received event, reloading now'); + updateData(); + }); - next( ); - }) + ctx.bus.on('data-loaded', function updatePlugins ( ) { + var sbx = require('./sandbox')().serverInit(env, ctx); + ctx.plugins.setProperties(sbx); + ctx.notifications.initRequests(); + ctx.plugins.checkNotifications(sbx); + ctx.notifications.process(sbx); + ctx.bus.emit('data-processed'); + }); + + ctx.bus.on('notification', ctx.pushnotify.emitNotification); + + next( ); + } + + function finishBoot (ctx, next) { + ctx.bus.uptime( ); + + next( ); + } + + var proc = bootevent( ) + .acquire(setupMongo) + .acquire(setupInternals) + .acquire(ensureIndexes) + .acquire(setupListeners) + .acquire(finishBoot) ; + return proc; } From 7af04ee422aece54484c381db815a294eaff775e Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 20 Jun 2015 21:02:18 -0700 Subject: [PATCH 186/937] made BG delta a normal plugin --- env.js | 9 ++++++++- lib/plugins/delta.js | 42 +++++++++++++++++++++++++++++++++++++++ lib/plugins/index.js | 3 ++- lib/plugins/pluginbase.js | 29 +++++++++++++++++++-------- static/js/client.js | 34 ------------------------------- tests/delta.test.js | 32 +++++++++++++++++++++++++++++ 6 files changed, 105 insertions(+), 44 deletions(-) create mode 100644 lib/plugins/delta.js create mode 100644 tests/delta.test.js diff --git a/env.js b/env.js index 86d6e8424e4..7f17514defc 100644 --- a/env.js +++ b/env.js @@ -93,6 +93,11 @@ function config ( ) { env.defaults.alarmTimeAgoUrgentMins = readENV('ALARM_TIMEAGO_URGENT_MINS', env.defaults.alarmTimeAgoUrgentMins); env.defaults.showPlugins = readENV('SHOW_PLUGINS', ''); + //TODO: figure out something for some plugins to have them shown by default + if (env.defaults.showPlugins != '') { + env.defaults.showPlugins += ' delta' + } + //console.log(JSON.stringify(env.defaults)); env.SSL_KEY = readENV('SSL_KEY'); @@ -183,7 +188,9 @@ function config ( ) { //TODO: after config changes are documented this shouldn't be auto enabled env.enable += ' treatmentnotify'; } - env.enable += ' errorcodes'; + + //TODO: figure out something for default plugins, how can they be disabled? + env.enable += ' delta errorcodes'; env.extendedSettings = findExtendedSettings(env.enable, process.env); diff --git a/lib/plugins/delta.js b/lib/plugins/delta.js new file mode 100644 index 00000000000..ae8c4e97187 --- /dev/null +++ b/lib/plugins/delta.js @@ -0,0 +1,42 @@ +'use strict'; + +function init() { + + function delta() { + return delta; + } + + delta.label = 'BG Delta'; + delta.pluginType = 'pill-major'; + delta.pillFlip = true; + + delta.setProperties = function setProperties (sbx) { + sbx.offerProperty('delta', function setDelta ( ) { + + var result = { display: null }; + + if (sbx.data.sgvs.length < 2) { return result; } + + var lastSVG = sbx.data.sgvs[sbx.data.sgvs.length - 1].y; + var prevSVG = sbx.data.sgvs[sbx.data.sgvs.length - 2].y; + + if (lastSVG < 40 || prevSVG < 40) { return result; } + + result.value = sbx.scaleBg(lastSVG) - sbx.scaleBg(prevSVG); + result.display = (result.value >= 0 ? '+' : '') + result.value; + + return result; + }); + }; + + delta.updateVisualisation = function updateVisualisation (sbx) { + var info = null; + var prop = sbx.properties.delta; + sbx.pluginBase.updatePillText(delta, prop.display, sbx.unitsLabel, info); + }; + + return delta(); + +} + +module.exports = init; \ No newline at end of file diff --git a/lib/plugins/index.js b/lib/plugins/index.js index 5a7c3ce27c9..c6f596f322f 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -14,7 +14,8 @@ function init() { plugins.base = require('./pluginbase'); var clientDefaultPlugins = [ - require('./iob')() + require('./delta')() + , require('./iob')() , require('./cob')() , require('./boluswizardpreview')() , require('./cannulaage')() diff --git a/lib/plugins/pluginbase.js b/lib/plugins/pluginbase.js index 9ef44913970..82d748a5dd7 100644 --- a/lib/plugins/pluginbase.js +++ b/lib/plugins/pluginbase.js @@ -8,23 +8,36 @@ function init (majorPills, minorPills, tooltip) { return pluginBase; } - pluginBase.updatePillText = function updatePillText (plugin, updatedText, label, info) { - - var pillName = "span.pill." + plugin.name; - + function findOrCreatePill (plugin, label) { var container = plugin.pluginType == 'pill-major' ? majorPills : minorPills; - + var pillName = "span.pill." + plugin.name; var pill = container.find(pillName); if (!pill || pill.length == 0) { - pill = $(''); + pill = $(''); + var pillLabel = $(''); + var pillValue = $(''); + if (plugin.pillFlip) { + pill.append(pillValue); + pill.append(pillLabel); + } else { + pill.append(pillLabel); + pill.append(pillValue); + } + container.append(pill); } - pill.find('em').text(updatedText); + return pill; + } - if (info) { + pluginBase.updatePillText = function updatePillText (plugin, updatedText, label, info) { + var pill = findOrCreatePill(plugin, label); + + pill.find('em').toggle(updatedText != null).text(updatedText); + + if (info) { var html = _.map(info, function mapInfo (i) { return '' + i.label + ' ' + i.value; }).join('
    \n'); diff --git a/static/js/client.js b/static/js/client.js index 4c0f0bc1c38..299eada6b03 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -469,30 +469,6 @@ function nsArrayDiff(oldArray, newArray) { } - function updateBGDelta(prevEntry, currentEntry) { - - var pill = majorPills.find('span.pill.bgdelta'); - if (!pill || pill.length == 0) { - pill = $(''); - majorPills.append(pill); - } - - var deltaDisplay = calcDeltaDisplay(prevEntry, currentEntry); - - if (deltaDisplay == null) { - pill.children('em').hide(); - } else { - pill.children('em').text(deltaDisplay).show(); - } - - if (browserSettings.units == 'mmol') { - pill.children('label').text('mmol/L'); - } else { - pill.children('label').text('mg/dL'); - } - - } - function updatePlugins(sgvs, time) { var pluginBase = Nightscout.plugins.base(majorPills, minorPills, tooltip); @@ -543,15 +519,7 @@ function nsArrayDiff(oldArray, newArray) { if (focusPoint) { updateCurrentSGV(focusPoint); currentDirection.html(focusPoint.y < 39 ? '✖' : focusPoint.direction); - - var prevfocusPoint = nowData.length > lookback ? nowData[nowData.length - 2] : null; - if (prevfocusPoint) { - updateBGDelta(prevfocusPoint, focusPoint); - } else { - updateBGDelta(); - } } else { - updateBGDelta(); currentBG.text('---'); currentDirection.text('-'); rawNoise.hide(); @@ -591,8 +559,6 @@ function nsArrayDiff(oldArray, newArray) { $('#uploaderBattery').hide(); } - updateBGDelta(prevSGV, latestSGV); - updatePlugins(nowData, nowDate); currentDirection.html(latestSGV.y < 39 ? '✖' : latestSGV.direction); diff --git a/tests/delta.test.js b/tests/delta.test.js new file mode 100644 index 00000000000..788811ad01f --- /dev/null +++ b/tests/delta.test.js @@ -0,0 +1,32 @@ +'use strict'; + +var should = require('should'); + +describe('Delta', function ( ) { + var delta = require('../lib/plugins/delta')(); + var sandbox = require('../lib/sandbox')(); + + var pluginBase = {}; + var data = {sgvs: [{y: 100}, {y: 105}]}; + var app = { }; + var clientSettings = { + units: 'mg/dl' + }; + + it('should calculate BG Delta', function (done) { + var sbx = sandbox.clientInit(app, clientSettings, Date.now(), pluginBase, data); + + sbx.offerProperty = function mockedOfferProperty (name, setter) { + name.should.equal('delta'); + var result = setter(); + result.value.should.equal(5); + result.display.should.equal('+5'); + done() + }; + + delta.setProperties(sbx); + + }); + + +}); From 0e45c3f1d4113435f17fd2d7a0b5a55bf0081a52 Mon Sep 17 00:00:00 2001 From: Fokko Driesprong Date: Sun, 21 Jun 2015 09:09:32 +0200 Subject: [PATCH 187/937] Restored bootevent in the package.json --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index ff62a4d44b1..463eca41490 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "async": "^0.9.0", "body-parser": "^1.4.3", "bower": "^1.3.8", + "bootevent": "0.0.1", "browserify-express": "^0.1.4", "compression": "^1.4.2", "errorhandler": "^1.1.1", From 6b8a17b7818c6fa7d63180570ada0e0ef09ac7c6 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 21 Jun 2015 00:21:40 -0700 Subject: [PATCH 188/937] converted the uploader battery pill to a plugin; some refactoring and cleanup --- env.js | 4 +- lib/plugins/boluswizardpreview.js | 17 +++++---- lib/plugins/cannulaage.js | 6 ++- lib/plugins/cob.js | 6 ++- lib/plugins/delta.js | 6 ++- lib/plugins/index.js | 1 + lib/plugins/iob.js | 6 ++- lib/plugins/pluginbase.js | 43 +++++++++++++++++----- lib/plugins/upbat.js | 61 +++++++++++++++++++++++++++++++ static/css/main.css | 52 ++++++++++++-------------- static/index.html | 9 ++--- static/js/client.js | 22 ++--------- tests/cannulaage.test.js | 4 +- tests/upbat.test.js | 49 +++++++++++++++++++++++++ 14 files changed, 207 insertions(+), 79 deletions(-) create mode 100644 lib/plugins/upbat.js create mode 100644 tests/upbat.test.js diff --git a/env.js b/env.js index 7f17514defc..6321bbee4fa 100644 --- a/env.js +++ b/env.js @@ -95,7 +95,7 @@ function config ( ) { //TODO: figure out something for some plugins to have them shown by default if (env.defaults.showPlugins != '') { - env.defaults.showPlugins += ' delta' + env.defaults.showPlugins += ' delta upbat' } //console.log(JSON.stringify(env.defaults)); @@ -190,7 +190,7 @@ function config ( ) { } //TODO: figure out something for default plugins, how can they be disabled? - env.enable += ' delta errorcodes'; + env.enable += ' delta upbat errorcodes'; env.extendedSettings = findExtendedSettings(env.enable, process.env); diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index a519713c584..5afb1000bc6 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -94,14 +94,15 @@ function init() { var results = bwp.calc(sbx); - // display text - var info = [ - {label: 'Insulin on Board', value: results.displayIOB + 'U'} - , {label: 'Expected effect', value: '-' + results.effectDisplay + ' ' + sbx.units} - , {label: 'Expected outcome', value: results.outcomeDisplay + ' ' + sbx.units} - ]; - - sbx.pluginBase.updatePillText(bwp, results.bolusEstimateDisplay + 'U', 'BWP', info); + sbx.pluginBase.updatePillText(bwp, { + value: results.bolusEstimateDisplay + 'U' + , label: 'BWP' + , info: [ + {label: 'Insulin on Board', value: results.displayIOB + 'U'} + , {label: 'Expected effect', value: '-' + results.effectDisplay + ' ' + sbx.units} + , {label: 'Expected outcome', value: results.outcomeDisplay + ' ' + sbx.units} + ] + }); }; diff --git a/lib/plugins/cannulaage.js b/lib/plugins/cannulaage.js index 41fd6c75344..50182984ccd 100644 --- a/lib/plugins/cannulaage.js +++ b/lib/plugins/cannulaage.js @@ -40,7 +40,11 @@ function init() { var info = [{label: 'Inserted:', value: moment(treatmentDate).format('lll')}]; if (message != '') info.push({label: 'Notes:', value: message}); - sbx.pluginBase.updatePillText(cage, age + 'h', 'CAGE', info); + sbx.pluginBase.updatePillText(cage, { + value: age + 'h' + , label: 'CAGE' + , info: info + }); }; diff --git a/lib/plugins/cob.js b/lib/plugins/cob.js index 7ce5e71619e..4877a5e464d 100644 --- a/lib/plugins/cob.js +++ b/lib/plugins/cob.js @@ -158,7 +158,11 @@ function init() { info = [{label: 'Last Carbs', value: amount + ' @ ' + when }] } - sbx.pluginBase.updatePillText(sbx, displayCob + " g", 'COB', info); + sbx.pluginBase.updatePillText(sbx, { + value: displayCob + " g" + , label: 'COB' + , info: info + }); }; return cob(); diff --git a/lib/plugins/delta.js b/lib/plugins/delta.js index ae8c4e97187..f5a10c0fe06 100644 --- a/lib/plugins/delta.js +++ b/lib/plugins/delta.js @@ -30,9 +30,11 @@ function init() { }; delta.updateVisualisation = function updateVisualisation (sbx) { - var info = null; var prop = sbx.properties.delta; - sbx.pluginBase.updatePillText(delta, prop.display, sbx.unitsLabel, info); + sbx.pluginBase.updatePillText(delta, { + value: prop.display + , label: sbx.unitsLabel + }); }; return delta(); diff --git a/lib/plugins/index.js b/lib/plugins/index.js index c6f596f322f..c874db5004b 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -19,6 +19,7 @@ function init() { , require('./cob')() , require('./boluswizardpreview')() , require('./cannulaage')() + , require('./upbat')() ]; var serverDefaultPlugins = [ diff --git a/lib/plugins/iob.js b/lib/plugins/iob.js index 806107f61c2..5df2f9a56dc 100644 --- a/lib/plugins/iob.js +++ b/lib/plugins/iob.js @@ -99,7 +99,11 @@ function init() { info = [{label: 'Last Bolus', value: amount + ' @ ' + when }] } - sbx.pluginBase.updatePillText(iob, sbx.roundInsulinForDisplayFormat(prop.display) + 'U', 'IOB', info); + sbx.pluginBase.updatePillText(iob, { + value: sbx.roundInsulinForDisplayFormat(prop.display) + 'U' + , label: 'IOB' + , info: info + }); }; diff --git a/lib/plugins/pluginbase.js b/lib/plugins/pluginbase.js index 82d748a5dd7..55e4599935b 100644 --- a/lib/plugins/pluginbase.js +++ b/lib/plugins/pluginbase.js @@ -2,20 +2,31 @@ var _ = require('lodash'); -function init (majorPills, minorPills, tooltip) { +function init (majorPills, minorPills, statusPills, tooltip) { function pluginBase ( ) { return pluginBase; } - function findOrCreatePill (plugin, label) { - var container = plugin.pluginType == 'pill-major' ? majorPills : minorPills; + function findOrCreatePill (plugin) { + var container = null; + + if (plugin.pluginType == 'pill-major') { + container = majorPills; + } else if (plugin.pluginType == 'pill-status') { + container = statusPills; + } else { + container = minorPills + } + var pillName = "span.pill." + plugin.name; var pill = container.find(pillName); + var classes = 'pill ' + plugin.name; + if (!pill || pill.length == 0) { - pill = $(''); - var pillLabel = $(''); + pill = $(''); + var pillLabel = $(''); var pillValue = $(''); if (plugin.pillFlip) { pill.append(pillValue); @@ -26,19 +37,31 @@ function init (majorPills, minorPills, tooltip) { } container.append(pill); + } else { + //reset in case a pill class was added and needs to be removed + pill.attr('class', classes); } return pill; } - pluginBase.updatePillText = function updatePillText (plugin, updatedText, label, info) { + pluginBase.updatePillText = function updatePillText (plugin, options) { + + var pill = findOrCreatePill(plugin); + + pill.toggle(!options.hide); + pill.addClass(options.pillClass); - var pill = findOrCreatePill(plugin, label); + pill.find('label').attr('class', options.labelClass).text(options.label); - pill.find('em').toggle(updatedText != null).text(updatedText); + pill.find('em') + .attr('class', options.valueClass) + .toggle(options.value != null) + .text(options.value) + ; - if (info) { - var html = _.map(info, function mapInfo (i) { + if (options.info) { + var html = _.map(options.info, function mapInfo (i) { return '' + i.label + ' ' + i.value; }).join('
    \n'); diff --git a/lib/plugins/upbat.js b/lib/plugins/upbat.js new file mode 100644 index 00000000000..2adb4df912e --- /dev/null +++ b/lib/plugins/upbat.js @@ -0,0 +1,61 @@ +'use strict'; + +function init() { + + function upbat() { + return upbat; + } + + upbat.label = 'Uploader Battery'; + upbat.pluginType = 'pill-status'; + upbat.pillFlip = true; + + upbat.setProperties = function setProperties (sbx) { + sbx.offerProperty('upbat', function setUpbat ( ) { + + var result = { display: null }; + + var battery = sbx.data.uploaderBattery; + + if (battery) { + result.value = battery; + result.display = battery + '%'; + + if (battery >= 95) { + result.level = 100; + } else if (battery < 95 && battery >= 55) { + result.level = 75; + } else if (battery < 55 && battery >= 30) { + result.level = 50; + } else { + result.level = 25; + } + + if (battery <= 30 && battery > 20) { + result.status = 'warn'; + } else if (battery <= 20) { + result.status = 'urgent'; + } else { + result.status = null; + } + } + + return result; + }); + }; + + upbat.updateVisualisation = function updateVisualisation (sbx) { + var prop = sbx.properties.upbat; + + sbx.pluginBase.updatePillText(upbat, { + value: prop && prop.display + , labelClass: prop && prop.level && 'icon-battery-' + prop.level + , pillClass: prop && prop.status + }); + }; + + return upbat(); + +} + +module.exports = init; \ No newline at end of file diff --git a/static/css/main.css b/static/css/main.css index 4f1bc73b5e9..bb119162bb4 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -27,14 +27,14 @@ body { z-index: 2; } -.status { +.primary { font-family: 'Ubuntu', Helvetica, Arial, sans-serif; height: 180px; vertical-align: middle; clear: left right; } -.has-minor-pills .status { +.has-minor-pills .primary { height: 200px; } @@ -145,44 +145,40 @@ body { text-decoration: line-through; } -.time { +.status { color: #808080; font-size: 100px; line-height: 100px; } -.timebox { +.statusBox { text-align: center; width: 250px; } -.timeOther { +.statusPills { font-size: 20px; line-height: 30px; } -.loading .timeOther { +.loading .statusPills { display: none; } -.timeOther .pill { +.statusPills .pill { background: #808080; border-color: #808080; } -.timeOther .pill em { +.statusPills .pill em { color: #bdbdbd; background: #000; border-radius: 4px 0 0 4px; } -.timeOther .pill label { +.statusPills .pill label { background: #808080; } -#uploaderBattery { - display: none; -} - -#uploaderBattery label { +.pill.upbat label { padding: 0 !important; } @@ -373,18 +369,18 @@ div.tooltip { font-size: 16px; } - .time { + .status { font-size: 70px; line-height: 60px; padding-top: 8px; } - .timeOther { + .statusPills { font-size: 15px; line-height: 40px; } - #uploaderBattery label { + .pill.upbat label { font-size: 15px !important; } @@ -455,7 +451,7 @@ div.tooltip { font-size: 12px; } - .time { + .status { font-size: 50px; line-height: 35px; width: 250px; @@ -471,7 +467,7 @@ div.tooltip { } @media (max-width: 400px) { - .status { + .primary { text-align: center; margin-bottom: 0; height: 152px; @@ -517,12 +513,12 @@ div.tooltip { font-size: 20px; } - .time { + .status { padding-top: 0; font-size: 20px !important; } - .timebox { + .statusBox { width: auto; } @@ -531,13 +527,13 @@ div.tooltip { vertical-align: middle; } - .timeOther { + .statusPills { display: inline; margin-left: auto; font-size: 15px; } - #uploaderBattery label { + .pill.upbat label { font-size: 15px !important; } @@ -639,17 +635,17 @@ div.tooltip { font-size: 12px; } - .time { + .status { font-size: 50px; line-height: 40px; padding-top: 5px; } - .timeOther { + .statusPills { font-size: 15px; } - #uploaderBattery label { + .pill.upbat label { font-size: 15px !important; } @@ -703,12 +699,12 @@ div.tooltip { display: block; } - .time { + .status { position: absolute; top: 90px; } - .timebox { + .statusBox { width: 220px; } diff --git a/static/index.html b/static/index.html index b422ed627bb..e8c5d0bae0c 100644 --- a/static/index.html +++ b/static/index.html @@ -44,7 +44,7 @@

    Nightscout

    -
    +
    @@ -61,12 +61,11 @@

    Nightscout

    -
    -
    +
    +
    ---
    -
    +
    -
    diff --git a/static/js/client.js b/static/js/client.js index 299eada6b03..045d92dcddc 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -419,6 +419,7 @@ function nsArrayDiff(oldArray, newArray) { , currentDirection = $('.bgStatus .currentDirection') , majorPills = $('.bgStatus .majorPills') , minorPills = $('.bgStatus .minorPills') + , statusPills = $('.status .statusPills') , rawNoise = bgButton.find('.rawnoise') , rawbg = rawNoise.find('em') , noiseLevel = rawNoise.find('label') @@ -471,12 +472,13 @@ function nsArrayDiff(oldArray, newArray) { function updatePlugins(sgvs, time) { - var pluginBase = Nightscout.plugins.base(majorPills, minorPills, tooltip); + var pluginBase = Nightscout.plugins.base(majorPills, minorPills, statusPills, tooltip); var sbx = Nightscout.sandbox.clientInit(app, browserSettings, time, pluginBase, { sgvs: sgvs , treatments: treatments , profile: profile + , uploaderBattery: devicestatusData && devicestatusData.uploaderBattery }); //all enabled plugins get a chance to set properties, even if they aren't shown @@ -541,24 +543,6 @@ function nsArrayDiff(oldArray, newArray) { updateCurrentSGV(latestSGV); updateClockDisplay(); updateTimeAgo(); - - var battery = devicestatusData && devicestatusData.uploaderBattery; - if (battery) { - $('#uploaderBattery em').text(battery + '%'); - $('#uploaderBattery label') - .toggleClass('icon-battery-100', battery >= 95) - .toggleClass('icon-battery-75', battery < 95 && battery >= 55) - .toggleClass('icon-battery-50', battery < 55 && battery >= 30) - .toggleClass('icon-battery-25', battery < 30); - - $('#uploaderBattery') - .show() - .toggleClass('warn', battery <= 30 && battery > 20) - .toggleClass('urgent', battery <= 20); - } else { - $('#uploaderBattery').hide(); - } - updatePlugins(nowData, nowDate); currentDirection.html(latestSGV.y < 39 ? '✖' : latestSGV.direction); diff --git a/tests/cannulaage.test.js b/tests/cannulaage.test.js index 2db06434073..2140b645391 100644 --- a/tests/cannulaage.test.js +++ b/tests/cannulaage.test.js @@ -14,8 +14,8 @@ describe('cage', function ( ) { }; var pluginBase = { - updatePillText: function mockedUpdatePillText (plugin, updatedText, label, info) { - updatedText.should.equal('24h'); + updatePillText: function mockedUpdatePillText (plugin, options) { + options.value.should.equal('24h'); done(); } }; diff --git a/tests/upbat.test.js b/tests/upbat.test.js new file mode 100644 index 00000000000..9b71d965c21 --- /dev/null +++ b/tests/upbat.test.js @@ -0,0 +1,49 @@ +'use strict'; + +var should = require('should'); + +describe('Uploader Battery', function ( ) { + var data = {uploaderBattery: 20}; + var app = { }; + var clientSettings = {}; + + it('display uploader battery status', function (done) { + var sandbox = require('../lib/sandbox')(); + var sbx = sandbox.clientInit(app, clientSettings, Date.now(), {}, data); + + sbx.offerProperty = function mockedOfferProperty (name, setter) { + name.should.equal('upbat'); + var result = setter(); + result.value.should.equal(20); + result.display.should.equal('20%'); + result.status.should.equal('urgent'); + result.level.should.equal(25); + done() + }; + + var upbat = require('../lib/plugins/upbat')(); + upbat.setProperties(sbx); + + }); + + it('set a pill to the uploader battery status', function (done) { + var pluginBase = { + updatePillText: function mockedUpdatePillText (plugin, options) { + options.value.should.equal('20%'); + options.labelClass.should.equal('icon-battery-25'); + options.pillClass.should.equal('urgent'); + done(); + } + }; + + var sandbox = require('../lib/sandbox')(); + var sbx = sandbox.clientInit(app, clientSettings, Date.now(), pluginBase, data); + var upbat = require('../lib/plugins/upbat')(); + upbat.setProperties(sbx); + upbat.updateVisualisation(sbx); + + }); + + + +}); From c9546bb167e8cb011c597e0c6d923f66d64a0c79 Mon Sep 17 00:00:00 2001 From: Fokko Driesprong Date: Sun, 21 Jun 2015 09:23:36 +0200 Subject: [PATCH 189/937] Restored the init-code. Now run some tests. --- lib/bootevent.js | 72 ++------------------------------------- tests/api.entries.test.js | 2 +- tests/api.status.test.js | 2 +- tests/security.test.js | 2 +- 4 files changed, 5 insertions(+), 73 deletions(-) diff --git a/lib/bootevent.js b/lib/bootevent.js index ed43eb51882..91006b1c049 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -1,68 +1,5 @@ 'use strict'; -function boot(env, booted) { - require('./storage')(env, function ready(err, store) { - -<<<<<<< HEAD - if(err) - console.warn('Could not succesfully connect to MongoDB.') - - console.log('Storage system ready'); - var ctx = { - store: store - }; - - /////////////////////////////////////////////////// - // api and json object variables - /////////////////////////////////////////////////// - ctx.plugins = require('./plugins')().registerServerDefaults().init(env); - ctx.pushnotify = require('./pushnotify')(env, ctx); - ctx.entries = require('./entries')(env, ctx); - ctx.treatments = require('./treatments')(env, ctx); - ctx.devicestatus = require('./devicestatus')(env.devicestatus_collection, ctx); - ctx.profile = require('./profile')(env.profile_collection, ctx); - ctx.pebble = require('./pebble')(env, ctx); - - console.info("Ensuring indexes"); - store.ensureIndexes(ctx.entries(), ctx.entries.indexedFields); - store.ensureIndexes(ctx.treatments(), ctx.treatments.indexedFields); - store.ensureIndexes(ctx.devicestatus(), ctx.devicestatus.indexedFields); - store.ensureIndexes(ctx.profile(), ctx.profile.indexedFields); - - ctx.bus = require('./bus')(env, ctx); - - ctx.data = require('./data')(env, ctx); - ctx.notifications = require('./notifications')(env, ctx); - - function updateData() { - ctx.data.update(function dataUpdated() { - ctx.bus.emit('data-loaded'); - }); - } - - ctx.bus.on('tick', function timedReloadData(tick) { - console.info('tick', tick.now); - updateData(); - }); - - ctx.bus.on('data-received', function forceReloadData() { - console.info('got data-received event, reloading now'); - updateData(); - }); - - ctx.bus.on('data-loaded', function updatePlugins() { - var sbx = require('./sandbox')().serverInit(env, ctx); - ctx.plugins.setProperties(sbx); - ctx.notifications.initRequests(); - ctx.plugins.checkNotifications(sbx); - ctx.notifications.process(sbx); - ctx.bus.emit('data-processed'); - }); - - ctx.bus.on('notification', ctx.pushnotify.emitNotification); - - ctx.bus.uptime(); -======= function boot (env) { function setupMongo (ctx, next) { @@ -141,19 +78,14 @@ function boot (env) { next( ); } - var proc = bootevent( ) + var proc = require('bootevent')( ) .acquire(setupMongo) .acquire(setupInternals) .acquire(ensureIndexes) .acquire(setupListeners) - .acquire(finishBoot) - ; + .acquire(finishBoot); return proc; ->>>>>>> 8e87e10a8473c50566f929b9ebdd2b378ede2336 - - booted(ctx); - }); } module.exports = boot; diff --git a/tests/api.entries.test.js b/tests/api.entries.test.js index 5985188f17a..39a7c435bb8 100644 --- a/tests/api.entries.test.js +++ b/tests/api.entries.test.js @@ -12,7 +12,7 @@ describe('Entries REST api', function ( ) { this.app = require('express')( ); this.app.enable('api'); var self = this; - require('../lib/bootevent')(env, function booted (ctx) { + require('../lib/bootevent')(env).boot(function booted (ctx) { self.app.use('/', entries(self.app, self.wares, ctx)); self.archive = require('../lib/entries')(env, ctx); self.archive.create(load('json'), done); diff --git a/tests/api.status.test.js b/tests/api.status.test.js index a8f5c1cf58a..435e6d82246 100644 --- a/tests/api.status.test.js +++ b/tests/api.status.test.js @@ -12,7 +12,7 @@ describe('Status REST api', function ( ) { this.app = require('express')( ); this.app.enable('api'); var self = this; - require('../lib/bootevent')(env, function booted (ctx) { + require('../lib/bootevent')(env).boot(function booted (ctx) { self.app.use('/api', api(env, ctx.entries)); done(); }); diff --git a/tests/security.test.js b/tests/security.test.js index 8154c505c16..d5a5b193763 100644 --- a/tests/security.test.js +++ b/tests/security.test.js @@ -10,7 +10,7 @@ describe('API_SECRET', function ( ) { var scope = this; function setup_app (env, fn) { - require('../lib/bootevent')(env, function booted (ctx) { + require('../lib/bootevent')(env).boot(function booted (ctx) { var wares = require('../lib/middleware/')(env); ctx.app = api(env, wares, ctx); scope.app = ctx.app; From 5bf9ad96b97ada18b3f02c0f732b08b250cfb7fe Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 21 Jun 2015 02:47:21 -0700 Subject: [PATCH 190/937] converted rawbg/noise into a plugin, refactoring, tests, etc --- lib/plugins/delta.js | 33 ++++++----- lib/plugins/index.js | 14 +++-- lib/plugins/pluginbase.js | 4 +- lib/plugins/rawbg.js | 104 +++++++++++++++++++++++++++++++++ lib/sandbox.js | 2 + static/css/main.css | 12 ++-- static/index.html | 2 +- static/js/client.js | 120 +++++++------------------------------- tests/plugins.test.js | 33 +++++++++++ tests/rawbg.test.js | 35 +++++++++++ 10 files changed, 233 insertions(+), 126 deletions(-) create mode 100644 lib/plugins/rawbg.js create mode 100644 tests/plugins.test.js create mode 100644 tests/rawbg.test.js diff --git a/lib/plugins/delta.js b/lib/plugins/delta.js index f5a10c0fe06..2bae298a952 100644 --- a/lib/plugins/delta.js +++ b/lib/plugins/delta.js @@ -12,20 +12,11 @@ function init() { delta.setProperties = function setProperties (sbx) { sbx.offerProperty('delta', function setDelta ( ) { - - var result = { display: null }; - - if (sbx.data.sgvs.length < 2) { return result; } - - var lastSVG = sbx.data.sgvs[sbx.data.sgvs.length - 1].y; - var prevSVG = sbx.data.sgvs[sbx.data.sgvs.length - 2].y; - - if (lastSVG < 40 || prevSVG < 40) { return result; } - - result.value = sbx.scaleBg(lastSVG) - sbx.scaleBg(prevSVG); - result.display = (result.value >= 0 ? '+' : '') + result.value; - - return result; + return delta.calc( + sbx.data.sgvs.length >= 2 ? sbx.data.sgvs[sbx.data.sgvs.length - 2].y : null + , sbx.data.sgvs.length >= 1 ? sbx.data.sgvs[sbx.data.sgvs.length - 1].y : null + , sbx + ); }); }; @@ -37,6 +28,20 @@ function init() { }); }; + delta.calc = function calc(prevSVG, currentSGV, sbx) { + var result = { display: null }; + + if (!prevSVG || !currentSGV) { return result; } + + if (currentSGV < 40 || prevSVG < 40) { return result; } + if (currentSGV > 400 || prevSVG > 400) { return result; } + + result.value = sbx.scaleBg(currentSGV) - sbx.scaleBg(prevSVG); + result.display = (result.value >= 0 ? '+' : '') + result.value; + + return result; + }; + return delta(); } diff --git a/lib/plugins/index.js b/lib/plugins/index.js index c874db5004b..e6976738594 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -7,14 +7,19 @@ function init() { var allPlugins = [] , enabledPlugins = []; - function plugins() { - return plugins; + function plugins(name) { + if (name) { + return _.find(allPlugins, {name: name}); + } else { + return plugins; + } } plugins.base = require('./pluginbase'); var clientDefaultPlugins = [ - require('./delta')() + require('./rawbg')() + , require('./delta')() , require('./iob')() , require('./cob')() , require('./boluswizardpreview')() @@ -23,7 +28,8 @@ function init() { ]; var serverDefaultPlugins = [ - require('./ar2')() + require('./rawbg')() + , require('./ar2')() , require('./simplealarms')() , require('./errorcodes')() , require('./iob')() diff --git a/lib/plugins/pluginbase.js b/lib/plugins/pluginbase.js index 55e4599935b..2cac7e6c447 100644 --- a/lib/plugins/pluginbase.js +++ b/lib/plugins/pluginbase.js @@ -2,7 +2,7 @@ var _ = require('lodash'); -function init (majorPills, minorPills, statusPills, tooltip) { +function init (majorPills, minorPills, statusPills, bgStatus, tooltip) { function pluginBase ( ) { return pluginBase; @@ -15,6 +15,8 @@ function init (majorPills, minorPills, statusPills, tooltip) { container = majorPills; } else if (plugin.pluginType == 'pill-status') { container = statusPills; + } else if (plugin.pluginType == 'pill-alt') { + container = bgStatus; } else { container = minorPills } diff --git a/lib/plugins/rawbg.js b/lib/plugins/rawbg.js new file mode 100644 index 00000000000..f7017070c88 --- /dev/null +++ b/lib/plugins/rawbg.js @@ -0,0 +1,104 @@ +'use strict'; + +var _ = require('lodash'); + +function init() { + + function rawbg() { + return rawbg; + } + + rawbg.label = 'Raw BG'; + rawbg.pluginType = 'pill-alt'; + rawbg.pillFlip = true; + + rawbg.setProperties = function setProperties (sbx) { + sbx.offerProperty('rawbg', function setRawBG ( ) { + var result = { }; + + var currentSGV = _.last(sbx.data.sgvs); + var currentCal = _.last(sbx.data.cals); + + if (currentSGV && currentCal) { + result.value = rawbg.calc(currentSGV, currentCal, sbx); + result.noiseLabel = rawbg.noiseCodeToDisplay(currentSGV.y, currentSGV.noise); + result.sgv = currentSGV; + result.cal = currentCal; + } + + return result; + }); + }; + + rawbg.updateVisualisation = function updateVisualisation (sbx) { + var prop = sbx.properties.rawbg; + + var options = rawbg.showRawBGs(prop.sgv.y, prop.sgv.noise, prop.cal, sbx) ? { + hide: !prop || !prop.value + , value: prop.value + , label: prop.noiseLabel + } : { + hide: true + }; + + sbx.pluginBase.updatePillText(rawbg, options); + }; + + rawbg.calc = function calc(sgv, cal, sbx) { + var raw = 0 + , unfiltered = parseInt(sgv.unfiltered) || 0 + , filtered = parseInt(sgv.filtered) || 0 + , sgv = sgv.y + , scale = parseFloat(cal.scale) || 0 + , intercept = parseFloat(cal.intercept) || 0 + , slope = parseFloat(cal.slope) || 0; + + + if (slope == 0 || unfiltered == 0 || scale == 0) { + raw = 0; + } else if (filtered == 0 || sgv < 40) { + raw = scale * (unfiltered - intercept) / slope; + } else { + var ratio = scale * (filtered - intercept) / slope / sgv; + raw = scale * ( unfiltered - intercept) / slope / ratio; + } + + return sbx.scaleBg(Math.round(raw)); + }; + + rawbg.isEnabled = function isEnabled(sbx) { + return sbx.enable && sbx.enable.indexOf('rawbg') > -1; + }; + + rawbg.showRawBGs = function showRawBGs(sgv, noise, cal, sbx) { + return cal + && rawbg.isEnabled(sbx) + && (sbx.defaults.showRawbg == 'always' + || (sbx.defaults.showRawbg == 'noise' && (noise >= 2 || sgv < 40)) + ); + }; + + rawbg.noiseCodeToDisplay = function noiseCodeToDisplay(sgv, noise) { + var display; + switch (parseInt(noise)) { + case 0: display = '---'; break; + case 1: display = 'Clean'; break; + case 2: display = 'Light'; break; + case 3: display = 'Medium'; break; + case 4: display = 'Heavy'; break; + default: + if (sgv < 40) { + display = 'Heavy'; + } else { + display = '~~~'; + } + break; + } + return display; + }; + + return rawbg(); + +} + +module.exports = init; \ No newline at end of file diff --git a/lib/sandbox.js b/lib/sandbox.js index 74315083cf8..889def13230 100644 --- a/lib/sandbox.js +++ b/lib/sandbox.js @@ -46,6 +46,7 @@ function init ( ) { sbx.time = Date.now(); sbx.units = env.DISPLAY_UNITS; sbx.defaults = env.defaults; + sbx.enable = env.enable; sbx.thresholds = env.thresholds; sbx.alarm_types = env.alarm_types; sbx.data = ctx.data.clone(); @@ -84,6 +85,7 @@ function init ( ) { sbx.units = clientSettings.units; sbx.defaults = clientSettings; //TODO: strip out extra stuff sbx.thresholds = app.thresholds; + sbx.enable = app.enabledOptions; sbx.alarm_types = clientSettings.alarm_types; sbx.showPlugins = clientSettings.showPlugins; sbx.time = time; diff --git a/static/css/main.css b/static/css/main.css index bb119162bb4..585f0b3390c 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -243,24 +243,24 @@ body { width: 360px; } -.loading .bgButton .rawnoise { +.loading .bgButton .pill.rawbg { display: none; } -.bgButton .rawnoise { +.bgButton .pill.rawbg { display: inline-block; border-radius: 2px; border: 1px solid #808080; } -.bgButton .rawnoise em { +.bgButton .pill.rawbg em { color: white; background-color: black; display: block; font-size: 20px; } -.bgButton .rawnoise label { +.bgButton .pill.rawbg label { display: block; font-size: 14px; } @@ -421,11 +421,11 @@ div.tooltip { padding: 0; } - .bgButton .rawnoise em { + .bgButton .pill.rawbg em { font-size: 14px; } - .bgButton .rawnoise label { + .bgButton .pill.rawbg label { font-size: 12px; } diff --git a/static/index.html b/static/index.html index e8c5d0bae0c..9a01e73a811 100644 --- a/static/index.html +++ b/static/index.html @@ -47,7 +47,7 @@

    Nightscout

    - + --- -
    diff --git a/static/js/client.js b/static/js/client.js index 045d92dcddc..b5594841f0e 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -57,6 +57,10 @@ function nsArrayDiff(oldArray, newArray) { , alarmSound = 'alarm.mp3' , urgentAlarmSound = 'alarm2.mp3'; + var sbx + , rawbg + , delta; + var jqWindow , tooltip , tickValues @@ -148,7 +152,7 @@ function nsArrayDiff(oldArray, newArray) { if (currentMgdl < 39) { bg_title = s(errorCodeToDisplay(currentMgdl), ' - ') + bg_title; } else { - var deltaDisplay = calcDeltaDisplay(prevSGV, latestSGV); + var deltaDisplay = delta.calc(prevSGV && prevSGV.y, latestSGV && latestSGV.y, sbx).display; bg_title = s(scaleBg(currentMgdl)) + s(deltaDisplay) + s(decodeEntities(latestSGV.direction)) + bg_title; } } @@ -156,81 +160,6 @@ function nsArrayDiff(oldArray, newArray) { $(document).attr('title', bg_title); } - function calcDeltaDisplay(prevEntry, currentEntry) { - var delta = null - , prevMgdl = prevEntry && prevEntry.y - , currentMgdl = currentEntry && currentEntry.y; - - if (prevMgdl === undefined || currentMgdl == undefined || prevMgdl < 40 || prevMgdl > 400 || currentMgdl < 40 || currentMgdl > 400) { - //TODO consider using raw data here - delta = null; - } else { - delta = scaleBg(currentMgdl) - scaleBg(prevMgdl); - if (browserSettings.units == 'mmol') { - delta = delta.toFixed(1); - } - - delta = (delta >= 0 ? '+' : '') + delta; - } - - return delta; - } - - function isRawBGEnabled() { - return app.enabledOptions && app.enabledOptions.indexOf('rawbg') > -1; - } - - function showRawBGs(sgv, noise, cal) { - return cal - && isRawBGEnabled() - && (browserSettings.showRawbg == 'always' - || (browserSettings.showRawbg == 'noise' && (noise >= 2 || sgv < 40)) - ); - } - - function noiseCodeToDisplay(sgv, noise) { - var display; - switch (parseInt(noise)) { - case 0: display = '---'; break; - case 1: display = 'Clean'; break; - case 2: display = 'Light'; break; - case 3: display = 'Medium'; break; - case 4: display = 'Heavy'; break; - default: - if (sgv < 40) { - display = 'Heavy'; - } else { - display = '~~~'; - } - break; - } - - return display; - } - - function rawIsigToRawBg(entry, cal) { - - var raw = 0 - , unfiltered = parseInt(entry.unfiltered) || 0 - , filtered = parseInt(entry.filtered) || 0 - , sgv = entry.y - , scale = parseFloat(cal.scale) || 0 - , intercept = parseFloat(cal.intercept) || 0 - , slope = parseFloat(cal.slope) || 0; - - - if (slope == 0 || unfiltered == 0 || scale == 0) { - raw = 0; - } else if (filtered == 0 || sgv < 40) { - raw = scale * (unfiltered - intercept) / slope; - } else { - var ratio = scale * (filtered - intercept) / slope / sgv; - raw = scale * ( unfiltered - intercept) / slope / ratio; - } - - return Math.round(raw); - } - // initial setup of chart when data is first made available function initializeCharts() { @@ -420,9 +349,6 @@ function nsArrayDiff(oldArray, newArray) { , majorPills = $('.bgStatus .majorPills') , minorPills = $('.bgStatus .minorPills') , statusPills = $('.status .statusPills') - , rawNoise = bgButton.find('.rawnoise') - , rawbg = rawNoise.find('em') - , noiseLevel = rawNoise.find('label') , lastEntry = $('#lastEntry'); @@ -453,15 +379,6 @@ function nsArrayDiff(oldArray, newArray) { } } - if (showRawBGs(entry.y, entry.noise, cal)) { - rawNoise.css('display', 'inline-block'); - rawbg.text(scaleBg(rawIsigToRawBg(entry, cal))); - noiseLevel.text(noiseCodeToDisplay(entry.y, entry.noise)); - } else { - rawNoise.hide(); - } - - currentBG.toggleClass('icon-hourglass', value == 9); currentBG.toggleClass('error-code', value < 39); currentBG.toggleClass('bg-limit', value == 39 || value > 400); @@ -472,10 +389,11 @@ function nsArrayDiff(oldArray, newArray) { function updatePlugins(sgvs, time) { - var pluginBase = Nightscout.plugins.base(majorPills, minorPills, statusPills, tooltip); + var pluginBase = Nightscout.plugins.base(majorPills, minorPills, statusPills, bgStatus, tooltip); - var sbx = Nightscout.sandbox.clientInit(app, browserSettings, time, pluginBase, { + sbx = Nightscout.sandbox.clientInit(app, browserSettings, time, pluginBase, { sgvs: sgvs + , cals: [cal] , treatments: treatments , profile: profile , uploaderBattery: devicestatusData && devicestatusData.uploaderBattery @@ -524,7 +442,6 @@ function nsArrayDiff(oldArray, newArray) { } else { currentBG.text('---'); currentDirection.text('-'); - rawNoise.hide(); bgButton.removeClass('urgent warning inrange'); } @@ -609,20 +526,20 @@ function nsArrayDiff(oldArray, newArray) { if (d.type != 'sgv' && d.type != 'mbg') return; var bgType = (d.type == 'sgv' ? 'CGM' : (isDexcom(d.device) ? 'Calibration' : 'Meter')) - , rawBG = 0 + , rawbgValue = 0 , noiseLabel = ''; if (d.type == 'sgv') { - if (showRawBGs(d.y, d.noise, cal)) { - rawBG = scaleBg(rawIsigToRawBg(d, cal)); + if (rawbg.showRawBGs(d.y, d.noise, cal, sbx)) { + rawbgValue = scaleBg(rawbg.calc(d, cal, sbx)); } - noiseLabel = noiseCodeToDisplay(d.y, d.noise); + noiseLabel = rawbg.noiseCodeToDisplay(d.y, d.noise); } tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); tooltip.html('' + bgType + ' BG: ' + d.sgv + (d.type == 'mbg' ? '
    Device: ' + d.device : '') + - (rawBG ? '
    Raw BG: ' + rawBG : '') + + (rawbgValue ? '
    Raw BG: ' + rawbgValue : '') + (noiseLabel ? '
    Noise: ' + noiseLabel : '') + '
    Time: ' + formatTime(d.date)) .style('left', (d3.event.pageX) + 'px') @@ -1644,11 +1561,11 @@ function nsArrayDiff(oldArray, newArray) { var temp1 = [ ]; - if (cal && isRawBGEnabled()) { + if (cal && rawbg.isEnabled(sbx)) { temp1 = SGVdata.map(function (entry) { - var rawBg = showRawBGs(entry.y, entry.noise, cal) ? rawIsigToRawBg(entry, cal) : 0; - if (rawBg > 0) { - return { date: new Date(entry.x - 2000), y: rawBg, sgv: scaleBg(rawBg), color: 'white', type: 'rawbg' }; + var rawbgValue = rawbg.showRawBGs(entry.y, entry.noise, cal, sbx) ? rawbg.calc(entry, cal, sbx) : 0; + if (rawbgValue > 0) { + return { date: new Date(entry.x - 2000), y: rawbgValue, sgv: scaleBg(rawbgValue), color: 'white', type: 'rawbg' }; } else { return null; } @@ -1778,6 +1695,9 @@ function nsArrayDiff(oldArray, newArray) { $('#treatmentDrawerToggle').toggle(app.careportalEnabled); Nightscout.plugins.init(app); browserSettings = getBrowserSettings(browserStorage); + sbx = Nightscout.sandbox.clientInit(app, browserSettings, Date.now()); + delta = Nightscout.plugins('delta'); + rawbg = Nightscout.plugins('rawbg'); $('.container').toggleClass('has-minor-pills', Nightscout.plugins.hasShownType('pill-minor', browserSettings)); init(); }); diff --git a/tests/plugins.test.js b/tests/plugins.test.js new file mode 100644 index 00000000000..c7fa5f11618 --- /dev/null +++ b/tests/plugins.test.js @@ -0,0 +1,33 @@ +'use strict'; + +var should = require('should'); + +describe('Plugins', function ( ) { + + + it('should find client plugins, but not server only plugins', function (done) { + var plugins = require('../lib/plugins/')().registerClientDefaults(); + + plugins('delta').name.should.equal('delta'); + plugins('rawbg').name.should.equal('rawbg'); + + //server only plugin + should.not.exist(plugins('treatmentnotify')); + + done( ); + }); + + it('should find sever plugins, but not client only plugins', function (done) { + var plugins = require('../lib/plugins/')().registerServerDefaults() + + plugins('rawbg').name.should.equal('rawbg'); + plugins('treatmentnotify').name.should.equal('treatmentnotify'); + + //client only plugin + should.not.exist(plugins('cannulaage')); + + done( ); + }); + + +}); diff --git a/tests/rawbg.test.js b/tests/rawbg.test.js new file mode 100644 index 00000000000..7695c01c076 --- /dev/null +++ b/tests/rawbg.test.js @@ -0,0 +1,35 @@ +'use strict'; + +var should = require('should'); + +describe('Raw BG', function ( ) { + var rawbg = require('../lib/plugins/rawbg')(); + var sandbox = require('../lib/sandbox')(); + + var pluginBase = {}; + var data = { + sgvs: [{unfiltered: 113680, filtered: 111232, y: 110, noise: 1}] + , cals: [{scale: 1, intercept: 25717.82377004309, slope: 766.895601715918}] + }; + var app = { }; + var clientSettings = { + units: 'mg/dl' + }; + + it('should calculate Raw BG', function (done) { + var sbx = sandbox.clientInit(app, clientSettings, Date.now(), pluginBase, data); + + sbx.offerProperty = function mockedOfferProperty (name, setter) { + name.should.equal('rawbg'); + var result = setter(); + result.value.should.equal(113); + result.noiseLabel.should.equal('Clean'); + done() + }; + + rawbg.setProperties(sbx); + + }); + + +}); From 00b5c50f98cceea0dd3f9d93c629ff8a56a1adaa Mon Sep 17 00:00:00 2001 From: Fokko Driesprong Date: Sun, 21 Jun 2015 14:27:31 +0200 Subject: [PATCH 191/937] Small change to bootevent --- lib/bootevent.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/bootevent.js b/lib/bootevent.js index 91006b1c049..bc5bc728d48 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -5,7 +5,7 @@ function boot (env) { function setupMongo (ctx, next) { var store = require('./storage')(env); // initialize db connections - store( function ready ( ) { + store( function ready ( err, store ) { console.log('storage system ready'); ctx.store = store; From 03a98c1c9df4e82ee506eb7e2f427773a623055e Mon Sep 17 00:00:00 2001 From: Fokko Driesprong Date: Sun, 21 Jun 2015 15:56:27 +0200 Subject: [PATCH 192/937] I thought I had rewritten this to the old situation, but somehow it was lost. --- lib/bootevent.js | 4 +--- server.js | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/bootevent.js b/lib/bootevent.js index bc5bc728d48..6c42e81d4a5 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -78,14 +78,12 @@ function boot (env) { next( ); } - var proc = require('bootevent')( ) + return require('bootevent')( ) .acquire(setupMongo) .acquire(setupInternals) .acquire(ensureIndexes) .acquire(setupListeners) .acquire(finishBoot); - - return proc; } module.exports = boot; diff --git a/server.js b/server.js index bb3c1cc61c8..2621cf10b81 100644 --- a/server.js +++ b/server.js @@ -43,7 +43,7 @@ function create (app) { return transport.createServer(app); } -require('./lib/bootevent')(env, function booted (ctx) { +require('./lib/bootevent')(env).boot(function booted (ctx) { var app = require('./app')(env, ctx); var server = create(app).listen(PORT); console.log('listening', PORT); From ee872499d1795dfff63b4c6b9f4ed07c8d32882c Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 21 Jun 2015 10:44:02 -0700 Subject: [PATCH 193/937] update readme for the rawbg, delta, and upbat plugins; some other clean up; and another env hack --- README.md | 13 +++---------- env.js | 5 ++++- static/js/client.js | 38 +++++++++++++------------------------- 3 files changed, 20 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index ba3933ec3f3..10ee92a7bc5 100644 --- a/README.md +++ b/README.md @@ -143,27 +143,20 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. **Built-in/Example Plugins:** + * `rawbg` (Raw BG) - Calculates BG using sensor and calibration records from and displays an alternate BG values and noise levels. * `iob` (Insulin-on-Board) - Adds the IOB pill visualization in the client and calculates values that used by other plugins. Uses treatments with insulin doses and the `dia` and `sens` fields from the [treatment profile](#treatment-profile). - * `cob` (Carbs-on-Board) - Adds the COB pill visualization in the client and calculates values that used by other plugins. Uses treatments with carb doses and the `carbs_hr`, `carbratio`, and `sens` fields from the [treatment profile](#treatment-profile). - * `bwp` (Bolus Wizard Preview) - This plugin in intended for the purpose of automatically snoozing alarms when the CGM indicates high blood sugar but there is also insulin on board (IOB) and secondly, alerting to user that it might be beneficial to measure the blood sugar using a glucometer and dosing insulin as calculated by the pump or instructed by trained medicare professionals. ***The values provided by the plugin are provided as a reference based on CGM data and insulin sensitivity you have configured, and are not intended to be used as a reference for bolus calculation.*** The plugin calculates the bolus amount when above your target, generates alarms when you should consider checking and bolusing, and snoozes alarms when there is enough IOB to cover a high BG. Uses the results of the `iob` plugin and `sens`, `target_high`, and `target_low` fields from the [treatment profile](#treatment-profile). Defaults that can be adjusted with [extended setting](#extended-settings) * `BWP_WARN` (`0.50`) - If `BWP` is > `BWP_WARN` a warning alarm will be triggered. - * `BWP_URGENT` (`1.00`) - If `BWP` is > `BWP_URGENT` an urgent alarm will be triggered. - * `BWP_SNOOZE_MINS` (`10`) - minutes to snooze when there is enough IOB to cover a high BG. - * `BWP_SNOOZE` - (`0.10`) If BG is higher then the `target_high` and `BWP` < `BWP_SNOOZE` alarms will be snoozed for `BWP_SNOOZE_MINS`. - * `cage` (Cannula Age) - Calculates the number of hours since the last `Site Change` treatment that was recorded. - + * `delta` (BG Delta) - Calculates and displays the change between the last 2 BG values. **Enabled by default.** + * `upbat` (Uploader Battery) - Displays the most recent battery status from the uploader phone. **Enabled by default.** * `ar2` ([Forcasting using AR2 algorithm](https://github.com/nightscout/nightscout.github.io/wiki/Forecasting)) - Generates alarms based on forecasted values. **Enabled by default.** - * `simplealarms` (Simple BG Alarms) - Uses `BG_HIGH`, `BG_TARGET_TOP`, `BG_TARGET_BOTTOM`, `BG_LOW` settings to generate alarms. - * `errorcodes` (CGM Error Codes) - Generates alarms for CGM codes `9` (hourglass) and `10` (???). **Enabled by default.** - * `treatmentnotify` (Treatment Notifications) - Generates notifications when a treatment has been entered and snoozes alarms minutes after a treatment. Default snooze is 10 minutes, and can be set using the `TREATMENTNOTIFY_SNOOZE_MINS` [extended setting](#extended-settings). #### Extended Settings diff --git a/env.js b/env.js index 6321bbee4fa..f879572221f 100644 --- a/env.js +++ b/env.js @@ -95,7 +95,10 @@ function config ( ) { //TODO: figure out something for some plugins to have them shown by default if (env.defaults.showPlugins != '') { - env.defaults.showPlugins += ' delta upbat' + env.defaults.showPlugins += ' delta upbat'; + if (env.defaults.showRawbg == 'always' || env.defaults.showRawbg == 'noise') { + env.defaults.showPlugins += 'rawbg'; + } } //console.log(JSON.stringify(env.defaults)); diff --git a/static/js/client.js b/static/js/client.js index b5594841f0e..17cdec5c5f3 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1,18 +1,6 @@ //TODO: clean up var app = {}, browserSettings = {}, browserStorage = $.localStorage; -var timeAgo = Nightscout.utils.timeAgo; - -function nsArrayDiff(oldArray, newArray) { - var seen = {}; - var l = oldArray.length; - for (var i = 0; i < l; i++) { seen[oldArray[i].x] = true } - var result = []; - l = newArray.length; - for (var j = 0; j < l; j++) { if (!seen.hasOwnProperty(newArray[j].x)) { result.push(newArray[j]); console.log('delta data found'); } } - return result; -} - (function () { 'use strict'; @@ -58,8 +46,9 @@ function nsArrayDiff(oldArray, newArray) { , urgentAlarmSound = 'alarm2.mp3'; var sbx - , rawbg - , delta; + , rawbg = Nightscout.plugins('rawbg') + , delta = Nightscout.plugins('delta') + , timeAgo = Nightscout.utils.timeAgo; var jqWindow , tooltip @@ -1510,16 +1499,18 @@ function nsArrayDiff(oldArray, newArray) { //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// socket = io.connect(); - function recordAlreadyStored(array,record) { - var l = array.length -1; - for (var i = 0; i <= l; i++) { - var oldRecord = array[i]; - if (record.x == oldRecord.x) return true; - } - } - function mergeDataUpdate(isDelta, cachedDataArray, receivedDataArray) { + function nsArrayDiff(oldArray, newArray) { + var seen = {}; + var l = oldArray.length; + for (var i = 0; i < l; i++) { seen[oldArray[i].x] = true } + var result = []; + l = newArray.length; + for (var j = 0; j < l; j++) { if (!seen.hasOwnProperty(newArray[j].x)) { result.push(newArray[j]); console.log('delta data found'); } } + return result; + } + // If there was no delta data, just return the original data if (!receivedDataArray) return cachedDataArray; @@ -1559,7 +1550,6 @@ function nsArrayDiff(oldArray, newArray) { prevSGV = SGVdata[SGVdata.length - 2]; } - var temp1 = [ ]; if (cal && rawbg.isEnabled(sbx)) { temp1 = SGVdata.map(function (entry) { @@ -1696,8 +1686,6 @@ function nsArrayDiff(oldArray, newArray) { Nightscout.plugins.init(app); browserSettings = getBrowserSettings(browserStorage); sbx = Nightscout.sandbox.clientInit(app, browserSettings, Date.now()); - delta = Nightscout.plugins('delta'); - rawbg = Nightscout.plugins('rawbg'); $('.container').toggleClass('has-minor-pills', Nightscout.plugins.hasShownType('pill-minor', browserSettings)); init(); }); From 4d6cc814a52ba1d1578b9d07e833fa2bbb6366a1 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 21 Jun 2015 16:08:04 -0700 Subject: [PATCH 194/937] add initial style guide and dev tips to CONTRIBUTING.md --- CONTRIBUTING.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c63417fa918..965a258a450 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,6 +30,23 @@ design. We develop on the `dev` branch. You can get the dev branch checked out using `git checkout dev`. +## Style Guide + +Some simple rules, that will make it easier to maintain our codebase: + +* All indenting should use 2 space where possible (js, css, html, etc) +* A space before function parameters, such as: `function boom (name, callback) { }`, this makes searching for calls easier +* Name your callback functions, such as `boom('the name', function afterBoom ( result ) { }` +* Don't include author names in the header of your files, if you need to give credit to someone else do it in the commit comment. +* Use the comma first style, for example: + javascript``` + var data = { + value: 'the value' + , detail: 'the details...' + , time: Date.now() + }; + ``` + ## Create a prototype Fork cgm-remote-monitor and create a branch. @@ -77,3 +94,13 @@ the version correctly. See sem-ver for versioning strategy. Every commit is tested by travis. We encourage adding tests to validate your design. We encourage discussing your use cases to help everyone get a better understanding of your design. + +## Other Dev Tips + +* Join the [Gitter chat][gitter-url] +* Get a local dev environment setup if you haven't already +* Try breaking up big features/improvements into small parts. It's much easier to accept small PR's +* Create tests for your new code, and for the old code too. We are aiming for a full test coverage. +* If your going to be working in old code that needs lots of reformatting consider doing the clean as a separate PR. +* If you can find others to help test your PR is will help get them merged in sooner. + From 40f99702061a79deee68c0ff9670da3631c78dc5 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 21 Jun 2015 16:14:25 -0700 Subject: [PATCH 195/937] Update CONTRIBUTING.md corrected markdown --- CONTRIBUTING.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 965a258a450..1dd0ba4d427 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -39,7 +39,8 @@ Some simple rules, that will make it easier to maintain our codebase: * Name your callback functions, such as `boom('the name', function afterBoom ( result ) { }` * Don't include author names in the header of your files, if you need to give credit to someone else do it in the commit comment. * Use the comma first style, for example: - javascript``` + + ```javascript var data = { value: 'the value' , detail: 'the details...' From 8323758685ca55711aadd9df993d100059a45e53 Mon Sep 17 00:00:00 2001 From: Fokko Driesprong Date: Mon, 22 Jun 2015 09:23:48 +0200 Subject: [PATCH 196/937] Eagle eye @jasoncalabrese, it is fixed now --- lib/bootevent.js | 7 +++---- lib/storage.js | 13 +++++++++++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/lib/bootevent.js b/lib/bootevent.js index 6c42e81d4a5..ffe90969661 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -3,10 +3,9 @@ function boot (env) { function setupMongo (ctx, next) { - var store = require('./storage')(env); - // initialize db connections - store( function ready ( err, store ) { - console.log('storage system ready'); + require('./storage')(env, function ready ( err, store ) { + // FIXME, error is always null, if there is an error, the storage.js will throw an exception + console.log('Storage system ready'); ctx.store = store; next( ); diff --git a/lib/storage.js b/lib/storage.js index 2d600dd2801..f6567ab12fd 100644 --- a/lib/storage.js +++ b/lib/storage.js @@ -6,26 +6,34 @@ function init (env, cb) { var my = { }; function maybe_connect (cb) { + if (my.db) { console.log("Reusing MongoDB connection handler"); + // If there is a valid callback, then return the Mongo-object if (cb && cb.call) { cb(null, mongo); } return; } + if (!env.mongo) { throw new Error("MongoDB connection string is missing"); } + console.log("Setting up new connection to MongoDB"); MongoClient.connect(env.mongo, function connected (err, db) { if (err) { console.log("Error connecting to MongoDB, ERROR: %j", err); throw err; } else { - console.log("Successfully established a connected to mongo"); + console.log("Successfully established a connected to MongoDB"); } + + // FIXME Fokko: I would suggest to just create a private db variable instead of the separate my, pool construction. my.db = db; mongo.pool.db = my.db = db; - if (cb && cb.call) { cb(err, mongo); } + // If there is a valid callback, then invoke the function to perform the callback + if (cb && cb.call) + cb(err, mongo); }); } @@ -55,6 +63,7 @@ function init (env, cb) { } return this; }; + mongo.ensureIndexes = function(collection, fields) { fields.forEach(function (field) { console.info("ensuring index for: " + field); From 2fde2052ca80657dc95a38993fe70bad73a5c36f Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Mon, 22 Jun 2015 19:40:23 +0300 Subject: [PATCH 197/937] Small fix to re-enable the ar2 LOW predictions --- lib/plugins/ar2.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 15ad1a20c3b..ab8f64c0bad 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -53,7 +53,7 @@ function init() { var avg = _.sum(predicted) / predicted.length; var max = _.max([first, last, avg]); - var min = _.max([first, last, avg]); + var min = _.min([first, last, avg]); var rangeLabel = ''; if (max > sbx.scaleBg(sbx.thresholds.bg_target_top)) { From 1399b9fe3cf8166956fbdc4081d0d4ca592203c4 Mon Sep 17 00:00:00 2001 From: Fokko Driesprong Date: Mon, 22 Jun 2015 21:17:07 +0200 Subject: [PATCH 198/937] Integrating docker support --- Dockerfile | 10 ++++++++++ app.js | 2 -- docker/docker-build.sh | 8 ++++++++ docker/docker-compose.yml | 20 ++++++++++++++++++++ env.js | 1 - setup.sh | 2 ++ 6 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 Dockerfile create mode 100755 docker/docker-build.sh create mode 100644 docker/docker-compose.yml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000000..a1b8ded65d3 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,10 @@ +FROM node:0.10.38-slim + +MAINTAINER Nightscout + +# Netcat is required to poll the database, so Nightscout starts when MongoDB is up and running +RUN apt-get update && apt-get -y install netcat +RUN npm install . + +EXPOSE 1337 +CMD ["sh", "docker/docker-start.sh"] \ No newline at end of file diff --git a/app.js b/app.js index c15b5e1a6df..a1fffb49e2f 100644 --- a/app.js +++ b/app.js @@ -41,8 +41,6 @@ function create (env, ctx) { var bundle = require('./bundle')(); app.use(bundle); -// Handle errors with express's errorhandler, to display more readable error messages. - // Handle errors with express's errorhandler, to display more readable error messages. var errorhandler = require('errorhandler'); //if (process.env.NODE_ENV === 'development') { diff --git a/docker/docker-build.sh b/docker/docker-build.sh new file mode 100755 index 00000000000..216e90bf504 --- /dev/null +++ b/docker/docker-build.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +cd ../ + +docker-compose -p nightscout_build -f docker/docker-compose.yml stop +docker-compose -p nightscout_build -f docker/docker-compose.yml rm --force -v +docker-compose -p nightscout_build -f docker/docker-compose.yml build +docker-compose -p nightscout_build -f docker/docker-compose.yml up \ No newline at end of file diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 00000000000..509d285ba8c --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,20 @@ +nightscout: + build: ../ + links: + - database + - broker + ports: + - "1337:1337" + environment: + - MONGO_CONNECTION=mongodb://database/nightscout + - API_SECRET=mylittlesecret + - MQTT_MONITOR=mqtt://broker + - PORT=1337 +database: + image: mongo:3.0.3 + ports: + - "27017" +broker: + image: prologic/mosquitto + ports: + - "1883" \ No newline at end of file diff --git a/env.js b/env.js index 86d6e8424e4..827b5bdf2fd 100644 --- a/env.js +++ b/env.js @@ -7,7 +7,6 @@ var consts = require('./lib/constants'); var fs = require('fs'); // Module to constrain all config and environment parsing to one spot. function config ( ) { - /* * First inspect a bunch of environment variables: * * PORT - serve http on this port diff --git a/setup.sh b/setup.sh index 62a9a5f186c..e520a97a025 100755 --- a/setup.sh +++ b/setup.sh @@ -1,3 +1,5 @@ +#!/bin/sh + sudo apt-get update sudo apt-get install -y python-software-properties python g++ make git sudo add-apt-repository ppa:chris-lea/node.js From 49b4d2600b71a95a4696b8892a124b223985aea2 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Tue, 23 Jun 2015 00:05:48 +0300 Subject: [PATCH 199/937] New profile object, which support the old data format and new time of day based values for all contained data structures, including basal. Converted existing profile code to use the profile object. Added basal plugin, which shows current basal rate and other profile information. Added temporary basal adjustment calculation to BWP, if basal profile is present. Fixed a significant bug in how COB called IOB (passed a date to IOB instead of profile). Test for profile code. --- lib/plugins/basalprofile.js | 52 ++++++++ lib/plugins/boluswizardpreview.js | 53 +++++++-- lib/plugins/cob.js | 24 ++-- lib/plugins/index.js | 1 + lib/plugins/iob.js | 17 +-- lib/profilefunctions.js | 99 ++++++++++------ lib/sandbox.js | 4 +- static/js/client.js | 7 +- tests/profile.test.js | 189 ++++++++++++++++++++++++++++++ 9 files changed, 377 insertions(+), 69 deletions(-) create mode 100644 lib/plugins/basalprofile.js create mode 100644 tests/profile.test.js diff --git a/lib/plugins/basalprofile.js b/lib/plugins/basalprofile.js new file mode 100644 index 00000000000..5c01f078ead --- /dev/null +++ b/lib/plugins/basalprofile.js @@ -0,0 +1,52 @@ +'use strict'; + +var _ = require('lodash'); + +function init() { + + function basal() { + return basal; + } + + basal.label = 'Basal Profile'; + basal.pluginType = 'pill-minor'; + + function hasRequiredInfo (sbx) { + + if (!sbx.data.profile) return false; + + if (!sbx.data.profile.hasData()) { + console.warn('For the Basal plugin to function you need a treatment profile'); + return false; + } + + if (!sbx.data.profile.getBasal()) { + console.warn('For the Basal plugin to function you need a basal profile'); + return false; + } + + return true; + } + + basal.updateVisualisation = function updateVisualisation (sbx) { + + if (!hasRequiredInfo(sbx)) { + return; + } + + var basalValue = sbx.data.profile.getBasal(sbx.time); + + var info = [{label: 'Current basal:', value: basalValue + ' IU'} + , {label: 'Current sensitivity:', value: sbx.data.profile.getSensitivity(sbx.time) + ' ' + sbx.units + '/ IU'} + , {label: 'Current carb ratio:', value: '1 IU /' + sbx.data.profile.getCarbRatio(sbx.time) + 'g'} + ]; + + sbx.pluginBase.updatePillText(basal, basalValue + 'U', 'BASAL', info); + + }; + + return basal(); +} + + +module.exports = init; \ No newline at end of file diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index a519713c584..cc66c052b05 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -16,12 +16,15 @@ function init() { bwp.pluginType = 'pill-minor'; function hasRequiredInfo (sbx) { - if (!sbx.data.profile) { + + if (!sbx.data.profile) return false; + + if (!sbx.data.profile.hasData()) { console.warn('For the BolusWizardPreview plugin to function you need a treatment profile'); return false; } - if (!sbx.data.profile.sens || !sbx.data.profile.target_high || !sbx.data.profile.target_low) { + if (!sbx.data.profile.getSensitivity(sbx.time) || !sbx.data.profile.getHighBGTarget(sbx.time) || !sbx.data.profile.getLowBGTarget(sbx.time)) { console.warn('For the BolusWizardPreview plugin to function your treatment profile must have both sens, target_high, and target_low fields'); return false; } @@ -50,7 +53,7 @@ function init() { var results = bwp.calc(sbx); - if (results.lastSGV < sbx.data.profile.target_high) return; + if (results.lastSGV < sbx.data.profile.getHighBGTarget(sbx.time)) return; var snoozeBWP = Number(sbx.extendedSettings.snooze) || 0.10; var warnBWP = Number(sbx.extendedSettings.warn) || 0.50; @@ -100,6 +103,19 @@ function init() { , {label: 'Expected effect', value: '-' + results.effectDisplay + ' ' + sbx.units} , {label: 'Expected outcome', value: results.outcomeDisplay + ' ' + sbx.units} ]; + + if (results.tempBasalAdjustment) { + if (results.tempBasalAdjustment.thirtymin > 0) { + info.push( {label: '30m temp basal', value: results.tempBasalAdjustment.thirtymin + '%'}); + } else { + info.push( {label: '30m temp basal', value: 'too large adjustment needed, give carbs?'}); + } + if (results.tempBasalAdjustment.onehour > 0) { + info.push( {label: '1h temp basal', value: results.tempBasalAdjustment.onehour + '%'}); + } else { + info.push( {label: '1h temp basal', value: 'too large adjustment needed, give carbs?'}); + } + } sbx.pluginBase.updatePillText(bwp, results.bolusEstimateDisplay + 'U', 'BWP', info); @@ -124,18 +140,35 @@ function init() { var profile = sbx.data.profile; var iob = results.iob = sbx.properties.iob.iob; - results.effect = iob * profile.sens; + results.effect = iob * profile.getSensitivity(sbx.time); results.outcome = sgv - results.effect; var delta = 0; + + var target_high = profile.getHighBGTarget(sbx.time); + var sens = profile.getSensitivity(sbx.time); - if (results.outcome > profile.target_high) { - delta = results.outcome - profile.target_high; - results.bolusEstimate = delta / profile.sens; + if (results.outcome > target_high) { + delta = results.outcome - target_high; + results.bolusEstimate = delta / sens; } - if (results.outcome < profile.target_low) { - delta = Math.abs(results.outcome - profile.target_low); - results.bolusEstimate = delta / profile.sens * -1; + var target_low = profile.getLowBGTarget(sbx.time); + + if (results.outcome < target_low) { + delta = Math.abs(results.outcome - target_low); + results.bolusEstimate = delta / sens * -1; + } + + if (results.bolusEstimate != 0 && sbx.data.profile.getBasal()) { + // Basal profile exists, calculate % change + var basal = sbx.data.profile.getBasal(sbx.time); + + var thirtyMinAdjustment = Math.round((basal/2 + results.bolusEstimate) / (basal / 2) * 100); + var oneHourAdjustment = Math.round((basal + results.bolusEstimate) / basal * 100); + + results.tempBasalAdjustment = { + 'thirtymin': thirtyMinAdjustment + ,'onehour': oneHourAdjustment}; } results.bolusEstimateDisplay = sbx.roundInsulinForDisplayFormat(results.bolusEstimate); diff --git a/lib/plugins/cob.js b/lib/plugins/cob.js index 7ce5e71619e..ec4702fb4a0 100644 --- a/lib/plugins/cob.js +++ b/lib/plugins/cob.js @@ -22,19 +22,17 @@ function init() { cob.cobTotal = function cobTotal(treatments, profile, time) { - if (!profile) { + if (!profile || !profile.hasData()) { console.warn('For the COB plugin to function you need a treatment profile'); return {}; } - if (!profile.sens || !profile.carbratio) { + if (!profile.getSensitivity(time) || !profile.getCarbRatio(time)) { console.warn('For the CPB plugin to function your treatment profile must have both sens and carbratio fields'); return {}; } var liverSensRatio = 1; - var sens = profile.sens; - var carbratio = profile.carbratio; var totalCOB = 0; var lastCarbs = null; if (!treatments) return {}; @@ -44,7 +42,6 @@ function init() { var isDecaying = 0; var lastDecayedBy = new Date('1/1/1970'); - var carbs_hr = profile.carbs_hr; _.forEach(treatments, function eachTreatment(treatment) { if (treatment.carbs && treatment.created_at < time) { @@ -52,11 +49,11 @@ function init() { var cCalc = cob.cobCalc(treatment, profile, lastDecayedBy, time); var decaysin_hr = (cCalc.decayedBy - time) / 1000 / 60 / 60; if (decaysin_hr > -10) { - var actStart = iob.calcTotal(treatments, lastDecayedBy).activity; - var actEnd = iob.calcTotal(treatments, cCalc.decayedBy).activity; + var actStart = iob.calcTotal(treatments, profile, lastDecayedBy).activity; + var actEnd = iob.calcTotal(treatments, profile, cCalc.decayedBy).activity; var avgActivity = (actStart + actEnd) / 2; - var delayedCarbs = avgActivity * liverSensRatio * sens / carbratio; - var delayMinutes = Math.round(delayedCarbs / carbs_hr * 60); + var delayedCarbs = avgActivity * liverSensRatio * profile.getSensitivity(treatment.created_at) / profile.getCarbRatio(treatment.created_at); + var delayMinutes = Math.round(delayedCarbs / profile.getCarbAbsorptionRate(treatment.created_at) * 60); if (delayMinutes > 0) { cCalc.decayedBy.setMinutes(cCalc.decayedBy.getMinutes() + delayMinutes); decaysin_hr = (cCalc.decayedBy - time) / 1000 / 60 / 60; @@ -79,12 +76,12 @@ function init() { } }); - var rawCarbImpact = isDecaying * sens / carbratio * carbs_hr / 60; + var rawCarbImpact = isDecaying * profile.getSensitivity(time) / profile.getCarbRatio(time) * profile.getCarbAbsorptionRate(time) / 60; return { decayedBy: lastDecayedBy, isDecaying: isDecaying, - carbs_hr: carbs_hr, + carbs_hr: profile.getCarbAbsorptionRate(time), rawCarbImpact: rawCarbImpact, cob: totalCOB, lastCarbs: lastCarbs @@ -106,14 +103,15 @@ function init() { cob.cobCalc = function cobCalc(treatment, profile, lastDecayedBy, time) { - var carbs_hr = profile.carbs_hr; var delay = 20; - var carbs_min = carbs_hr / 60; var isDecaying = 0; var initialCarbs; if (treatment.carbs) { var carbTime = new Date(treatment.created_at); + + var carbs_hr = profile.getCarbAbsorptionRate(treatment.created_at); + var carbs_min = carbs_hr / 60; var decayedBy = new Date(carbTime); var minutesleft = (lastDecayedBy - carbTime) / 1000 / 60; diff --git a/lib/plugins/index.js b/lib/plugins/index.js index 5a7c3ce27c9..9479f270473 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -18,6 +18,7 @@ function init() { , require('./cob')() , require('./boluswizardpreview')() , require('./cannulaage')() + , require('./basalprofile')() ]; var serverDefaultPlugins = [ diff --git a/lib/plugins/iob.js b/lib/plugins/iob.js index 806107f61c2..7eeb8dc66f4 100644 --- a/lib/plugins/iob.js +++ b/lib/plugins/iob.js @@ -26,11 +26,6 @@ function init() { if (!treatments) return {}; - if (profile === undefined) { - //if there is no profile default to 3 hour dia - profile = {dia: 3, sens: 0}; - } - if (time === undefined) { time = new Date(); } @@ -58,10 +53,16 @@ function init() { iob.calcTreatment = function calcTreatment(treatment, profile, time) { - var dia = profile.dia - , scaleFactor = 3.0 / dia + var dia = 3 + , sens = 0; + + if (profile !== undefined) { + dia = profile.getDIA(); + sens = profile.getSensitivity(time); + } + + var scaleFactor = 3.0 / dia , peak = 75 - , sens = profile.sens , result = { iobContrib: 0 , activityContrib: 0 diff --git a/lib/profilefunctions.js b/lib/profilefunctions.js index ff684472b7f..024f617e937 100644 --- a/lib/profilefunctions.js +++ b/lib/profilefunctions.js @@ -1,34 +1,49 @@ 'use strict'; - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // multiple profile support for predictions - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +var _ = require('lodash'); +var moment = require('moment'); - function timeStringToSeconds(time) { +function init(profileData) { + + function profile() { + return profile; + } + + profile.loadData = function loadData(profileData) { + profile.data = _.cloneDeep(profileData); + profile.preprocessProfileOnLoad(profile.data[0]); + } + + profile.timeStringToSeconds = function timeStringToSeconds(time) { var split = time.split(":"); return parseInt(split[0])*3600 + parseInt(split[1])*60; } // preprocess the timestamps to seconds for a couple orders of magnitude faster operation - function preprocessProfileOnLoad(container) + profile.preprocessProfileOnLoad = function preprocessProfileOnLoad(container) { for (var key in container) { var value = container[key]; + if( Object.prototype.toString.call(value) === '[object Array]' ) { - preprocessProfileOnLoad(value); - } else { - if (value.time) { - var sec = timeStringToSeconds(value.time); - if (!isNaN(sec)) value.timeAsSeconds = sec; - } + profile.preprocessProfileOnLoad(value); + } + + if (value.time) { + var sec = profile.timeStringToSeconds(value.time); + if (!isNaN(sec)) value.timeAsSeconds = sec; } + } container.timestampsPreProcessed = true; } + + if (profileData) profile.loadData(profileData); - - function getValueByTime(profile, time, valueContainer) + profile.getValueByTime = function getValueByTime(time, valueContainer) { + if (!time) time = new Date(); + // If the container is an Array, assume it's a valid timestamped value container var returnValue = valueContainer; @@ -37,7 +52,7 @@ var timeAsDate = new Date(time); var timeAsSecondsFromMidnight = timeAsDate.getHours()*3600 + timeAsDate.getMinutes()*60; - + for (var t in valueContainer) { var value = valueContainer[t]; if (timeAsSecondsFromMidnight >= value.timeAsSeconds) { @@ -49,33 +64,47 @@ return returnValue; } - function getDIA(profile, time) - { - return getValueByTime(profile, time,profile.dia); + profile.getCurrentProfile = function getCurrentProfile() { + if (profile.hasData()) return profile.data[0]; + return {}; } - function getSensitivity(profile, time) - { - return getValueByTime(profile, time,profile.sens); - } + profile.hasData = function hasData() { + var rVal = false; + if (profile.data) rVal = true; + return (rVal); + } - function getCarbRatio(profile, time) - { - return getValueByTime(profile, time,profile.carbratio); - } - - function getCarbAbsorptionRate(profile, time) - { - return getValueByTime(profile, time,profile.carbs_hr); - } + profile.getDIA = function getDIA(time) { + return profile.getValueByTime(time,profile.getCurrentProfile()['dia']); + } + profile.getSensitivity = function getSensitivity(time) { + return profile.getValueByTime(time,profile.getCurrentProfile()['sens']); + } -function Profile(opts) { + profile.getCarbRatio = function getCarbRatio(time) { + return profile.getValueByTime(time,profile.getCurrentProfile()['carbratio']); + } + + profile.getCarbAbsorptionRate = function getCarbAbsorptionRate(time) { + return profile.getValueByTime(time,profile.getCurrentProfile()['carbs_hr']); + } + + profile.getLowBGTarget = function getLowBGTarget(time) { + return profile.getValueByTime(time,profile.getCurrentProfile()['target_low']); + } - return { - preprocessProfileOnLoad: preprocessProfileOnLoad - }; + profile.getHighBGTarget = function getHighBGTarget(time) { + return profile.getValueByTime(time,profile.getCurrentProfile()['target_high']); + } + profile.getBasal = function getBasal(time) { + return profile.getValueByTime(time,profile.getCurrentProfile()['basal']); + } + + + return profile(); } -module.exports = Profile; \ No newline at end of file +module.exports = init; \ No newline at end of file diff --git a/lib/sandbox.js b/lib/sandbox.js index 74315083cf8..aa9a6814ce2 100644 --- a/lib/sandbox.js +++ b/lib/sandbox.js @@ -3,6 +3,7 @@ var _ = require('lodash'); var units = require('./units')(); var utils = require('./utils'); +var profile = require('./profilefunctions')(); function init ( ) { var sbx = {}; @@ -54,7 +55,8 @@ function init ( ) { sbx.notifications = _.pick(ctx.notifications, ['levels', 'requestNotify', 'requestSnooze', 'requestClear']); //Plugins will expect the right profile based on time - sbx.data.profile = _.first(ctx.data.profiles); + profile.loadData(ctx.data.profiles); + sbx.data.profile = profile; delete sbx.data.profiles; sbx.properties = []; diff --git a/static/js/client.js b/static/js/client.js index 4c0f0bc1c38..be71f6aeaad 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -500,7 +500,7 @@ function nsArrayDiff(oldArray, newArray) { var sbx = Nightscout.sandbox.clientInit(app, browserSettings, time, pluginBase, { sgvs: sgvs , treatments: treatments - , profile: profile + , profile: Nightscout.profile }); //all enabled plugins get a chance to set properties, even if they aren't shown @@ -1675,7 +1675,10 @@ function nsArrayDiff(oldArray, newArray) { SGVdata = mergeDataUpdate(d.delta, SGVdata, d.sgvs); MBGdata = mergeDataUpdate(d.delta,MBGdata, d.mbgs); treatments = mergeDataUpdate(d.delta,treatments, d.treatments); - if (d.profiles) profile = d.profiles[0]; + if (d.profiles) { + profile = d.profiles[0]; + Nightscout.profile.loadData(d.profiles); + } if (d.cals) cal = d.cals[d.cals.length-1]; if (d.devicestatus) devicestatusData = d.devicestatus; diff --git a/tests/profile.test.js b/tests/profile.test.js new file mode 100644 index 00000000000..b49bd5cae33 --- /dev/null +++ b/tests/profile.test.js @@ -0,0 +1,189 @@ +var should = require('should'); + +describe('Profile', function ( ) { + + var env = require('../env')(); + + var profile_empty = require('../lib/profilefunctions')(); + + it('should say it does not have data before it has data', function() { + var hasData = profile_empty.hasData(); + hasData.should.equal(false); + }); + + it('should return undefined if asking for keys before init', function() { + var dia = profile_empty.getDIA(now); + should.not.exist(dia); + }); + + var profileDataPartial = { + "dia": 3, + "carbs_hr": 30, + }; + + var profilePartial = require('../lib/profilefunctions')([profileDataPartial]); + + it('should return undefined if asking for missing keys', function() { + var sens = profile_empty.getSensitivity(now); + should.not.exist(sens); + }); + + var profileData = { + "dia": 3, + "carbs_hr": 30, + "carbratio": 7, + "sens": 35, + "target_low": 95, + "target_high": 120 + }; + + var profile = require('../lib/profilefunctions')([profileData]); +// console.log(profile); + + var now = new Date(); + + it('should know what the DIA is with old style profiles', function() { + var dia = profile.getDIA(now); + dia.should.equal(3); + }); + + it('should know what the DIA is with old style profiles, with missing date argument', function() { + var dia = profile.getDIA(); + dia.should.equal(3); + }); + + it('should know what the carbs_hr is with old style profiles', function() { + var carbs_hr = profile.getCarbAbsorptionRate(now); + carbs_hr.should.equal(30); + }); + + it('should know what the carbratio is with old style profiles', function() { + var carbRatio = profile.getCarbRatio(now); + carbRatio.should.equal(7); + }); + + it('should know what the sensitivity is with old style profiles', function() { + var dia = profile.getSensitivity(now); + dia.should.equal(35); + }); + + it('should know what the low target is with old style profiles', function() { + var dia = profile.getLowBGTarget(now); + dia.should.equal(95); + }); + + it('should know what the high target is with old style profiles', function() { + var dia = profile.getHighBGTarget(now); + dia.should.equal(120); + }); + + it('should know how to reload data and still know what the low target is with old style profiles', function() { + + var profileData2 = { + "dia": 3, + "carbs_hr": 30, + "carbratio": 7, + "sens": 35, + "target_low": 50, + "target_high": 120 + }; + + profile.loadData([profileData2]); + var dia = profile.getLowBGTarget(now); + dia.should.equal(50); + }); + + var complexProfileData = + { + "sens": [ + { + "time": "00:00", + "value": 10 + }, + { + "time": "02:00", + "value": 10 + }, + { + "time": "07:00", + "value": 9 + } + ], + "dia": 3, + "carbratio": [ + { + "time": "00:00", + "value": 16 + }, + { + "time": "06:00", + "value": 15 + }, + { + "time": "14:00", + "value": 16 + } + ], + "carbs_hr": 30, + "startDate": "2015-06-21", + "basal": [ + { + "time": "00:00", + "value": 0.175 + }, + { + "time": "02:30", + "value": 0.125 + }, + { + "time": "05:00", + "value": 0.075 + }, + { + "time": "08:00", + "value": 0.1 + }, + { + "time": "14:00", + "value": 0.125 + }, + { + "time": "20:00", + "value": 0.3 + }, + { + "time": "22:00", + "value": 0.225 + } + ], + "target_low": 4.5, + "target_high": 8 +}; + + var complexProfile = require('../lib/profilefunctions')([complexProfileData]); + + var noon = new Date('2015-06-22 12:00:00'); + var threepm = new Date('2015-06-22 15:00:00'); + + it('should know what the basal rate is at 12:00 with complex style profiles', function() { + var value = complexProfile.getBasal(noon); + value.should.equal(0.1); + }); + + it('should know what the basal rate is at 15:00 with complex style profiles', function() { + var value = complexProfile.getBasal(threepm); + value.should.equal(0.125); + }); + + it('should know what the carbratio is at 12:00 with complex style profiles', function() { + var carbRatio = complexProfile.getCarbRatio(noon); + carbRatio.should.equal(15); + }); + + it('should know what the sensitivity is at 12:00 with complex style profiles', function() { + var dia = complexProfile.getSensitivity(noon); + dia.should.equal(9); + }); + + +}); \ No newline at end of file From d3c548ebf4ce8a794e4a8e4655fa50f3fac445d2 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Tue, 23 Jun 2015 00:14:23 +0300 Subject: [PATCH 200/937] Removed setting timestampsPreProcessed --- lib/profilefunctions.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/profilefunctions.js b/lib/profilefunctions.js index 024f617e937..8167bb309e3 100644 --- a/lib/profilefunctions.js +++ b/lib/profilefunctions.js @@ -35,7 +35,6 @@ function init(profileData) { } } - container.timestampsPreProcessed = true; } if (profileData) profile.loadData(profileData); From 6b92df90c57fa4602f6e971802a3910fc6198eb7 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Tue, 23 Jun 2015 00:36:23 +0300 Subject: [PATCH 201/937] Fixed IOB tests and a bug in the COB code from my changes. The COB test is still broken, probably as a result of the IOB call change I did, where COB was passing a date instead of profile, which must have caused miscalculation in IOB code. --- lib/plugins/cob.js | 2 +- tests/cob.test.js | 6 ++++-- tests/iob.test.js | 31 +++++++++++++++++++------------ 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/lib/plugins/cob.js b/lib/plugins/cob.js index ec4702fb4a0..1640b3be935 100644 --- a/lib/plugins/cob.js +++ b/lib/plugins/cob.js @@ -66,7 +66,7 @@ function init() { if (decaysin_hr > 0) { //console.info('Adding ' + delayMinutes + ' minutes to decay of ' + treatment.carbs + 'g bolus at ' + treatment.created_at); - totalCOB += Math.min(Number(treatment.carbs), decaysin_hr * carbs_hr); + totalCOB += Math.min(Number(treatment.carbs), decaysin_hr * profile.getCarbRatio(treatment.created_at)); //console.log("cob: " + Math.min(cCalc.initialCarbs, decaysin_hr * carbs_hr)); isDecaying = cCalc.isDecaying; } else { diff --git a/tests/cob.test.js b/tests/cob.test.js index db2cc9fbef3..64eb510a331 100644 --- a/tests/cob.test.js +++ b/tests/cob.test.js @@ -4,13 +4,15 @@ var should = require('should'); describe('COB', function ( ) { var cob = require('../lib/plugins/cob')(); - - var profile = { + + var profileData = { sens: 95 , carbratio: 18 , carbs_hr: 30 }; + var profile = require('../lib/profilefunctions')([profileData]); + it('should calculate IOB, multiple treatments', function() { var treatments = [ diff --git a/tests/iob.test.js b/tests/iob.test.js index 042141dd7cc..466fe87923c 100644 --- a/tests/iob.test.js +++ b/tests/iob.test.js @@ -5,6 +5,7 @@ var FIVE_MINS = 10 * 60 * 1000; describe('IOB', function ( ) { var iob = require('../lib/plugins/iob')(); + it('should calculate IOB', function() { var time = new Date() @@ -12,11 +13,14 @@ describe('IOB', function ( ) { created_at: time - 1, insulin: "1.00" } - ] - , profile = { - dia: 3, - sens: 0 - }; + ]; + + + var profileData = { + dia: 3, + sens: 0}; + + var profile = require('../lib/profilefunctions')([profileData]); var rightAfterBolus = iob.calcTotal(treatments, profile, time); @@ -69,12 +73,15 @@ describe('IOB', function ( ) { created_at: time - 1, insulin: "1.00" } - ] - , profile = { - dia: 4, - sens: 0 - }; + ]; + + var profileData = { + dia: 4, + sens: 0}; + + var profile = require('../lib/profilefunctions')([profileData]); + var rightAfterBolus = iob.calcTotal(treatments, profile, time); rightAfterBolus.display.should.equal('1.00'); @@ -88,9 +95,9 @@ describe('IOB', function ( ) { after3hDIA.iob.should.greaterThan(0); - var after4hDIA = iob.calcTotal(treatments, profile, new Date(time.getTime() + (3 * 60 * 60 * 1000))); + var after4hDIA = iob.calcTotal(treatments, profile, new Date(time.getTime() + (4 * 60 * 60 * 1000))); - after4hDIA.iob.should.greaterThan(0); + after4hDIA.iob.should.equal(0); }); From 32969ec345a6a45631019d43aa76dd35944340df Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Tue, 23 Jun 2015 07:53:43 +0300 Subject: [PATCH 202/937] Found the bug introduced in changing the profile API --- lib/plugins/cob.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/plugins/cob.js b/lib/plugins/cob.js index 1640b3be935..4a3c0a3ad01 100644 --- a/lib/plugins/cob.js +++ b/lib/plugins/cob.js @@ -47,6 +47,7 @@ function init() { if (treatment.carbs && treatment.created_at < time) { lastCarbs = treatment; var cCalc = cob.cobCalc(treatment, profile, lastDecayedBy, time); + console.log(cCalc); var decaysin_hr = (cCalc.decayedBy - time) / 1000 / 60 / 60; if (decaysin_hr > -10) { var actStart = iob.calcTotal(treatments, profile, lastDecayedBy).activity; @@ -66,8 +67,8 @@ function init() { if (decaysin_hr > 0) { //console.info('Adding ' + delayMinutes + ' minutes to decay of ' + treatment.carbs + 'g bolus at ' + treatment.created_at); - totalCOB += Math.min(Number(treatment.carbs), decaysin_hr * profile.getCarbRatio(treatment.created_at)); - //console.log("cob: " + Math.min(cCalc.initialCarbs, decaysin_hr * carbs_hr)); + totalCOB += Math.min(Number(treatment.carbs), decaysin_hr * profile.getCarbAbsorptionRate(treatment.created_at)); + //console.log("cob:", Math.min(cCalc.initialCarbs, decaysin_hr * profile.getCarbAbsorptionRate(treatment.created_at)),cCalc.initialCarbs,decaysin_hr,profile.getCarbAbsorptionRate(treatment.created_at)); isDecaying = cCalc.isDecaying; } else { totalCOB = 0; From 711b292861c57ca3933bb7da37a39402c6fdbdb5 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Tue, 23 Jun 2015 08:10:16 +0300 Subject: [PATCH 203/937] Removing a logging line --- lib/plugins/cob.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/plugins/cob.js b/lib/plugins/cob.js index 4a3c0a3ad01..dba62c81b26 100644 --- a/lib/plugins/cob.js +++ b/lib/plugins/cob.js @@ -47,7 +47,6 @@ function init() { if (treatment.carbs && treatment.created_at < time) { lastCarbs = treatment; var cCalc = cob.cobCalc(treatment, profile, lastDecayedBy, time); - console.log(cCalc); var decaysin_hr = (cCalc.decayedBy - time) / 1000 / 60 / 60; if (decaysin_hr > -10) { var actStart = iob.calcTotal(treatments, profile, lastDecayedBy).activity; From 73c08428df4a7108dfd546d51bd80afea5b6bf9e Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Tue, 23 Jun 2015 08:39:51 +0300 Subject: [PATCH 204/937] Fix Pebble API. This really needs to start using the sbx --- lib/pebble.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/pebble.js b/lib/pebble.js index 8f7bf1ee241..b13280a9a52 100644 --- a/lib/pebble.js +++ b/lib/pebble.js @@ -16,6 +16,7 @@ var DIRECTIONS = { var iob = require("./plugins/iob")(); var async = require('async'); var units = require('./units')(); +var profileObject = require("./profilefunctions")(); function directionToTrend (direction) { var trend = 8; @@ -83,7 +84,7 @@ function pebble (req, res) { profileResults.forEach(function (profile) { if (profile) { if (profile.dia) { - profileResult = profile; + profileResult = profileObject.loadData([profile]); } } }); From c5408bc412cb7ac3a3c8352b7ea25c855a2fe463 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 23 Jun 2015 17:49:45 -0700 Subject: [PATCH 205/937] some plugins such as delta and upbat are always shown for now --- lib/plugins/index.js | 4 +++- static/js/ui-utils.js | 12 ++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/plugins/index.js b/lib/plugins/index.js index 09024bddc06..3da8783b863 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -80,9 +80,11 @@ function init() { _.forEach(enabledPlugins, f); }; + plugins.alwaysShown = 'delta upbat'; + plugins.shownPlugins = function(sbx) { return _.filter(enabledPlugins, function filterPlugins(plugin) { - return sbx && sbx.showPlugins && sbx.showPlugins.indexOf(plugin.name) > -1; + return plugins.alwaysShown.indexOf(plugin.name) > -1 || (sbx && sbx.showPlugins && sbx.showPlugins.indexOf(plugin.name) > -1); }); }; diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js index ec744ae1318..d52ce7fd7ca 100644 --- a/static/js/ui-utils.js +++ b/static/js/ui-utils.js @@ -99,10 +99,14 @@ function getBrowserSettings(storage) { json.showPlugins = setDefault(json.showPlugins, app.defaults.showPlugins || Nightscout.plugins.enabledPluginNames()); var showPluginsSettings = $('#show-plugins'); Nightscout.plugins.eachEnabledPlugin(function each(plugin) { - var id = 'plugin-' + plugin.name; - var dd = $('
    '); - showPluginsSettings.append(dd); - dd.find('input').prop('checked', json.showPlugins.indexOf(plugin.name) > -1); + if (Nightscout.plugins.alwaysShown.indexOf(plugin.name) > -1) { + //ignore these, they are always on for now + } else { + var id = 'plugin-' + plugin.name; + var dd = $('
    '); + showPluginsSettings.append(dd); + dd.find('input').prop('checked', json.showPlugins.indexOf(plugin.name) > -1); + } }); From 2cb7009f94a0bbc40dda29d246b2458925bdffd1 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 23 Jun 2015 21:17:49 -0700 Subject: [PATCH 206/937] add a little special handling for rawbg, similar to delta and uploader battery --- lib/plugins/index.js | 5 +++-- lib/plugins/pluginbase.js | 7 ++++++- static/css/main.css | 16 ++++++++++------ static/index.html | 2 +- static/js/ui-utils.js | 2 +- 5 files changed, 21 insertions(+), 11 deletions(-) diff --git a/lib/plugins/index.js b/lib/plugins/index.js index 3da8783b863..f6bb629bf4e 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -80,11 +80,12 @@ function init() { _.forEach(enabledPlugins, f); }; - plugins.alwaysShown = 'delta upbat'; + //these plugins are either always on or have custom settings + plugins.specialPlugins = 'delta upbat rawbg'; plugins.shownPlugins = function(sbx) { return _.filter(enabledPlugins, function filterPlugins(plugin) { - return plugins.alwaysShown.indexOf(plugin.name) > -1 || (sbx && sbx.showPlugins && sbx.showPlugins.indexOf(plugin.name) > -1); + return plugins.specialPlugins.indexOf(plugin.name) > -1 || (sbx && sbx.showPlugins && sbx.showPlugins.indexOf(plugin.name) > -1); }); }; diff --git a/lib/plugins/pluginbase.js b/lib/plugins/pluginbase.js index 2cac7e6c447..18800551b8a 100644 --- a/lib/plugins/pluginbase.js +++ b/lib/plugins/pluginbase.js @@ -51,7 +51,12 @@ function init (majorPills, minorPills, statusPills, bgStatus, tooltip) { var pill = findOrCreatePill(plugin); - pill.toggle(!options.hide); + if (options.hide) { + pill.addClass('hidden'); + } else { + pill.removeClass('hidden'); + } + pill.addClass(options.pillClass); pill.find('label').attr('class', options.labelClass).text(options.label); diff --git a/static/css/main.css b/static/css/main.css index 585f0b3390c..bb17a8fda3a 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -243,24 +243,24 @@ body { width: 360px; } -.loading .bgButton .pill.rawbg { +.loading .pill.rawbg { display: none; } -.bgButton .pill.rawbg { +.pill.rawbg { display: inline-block; border-radius: 2px; border: 1px solid #808080; } -.bgButton .pill.rawbg em { +.pill.rawbg em { color: white; background-color: black; display: block; font-size: 20px; } -.bgButton .pill.rawbg label { +.pill.rawbg label { display: block; font-size: 14px; } @@ -338,6 +338,10 @@ div.tooltip { background: #808080; } +.pill.hidden { + display: none; +} + @media (max-width: 800px) { .bgStatus { width: 300px; @@ -421,11 +425,11 @@ div.tooltip { padding: 0; } - .bgButton .pill.rawbg em { + .pill.rawbg em { font-size: 14px; } - .bgButton .pill.rawbg label { + .pill.rawbg label { font-size: 12px; } diff --git a/static/index.html b/static/index.html index 9a01e73a811..2b2d40ab14b 100644 --- a/static/index.html +++ b/static/index.html @@ -47,7 +47,7 @@

    Nightscout

    - + --- -
    diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js index d52ce7fd7ca..5667070df51 100644 --- a/static/js/ui-utils.js +++ b/static/js/ui-utils.js @@ -99,7 +99,7 @@ function getBrowserSettings(storage) { json.showPlugins = setDefault(json.showPlugins, app.defaults.showPlugins || Nightscout.plugins.enabledPluginNames()); var showPluginsSettings = $('#show-plugins'); Nightscout.plugins.eachEnabledPlugin(function each(plugin) { - if (Nightscout.plugins.alwaysShown.indexOf(plugin.name) > -1) { + if (Nightscout.plugins.specialPlugins.indexOf(plugin.name) > -1) { //ignore these, they are always on for now } else { var id = 'plugin-' + plugin.name; From b85cd25b5386d64f994fbcede840ece84e02b171 Mon Sep 17 00:00:00 2001 From: Fokko Driesprong Date: Wed, 24 Jun 2015 20:07:54 +0200 Subject: [PATCH 207/937] Added the dockerfile --- .dockerignore | 17 +++++++++++++++++ Dockerfile | 10 ++++++++-- docker-build.sh | 4 ++++ 3 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 .dockerignore create mode 100644 docker-build.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000000..9bd34f41610 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,17 @@ +bower_components/ +node_modules/ + +.idea/ +*.iml + +*.env +static/bower_components/ +.*.sw? +.DS_Store + +.vagrant +/iisnode + +# istanbul output +coverage/ +npm-debug.log diff --git a/Dockerfile b/Dockerfile index a1b8ded65d3..9e91a997d0d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,15 @@ -FROM node:0.10.38-slim +FROM node:0.12.38-slim MAINTAINER Nightscout # Netcat is required to poll the database, so Nightscout starts when MongoDB is up and running -RUN apt-get update && apt-get -y install netcat +RUN apt-get update && apt-get -y install netcat git + +# Got this from the setup.sh +RUN apt-get install -y python-software-properties python g++ make git + +RUN apt-get upgrade -y + RUN npm install . EXPOSE 1337 diff --git a/docker-build.sh b/docker-build.sh new file mode 100644 index 00000000000..05ed0164c72 --- /dev/null +++ b/docker-build.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +sudo docker build -t local/nightscout . +sudo docker run -t local/nightscout \ No newline at end of file From 6153e6305bd0b83137c8b729627a028e95a136c7 Mon Sep 17 00:00:00 2001 From: Fokko Driesprong Date: Wed, 24 Jun 2015 22:24:26 +0200 Subject: [PATCH 208/937] stash --- lib/devicestatus.js | 2 +- lib/entries.js | 2 +- lib/profile.js | 2 +- lib/storage.js | 97 ++++++++++++++++++++----------------------- lib/treatments.js | 2 +- tests/storage.test.js | 58 ++++++++++++++++++++++++++ 6 files changed, 106 insertions(+), 57 deletions(-) create mode 100644 tests/storage.test.js diff --git a/lib/devicestatus.js b/lib/devicestatus.js index a831e936bab..ace6a6e3b32 100644 --- a/lib/devicestatus.js +++ b/lib/devicestatus.js @@ -33,7 +33,7 @@ function storage (collection, ctx) { } function api() { - return ctx.store.pool.db.collection(collection); + return ctx.store.db.collection(collection); } diff --git a/lib/entries.js b/lib/entries.js index e271272ff3c..07e336b91b8 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -143,7 +143,7 @@ function storage(env, ctx) { function api ( ) { // obtain handle usable for querying the collection associated // with these records - return ctx.store.pool.db.collection(env.mongo_collection); + return ctx.store.db.collection(env.mongo_collection); } // Expose all the useful functions diff --git a/lib/profile.js b/lib/profile.js index 7a7486411cb..ca97ff6d841 100644 --- a/lib/profile.js +++ b/lib/profile.js @@ -28,7 +28,7 @@ function storage (collection, ctx) { } function api () { - return ctx.store.pool.db.collection(collection); + return ctx.store.db.collection(collection); } api.list = list; diff --git a/lib/storage.js b/lib/storage.js index f6567ab12fd..a3e7c71548c 100644 --- a/lib/storage.js +++ b/lib/storage.js @@ -4,32 +4,49 @@ var mongodb = require('mongodb'); function init (env, cb) { var MongoClient = mongodb.MongoClient; - var my = { }; - function maybe_connect (cb) { - - if (my.db) { - console.log("Reusing MongoDB connection handler"); - // If there is a valid callback, then return the Mongo-object - if (cb && cb.call) { cb(null, mongo); } - return; - } + var mongo = { + collection: function get_collection (name) { + return mongo.db.collection(name); + }, + with_collection: function with_collection (name) { + return function use_collection(fn) { + fn(null, mongo.db.collection(name)); + } + }, + limit: function limit (opts) { + if (opts && opts.count) { + return this.limit(parseInt(opts.count)); + } + return this; + }, + ensureIndexes: function(collection, fields) { + fields.forEach(function (field) { + console.info('Ensuring index for: ' + field); + collection.ensureIndex(field, function (err) { + if (err) { + console.error('unable to ensureIndex for: ' + field + ' - ' + err); + } + }); + }) + }, + db: null // Yet to be assigned + }; + function newConnection (cb) { if (!env.mongo) { - throw new Error("MongoDB connection string is missing"); + throw new Error('MongoDB connection string is missing'); } - console.log("Setting up new connection to MongoDB"); + console.log('Setting up new connection to MongoDB'); MongoClient.connect(env.mongo, function connected (err, db) { if (err) { - console.log("Error connecting to MongoDB, ERROR: %j", err); + console.log('Error connecting to MongoDB, ERROR: %j', err); throw err; } else { - console.log("Successfully established a connected to MongoDB"); + console.log('Successfully established a connected to MongoDB'); } - // FIXME Fokko: I would suggest to just create a private db variable instead of the separate my, pool construction. - my.db = db; - mongo.pool.db = my.db = db; + mongo.db = db; // If there is a valid callback, then invoke the function to perform the callback if (cb && cb.call) @@ -37,45 +54,19 @@ function init (env, cb) { }); } - function mongo (cb) { - maybe_connect(cb); - mongo.pool.db = my.db; - return mongo; - } - - mongo.pool = function ( ) { - return my; - }; - - mongo.collection = function get_collection (name) { - return mongo.pool( ).db.collection(name); - }; - - mongo.with_collection = function with_collection (name) { - return function use_collection (fn) { - fn(null, mongo.pool( ).db.collection(name)); - }; - }; - - mongo.limit = function limit (opts) { - if (opts && opts.count) { - return this.limit(parseInt(opts.count)); + return function mongo (cb) { + if (mongo.db != null) { + console.log('Reusing MongoDB connection handler'); + // If there is a valid callback, then return the Mongo-object + if (cb && cb.call) { + cb(null, mongo); + } + } else { + newConnection(cb); } - return this; - }; - mongo.ensureIndexes = function(collection, fields) { - fields.forEach(function (field) { - console.info("ensuring index for: " + field); - collection.ensureIndex(field, function (err) { - if (err) { - console.error("unable to ensureIndex for: " + field + " - " + err); - } - }); - }); - }; - - return mongo(cb); + return mongo; + } } module.exports = init; diff --git a/lib/treatments.js b/lib/treatments.js index 8e92128dcc8..98821fd858a 100644 --- a/lib/treatments.js +++ b/lib/treatments.js @@ -66,7 +66,7 @@ function storage (env, ctx) { } function api ( ) { - return ctx.store.pool.db.collection(env.treatments_collection); + return ctx.store.db.collection(env.treatments_collection); } api.list = list; diff --git a/tests/storage.test.js b/tests/storage.test.js new file mode 100644 index 00000000000..7698f94d606 --- /dev/null +++ b/tests/storage.test.js @@ -0,0 +1,58 @@ +'use strict'; + +var request = require('supertest'); +var should = require('should'); +var assert = require('assert'); +var load = require('./fixtures/load'); + +describe('STORAGE', function () { + var env = require('../env')( ); + + before(function (done) { + delete env.api_secret; + done(); + }); + + it('The storage class should be OK.', function (done) { + require('../lib/storage').should.be.ok; + done(); + }); + + it('After initializing the storage class it should re-use the open connection', function (done) { + var store = require('../lib/storage'); + store(env, function (err1, db1) { + should.not.exist(err1); + + store(env, function (err2, db2) { + should.not.exist(err2); + + console.log(db1 == db2) + + done(); + }); + }); + }); + + it('When no connection-string is given the storage-class should throw an error.', function (done) { + delete env.mongo; + should.not.exist(env.mongo); + + (function(){ + return require('../lib/storage')(env); + }).should.throw('MongoDB connection string is missing'); + + done(); + }); + + it('An invalid connection-string should throw an error.', function (done) { + env.mongo = 'This is not a MongoDB connection-string'; + + (function(){ + return require('../lib/storage')(env); + }).should.throw('URL must be in the format mongodb://user:pass@host:port/dbname'); + + done(); + }); + +}); + From 1c28e073497e7e405047bad7f3158bc7dca3f790 Mon Sep 17 00:00:00 2001 From: Fokko Driesprong Date: Wed, 24 Jun 2015 23:47:45 +0200 Subject: [PATCH 209/937] Fixed the re-use of the socket and written tests which hit the few missed lines --- lib/storage.js | 113 ++++++++++++++++++++++-------------------- tests/storage.test.js | 13 +++-- 2 files changed, 65 insertions(+), 61 deletions(-) diff --git a/lib/storage.js b/lib/storage.js index a3e7c71548c..ac8f745c40c 100644 --- a/lib/storage.js +++ b/lib/storage.js @@ -1,72 +1,77 @@ 'use strict'; + var mongodb = require('mongodb'); -function init (env, cb) { +var connection = null; + +function init(env, cb, forceNewConnection) { var MongoClient = mongodb.MongoClient; + var mongo = {}; + + function maybe_connect(cb) { + + if (connection != null && !forceNewConnection) { + console.log('Reusing MongoDB connection handler'); + // If there is a valid callback, then return the Mongo-object + mongo.db = connection; - var mongo = { - collection: function get_collection (name) { - return mongo.db.collection(name); - }, - with_collection: function with_collection (name) { - return function use_collection(fn) { - fn(null, mongo.db.collection(name)); + if (cb && cb.call) { + cb(null, mongo); } - }, - limit: function limit (opts) { - if (opts && opts.count) { - return this.limit(parseInt(opts.count)); + } else { + if (!env.mongo) { + throw new Error('MongoDB connection string is missing'); } - return this; - }, - ensureIndexes: function(collection, fields) { - fields.forEach(function (field) { - console.info('Ensuring index for: ' + field); - collection.ensureIndex(field, function (err) { - if (err) { - console.error('unable to ensureIndex for: ' + field + ' - ' + err); - } - }); - }) - }, - db: null // Yet to be assigned - }; - function newConnection (cb) { - if (!env.mongo) { - throw new Error('MongoDB connection string is missing'); - } + console.log('Setting up new connection to MongoDB'); + MongoClient.connect(env.mongo, function connected(err, db) { + if (err) { + console.log('Error connecting to MongoDB, ERROR: %j', err); + throw err; + } else { + console.log('Successfully established a connected to MongoDB'); + } - console.log('Setting up new connection to MongoDB'); - MongoClient.connect(env.mongo, function connected (err, db) { - if (err) { - console.log('Error connecting to MongoDB, ERROR: %j', err); - throw err; - } else { - console.log('Successfully established a connected to MongoDB'); - } + connection = db; - mongo.db = db; + mongo.db = connection; - // If there is a valid callback, then invoke the function to perform the callback - if (cb && cb.call) + // If there is a valid callback, then invoke the function to perform the callback + if (cb && cb.call) cb(err, mongo); - }); + }); + } } - return function mongo (cb) { - if (mongo.db != null) { - console.log('Reusing MongoDB connection handler'); - // If there is a valid callback, then return the Mongo-object - if (cb && cb.call) { - cb(null, mongo); - } - } else { - newConnection(cb); + mongo.collection = function get_collection(name) { + return connection.collection(name); + }; + + mongo.with_collection = function with_collection(name) { + return function use_collection(fn) { + fn(null, connection.collection(name)); + }; + }; + + mongo.limit = function limit(opts) { + if (opts && opts.count) { + return this.limit(parseInt(opts.count)); } + return this; + }; - return mongo; - } + mongo.ensureIndexes = function (collection, fields) { + fields.forEach(function (field) { + console.info('ensuring index for: ' + field); + collection.ensureIndex(field, function (err) { + if (err) { + console.error('unable to ensureIndex for: ' + field + ' - ' + err); + } + }); + }); + }; + + return maybe_connect(cb); } -module.exports = init; +module.exports = init; \ No newline at end of file diff --git a/tests/storage.test.js b/tests/storage.test.js index 7698f94d606..a4a5d8152a5 100644 --- a/tests/storage.test.js +++ b/tests/storage.test.js @@ -6,7 +6,7 @@ var assert = require('assert'); var load = require('./fixtures/load'); describe('STORAGE', function () { - var env = require('../env')( ); + var env = require('../env')(); before(function (done) { delete env.api_secret; @@ -25,8 +25,7 @@ describe('STORAGE', function () { store(env, function (err2, db2) { should.not.exist(err2); - - console.log(db1 == db2) + assert(db1.db, db2.db, 'Check if the handlers are the same.') done(); }); @@ -37,8 +36,8 @@ describe('STORAGE', function () { delete env.mongo; should.not.exist(env.mongo); - (function(){ - return require('../lib/storage')(env); + (function () { + return require('../lib/storage')(env, false, true); }).should.throw('MongoDB connection string is missing'); done(); @@ -47,8 +46,8 @@ describe('STORAGE', function () { it('An invalid connection-string should throw an error.', function (done) { env.mongo = 'This is not a MongoDB connection-string'; - (function(){ - return require('../lib/storage')(env); + (function () { + return require('../lib/storage')(env, false, true); }).should.throw('URL must be in the format mongodb://user:pass@host:port/dbname'); done(); From 4d888696cb9a064166b18e7951c14ba3101fe881 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 24 Jun 2015 15:25:03 -0700 Subject: [PATCH 210/937] make sure we really have a rawbg prop before trying to display it --- lib/plugins/rawbg.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/rawbg.js b/lib/plugins/rawbg.js index f7017070c88..17814c2a92d 100644 --- a/lib/plugins/rawbg.js +++ b/lib/plugins/rawbg.js @@ -33,7 +33,7 @@ function init() { rawbg.updateVisualisation = function updateVisualisation (sbx) { var prop = sbx.properties.rawbg; - var options = rawbg.showRawBGs(prop.sgv.y, prop.sgv.noise, prop.cal, sbx) ? { + var options = prop && prop.sgv && rawbg.showRawBGs(prop.sgv.y, prop.sgv.noise, prop.cal, sbx) ? { hide: !prop || !prop.value , value: prop.value , label: prop.noiseLabel From 1b871b97f431618aa425ed82f4f834f717f9a6d5 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Thu, 25 Jun 2015 09:55:08 +0300 Subject: [PATCH 211/937] Fix delta rounding for mmol users to prevent values such as -0.2999999999997 from being shown --- lib/plugins/delta.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/delta.js b/lib/plugins/delta.js index 2bae298a952..f8c9112e9d1 100644 --- a/lib/plugins/delta.js +++ b/lib/plugins/delta.js @@ -36,7 +36,7 @@ function init() { if (currentSGV < 40 || prevSVG < 40) { return result; } if (currentSGV > 400 || prevSVG > 400) { return result; } - result.value = sbx.scaleBg(currentSGV) - sbx.scaleBg(prevSVG); + result.value = sbx.roundBGToDisplayFormat(sbx.scaleBg(currentSGV) - sbx.scaleBg(prevSVG)); result.display = (result.value >= 0 ? '+' : '') + result.value; return result; From 855e95145cbddda9c8fe119ee7cef0f17c88f16e Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Thu, 25 Jun 2015 09:56:23 +0300 Subject: [PATCH 212/937] IU -> U on basal display --- lib/plugins/basalprofile.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/plugins/basalprofile.js b/lib/plugins/basalprofile.js index adc8c04134b..2d0851becbd 100644 --- a/lib/plugins/basalprofile.js +++ b/lib/plugins/basalprofile.js @@ -36,9 +36,9 @@ function init() { var basalValue = sbx.data.profile.getBasal(sbx.time); - var info = [{label: 'Current basal:', value: basalValue + ' IU'} - , {label: 'Current sensitivity:', value: sbx.data.profile.getSensitivity(sbx.time) + ' ' + sbx.units + '/ IU'} - , {label: 'Current carb ratio:', value: '1 IU /' + sbx.data.profile.getCarbRatio(sbx.time) + 'g'} + var info = [{label: 'Current basal:', value: basalValue + ' U'} + , {label: 'Current sensitivity:', value: sbx.data.profile.getSensitivity(sbx.time) + ' ' + sbx.units + '/ U'} + , {label: 'Current carb ratio:', value: '1 U /' + sbx.data.profile.getCarbRatio(sbx.time) + 'g'} ]; sbx.pluginBase.updatePillText(basal, { From ebaa72096b67776dc310f187a5d6ab02c48d846b Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 25 Jun 2015 19:37:02 -0700 Subject: [PATCH 213/937] better BWP and AR2 pushover messages; send all pushover alarms as emergengy so we get the full ack support --- lib/plugins/ar2.js | 12 +++++++- lib/plugins/boluswizardpreview.js | 46 ++++++++++++++++++++++--------- lib/pushnotify.js | 29 +++++++++++-------- tests/boluswizardpreview.test.js | 3 ++ 4 files changed, 65 insertions(+), 25 deletions(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index ab8f64c0bad..0654db56a0f 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -72,9 +72,19 @@ function init() { , ['15m', predicted[2], sbx.unitsLabel].join(' ') ]; + var bwp = sbx.properties.bwp && sbx.properties.bwp.bolusEstimateDisplay; + if (bwp) { + lines.push(['BWP:', bwp, 'U'].join(' ')); + } + var iob = sbx.properties.iob && sbx.properties.iob.display; if (iob) { - lines.unshift(['\nIOB:', iob, 'U'].join(' ')); + lines.push(['IOB:', iob, 'U'].join(' ')); + } + + var cob = sbx.properties.cob && sbx.properties.cob.cob; + if (cob) { + lines.push(['COB:', cob, 'g'].join(' ')); } var message = lines.join('\n'); diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 6adcdcdc8ce..a76fc4af016 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -45,13 +45,19 @@ function init() { } - bwp.checkNotifications = function checkNotifications (sbx) { + bwp.setProperties = function setProperties(sbx) { + sbx.offerProperty('bwp', function setBWP ( ) { + if (hasRequiredInfo(sbx)) { + return bwp.calc(sbx); + } + }); + }; - if (!hasRequiredInfo(sbx)) { - return; - } - var results = bwp.calc(sbx); + bwp.checkNotifications = function checkNotifications (sbx) { + + var results = sbx.properties.bwp; + if (results == undefined) return; if (results.lastSGV < sbx.data.profile.getHighBGTarget(sbx.time)) return; @@ -71,15 +77,32 @@ function init() { var level = results.bolusEstimate > urgentBWP ? sbx.notifications.levels.URGENT : sbx.notifications.levels.WARN; var levelLabel = sbx.notifications.levels.toString(level); var sound = level == sbx.notifications.levels.URGENT ? 'updown' : 'bike'; - var message = [levelLabel, results.lastSGV, sbx.unitsLabel].join(' '); + + var lines = [ + ['BG NOW:', results.lastSGV, sbx.unitsLabel].join(' ') + , ['BWP:', results.bolusEstimate, 'U'].join(' ') + ]; + var iob = sbx.properties.iob && sbx.properties.iob.display; if (iob) { - message += ['\nIOB:', iob, 'U'].join(' '); + lines.push(['\nIOB:', iob, 'U'].join(' ')); } + var iob = sbx.properties.iob && sbx.properties.iob.display; + if (iob) { + lines.push(['IOB:', iob, 'U'].join(' ')); + } + + var cob = sbx.properties.cob && sbx.properties.cob.cob; + if (cob) { + lines.push(['COB:', cob, 'g'].join(' ')); + } + + var message = lines.join('\n'); + sbx.notifications.requestNotify({ level: level - , title: 'Check BG, time to bolus?' + , title: levelLabel + 'Check BG, time to bolus?' , message: message , pushoverSound: sound , plugin: bwp @@ -91,11 +114,8 @@ function init() { bwp.updateVisualisation = function updateVisualisation (sbx) { - if (!hasRequiredInfo(sbx)) { - return; - } - - var results = bwp.calc(sbx); + var results = sbx.properties.bwp; + if (results == undefined) return; // display text var info = [ diff --git a/lib/pushnotify.js b/lib/pushnotify.js index 2de184fac8d..6f9f23ce754 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -9,8 +9,12 @@ function init(env, ctx) { var pushover = require('./pushover')(env); + var PUSHOVER_EMERGENCY = 2; + var PUSHOVER_NORMAL = 0; + // declare local constants for time differences - var TIME_15_MINS_S = 15 * 60 + var TIME_2_MINS_S = 120 + , TIME_15_MINS_S = 15 * 60 , TIME_15_MINS_MS = TIME_15_MINS_S * 1000 , TIME_30_MINS_MS = 30 * 60 * 1000 ; @@ -46,23 +50,26 @@ function init(env, ctx) { , message: notify.message , sound: notify.pushoverSound || 'gamelan' , timestamp: new Date() - , priority: notify.level + //USE PUSHOVER_EMERGENCY for WARN and URGENT so we get the acks + , priority: notify.level > ctx.notifications.levels.WARN ? PUSHOVER_EMERGENCY : PUSHOVER_NORMAL }; - if (notify.level == ctx.notifications.levels.URGENT) { - msg.retry = 120; + if (notify.level >= ctx.notifications.levels.WARN) { + //ADJUST RETRY TIME based on WARN or URGENT + msg.retry = notify.level == ctx.notifications.levels.URGENT ? TIME_2_MINS_S : TIME_15_MINS_S; if (env.baseUrl) { msg.callback = env.baseUrl + '/api/v1/notifications/pushovercallback'; } - } else if (notify.level == ctx.notifications.levels.WARN && env.baseUrl) { - var now = Date.now(); - var sig = ctx.notifications.sign(1, TIME_30_MINS_MS, Date.now()); - if (sig) { - msg.url_title = 'Snooze for 30 minutes'; - msg.url = env.baseUrl + '/api/v1/notifications/snooze?level=1&lengthMills=' + TIME_30_MINS_MS + '&t=' + now + '&sig=' + sig; - } } + // if we want to have a callback snooze url this is the way, but emergency ack work better + // var now = Date.now(); + // var sig = ctx.notifications.sign(1, TIME_30_MINS_MS, Date.now()); + // if (sig) { + // msg.url_title = 'Snooze for 30 minutes'; + // msg.url = env.baseUrl + '/api/v1/notifications/snooze?level=1&lengthMills=' + TIME_30_MINS_MS + '&t=' + now + '&sig=' + sig; + // } + //add the key to the cache before sending, but with a short TTL recentlySent.set(key, notify, 30); pushover.send(msg, function(err, result) { diff --git a/tests/boluswizardpreview.test.js b/tests/boluswizardpreview.test.js index 7338e15c98c..9677c9d2c5e 100644 --- a/tests/boluswizardpreview.test.js +++ b/tests/boluswizardpreview.test.js @@ -27,6 +27,7 @@ describe('boluswizardpreview', function ( ) { return {iob: 0} }); + boluswizardpreview.setProperties(sbx); boluswizardpreview.checkNotifications(sbx); should.not.exist(ctx.notifications.findHighestAlarm()); @@ -43,6 +44,7 @@ describe('boluswizardpreview', function ( ) { return {iob: 0} }); + boluswizardpreview.setProperties(sbx); boluswizardpreview.checkNotifications(sbx); ctx.notifications.findHighestAlarm().level.should.equal(ctx.notifications.levels.WARN); @@ -59,6 +61,7 @@ describe('boluswizardpreview', function ( ) { return {iob: 0} }); + boluswizardpreview.setProperties(sbx); boluswizardpreview.checkNotifications(sbx); ctx.notifications.findHighestAlarm().level.should.equal(ctx.notifications.levels.URGENT); From 151b952a537f0e9786cee465215f271e3f8a68fc Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 26 Jun 2015 00:02:43 -0700 Subject: [PATCH 214/937] even better notification messages, added option to use raw bgs for ar2 forecast --- lib/notifications.js | 2 +- lib/plugins/ar2.js | 59 +++++++++++++++++++++++-------- lib/plugins/boluswizardpreview.js | 24 ++++++++----- lib/plugins/cob.js | 13 +++---- lib/plugins/rawbg.js | 4 +-- lib/plugins/simplealarms.js | 6 ++++ lib/plugins/treatmentnotify.js | 5 +-- 7 files changed, 77 insertions(+), 36 deletions(-) diff --git a/lib/notifications.js b/lib/notifications.js index 1b219a0dc52..4ac61fe82a5 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -36,7 +36,7 @@ function init (env, ctx) { case 2: return 'Urgent'; case 1: - return 'Warn'; + return 'Warning'; case 0: return 'Info'; case -1: diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 0654db56a0f..fa64d71170f 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -1,6 +1,7 @@ 'use strict'; var _ = require('lodash'); +var rawbg = require('./rawbg')(); function init() { @@ -11,6 +12,9 @@ function init() { ar2.label = 'AR2'; ar2.pluginType = 'forecast'; + var BG_REF = 140; //Central tendency + var BG_MIN = 36; //Not 39, but why? + var BG_MAX = 400; var WARN_THRESHOLD = 0.05; var URGENT_THRESHOLD = 0.10; @@ -31,7 +35,7 @@ function init() { ; if (lastSGVEntry && Date.now() - lastSGVEntry.x < TEN_MINUTES) { - forecast = ar2.forecast(sbx.data.sgvs); + forecast = ar2.forecast(sbx.data.sgvs, sbx); if (forecast.avgLoss > URGENT_THRESHOLD) { trigger = true; level = 2; @@ -66,14 +70,26 @@ function init() { rangeLabel = ''; } + var rawbgProp = sbx.properties.rawbg; + var useRaw = rawbgProp && sbx.extendedSettings.useRaw; + var title = [levelLabel, rangeLabel, 'predicted'].join(' ').replace(' ', ' '); - var lines = [ - ['Now', sbx.scaleBg(sbx.data.lastSGV()), sbx.unitsLabel].join(' ') - , ['15m', predicted[2], sbx.unitsLabel].join(' ') - ]; + var lines = []; + + if (useRaw) { + lines.push(['Raw BG:', sbx.scaleBg(rawbgProp.value), sbx.unitsLabel, rawbgProp.noiseLabel].join(' ')); + } else { + lines.push(['BG Now:', sbx.scaleBg(sbx.data.lastSGV()), sbx.unitsLabel].join(' ')); + } + + lines.push([useRaw ? 'Raw BG' : 'BG Now', '15m:', sbx.scaleBg(predicted[2]), sbx.unitsLabel].join(' ')); + + if (rawbgProp && !useRaw) { //If we're using raw for the forecast, don't add dupe line to the message + lines.push(['Raw BG:', sbx.scaleBg(rawbgProp.value), sbx.unitsLabel, rawbgProp.noiseLabel].join(' ')); + } var bwp = sbx.properties.bwp && sbx.properties.bwp.bolusEstimateDisplay; - if (bwp) { + if (bwp && bwp > 0) { lines.push(['BWP:', bwp, 'U'].join(' ')); } @@ -82,7 +98,7 @@ function init() { lines.push(['IOB:', iob, 'U'].join(' ')); } - var cob = sbx.properties.cob && sbx.properties.cob.cob; + var cob = sbx.properties.cob && sbx.properties.cob.display; if (cob) { lines.push(['COB:', cob, 'g'].join(' ')); } @@ -105,7 +121,7 @@ function init() { } }; - ar2.forecast = function forecast(sgvs) { + ar2.forecast = function forecast(sgvs, sbx) { var lastIndex = sgvs.length - 1; @@ -114,16 +130,31 @@ function init() { , avgLoss: 0 }; - if (lastIndex > 0 && sgvs[lastIndex].y > 39 && sgvs[lastIndex - 1].y > 39) { + var current = sgvs[lastIndex].y; + var prev = sgvs[lastIndex - 1].y; + + var useRaw = sbx && sbx.extendedSettings.useRaw; + + if (useRaw) { + var cal = _.last(sbx.data.cals); + var currentRaw = rawbg.calc(sgvs[lastIndex], cal); + if (currentRaw) { + var prevRaw = rawbg.calc(sgvs[lastIndex - 1], cal); + if (prevRaw) { + current = currentRaw; + prev = prevRaw; + console.info('Using raw value for forecasting :)', prev, current); + } + } + } + + if (lastIndex > 0 && current > BG_MIN && sgvs[lastIndex - 1].y > BG_MIN) { // predict using AR model var lastValidReadingTime = sgvs[lastIndex].x; var elapsedMins = (sgvs[lastIndex].x - sgvs[lastIndex - 1].x) / ONE_MINUTE; - var BG_REF = 140; - var BG_MIN = 36; - var BG_MAX = 400; - var y = Math.log(sgvs[lastIndex].y / BG_REF); + var y = Math.log(current / BG_REF); if (elapsedMins < 5.1) { - y = [Math.log(sgvs[lastIndex - 1].y / BG_REF), y]; + y = [Math.log(prev / BG_REF), y]; } else { y = [y, y]; } diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index a76fc4af016..282e479aa5a 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -78,22 +78,28 @@ function init() { var levelLabel = sbx.notifications.levels.toString(level); var sound = level == sbx.notifications.levels.URGENT ? 'updown' : 'bike'; - var lines = [ - ['BG NOW:', results.lastSGV, sbx.unitsLabel].join(' ') - , ['BWP:', results.bolusEstimate, 'U'].join(' ') - ]; + var lines = []; - var iob = sbx.properties.iob && sbx.properties.iob.display; - if (iob) { - lines.push(['\nIOB:', iob, 'U'].join(' ')); + var rawbgProp = sbx.properties.rawbg; + + lines.push(['BG NOW:', results.lastSGV, sbx.unitsLabel].join(' ')); + if (rawbgProp) { + lines.push(['Raw BG:', rawbgProp.value, sbx.unitsLabel, rawbgProp.noiseLabel].join(' ')); } + var delta = sbx.properties.delta && sbx.properties.delta.display; + if (delta) { + lines.push('Delta: ' + delta); + } + + lines.push(['BWP:', results.bolusEstimateDisplay, 'U'].join(' ')); + var iob = sbx.properties.iob && sbx.properties.iob.display; if (iob) { lines.push(['IOB:', iob, 'U'].join(' ')); } - var cob = sbx.properties.cob && sbx.properties.cob.cob; + var cob = sbx.properties.cob && sbx.properties.cob.display; if (cob) { lines.push(['COB:', cob, 'g'].join(' ')); } @@ -102,7 +108,7 @@ function init() { sbx.notifications.requestNotify({ level: level - , title: levelLabel + 'Check BG, time to bolus?' + , title: levelLabel + ', Check BG, time to bolus?' , message: message , pushoverSound: sound , plugin: bwp diff --git a/lib/plugins/cob.js b/lib/plugins/cob.js index cc443d62f64..e2b9c0c6648 100644 --- a/lib/plugins/cob.js +++ b/lib/plugins/cob.js @@ -79,12 +79,13 @@ function init() { var rawCarbImpact = isDecaying * profile.getSensitivity(time) / profile.getCarbRatio(time) * profile.getCarbAbsorptionRate(time) / 60; return { - decayedBy: lastDecayedBy, - isDecaying: isDecaying, - carbs_hr: profile.getCarbAbsorptionRate(time), - rawCarbImpact: rawCarbImpact, - cob: totalCOB, - lastCarbs: lastCarbs + decayedBy: lastDecayedBy + , isDecaying: isDecaying + , carbs_hr: profile.getCarbAbsorptionRate(time) + , rawCarbImpact: rawCarbImpact + , cob: totalCOB + , display: Math.round(totalCOB * 10) / 10 + , lastCarbs: lastCarbs }; }; diff --git a/lib/plugins/rawbg.js b/lib/plugins/rawbg.js index 17814c2a92d..cbd3410bc0a 100644 --- a/lib/plugins/rawbg.js +++ b/lib/plugins/rawbg.js @@ -44,7 +44,7 @@ function init() { sbx.pluginBase.updatePillText(rawbg, options); }; - rawbg.calc = function calc(sgv, cal, sbx) { + rawbg.calc = function calc(sgv, cal) { var raw = 0 , unfiltered = parseInt(sgv.unfiltered) || 0 , filtered = parseInt(sgv.filtered) || 0 @@ -63,7 +63,7 @@ function init() { raw = scale * ( unfiltered - intercept) / slope / ratio; } - return sbx.scaleBg(Math.round(raw)); + return Math.round(raw); }; rawbg.isEnabled = function isEnabled(sbx) { diff --git a/lib/plugins/simplealarms.js b/lib/plugins/simplealarms.js index 8db1aea089d..506326ff4a3 100644 --- a/lib/plugins/simplealarms.js +++ b/lib/plugins/simplealarms.js @@ -54,6 +54,12 @@ function init() { pushoverSound = 'magic'; } + var message = lastSGV + ' ' + sbx.unitsLabel; + + var delta = sbx.properties.delta && sbx.properties.delta.display; + if (delta) { + message += '\nDelta: ' + delta; + } if (trigger) { diff --git a/lib/plugins/treatmentnotify.js b/lib/plugins/treatmentnotify.js index 255aa7d9f26..c68346b152a 100644 --- a/lib/plugins/treatmentnotify.js +++ b/lib/plugins/treatmentnotify.js @@ -43,7 +43,6 @@ function init() { sbx.notifications.requestSnooze({ level: sbx.notifications.levels.URGENT , lengthMills: snoozeLength - //, debug: results }); } @@ -53,10 +52,9 @@ function init() { level: sbx.notifications.levels.INFO , title: 'Calibration' //assume all MGBs are calibrations for now //TODO: figure out why mbg is y here #CleanUpDataModel - , message: '\nMeter BG: ' + sbx.scaleBg(lastMBG.y) + ' ' + sbx.unitsLabel + , message: 'Meter BG: ' + sbx.scaleBg(lastMBG.y) + ' ' + sbx.unitsLabel , plugin: treatmentnotify , pushoverSound: 'magic' - //, debug: results }); } @@ -81,7 +79,6 @@ function init() { , title: lastTreatment.eventType , message: message , plugin: treatmentnotify -// , debug: results }); } From ff439acd7be19eac70b70759d676c8b2864320a5 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 26 Jun 2015 00:22:02 -0700 Subject: [PATCH 215/937] update readme with new plugin/teatment profile info --- README.md | 68 +++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 61 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 10ee92a7bc5..9d4d99a4f49 100644 --- a/README.md +++ b/README.md @@ -154,10 +154,11 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * `cage` (Cannula Age) - Calculates the number of hours since the last `Site Change` treatment that was recorded. * `delta` (BG Delta) - Calculates and displays the change between the last 2 BG values. **Enabled by default.** * `upbat` (Uploader Battery) - Displays the most recent battery status from the uploader phone. **Enabled by default.** - * `ar2` ([Forcasting using AR2 algorithm](https://github.com/nightscout/nightscout.github.io/wiki/Forecasting)) - Generates alarms based on forecasted values. **Enabled by default.** + * `ar2` ([Forcasting using AR2 algorithm](https://github.com/nightscout/nightscout.github.io/wiki/Forecasting)) - Generates alarms based on forecasted values. **Enabled by default.** Use [extended setting](#extended-settings) `AR2_USE_RAW=true` to forecast using `rawbg` values. * `simplealarms` (Simple BG Alarms) - Uses `BG_HIGH`, `BG_TARGET_TOP`, `BG_TARGET_BOTTOM`, `BG_LOW` settings to generate alarms. * `errorcodes` (CGM Error Codes) - Generates alarms for CGM codes `9` (hourglass) and `10` (???). **Enabled by default.** * `treatmentnotify` (Treatment Notifications) - Generates notifications when a treatment has been entered and snoozes alarms minutes after a treatment. Default snooze is 10 minutes, and can be set using the `TREATMENTNOTIFY_SNOOZE_MINS` [extended setting](#extended-settings). + * `basal` (Basal Profile) - Adds the Basal pill visualization to display the basal rate for the current time. Also enables the `bwp` plugin to calculate correction temp basal suggestions. Uses the `basal` field from the [treatment profile](#treatment-profile). #### 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. @@ -165,7 +166,7 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. Plugins only have access to their own extended settings, all the extended settings of client plugins will be sent to the browser. ### Treatment Profile - Some of the [plugins](#plugins) make use of a treatment profile that is stored in Mongo. To use those plugins there should only be a single doc in the `profile` collection. For example (change it to fit you): + Some of the [plugins](#plugins) make use of a treatment profile that is stored in Mongo. To use those plugins there should only be a single doc in the `profile` collection. A simple example (change it to fit you): ```json { @@ -173,19 +174,72 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. "carbs_hr": 30, "carbratio": 7.5, "sens": 35, + "basal": 1.00 "target_low": 95, "target_high": 120 } ``` + + Profiles can also use time periods for any field, for example: + + ```json + { + "carbratio": [ + { + "time": "00:00", + "value": 16 + }, + { + "time": "06:00", + "value": 15 + }, + { + "time": "14:00", + "value": 16 + } + ], + "basal": [ + { + "time": "00:00", + "value": 0.175 + }, + { + "time": "02:30", + "value": 0.125 + }, + { + "time": "05:00", + "value": 0.075 + }, + { + "time": "08:00", + "value": 0.1 + }, + { + "time": "14:00", + "value": 0.125 + }, + { + "time": "20:00", + "value": 0.3 + }, + { + "time": "22:00", + "value": 0.225 + } + ] + } + ``` Treatment Profile Fields: - * `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 + * `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. * `sens` (Insulin sensitivity) How much one unit of insulin will normally lower blood glucose. - * `target_high` - Upper target for correction boluses - * `target_low` - Lower target for correction boluses + * `basal` The basal rate set on the pump. + * `target_high` - Upper target for correction boluses. + * `target_low` - Lower target for correction boluses. Additional information can be found [here](http://www.nightscout.info/wiki/labs/the-nightscout-iob-cob-website). From 6dce8e1c2387716cd19432d707078e296711ba7b Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 26 Jun 2015 23:25:25 -0700 Subject: [PATCH 216/937] only use raw for ar2 if an alarms wasn't triggered for the sgvs; more message and test improvements --- lib/plugins/ar2.js | 121 ++++++++++++++++-------------- lib/plugins/boluswizardpreview.js | 15 ++-- lib/plugins/simplealarms.js | 8 +- tests/ar2.test.js | 67 +++++++++++++++-- tests/simplealarms.test.js | 10 ++- 5 files changed, 143 insertions(+), 78 deletions(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index fa64d71170f..15e4dc7f8dc 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -26,31 +26,32 @@ function init() { ar2.checkNotifications = function checkNotifications(sbx) { - var trigger = false - , lastSGVEntry = _.last(sbx.data.sgvs) - , forecast = null - , level = 0 - , levelLabel = '' - , pushoverSound = null - ; + var lastSGVEntry = _.last(sbx.data.sgvs); + + var result = {}; if (lastSGVEntry && Date.now() - lastSGVEntry.x < TEN_MINUTES) { - forecast = ar2.forecast(sbx.data.sgvs, sbx); - if (forecast.avgLoss > URGENT_THRESHOLD) { - trigger = true; - level = 2; - levelLabel = 'Urgent'; - pushoverSound = 'persistent'; - } else if (forecast.avgLoss > WARN_THRESHOLD) { - trigger = true; - level = 1; - levelLabel = 'Warning'; + result = ar2.forcastAndCheck(sbx.data.sgvs) + } + + var usingRaw = false; + if (!result.trigger && sbx.extendedSettings.useRaw) { + var cal = _.last(sbx.data.cals); + if (cal) { + var rawSGVs = _.map(_.takeRight(sbx.data.sgvs, 2), function eachSGV (sgv) { + return { + x: sgv.x + , y: Math.max(rawbg.calc(sgv, cal), BG_MIN) //stay above BG_MIN + }; + }); + result = ar2.forcastAndCheck(rawSGVs, true); + usingRaw = true; } } - if (trigger) { + if (result.trigger) { - var predicted = _.map(forecast.predicted, function(p) { return sbx.scaleBg(p.y) } ); + var predicted = _.map(result.forecast.predicted, function(p) { return sbx.scaleBg(p.y) } ); var first = _.first(predicted); var last = _.last(predicted); @@ -62,32 +63,36 @@ function init() { var rangeLabel = ''; if (max > sbx.scaleBg(sbx.thresholds.bg_target_top)) { rangeLabel = 'HIGH'; - if (!pushoverSound) pushoverSound = 'climb' } else if (min < sbx.scaleBg(sbx.thresholds.bg_target_bottom)) { - rangeLabel = 'LOW'; - if (!pushoverSound) pushoverSound = 'falling' + rangeLabel = 'LOW'; } else { - rangeLabel = ''; + rangeLabel = 'Check BG'; } - var rawbgProp = sbx.properties.rawbg; - var useRaw = rawbgProp && sbx.extendedSettings.useRaw; + var title = sbx.notifications.levels.toString(result.level) + ', ' + rangeLabel; + title += ' BG'; + if (lastSGVEntry.y > sbx.thresholds.bg_target_bottom && lastSGVEntry.y < sbx.thresholds.bg_target_top) { + title += ' predicted'; + } + if (usingRaw) { + title += ' w/raw'; + } - var title = [levelLabel, rangeLabel, 'predicted'].join(' ').replace(' ', ' '); - var lines = []; + var lines = ['BG Now: ' + sbx.scaleBg(sbx.data.lastSGV())]; - if (useRaw) { - lines.push(['Raw BG:', sbx.scaleBg(rawbgProp.value), sbx.unitsLabel, rawbgProp.noiseLabel].join(' ')); - } else { - lines.push(['BG Now:', sbx.scaleBg(sbx.data.lastSGV()), sbx.unitsLabel].join(' ')); + var delta = sbx.properties.delta && sbx.properties.delta.display; + if (delta) { + lines[0] += ' ' + delta; } + lines[0] += ' ' + sbx.unitsLabel; - lines.push([useRaw ? 'Raw BG' : 'BG Now', '15m:', sbx.scaleBg(predicted[2]), sbx.unitsLabel].join(' ')); - - if (rawbgProp && !useRaw) { //If we're using raw for the forecast, don't add dupe line to the message - lines.push(['Raw BG:', sbx.scaleBg(rawbgProp.value), sbx.unitsLabel, rawbgProp.noiseLabel].join(' ')); + var rawbgProp = sbx.properties.rawbg; + if (rawbgProp) { + lines.push(['Raw BG:', rawbgProp.value, sbx.unitsLabel, rawbgProp.noiseLabel].join(' ')); } + lines.push([usingRaw ? 'Raw BG' : 'BG Now', '15m:', sbx.scaleBg(predicted[2]), sbx.unitsLabel].join(' ')); + var bwp = sbx.properties.bwp && sbx.properties.bwp.bolusEstimateDisplay; if (bwp && bwp > 0) { lines.push(['BWP:', bwp, 'U'].join(' ')); @@ -105,23 +110,40 @@ function init() { var message = lines.join('\n'); - forecast.predicted = _.map(forecast.predicted, function(p) { return sbx.scaleBg(p.y) } ).join(', '); - sbx.notifications.requestNotify({ - level: level + level: result.level , title: title , message: message - , pushoverSound: pushoverSound + , pushoverSound: result.pushoverSound , plugin: ar2 , debug: { - forecast: forecast + forecast: { + predicted: _.map(result.forecast.predicted, function(p) { return sbx.scaleBg(p.y) } ).join(', ') + } , thresholds: sbx.thresholds } }); } }; - ar2.forecast = function forecast(sgvs, sbx) { + ar2.forcastAndCheck = function forcastAndCheck(sgvs, usingRaw) { + var result = { + forecast: ar2.forecast(sgvs) + }; + + if (result.forecast.avgLoss > URGENT_THRESHOLD && !usingRaw) { + result.trigger = true; + result.level = 2; + result.pushoverSound = 'persistent'; + } else if (result.forecast.avgLoss > WARN_THRESHOLD) { + result.trigger = true; + result.level = 1; + } + + return result; + }; + + ar2.forecast = function forecast(sgvs) { var lastIndex = sgvs.length - 1; @@ -133,22 +155,7 @@ function init() { var current = sgvs[lastIndex].y; var prev = sgvs[lastIndex - 1].y; - var useRaw = sbx && sbx.extendedSettings.useRaw; - - if (useRaw) { - var cal = _.last(sbx.data.cals); - var currentRaw = rawbg.calc(sgvs[lastIndex], cal); - if (currentRaw) { - var prevRaw = rawbg.calc(sgvs[lastIndex - 1], cal); - if (prevRaw) { - current = currentRaw; - prev = prevRaw; - console.info('Using raw value for forecasting :)', prev, current); - } - } - } - - if (lastIndex > 0 && current > BG_MIN && sgvs[lastIndex - 1].y > BG_MIN) { + if (lastIndex > 0 && current >= BG_MIN && sgvs[lastIndex - 1].y >= BG_MIN) { // predict using AR model var lastValidReadingTime = sgvs[lastIndex].x; var elapsedMins = (sgvs[lastIndex].x - sgvs[lastIndex - 1].x) / ONE_MINUTE; diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 282e479aa5a..c575afce812 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -78,20 +78,19 @@ function init() { var levelLabel = sbx.notifications.levels.toString(level); var sound = level == sbx.notifications.levels.URGENT ? 'updown' : 'bike'; - var lines = []; + var lines = ['BG Now: ' + results.lastSGV]; - var rawbgProp = sbx.properties.rawbg; + var delta = sbx.properties.delta && sbx.properties.delta.display; + if (delta) { + lines[0] += ' ' + delta; + } + lines[0] += ' ' + sbx.unitsLabel; - lines.push(['BG NOW:', results.lastSGV, sbx.unitsLabel].join(' ')); + var rawbgProp = sbx.properties.rawbg; if (rawbgProp) { lines.push(['Raw BG:', rawbgProp.value, sbx.unitsLabel, rawbgProp.noiseLabel].join(' ')); } - var delta = sbx.properties.delta && sbx.properties.delta.display; - if (delta) { - lines.push('Delta: ' + delta); - } - lines.push(['BWP:', results.bolusEstimateDisplay, 'U'].join(' ')); var iob = sbx.properties.iob && sbx.properties.iob.display; diff --git a/lib/plugins/simplealarms.js b/lib/plugins/simplealarms.js index 506326ff4a3..fc4d61c7364 100644 --- a/lib/plugins/simplealarms.js +++ b/lib/plugins/simplealarms.js @@ -54,19 +54,19 @@ function init() { pushoverSound = 'magic'; } - var message = lastSGV + ' ' + sbx.unitsLabel; + var message = 'BG Now: ' + lastSGV; var delta = sbx.properties.delta && sbx.properties.delta.display; if (delta) { - message += '\nDelta: ' + delta; + message += ' ' + delta; } - + message += ' ' + sbx.unitsLabel; if (trigger) { sbx.notifications.requestNotify({ level: level , title: title - , message: [lastSGV, sbx.unitsLabel].join(' ') + , message: message , plugin: simplealarms , pushoverSound: pushoverSound , debug: { diff --git a/tests/ar2.test.js b/tests/ar2.test.js index a84f8febf66..c94cfe74e57 100644 --- a/tests/ar2.test.js +++ b/tests/ar2.test.js @@ -3,8 +3,12 @@ var should = require('should'); describe('ar2', function ( ) { var ar2 = require('../lib/plugins/ar2')(); + var delta = require('../lib/plugins/delta')(); var env = require('../env')(); + var envRaw = require('../env')(); + envRaw.extendedSettings = {'ar2': {useRaw: true}}; + var ctx = {}; ctx.data = require('../lib/data')(env, ctx); ctx.notifications = require('../lib/notifications')(env, ctx); @@ -29,8 +33,12 @@ describe('ar2', function ( ) { ctx.data.sgvs = [{y: 150, x: before}, {y: 170, x: now}]; var sbx = require('../lib/sandbox')().serverInit(env, ctx); + delta.setProperties(sbx); ar2.checkNotifications(sbx); - ctx.notifications.findHighestAlarm().level.should.equal(ctx.notifications.levels.WARN); + var highest = ctx.notifications.findHighestAlarm(); + highest.level.should.equal(ctx.notifications.levels.WARN); + highest.title.should.equal('Warning, HIGH BG predicted'); + highest.message.should.startWith('BG Now: 170 +20 mg/dl'); done(); }); @@ -41,7 +49,9 @@ describe('ar2', function ( ) { var sbx = require('../lib/sandbox')().serverInit(env, ctx); ar2.checkNotifications(sbx); - ctx.notifications.findHighestAlarm().level.should.equal(ctx.notifications.levels.URGENT); + var highest = ctx.notifications.findHighestAlarm(); + highest.level.should.equal(ctx.notifications.levels.URGENT); + highest.title.should.equal('Urgent, HIGH BG'); done(); }); @@ -52,18 +62,63 @@ describe('ar2', function ( ) { var sbx = require('../lib/sandbox')().serverInit(env, ctx); ar2.checkNotifications(sbx); - ctx.notifications.findHighestAlarm().level.should.equal(ctx.notifications.levels.WARN); + var highest = ctx.notifications.findHighestAlarm(); + highest.level.should.equal(ctx.notifications.levels.WARN); + highest.title.should.equal('Warning, LOW BG'); + + done(); + }); + + it('should trigger a warning when almost below target', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{y: 90, x: before}, {y: 83, x: now}]; + + var sbx = require('../lib/sandbox')().serverInit(env, ctx); + ar2.checkNotifications(sbx); + var highest = ctx.notifications.findHighestAlarm(); + highest.level.should.equal(ctx.notifications.levels.WARN); + highest.title.should.equal('Warning, LOW BG predicted'); done(); }); - it('should trigger a urgent alarm when low fast', function (done) { + it('should trigger a urgent alarm when falling fast', function (done) { ctx.notifications.initRequests(); - ctx.data.sgvs = [{y: 120, x: before}, {y: 80, x: now}]; + ctx.data.sgvs = [{y: 120, x: before}, {y: 85, x: now}]; var sbx = require('../lib/sandbox')().serverInit(env, ctx); ar2.checkNotifications(sbx); - ctx.notifications.findHighestAlarm().level.should.equal(ctx.notifications.levels.URGENT); + var highest = ctx.notifications.findHighestAlarm(); + highest.level.should.equal(ctx.notifications.levels.URGENT); + highest.title.should.equal('Urgent, LOW BG predicted'); + + done(); + }); + + it('should trigger a warning (no urgent for raw) when raw is falling really fast, but sgv is steady', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{unfiltered: 113680, filtered: 111232, y: 100, x: before, noise: 1}, {unfiltered: 43680, filtered: 111232, y: 100, x: now, noise: 1}]; + ctx.data.cals = [{scale: 1, intercept: 25717.82377004309, slope: 766.895601715918}]; + + var sbx = require('../lib/sandbox')().serverInit(envRaw, ctx); + ar2.checkNotifications(sbx.withExtendedSettings(ar2)); + var highest = ctx.notifications.findHighestAlarm(); + highest.level.should.equal(ctx.notifications.levels.WARN); + highest.title.should.equal('Warning, LOW BG predicted w/raw'); + + done(); + }); + + it('should trigger a warning (no urgent for raw) when raw is rising really fast, but sgv is steady', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{unfiltered: 113680, filtered: 111232, y: 100, x: before, noise: 1}, {unfiltered: 183680, filtered: 111232, y: 100, x: now, noise: 1}]; + ctx.data.cals = [{scale: 1, intercept: 25717.82377004309, slope: 766.895601715918}]; + + var sbx = require('../lib/sandbox')().serverInit(envRaw, ctx); + ar2.checkNotifications(sbx.withExtendedSettings(ar2)); + var highest = ctx.notifications.findHighestAlarm(); + highest.level.should.equal(ctx.notifications.levels.WARN); + highest.title.should.equal('Warning, HIGH BG predicted w/raw'); done(); }); diff --git a/tests/simplealarms.test.js b/tests/simplealarms.test.js index 1aac1ea2043..95b82c465ad 100644 --- a/tests/simplealarms.test.js +++ b/tests/simplealarms.test.js @@ -3,6 +3,7 @@ var should = require('should'); describe('simplealarms', function ( ) { var simplealarms = require('../lib/plugins/simplealarms')(); + var delta = require('../lib/plugins/delta')(); var env = require('../env')(); var ctx = {}; @@ -10,6 +11,7 @@ describe('simplealarms', function ( ) { ctx.notifications = require('../lib/notifications')(env, ctx); var now = Date.now(); + var before = now - (5 * 60 * 1000); it('Not trigger an alarm when in range', function (done) { @@ -25,12 +27,14 @@ describe('simplealarms', function ( ) { it('should trigger a warning when above target', function (done) { ctx.notifications.initRequests(); - ctx.data.sgvs = [{x: now, y: 181}]; + ctx.data.sgvs = [{x: before, y: 171}, {x: now, y: 181}]; var sbx = require('../lib/sandbox')().serverInit(env, ctx); + delta.setProperties(sbx); simplealarms.checkNotifications(sbx); - ctx.notifications.findHighestAlarm().level.should.equal(ctx.notifications.levels.WARN); - + var highest = ctx.notifications.findHighestAlarm(); + highest.level.should.equal(ctx.notifications.levels.WARN); + highest.message.should.equal('BG Now: 181 +10 mg/dl'); done(); }); From 2e0e976c64de268f0c83b27ed7a1c973250b5aef Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 26 Jun 2015 23:48:59 -0700 Subject: [PATCH 217/937] removed some debug --- tests/cob.test.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/cob.test.js b/tests/cob.test.js index 64eb510a331..c674ea4ee91 100644 --- a/tests/cob.test.js +++ b/tests/cob.test.js @@ -30,10 +30,6 @@ describe('COB', function ( ) { var before10 = cob.cobTotal(treatments, profile, new Date("2015-05-29T03:45:10.670Z")); var after10 = cob.cobTotal(treatments, profile, new Date("2015-05-29T03:45:11.670Z")); - console.info('>>>>after100:', after100); - console.info('>>>>before10:', before10); - console.info('>>>>after2nd:', after10); - after100.cob.should.equal(100); Math.round(before10.cob).should.equal(59); Math.round(after10.cob).should.equal(69); //WTF == 128 From 7a1f0edfce87d0d3b99f2a0451549324b751fa43 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 26 Jun 2015 23:49:20 -0700 Subject: [PATCH 218/937] fix ar2 test when run as full suite --- tests/ar2.test.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/ar2.test.js b/tests/ar2.test.js index c94cfe74e57..57073edefea 100644 --- a/tests/ar2.test.js +++ b/tests/ar2.test.js @@ -6,8 +6,6 @@ describe('ar2', function ( ) { var delta = require('../lib/plugins/delta')(); var env = require('../env')(); - var envRaw = require('../env')(); - envRaw.extendedSettings = {'ar2': {useRaw: true}}; var ctx = {}; ctx.data = require('../lib/data')(env, ctx); @@ -95,12 +93,18 @@ describe('ar2', function ( ) { done(); }); + function rawSandbox(ctx) { + var envRaw = require('../env')(); + envRaw.extendedSettings = {'ar2': {useRaw: true}}; + return require('../lib/sandbox')().serverInit(envRaw, ctx); + } + it('should trigger a warning (no urgent for raw) when raw is falling really fast, but sgv is steady', function (done) { ctx.notifications.initRequests(); ctx.data.sgvs = [{unfiltered: 113680, filtered: 111232, y: 100, x: before, noise: 1}, {unfiltered: 43680, filtered: 111232, y: 100, x: now, noise: 1}]; ctx.data.cals = [{scale: 1, intercept: 25717.82377004309, slope: 766.895601715918}]; - var sbx = require('../lib/sandbox')().serverInit(envRaw, ctx); + var sbx = rawSandbox(ctx); ar2.checkNotifications(sbx.withExtendedSettings(ar2)); var highest = ctx.notifications.findHighestAlarm(); highest.level.should.equal(ctx.notifications.levels.WARN); @@ -114,7 +118,7 @@ describe('ar2', function ( ) { ctx.data.sgvs = [{unfiltered: 113680, filtered: 111232, y: 100, x: before, noise: 1}, {unfiltered: 183680, filtered: 111232, y: 100, x: now, noise: 1}]; ctx.data.cals = [{scale: 1, intercept: 25717.82377004309, slope: 766.895601715918}]; - var sbx = require('../lib/sandbox')().serverInit(envRaw, ctx); + var sbx = rawSandbox(ctx); ar2.checkNotifications(sbx.withExtendedSettings(ar2)); var highest = ctx.notifications.findHighestAlarm(); highest.level.should.equal(ctx.notifications.levels.WARN); @@ -123,5 +127,4 @@ describe('ar2', function ( ) { done(); }); - }); \ No newline at end of file From f5d8340c14145244c5a2d98e6585496198ba1336 Mon Sep 17 00:00:00 2001 From: Fokko Driesprong Date: Sat, 27 Jun 2015 10:29:38 +0200 Subject: [PATCH 219/937] Modified the Dockerfile for building the image, the rest will be in the separate repository as discussed with @bewest and @jasoncalabrese. --- Dockerfile | 16 ++++++++++++---- docker-build.sh | 4 ---- docker/docker-build.sh | 8 -------- docker/docker-compose.yml | 20 -------------------- 4 files changed, 12 insertions(+), 36 deletions(-) delete mode 100644 docker-build.sh delete mode 100755 docker/docker-build.sh delete mode 100644 docker/docker-compose.yml diff --git a/Dockerfile b/Dockerfile index 9e91a997d0d..de11c7eb878 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,16 +1,24 @@ -FROM node:0.12.38-slim +FROM node:latest -MAINTAINER Nightscout +MAINTAINER fokko@driesprong.frl + +WORKDIR /opt/app +ADD . /opt/app # Netcat is required to poll the database, so Nightscout starts when MongoDB is up and running -RUN apt-get update && apt-get -y install netcat git +# RUN apt-get update && apt-get -y install netcat git + +RUN apt-get update && apt-get -y install git # Got this from the setup.sh RUN apt-get install -y python-software-properties python g++ make git +# Upgrade RUN apt-get upgrade -y +# Install using NPM RUN npm install . +# Expose the default port, although this does not matter at it will be exposed as an arbitrary port by the Docker network driver. EXPOSE 1337 -CMD ["sh", "docker/docker-start.sh"] \ No newline at end of file +CMD ["node", "server.js"] \ No newline at end of file diff --git a/docker-build.sh b/docker-build.sh deleted file mode 100644 index 05ed0164c72..00000000000 --- a/docker-build.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -sudo docker build -t local/nightscout . -sudo docker run -t local/nightscout \ No newline at end of file diff --git a/docker/docker-build.sh b/docker/docker-build.sh deleted file mode 100755 index 216e90bf504..00000000000 --- a/docker/docker-build.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -cd ../ - -docker-compose -p nightscout_build -f docker/docker-compose.yml stop -docker-compose -p nightscout_build -f docker/docker-compose.yml rm --force -v -docker-compose -p nightscout_build -f docker/docker-compose.yml build -docker-compose -p nightscout_build -f docker/docker-compose.yml up \ No newline at end of file diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml deleted file mode 100644 index 509d285ba8c..00000000000 --- a/docker/docker-compose.yml +++ /dev/null @@ -1,20 +0,0 @@ -nightscout: - build: ../ - links: - - database - - broker - ports: - - "1337:1337" - environment: - - MONGO_CONNECTION=mongodb://database/nightscout - - API_SECRET=mylittlesecret - - MQTT_MONITOR=mqtt://broker - - PORT=1337 -database: - image: mongo:3.0.3 - ports: - - "27017" -broker: - image: prologic/mosquitto - ports: - - "1883" \ No newline at end of file From 8317a528ae2cdfb6d1e4bc302abf7cf037cd9824 Mon Sep 17 00:00:00 2001 From: Fokko Driesprong Date: Sat, 27 Jun 2015 15:43:55 +0200 Subject: [PATCH 220/937] Obvious mistake.. --- Dockerfile | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/Dockerfile b/Dockerfile index de11c7eb878..abadf434d79 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,18 +5,13 @@ MAINTAINER fokko@driesprong.frl WORKDIR /opt/app ADD . /opt/app -# Netcat is required to poll the database, so Nightscout starts when MongoDB is up and running -# RUN apt-get update && apt-get -y install netcat git - -RUN apt-get update && apt-get -y install git - -# Got this from the setup.sh -RUN apt-get install -y python-software-properties python g++ make git +# Installing the required packages. +RUN apt-get update && apt-get install -y python-software-properties python g++ make git # Upgrade RUN apt-get upgrade -y -# Install using NPM +# Install Nightscout using NPM RUN npm install . # Expose the default port, although this does not matter at it will be exposed as an arbitrary port by the Docker network driver. From e5cf3cd0bd9e0a3e8632d12d9ed2794fd00d07d5 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 27 Jun 2015 11:11:42 -0700 Subject: [PATCH 221/937] added support for canceling pushover alarms; more refactoring and tests --- lib/api/notifications-api.js | 2 +- lib/bootevent.js | 1 + lib/plugins/ar2.js | 3 + lib/plugins/simplealarms.js | 5 -- lib/pushnotify.js | 32 +++++++--- lib/pushover.js | 36 +++++++---- package.json | 1 + tests/pushnotify.test.js | 115 +++++++++++++++++++++++++++++++++++ 8 files changed, 168 insertions(+), 27 deletions(-) create mode 100644 tests/pushnotify.test.js diff --git a/lib/api/notifications-api.js b/lib/api/notifications-api.js index 5e502bc24c4..933396db187 100644 --- a/lib/api/notifications-api.js +++ b/lib/api/notifications-api.js @@ -18,7 +18,7 @@ function configure (app, wares, ctx) { notifications.post('/notifications/pushovercallback', function (req, res) { console.info('GOT Pushover callback', req.body); - var result = ctx.pushnotify.ack(req.body); + var result = ctx.pushnotify.pushoverAck(req.body); res.redirect(302, '/result/?ok=' + result); }); diff --git a/lib/bootevent.js b/lib/bootevent.js index ffe90969661..7d508f2ea08 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -17,6 +17,7 @@ function boot (env) { // api and json object variables /////////////////////////////////////////////////// ctx.plugins = require('./plugins')().registerServerDefaults().init(env); + ctx.pushover = require('./pushover')(env); ctx.pushnotify = require('./pushnotify')(env, ctx); ctx.entries = require('./entries')(env, ctx); ctx.treatments = require('./treatments')(env, ctx); diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 15e4dc7f8dc..59bfb7cd90b 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -63,8 +63,10 @@ function init() { var rangeLabel = ''; if (max > sbx.scaleBg(sbx.thresholds.bg_target_top)) { rangeLabel = 'HIGH'; + if (!result.pushoverSound) result.pushoverSound = 'climb'; } else if (min < sbx.scaleBg(sbx.thresholds.bg_target_bottom)) { rangeLabel = 'LOW'; + if (!result.pushoverSound) result.pushoverSound = 'falling'; } else { rangeLabel = 'Check BG'; } @@ -138,6 +140,7 @@ function init() { } else if (result.forecast.avgLoss > WARN_THRESHOLD) { result.trigger = true; result.level = 1; + result.pushoverSound = 'persistent'; } return result; diff --git a/lib/plugins/simplealarms.js b/lib/plugins/simplealarms.js index fc4d61c7364..02a75d1118d 100644 --- a/lib/plugins/simplealarms.js +++ b/lib/plugins/simplealarms.js @@ -47,11 +47,6 @@ function init() { title = 'Low warning'; pushoverSound = 'falling'; console.info(title + ': ' + (lastSGV + ' < ' + sbx.scaleBg(sbx.thresholds.bg_target_bottom))); - } else if (sbx.thresholds.bg_magic && lastSVG == sbx.thresholds.bg_magic && lastSGVEntry.direction == 'Flat') { - trigger = true; - level = o; - title = 'Perfect'; - pushoverSound = 'magic'; } var message = 'BG Now: ' + lastSGV; diff --git a/lib/pushnotify.js b/lib/pushnotify.js index 6f9f23ce754..89db5fb7bb4 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -7,11 +7,6 @@ var NodeCache = require( "node-cache" ); function init(env, ctx) { - var pushover = require('./pushover')(env); - - var PUSHOVER_EMERGENCY = 2; - var PUSHOVER_NORMAL = 0; - // declare local constants for time differences var TIME_2_MINS_S = 120 , TIME_15_MINS_S = 15 * 60 @@ -27,8 +22,25 @@ function init(env, ctx) { var recentlySent = new NodeCache({ stdTTL: TIME_15_MINS_MS, checkperiod: 20 }); pushnotify.emitNotification = function emitNotification (notify) { - if (!pushover) return; - if (notify.clear) return; + if (!ctx.pushover) return; + + if (notify.clear) { + console.info('got a notify clear'); + var receiptKeys = receipts.keys(); + + _.forEach(receiptKeys, function eachKey (receipt) { + ctx.pushover.cancelWithReceipt(receipt, function cancelCallback (err, response) { + if (err) { + console.error('error canceling receipt, err: ', err); + } else { + console.info('got a receipt cancel response'); + } + }); + receipts.del(receipt); + }); + + return; + } var key = null; if (notify.level >= ctx.notifications.levels.WARN) { @@ -51,7 +63,7 @@ function init(env, ctx) { , sound: notify.pushoverSound || 'gamelan' , timestamp: new Date() //USE PUSHOVER_EMERGENCY for WARN and URGENT so we get the acks - , priority: notify.level > ctx.notifications.levels.WARN ? PUSHOVER_EMERGENCY : PUSHOVER_NORMAL + , priority: notify.level >= ctx.notifications.levels.WARN ? ctx.pushover.PRIORITY_EMERGENCY : ctx.pushover.PRIORITY_NORMAL }; if (notify.level >= ctx.notifications.levels.WARN) { @@ -72,7 +84,7 @@ function init(env, ctx) { //add the key to the cache before sending, but with a short TTL recentlySent.set(key, notify, 30); - pushover.send(msg, function(err, result) { + ctx.pushover.send(msg, function pushoverCallback (err, result) { if (err) { console.error('unable to send pushover notification', err); } else { @@ -91,7 +103,7 @@ function init(env, ctx) { }; - pushnotify.ack = function ack (response) { + pushnotify.pushoverAck = function pushoverAck (response) { if (!response.receipt) return false; var notify = receipts.get(response.receipt); diff --git a/lib/pushover.js b/lib/pushover.js index ce30c40cb26..8f8ba745cf7 100644 --- a/lib/pushover.js +++ b/lib/pushover.js @@ -1,19 +1,33 @@ 'use strict'; var Pushover = require('pushover-notifications'); +var request = require('request'); -function init(env) { - if (env.pushover_api_token && env.pushover_user_key) { - return new Pushover({ - token: env.pushover_api_token, - user: env.pushover_user_key, - onerror: function (err) { - console.log(err); - } +function init (env) { + var pushover = null; + + if (env.pushover_api_token && env.pushover_user_key) { + pushover = new Pushover({ + token: env.pushover_api_token, + user: env.pushover_user_key + }); + + pushover.PRIORITY_NORMAL = 0; + pushover.PRIORITY_EMERGENCY = 2; + + pushover.cancelWithReceipt = function cancelWithReceipt (receipt, callback) { + request + .get('https://api.pushover.net/1/receipts/' + receipt + '/cancel.json?token=' + env.pushover_api_token) + .on('response', function(response) { + callback(null, response); + }) + .on('error', function(err) { + callback(err); }); - } else { - return null; - } + }; + } + + return pushover; } module.exports = init; \ No newline at end of file diff --git a/package.json b/package.json index 463eca41490..da5c4c58c36 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "mqtt": "~0.3.11", "node-cache": "^3.0.0", "pushover-notifications": "0.2.0", + "request": "^2.58.0", "sgvdata": "git://github.com/ktind/sgvdata.git#wip/protobuf", "socket.io": "^1.3.5" }, diff --git a/tests/pushnotify.test.js b/tests/pushnotify.test.js new file mode 100644 index 00000000000..0f81a5a5755 --- /dev/null +++ b/tests/pushnotify.test.js @@ -0,0 +1,115 @@ +var should = require('should'); + +describe('pushnotify', function ( ) { + + it('send a pushover alarm, but only 1 time', function (done) { + var env = require('../env')(); + var ctx = {}; + + ctx.notifications = require('../lib/notifications')(env, ctx); + + var notify = { + title: 'Warning, this is a test!' + , message: 'details details details details' + , level: ctx.notifications.levels.WARN + , pushoverSound: 'climb' + , plugin: function test () {} + }; + + ctx.pushover = { + PRIORITY_NORMAL: 0 + , PRIORITY_EMERGENCY: 2 + , send: function mockedSend (msg, callback) { + msg.title.should.equal(notify.title); + msg.priority.should.equal(2); + msg.sound.should.equal('climb'); + callback(null, JSON.stringify({receipt: 'abcd12345'})); + done() + } + }; + + ctx.pushnotify = require('../lib/pushnotify')(env, ctx); + + ctx.pushnotify.emitNotification(notify); + + //call again, but should be deduped, or fail with 'done() called multiple times' + ctx.pushnotify.emitNotification(notify); + + }); + + it('send a pushover notification, but only 1 time', function (done) { + var env = require('../env')(); + var ctx = {}; + + ctx.notifications = require('../lib/notifications')(env, ctx); + + var notify = { + title: 'Sent from a test' + , message: 'details details details details' + , level: ctx.notifications.levels.INFO + , plugin: function test () {} + }; + + ctx.pushover = { + PRIORITY_NORMAL: 0 + , PRIORITY_EMERGENCY: 2 + , send: function mockedSend (msg, callback) { + msg.title.should.equal(notify.title); + msg.priority.should.equal(0); + msg.sound.should.equal('gamelan'); + callback(null, JSON.stringify({})); + done() + } + }; + + ctx.pushnotify = require('../lib/pushnotify')(env, ctx); + + ctx.pushnotify.emitNotification(notify); + + //call again, but should be deduped, or fail with 'done() called multiple times' + ctx.pushnotify.emitNotification(notify); + + }); + + it('send a pushover alarm, and then cancel', function (done) { + var env = require('../env')(); + var ctx = {}; + + ctx.notifications = require('../lib/notifications')(env, ctx); + + var notify = { + title: 'Warning, this is a test!' + , message: 'details details details details' + , level: ctx.notifications.levels.WARN + , pushoverSound: 'climb' + , plugin: function test () {} + }; + + ctx.pushover = { + PRIORITY_NORMAL: 0 + , PRIORITY_EMERGENCY: 2 + , send: function mockedSend (msg, callback) { + msg.title.should.equal(notify.title); + msg.priority.should.equal(2); + msg.sound.should.equal('climb'); + callback(null, JSON.stringify({receipt: 'abcd12345'})); + } + , cancelWithReceipt: function mockedCancel (receipt) { + receipt.should.equal('abcd12345'); + done(); + } + }; + + ctx.pushnotify = require('../lib/pushnotify')(env, ctx); + + //first send the warning + ctx.pushnotify.emitNotification(notify); + + //then pretend is was acked from the web + ctx.pushnotify.emitNotification({clear: true}); + + }); + + + +}); From e5a2c84b6765ad0dcd084f07f18de8b74ca6ea16 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 27 Jun 2015 12:11:28 -0700 Subject: [PATCH 222/937] fix COB pill formatting; add more tests --- lib/plugins/cob.js | 2 +- tests/cob.test.js | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/lib/plugins/cob.js b/lib/plugins/cob.js index e2b9c0c6648..1507ce4c96d 100644 --- a/lib/plugins/cob.js +++ b/lib/plugins/cob.js @@ -158,7 +158,7 @@ function init() { } sbx.pluginBase.updatePillText(sbx, { - value: displayCob + " g" + value: displayCob + 'g' , label: 'COB' , info: info }); diff --git a/tests/cob.test.js b/tests/cob.test.js index c674ea4ee91..60eef9db15c 100644 --- a/tests/cob.test.js +++ b/tests/cob.test.js @@ -63,5 +63,29 @@ describe('COB', function ( ) { result5.cob.should.equal(0); }); + it('set a pill to the current COB', function (done) { + + var app = {}; + var clientSettings = {}; + + var data = { + treatments: [{carbs: "8", "created_at": new Date()}] + , profile: require('../lib/profilefunctions')([profileData]) + }; + + var pluginBase = { + updatePillText: function mockedUpdatePillText (plugin, options) { + options.value.should.equal('8g'); + done(); + } + }; + + var sandbox = require('../lib/sandbox')(); + var sbx = sandbox.clientInit(app, clientSettings, Date.now(), pluginBase, data); + cob.setProperties(sbx); + cob.updateVisualisation(sbx); + + }); + }); \ No newline at end of file From 0dc909e36d00fef3835481f512d839a1655baaf0 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 27 Jun 2015 12:34:15 -0700 Subject: [PATCH 223/937] fix cob test and a bug --- lib/plugins/cob.js | 7 ++++--- tests/cob.test.js | 7 +++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/plugins/cob.js b/lib/plugins/cob.js index 1507ce4c96d..8969d2e1246 100644 --- a/lib/plugins/cob.js +++ b/lib/plugins/cob.js @@ -145,10 +145,11 @@ function init() { cob.updateVisualisation = function updateVisualisation(sbx) { - var prop = sbx.properties.cob.cob; - if (prop == undefined) return; + var prop = sbx.properties.cob; - var displayCob = Math.round(prop * 10) / 10; + if (prop == undefined || prop.cob == undefined) return; + + var displayCob = Math.round(prop.cob * 10) / 10; var info = null; if (prop.lastCarbs) { diff --git a/tests/cob.test.js b/tests/cob.test.js index 60eef9db15c..c34237d0823 100644 --- a/tests/cob.test.js +++ b/tests/cob.test.js @@ -69,8 +69,11 @@ describe('COB', function ( ) { var clientSettings = {}; var data = { - treatments: [{carbs: "8", "created_at": new Date()}] - , profile: require('../lib/profilefunctions')([profileData]) + treatments: [{ + carbs: "8" + , "created_at": Date.now() - 60000 //1m ago + }] + , profile: profile }; var pluginBase = { From 6f206930bc5efbfda54f8f4afaddf51439a56826 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 27 Jun 2015 14:57:13 -0700 Subject: [PATCH 224/937] added delta to the default server plugins for use in the notifications --- lib/plugins/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/plugins/index.js b/lib/plugins/index.js index f6bb629bf4e..2f45802de78 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -30,6 +30,7 @@ function init() { var serverDefaultPlugins = [ require('./rawbg')() + , require('./delta')() , require('./ar2')() , require('./simplealarms')() , require('./errorcodes')() From 665bf2a5389cc9a30dda580c274b74c11f9a3515 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 27 Jun 2015 22:35:23 -0700 Subject: [PATCH 225/937] fix typo --- lib/plugins/ar2.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 59bfb7cd90b..96f4c35ebd4 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -93,7 +93,7 @@ function init() { lines.push(['Raw BG:', rawbgProp.value, sbx.unitsLabel, rawbgProp.noiseLabel].join(' ')); } - lines.push([usingRaw ? 'Raw BG' : 'BG Now', '15m:', sbx.scaleBg(predicted[2]), sbx.unitsLabel].join(' ')); + lines.push([usingRaw ? 'Raw BG' : 'BG', '15m:', sbx.scaleBg(predicted[2]), sbx.unitsLabel].join(' ')); var bwp = sbx.properties.bwp && sbx.properties.bwp.bolusEstimateDisplay; if (bwp && bwp > 0) { From f805633f52df8fb1c181ace97fa0d357b153fe9b Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 28 Jun 2015 03:05:53 -0700 Subject: [PATCH 226/937] initial IFTTT Maker integration --- lib/bootevent.js | 1 + lib/maker.js | 51 ++++++++++++++++++++++++++++ lib/pushnotify.js | 84 +++++++++++++++++++++++++++++++---------------- 3 files changed, 107 insertions(+), 29 deletions(-) create mode 100644 lib/maker.js diff --git a/lib/bootevent.js b/lib/bootevent.js index 7d508f2ea08..1865077230f 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -18,6 +18,7 @@ function boot (env) { /////////////////////////////////////////////////// ctx.plugins = require('./plugins')().registerServerDefaults().init(env); ctx.pushover = require('./pushover')(env); + ctx.maker = require('./maker')(env); ctx.pushnotify = require('./pushnotify')(env, ctx); ctx.entries = require('./entries')(env, ctx); ctx.treatments = require('./treatments')(env, ctx); diff --git a/lib/maker.js b/lib/maker.js new file mode 100644 index 00000000000..165df71a673 --- /dev/null +++ b/lib/maker.js @@ -0,0 +1,51 @@ +'use strict'; + +var request = require('request'); + +function init (env) { + + console.info('>>>env.extendedSettings', env.extendedSettings); + var key = env.extendedSettings && env.extendedSettings.maker && env.extendedSettings.maker.key; + + function maker() { + return maker; + } + + maker.sendEvent = function sendEvent (event, callback) { + + if (!event || !event.name) { + callback('No event name found'); + } else { + var query = ''; + + if (event.values && event.values.length) { + _.forEach(event.values, function eachValue (value, index) { + if (query) { + query += '&'; + } else { + query += '?'; + } + query += 'value' + (index + 1) + '=\'' + encodeURIComponent(value) + '\''; + }); + } + + request + .get('https://maker.ifttt.com/trigger/' + event.name + '/with/key/' + key + query) + .on('response', function (response) { + callback(null, response); + }) + .on('error', function (err) { + callback(err); + }); + } + }; + + if (key) { + return maker(); + } else { + return null; + } + +} + +module.exports = init; \ No newline at end of file diff --git a/lib/pushnotify.js b/lib/pushnotify.js index 89db5fb7bb4..52172055200 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -22,22 +22,9 @@ function init(env, ctx) { var recentlySent = new NodeCache({ stdTTL: TIME_15_MINS_MS, checkperiod: 20 }); pushnotify.emitNotification = function emitNotification (notify) { - if (!ctx.pushover) return; - if (notify.clear) { console.info('got a notify clear'); - var receiptKeys = receipts.keys(); - - _.forEach(receiptKeys, function eachKey (receipt) { - ctx.pushover.cancelWithReceipt(receipt, function cancelCallback (err, response) { - if (err) { - console.error('error canceling receipt, err: ', err); - } else { - console.info('got a receipt cancel response'); - } - }); - receipts.del(receipt); - }); + if (ctx.pushover) cancelPushoverNotifications(); return; } @@ -51,11 +38,49 @@ function init(env, ctx) { key = notifyToHash(notify); } + notify.key = key; + if (recentlySent.get(key)) { console.info('notify: ' + key + ' has ALREADY been sent'); + return; } + recentlySent.set(key, notify, 30); + + if (ctx.pushover) sendPushoverNotifications(notify); + if (ctx.maker) sendMakerEvent(notify); + + }; + + pushnotify.pushoverAck = function pushoverAck (response) { + if (!response.receipt) return false; + + var notify = receipts.get(response.receipt); + console.info('push ack, response: ', response, ', notify: ', notify); + if (notify) { + ctx.notifications.ack(notify.level, TIME_30_MINS_MS, true) + } + return !!notify; + }; + + function cancelPushoverNotifications ( ) { + var receiptKeys = receipts.keys(); + + _.forEach(receiptKeys, function eachKey (receipt) { + ctx.pushover.cancelWithReceipt(receipt, function cancelCallback (err, response) { + if (err) { + console.error('error canceling receipt, err: ', err); + } else { + console.info('got a receipt cancel response'); + } + }); + receipts.del(receipt); + }); + + } + + function sendPushoverNotifications (notify, key) { var msg = { expire: TIME_15_MINS_S , title: notify.title @@ -83,7 +108,6 @@ function init(env, ctx) { // } //add the key to the cache before sending, but with a short TTL - recentlySent.set(key, notify, 30); ctx.pushover.send(msg, function pushoverCallback (err, result) { if (err) { console.error('unable to send pushover notification', err); @@ -92,7 +116,7 @@ function init(env, ctx) { result = JSON.parse(result); console.info('sent pushover notification: ', msg, 'result: ', result); //after successfully sent, increase the TTL - recentlySent.ttl(key, TIME_15_MINS_S); + recentlySent.ttl(notify.key, TIME_15_MINS_S); if (result.receipt) { //if this was an emergency alarm, also hold on to the receipt/notify mapping, for later acking @@ -100,21 +124,23 @@ function init(env, ctx) { } } }); + } - }; - - pushnotify.pushoverAck = function pushoverAck (response) { - if (!response.receipt) return false; - - var notify = receipts.get(response.receipt); - console.info('push ack, response: ', response, ', notify: ', notify); - if (notify) { - ctx.notifications.ack(notify.level, TIME_30_MINS_MS, true) - } - return !!notify; - }; + function sendMakerEvent (notify) { + ctx.maker.sendEvent({ + name: notify.makerEvent || notify.plugin.name + , values: notify.makerValues + }, function makerCallback (err, result) { + if (err) { + console.error('unable to send maker event', err, notify); + } else { + console.info('sent maker event: ', notify); + recentlySent.ttl(notify.key, TIME_15_MINS_S); + } + }) + } - function notifyToHash(notify) { + function notifyToHash (notify) { var hash = crypto.createHash('sha1'); var info = JSON.stringify(_.pick(notify, ['title', 'message'])); hash.update(info); From 80e3c2be2a34d9361f848ae180354a0da74e0dc7 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 28 Jun 2015 03:12:36 -0700 Subject: [PATCH 227/937] remove debug --- lib/maker.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/maker.js b/lib/maker.js index 165df71a673..11d20f8b5c8 100644 --- a/lib/maker.js +++ b/lib/maker.js @@ -4,7 +4,6 @@ var request = require('request'); function init (env) { - console.info('>>>env.extendedSettings', env.extendedSettings); var key = env.extendedSettings && env.extendedSettings.maker && env.extendedSettings.maker.key; function maker() { From 1b9cb01fe679f8340b100a326b54cecec2cc8316 Mon Sep 17 00:00:00 2001 From: Fokko Driesprong Date: Sun, 28 Jun 2015 13:25:21 +0200 Subject: [PATCH 228/937] Bower did not kick in because of permission-issue, the Docker-build now switches to a separate user for the node build instead of root. --- Dockerfile | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index abadf434d79..847f2391b2d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,16 +2,24 @@ FROM node:latest MAINTAINER fokko@driesprong.frl -WORKDIR /opt/app -ADD . /opt/app - # Installing the required packages. RUN apt-get update && apt-get install -y python-software-properties python g++ make git # Upgrade RUN apt-get upgrade -y -# Install Nightscout using NPM +# https://github.com/jspm/jspm-cli/issues/865 +ENV user node + +RUN groupadd --system $user && useradd --system --create-home --gid $user $user + +COPY . /home/$user/ +WORKDIR /home/$user + +# We don't wat to run in root. +RUN chown $user --recursive . +USER $user + RUN npm install . # Expose the default port, although this does not matter at it will be exposed as an arbitrary port by the Docker network driver. From c931d03a57a3c3accdee5e1d957e5fb8e4083945 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 28 Jun 2015 12:45:58 -0700 Subject: [PATCH 229/937] added more maker event names and tests --- lib/maker.js | 79 +++++++++++++++++++++++-------- lib/plugins/ar2.js | 4 ++ lib/plugins/boluswizardpreview.js | 2 + lib/plugins/errorcodes.js | 4 ++ lib/plugins/simplealarms.js | 7 +++ lib/pushnotify.js | 29 ++++++++---- tests/maker.test.js | 35 ++++++++++++++ 7 files changed, 130 insertions(+), 30 deletions(-) create mode 100644 tests/maker.test.js diff --git a/lib/maker.js b/lib/maker.js index 11d20f8b5c8..901575fa567 100644 --- a/lib/maker.js +++ b/lib/maker.js @@ -1,44 +1,81 @@ 'use strict'; +var _ = require('lodash'); var request = require('request'); function init (env) { var key = env.extendedSettings && env.extendedSettings.maker && env.extendedSettings.maker.key; + var TIME_30_MINS_MS = 30 * 60 * 1000; + function maker() { return maker; } - maker.sendEvent = function sendEvent (event, callback) { + var lastAllClear = 0; + + maker.sendAllClear = function sendAllClear (callback) { + if (Date.now() - lastAllClear > TIME_30_MINS_MS) { + lastAllClear = Date.now(); + maker.makeRequest('ns-allclear', function allClearCallback (err) { + if (err) { + lastAllClear = 0; + callback(err); + } else { + callback && callback(null, {sent: true}); + } + }); + } else { + callback && callback(null, {sent: false}); + } + }; + maker.sendEvent = function sendEvent (event, callback) { if (!event || !event.name) { callback('No event name found'); } else { - var query = ''; - - if (event.values && event.values.length) { - _.forEach(event.values, function eachValue (value, index) { - if (query) { - query += '&'; - } else { - query += '?'; - } - query += 'value' + (index + 1) + '=\'' + encodeURIComponent(value) + '\''; - }); - } - - request - .get('https://maker.ifttt.com/trigger/' + event.name + '/with/key/' + key + query) - .on('response', function (response) { - callback(null, response); - }) - .on('error', function (err) { + maker.makeRequest(event, function sendCallback (err, response) { + if (err) { callback(err); - }); + } else { + lastAllClear = 0; + callback && callback(null, response); + } + }); } }; + //exposed for testing + maker.valuesToQuery = function valuesToQuery (values) { + var query = ''; + + if (values && values.length) { + lastAllClear = 0; + _.forEach(values, function eachValue (value, index) { + if (query) { + query += '&'; + } else { + query += '?'; + } + query += 'value' + (index + 1) + '=' + encodeURIComponent(value); + }); + } + + return query; + }; + + maker.makeRequest = function makeRequest(event, callback) { + request + .get('https://maker.ifttt.com/trigger/' + event.name + '/with/key/' + key + maker.valuesToQuery(event.values)) + .on('response', function (response) { + callback && callback(null, response); + }) + .on('error', function (err) { + callback && callback(err); + }); + }; + if (key) { return maker(); } else { diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 96f4c35ebd4..58c53a4e454 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -61,11 +61,14 @@ function init() { var min = _.min([first, last, avg]); var rangeLabel = ''; + var eventName = 'ns-' + sbx.notifications.levels.toString(result.level).toLowerCase(); if (max > sbx.scaleBg(sbx.thresholds.bg_target_top)) { rangeLabel = 'HIGH'; + eventName += '-high'; if (!result.pushoverSound) result.pushoverSound = 'climb'; } else if (min < sbx.scaleBg(sbx.thresholds.bg_target_bottom)) { rangeLabel = 'LOW'; + eventName += '-low'; if (!result.pushoverSound) result.pushoverSound = 'falling'; } else { rangeLabel = 'Check BG'; @@ -116,6 +119,7 @@ function init() { level: result.level , title: title , message: message + , eventName: eventName , pushoverSound: result.pushoverSound , plugin: ar2 , debug: { diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index c575afce812..c2c464ca766 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -77,6 +77,7 @@ function init() { var level = results.bolusEstimate > urgentBWP ? sbx.notifications.levels.URGENT : sbx.notifications.levels.WARN; var levelLabel = sbx.notifications.levels.toString(level); var sound = level == sbx.notifications.levels.URGENT ? 'updown' : 'bike'; + var eventName = 'ns-bwp-' + levelLabel.toLowerCase(); var lines = ['BG Now: ' + results.lastSGV]; @@ -109,6 +110,7 @@ function init() { level: level , title: levelLabel + ', Check BG, time to bolus?' , message: message + , eventName: eventName , pushoverSound: sound , plugin: bwp , debug: results diff --git a/lib/plugins/errorcodes.js b/lib/plugins/errorcodes.js index 2462dd2a5de..2b0d3a1ee5e 100644 --- a/lib/plugins/errorcodes.js +++ b/lib/plugins/errorcodes.js @@ -22,6 +22,7 @@ function init() { var errorDisplay; var pushoverSound = null; var notifyLevel = sbx.notifications.levels.LOW; + var eventName = 'ns-cgm-errorcode'; switch (parseInt(lastSGV.y)) { case 0: //None @@ -52,11 +53,13 @@ function init() { errorDisplay = '?AD'; pushoverSound = 'alien'; notifyLevel = sbx.notifications.levels.URGENT; + eventName += '-hourglass'; break; case 10: //POWER_DEVIATION errorDisplay = '???'; pushoverSound = 'alien'; notifyLevel = sbx.notifications.levels.URGENT; + eventName += '-???'; break; case 12: //BAD_RF errorDisplay = '?RF'; @@ -72,6 +75,7 @@ function init() { level: notifyLevel , title: 'CGM Error Code' , message: errorDisplay + , eventName: eventName , plugin: errorcodes , pushoverSound: pushoverSound , debug: { diff --git a/lib/plugins/simplealarms.js b/lib/plugins/simplealarms.js index 02a75d1118d..ccbc993cea1 100644 --- a/lib/plugins/simplealarms.js +++ b/lib/plugins/simplealarms.js @@ -22,30 +22,36 @@ function init() { , pushoverSound = null ; + var eventName = ''; + if (lastSGV && lastSGVEntry && lastSGVEntry.y > 39 && Date.now() - lastSGVEntry.x < TIME_10_MINS_MS) { if (lastSGV > sbx.scaleBg(sbx.thresholds.bg_high)) { trigger = true; level = 2; title = 'Urgent HIGH'; pushoverSound = 'persistent'; + eventName = 'ns-urgent-high'; console.info(title + ': ' + (lastSGV + ' > ' + sbx.scaleBg(sbx.thresholds.bg_high))); } else if (lastSGV > sbx.scaleBg(sbx.thresholds.bg_target_top)) { trigger = true; level = 1; title = 'High warning'; pushoverSound = 'climb'; + eventName = 'ns-warning-high'; console.info(title + ': ' + (lastSGV + ' > ' + sbx.scaleBg(sbx.thresholds.bg_target_top))); } else if (lastSGV < sbx.scaleBg(sbx.thresholds.bg_low)) { trigger = true; level = 2; title = 'Urgent LOW'; pushoverSound = 'persistent'; + eventName = 'ns-urgent-low'; console.info(title + ': ' + (lastSGV + ' < ' + sbx.scaleBg(sbx.thresholds.bg_low))); } else if (lastSGV < sbx.scaleBg(sbx.thresholds.bg_target_bottom)) { trigger = true; level = 1; title = 'Low warning'; pushoverSound = 'falling'; + eventName = 'ns-warning-low'; console.info(title + ': ' + (lastSGV + ' < ' + sbx.scaleBg(sbx.thresholds.bg_target_bottom))); } @@ -62,6 +68,7 @@ function init() { level: level , title: title , message: message + , eventName: eventName , plugin: simplealarms , pushoverSound: pushoverSound , debug: { diff --git a/lib/pushnotify.js b/lib/pushnotify.js index 52172055200..37724069b43 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -25,6 +25,7 @@ function init(env, ctx) { if (notify.clear) { console.info('got a notify clear'); if (ctx.pushover) cancelPushoverNotifications(); + if (ctx.maker) sendMakerAllClear(); return; } @@ -77,10 +78,9 @@ function init(env, ctx) { }); receipts.del(receipt); }); - } - function sendPushoverNotifications (notify, key) { + function sendPushoverNotifications (notify) { var msg = { expire: TIME_15_MINS_S , title: notify.title @@ -126,18 +126,29 @@ function init(env, ctx) { }); } + function sendMakerAllClear ( ) { + ctx.maker.sendAllClear(function makerCallback (err, result) { + if (err) { + console.error('unable to send maker allclear', err); + } else if (result && result.sent) { + console.info('sent maker allclear'); + } + }); + } + function sendMakerEvent (notify) { - ctx.maker.sendEvent({ - name: notify.makerEvent || notify.plugin.name - , values: notify.makerValues - }, function makerCallback (err, result) { + var event = { + name: notify.eventName || 'ns-' + notify.plugin.name + , values: [notify.title, notify.message] + }; + ctx.maker.sendEvent(event, function makerCallback (err) { if (err) { - console.error('unable to send maker event', err, notify); + console.error('unable to send maker event', err, event); } else { - console.info('sent maker event: ', notify); + console.info('sent maker event: ', event); recentlySent.ttl(notify.key, TIME_15_MINS_S); } - }) + }); } function notifyToHash (notify) { diff --git a/tests/maker.test.js b/tests/maker.test.js new file mode 100644 index 00000000000..9fd1d310908 --- /dev/null +++ b/tests/maker.test.js @@ -0,0 +1,35 @@ +var should = require('should'); + +describe('maker', function ( ) { + var maker = require('../lib/maker')({extendedSettings: {maker: {key: '12345'}}}); + + //prevent any calls to iftt + maker.makeRequest = function noOpMakeRequest (event, callback) {}; + + it('turn values to a query', function (done) { + maker.valuesToQuery(['This is a title', 'This is the message']).should.equal('?value1=This%20is%20a%20title&value2=This%20is%20the%20message'); + done(); + }); + + it('send a request', function (done) { + maker.makeRequest = function mockedMakeRequest ( ) { done(); }; + maker.sendEvent({name: 'test'}, function sendCallback (err) { + should.not.exist(err); + }); + }); + + it('send a allclear, but only once', function (done) { + maker.makeRequest = function mockedMakeRequest ( ) { done(); }; + maker.sendAllClear(function sendCallback (err, result) { + should.not.exist(err); + result.sent.should.equal(true); + }); + + //send again, if done is called again test will fail + maker.sendAllClear(function sendCallback (err, result) { + should.not.exist(err); + result.sent.should.equal(false); + }); + }); + +}); From 7d02d7a21e0bc6b99b68ef89ae2db050a1a261d3 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 28 Jun 2015 12:56:02 -0700 Subject: [PATCH 230/937] a little better maker tests --- tests/maker.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/maker.test.js b/tests/maker.test.js index 9fd1d310908..1b0605f8b13 100644 --- a/tests/maker.test.js +++ b/tests/maker.test.js @@ -4,7 +4,7 @@ describe('maker', function ( ) { var maker = require('../lib/maker')({extendedSettings: {maker: {key: '12345'}}}); //prevent any calls to iftt - maker.makeRequest = function noOpMakeRequest (event, callback) {}; + maker.makeRequest = function noOpMakeRequest (event, callback) { callback && callback()}; it('turn values to a query', function (done) { maker.valuesToQuery(['This is a title', 'This is the message']).should.equal('?value1=This%20is%20a%20title&value2=This%20is%20the%20message'); @@ -12,14 +12,14 @@ describe('maker', function ( ) { }); it('send a request', function (done) { - maker.makeRequest = function mockedMakeRequest ( ) { done(); }; maker.sendEvent({name: 'test'}, function sendCallback (err) { should.not.exist(err); + done(); }); }); it('send a allclear, but only once', function (done) { - maker.makeRequest = function mockedMakeRequest ( ) { done(); }; + maker.makeRequest = function mockedToTestSingleDone (event, callback) { callback(); done(); }; maker.sendAllClear(function sendCallback (err, result) { should.not.exist(err); result.sent.should.equal(true); From 6999b87c2491c11aa2c3f52c0b1e6b26097d2833 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 28 Jun 2015 14:59:16 -0700 Subject: [PATCH 231/937] send snooze link to maker; add new line before message --- lib/pushnotify.js | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/lib/pushnotify.js b/lib/pushnotify.js index 37724069b43..77f9cddc3a5 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -99,14 +99,6 @@ function init(env, ctx) { } } - // if we want to have a callback snooze url this is the way, but emergency ack work better - // var now = Date.now(); - // var sig = ctx.notifications.sign(1, TIME_30_MINS_MS, Date.now()); - // if (sig) { - // msg.url_title = 'Snooze for 30 minutes'; - // msg.url = env.baseUrl + '/api/v1/notifications/snooze?level=1&lengthMills=' + TIME_30_MINS_MS + '&t=' + now + '&sig=' + sig; - // } - //add the key to the cache before sending, but with a short TTL ctx.pushover.send(msg, function pushoverCallback (err, result) { if (err) { @@ -137,9 +129,24 @@ function init(env, ctx) { } function sendMakerEvent (notify) { + var snoozeLink = ''; + if (notify.level < ctx.notifications.levels.WARN) { + //add the key to the cache before sending, but with a short TTL + var now = Date.now(); + var sig = ctx.notifications.sign(1, TIME_30_MINS_MS, Date.now()); + if (sig) { + snoozeLink = 'Snooze for 30 minutes: ' + + env.baseUrl + '/api/v1/notifications/snooze?level=1&lengthMills=' + + TIME_30_MINS_MS + '&t=' + now + '&sig=' + sig; + } + } var event = { name: notify.eventName || 'ns-' + notify.plugin.name - , values: [notify.title, notify.message] + , values: [ + notify.title + , notify.message && '\n' + notify.message + , snoozeLink + ] }; ctx.maker.sendEvent(event, function makerCallback (err) { if (err) { From b7bc3a35774a8bd1cff90e9f269e1c66e91f7dca Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 28 Jun 2015 18:35:04 -0700 Subject: [PATCH 232/937] no more newline hack --- lib/pushnotify.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pushnotify.js b/lib/pushnotify.js index 77f9cddc3a5..6a3a485887a 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -144,7 +144,7 @@ function init(env, ctx) { name: notify.eventName || 'ns-' + notify.plugin.name , values: [ notify.title - , notify.message && '\n' + notify.message + , notify.message , snoozeLink ] }; From f6acad50d4ba4f6d2383b4becb8e9167c5f738ae Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 29 Jun 2015 00:26:39 -0700 Subject: [PATCH 233/937] send multiple events so we can create recipes at the right level; fix snooze url level bug; message formatting --- lib/maker.js | 43 +++++++++++++++++++++++-------- lib/notifications.js | 4 +++ lib/plugins/ar2.js | 8 +++--- lib/plugins/boluswizardpreview.js | 6 ++--- lib/plugins/errorcodes.js | 6 +---- lib/plugins/simplealarms.js | 11 ++++---- lib/pushnotify.js | 13 +++++----- lib/sandbox.js | 11 ++++++++ tests/maker.test.js | 9 ++++--- 9 files changed, 73 insertions(+), 38 deletions(-) diff --git a/lib/maker.js b/lib/maker.js index 901575fa567..44734ba37ef 100644 --- a/lib/maker.js +++ b/lib/maker.js @@ -1,6 +1,7 @@ 'use strict'; var _ = require('lodash'); +var async = require('async'); var request = require('request'); function init (env) { @@ -18,7 +19,7 @@ function init (env) { maker.sendAllClear = function sendAllClear (callback) { if (Date.now() - lastAllClear > TIME_30_MINS_MS) { lastAllClear = Date.now(); - maker.makeRequest('ns-allclear', function allClearCallback (err) { + maker.makeRequest({}, 'allclear', function allClearCallback (err) { if (err) { lastAllClear = 0; callback(err); @@ -35,7 +36,7 @@ function init (env) { if (!event || !event.name) { callback('No event name found'); } else { - maker.makeRequest(event, function sendCallback (err, response) { + maker.makeRequests(event, function sendCallback (err, response) { if (err) { callback(err); } else { @@ -47,35 +48,55 @@ function init (env) { }; //exposed for testing - maker.valuesToQuery = function valuesToQuery (values) { + maker.valuesToQuery = function valuesToQuery (event) { var query = ''; - if (values && values.length) { + for (var i = 1; i <= 3; i++) { + var name = 'value' + i; + var value = event[name]; lastAllClear = 0; - _.forEach(values, function eachValue (value, index) { + if (value) { if (query) { query += '&'; } else { query += '?'; } - query += 'value' + (index + 1) + '=' + encodeURIComponent(value); - }); + query += name + '=' + encodeURIComponent(value); + } } return query; }; - maker.makeRequest = function makeRequest(event, callback) { + maker.makeRequest = function makeRequest(event, eventName, callback) { + var url = 'https://maker.ifttt.com/trigger/' + eventName + '/with/key/' + key + maker.valuesToQuery(event); request - .get('https://maker.ifttt.com/trigger/' + event.name + '/with/key/' + key + maker.valuesToQuery(event.values)) + .get(url) .on('response', function (response) { - callback && callback(null, response); + callback(null, response); }) .on('error', function (err) { - callback && callback(err); + callback(err); }); }; + maker.makeRequests = function makeRequests(event, callback) { + function sendGeneric (callback) { + maker.makeRequest(event, 'ns-event', callback); + } + + function sendByLevel (callback) { + maker.makeRequest (event, 'ns-' + event.level, callback); + } + + function sendByLevelAndName (callback) { + maker.makeRequest(event, 'ns' + ((event.level && '-' + event.level) || '') + '-' + event.name, callback); + } + + //since maker events only filter on name, we are sending multiple events and different levels of granularity + async.series([sendGeneric, sendByLevel, sendByLevelAndName], callback); + }; + if (key) { return maker(); } else { diff --git a/lib/notifications.js b/lib/notifications.js index 4ac61fe82a5..6645343ceeb 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -46,6 +46,10 @@ function init (env, ctx) { } }; + notifications.levels.toLowerCase = function toLowerCase(level) { + return notifications.levels.toString(level).toLowerCase(); + }; + function getAlarm (level) { var alarm = alarms[level]; if (!alarm) { diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 58c53a4e454..2df97ff1cae 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -61,14 +61,14 @@ function init() { var min = _.min([first, last, avg]); var rangeLabel = ''; - var eventName = 'ns-' + sbx.notifications.levels.toString(result.level).toLowerCase(); + var eventName = ''; if (max > sbx.scaleBg(sbx.thresholds.bg_target_top)) { rangeLabel = 'HIGH'; - eventName += '-high'; + eventName = 'high'; if (!result.pushoverSound) result.pushoverSound = 'climb'; } else if (min < sbx.scaleBg(sbx.thresholds.bg_target_bottom)) { rangeLabel = 'LOW'; - eventName += '-low'; + eventName = 'low'; if (!result.pushoverSound) result.pushoverSound = 'falling'; } else { rangeLabel = 'Check BG'; @@ -83,7 +83,7 @@ function init() { title += ' w/raw'; } - var lines = ['BG Now: ' + sbx.scaleBg(sbx.data.lastSGV())]; + var lines = ['BG Now: ' + sbx.displayBg(sbx.data.lastSGV())]; var delta = sbx.properties.delta && sbx.properties.delta.display; if (delta) { diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index c2c464ca766..9d3210a4598 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -77,9 +77,8 @@ function init() { var level = results.bolusEstimate > urgentBWP ? sbx.notifications.levels.URGENT : sbx.notifications.levels.WARN; var levelLabel = sbx.notifications.levels.toString(level); var sound = level == sbx.notifications.levels.URGENT ? 'updown' : 'bike'; - var eventName = 'ns-bwp-' + levelLabel.toLowerCase(); - var lines = ['BG Now: ' + results.lastSGV]; + var lines = ['BG Now: ' + results.displaySGV]; var delta = sbx.properties.delta && sbx.properties.delta.display; if (delta) { @@ -110,7 +109,7 @@ function init() { level: level , title: levelLabel + ', Check BG, time to bolus?' , message: message - , eventName: eventName + , eventName: 'bwp' , pushoverSound: sound , plugin: bwp , debug: results @@ -164,6 +163,7 @@ function init() { var sgv = sbx.scaleBg(sbx.data.lastSGV()); results.lastSGV = sgv; + results.displaySGV = sbx.displayBg(sbx.data.lastSGV()); if (!hasRequiredInfo(sbx)) { return results; diff --git a/lib/plugins/errorcodes.js b/lib/plugins/errorcodes.js index 2b0d3a1ee5e..8af645e9232 100644 --- a/lib/plugins/errorcodes.js +++ b/lib/plugins/errorcodes.js @@ -17,12 +17,11 @@ function init() { var now = Date.now(); var lastSGV = _.last(sbx.data.sgvs); - if (lastSGV && now - lastSGV.x < TIME_10_MINS_MS && lastSGV.y < 40) { + if (lastSGV && now - lastSGV.x < TIME_10_MINS_MS && lastSGV.y < 39) { var errorDisplay; var pushoverSound = null; var notifyLevel = sbx.notifications.levels.LOW; - var eventName = 'ns-cgm-errorcode'; switch (parseInt(lastSGV.y)) { case 0: //None @@ -53,13 +52,11 @@ function init() { errorDisplay = '?AD'; pushoverSound = 'alien'; notifyLevel = sbx.notifications.levels.URGENT; - eventName += '-hourglass'; break; case 10: //POWER_DEVIATION errorDisplay = '???'; pushoverSound = 'alien'; notifyLevel = sbx.notifications.levels.URGENT; - eventName += '-???'; break; case 12: //BAD_RF errorDisplay = '?RF'; @@ -75,7 +72,6 @@ function init() { level: notifyLevel , title: 'CGM Error Code' , message: errorDisplay - , eventName: eventName , plugin: errorcodes , pushoverSound: pushoverSound , debug: { diff --git a/lib/plugins/simplealarms.js b/lib/plugins/simplealarms.js index ccbc993cea1..38e5fb4542f 100644 --- a/lib/plugins/simplealarms.js +++ b/lib/plugins/simplealarms.js @@ -15,6 +15,7 @@ function init() { simplealarms.checkNotifications = function checkNotifications(sbx) { var lastSGV = sbx.scaleBg(sbx.data.lastSGV()) + , displaySGV = sbx.displayBg(sbx.data.lastSGV()) , lastSGVEntry = _.last(sbx.data.sgvs) , trigger = false , level = 0 @@ -30,32 +31,32 @@ function init() { level = 2; title = 'Urgent HIGH'; pushoverSound = 'persistent'; - eventName = 'ns-urgent-high'; + eventName = 'high'; console.info(title + ': ' + (lastSGV + ' > ' + sbx.scaleBg(sbx.thresholds.bg_high))); } else if (lastSGV > sbx.scaleBg(sbx.thresholds.bg_target_top)) { trigger = true; level = 1; title = 'High warning'; pushoverSound = 'climb'; - eventName = 'ns-warning-high'; + eventName = 'high'; console.info(title + ': ' + (lastSGV + ' > ' + sbx.scaleBg(sbx.thresholds.bg_target_top))); } else if (lastSGV < sbx.scaleBg(sbx.thresholds.bg_low)) { trigger = true; level = 2; title = 'Urgent LOW'; pushoverSound = 'persistent'; - eventName = 'ns-urgent-low'; + eventName = 'low'; console.info(title + ': ' + (lastSGV + ' < ' + sbx.scaleBg(sbx.thresholds.bg_low))); } else if (lastSGV < sbx.scaleBg(sbx.thresholds.bg_target_bottom)) { trigger = true; level = 1; title = 'Low warning'; pushoverSound = 'falling'; - eventName = 'ns-warning-low'; + eventName = 'low'; console.info(title + ': ' + (lastSGV + ' < ' + sbx.scaleBg(sbx.thresholds.bg_target_bottom))); } - var message = 'BG Now: ' + lastSGV; + var message = 'BG Now: ' + displaySGV; var delta = sbx.properties.delta && sbx.properties.delta.display; if (delta) { diff --git a/lib/pushnotify.js b/lib/pushnotify.js index 6a3a485887a..456137dd6a0 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -130,7 +130,7 @@ function init(env, ctx) { function sendMakerEvent (notify) { var snoozeLink = ''; - if (notify.level < ctx.notifications.levels.WARN) { + if (notify.level >= ctx.notifications.levels.WARN) { //add the key to the cache before sending, but with a short TTL var now = Date.now(); var sig = ctx.notifications.sign(1, TIME_30_MINS_MS, Date.now()); @@ -141,12 +141,11 @@ function init(env, ctx) { } } var event = { - name: notify.eventName || 'ns-' + notify.plugin.name - , values: [ - notify.title - , notify.message - , snoozeLink - ] + name: notify.eventName || notify.plugin.name + , level: ctx.notifications.levels.toLowerCase(notify.level) + , value1: notify.title + , value2: notify.message && '\n' + notify.message + , value3: snoozeLink }; ctx.maker.sendEvent(event, function makerCallback (err) { if (err) { diff --git a/lib/sandbox.js b/lib/sandbox.js index edd1577cd75..141794addbe 100644 --- a/lib/sandbox.js +++ b/lib/sandbox.js @@ -11,6 +11,7 @@ function init ( ) { function reset () { sbx.properties = []; sbx.scaleBg = scaleBg; + sbx.displayBg = displayBg; sbx.roundInsulinForDisplayFormat = roundInsulinForDisplayFormat; sbx.roundBGToDisplayFormat = roundBGToDisplayFormat; } @@ -119,6 +120,16 @@ function init ( ) { } }; + function displayBg (bg) { + if (Number(bg) == 39) { + return 'LOW'; + } else if (Number(bg) == 401) { + return 'HIGH'; + } else { + return scaleBg(bg); + } + } + function scaleBg (bg) { if (sbx.units == 'mmol' && bg) { return Number(units.mgdlToMMOL(bg)); diff --git a/tests/maker.test.js b/tests/maker.test.js index 1b0605f8b13..f4088e902a4 100644 --- a/tests/maker.test.js +++ b/tests/maker.test.js @@ -4,10 +4,13 @@ describe('maker', function ( ) { var maker = require('../lib/maker')({extendedSettings: {maker: {key: '12345'}}}); //prevent any calls to iftt - maker.makeRequest = function noOpMakeRequest (event, callback) { callback && callback()}; + maker.makeRequest = function noOpMakeRequest (event, eventName, callback) { callback && callback()}; it('turn values to a query', function (done) { - maker.valuesToQuery(['This is a title', 'This is the message']).should.equal('?value1=This%20is%20a%20title&value2=This%20is%20the%20message'); + maker.valuesToQuery({ + value1: 'This is a title' + , value2: 'This is the message' + }).should.equal('?value1=This%20is%20a%20title&value2=This%20is%20the%20message'); done(); }); @@ -19,7 +22,7 @@ describe('maker', function ( ) { }); it('send a allclear, but only once', function (done) { - maker.makeRequest = function mockedToTestSingleDone (event, callback) { callback(); done(); }; + maker.makeRequest = function mockedToTestSingleDone (event, eventName, callback) { callback(); done(); }; maker.sendAllClear(function sendCallback (err, result) { should.not.exist(err); result.sent.should.equal(true); From eb542d1a6de81c73fa70c77974f05f9068da53e9 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 29 Jun 2015 00:43:56 -0700 Subject: [PATCH 234/937] fix snoozelink formatting; added more properties to simplealarms --- lib/plugins/ar2.js | 1 - lib/plugins/simplealarms.js | 28 +++++++++++++++++++++++++--- lib/pushnotify.js | 2 +- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 2df97ff1cae..dae234d15ce 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -75,7 +75,6 @@ function init() { } var title = sbx.notifications.levels.toString(result.level) + ', ' + rangeLabel; - title += ' BG'; if (lastSGVEntry.y > sbx.thresholds.bg_target_bottom && lastSGVEntry.y < sbx.thresholds.bg_target_top) { title += ' predicted'; } diff --git a/lib/plugins/simplealarms.js b/lib/plugins/simplealarms.js index 38e5fb4542f..7ce39c72d79 100644 --- a/lib/plugins/simplealarms.js +++ b/lib/plugins/simplealarms.js @@ -56,13 +56,35 @@ function init() { console.info(title + ': ' + (lastSGV + ' < ' + sbx.scaleBg(sbx.thresholds.bg_target_bottom))); } - var message = 'BG Now: ' + displaySGV; + var lines = ['BG Now: ' + displaySGV]; var delta = sbx.properties.delta && sbx.properties.delta.display; if (delta) { - message += ' ' + delta; + lines[0] += ' ' + delta; } - message += ' ' + sbx.unitsLabel; + lines[0] += ' ' + sbx.unitsLabel; + + var rawbgProp = sbx.properties.rawbg; + if (rawbgProp) { + lines.push(['Raw BG:', rawbgProp.value, sbx.unitsLabel, rawbgProp.noiseLabel].join(' ')); + } + + var bwp = sbx.properties.bwp && sbx.properties.bwp.bolusEstimateDisplay; + if (bwp && bwp > 0) { + lines.push(['BWP:', bwp, 'U'].join(' ')); + } + + var iob = sbx.properties.iob && sbx.properties.iob.display; + if (iob) { + lines.push(['IOB:', iob, 'U'].join(' ')); + } + + var cob = sbx.properties.cob && sbx.properties.cob.display; + if (cob) { + lines.push(['COB:', cob, 'g'].join(' ')); + } + + var message = lines.join('\n'); if (trigger) { sbx.notifications.requestNotify({ diff --git a/lib/pushnotify.js b/lib/pushnotify.js index 456137dd6a0..734321b839d 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -135,7 +135,7 @@ function init(env, ctx) { var now = Date.now(); var sig = ctx.notifications.sign(1, TIME_30_MINS_MS, Date.now()); if (sig) { - snoozeLink = 'Snooze for 30 minutes: ' + + snoozeLink = '\nSnooze(30m): ' + env.baseUrl + '/api/v1/notifications/snooze?level=1&lengthMills=' + TIME_30_MINS_MS + '&t=' + now + '&sig=' + sig; } From f173f316d09d893622c3e61e56fab2a272f503e9 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 29 Jun 2015 00:45:52 -0700 Subject: [PATCH 235/937] and fix the ar2 test to expect simplified formatting --- tests/ar2.test.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/ar2.test.js b/tests/ar2.test.js index 57073edefea..f560e5937b4 100644 --- a/tests/ar2.test.js +++ b/tests/ar2.test.js @@ -35,7 +35,7 @@ describe('ar2', function ( ) { ar2.checkNotifications(sbx); var highest = ctx.notifications.findHighestAlarm(); highest.level.should.equal(ctx.notifications.levels.WARN); - highest.title.should.equal('Warning, HIGH BG predicted'); + highest.title.should.equal('Warning, HIGH predicted'); highest.message.should.startWith('BG Now: 170 +20 mg/dl'); done(); @@ -49,7 +49,7 @@ describe('ar2', function ( ) { ar2.checkNotifications(sbx); var highest = ctx.notifications.findHighestAlarm(); highest.level.should.equal(ctx.notifications.levels.URGENT); - highest.title.should.equal('Urgent, HIGH BG'); + highest.title.should.equal('Urgent, HIGH'); done(); }); @@ -62,7 +62,7 @@ describe('ar2', function ( ) { ar2.checkNotifications(sbx); var highest = ctx.notifications.findHighestAlarm(); highest.level.should.equal(ctx.notifications.levels.WARN); - highest.title.should.equal('Warning, LOW BG'); + highest.title.should.equal('Warning, LOW'); done(); }); @@ -75,7 +75,7 @@ describe('ar2', function ( ) { ar2.checkNotifications(sbx); var highest = ctx.notifications.findHighestAlarm(); highest.level.should.equal(ctx.notifications.levels.WARN); - highest.title.should.equal('Warning, LOW BG predicted'); + highest.title.should.equal('Warning, LOW predicted'); done(); }); @@ -88,7 +88,7 @@ describe('ar2', function ( ) { ar2.checkNotifications(sbx); var highest = ctx.notifications.findHighestAlarm(); highest.level.should.equal(ctx.notifications.levels.URGENT); - highest.title.should.equal('Urgent, LOW BG predicted'); + highest.title.should.equal('Urgent, LOW predicted'); done(); }); @@ -108,7 +108,7 @@ describe('ar2', function ( ) { ar2.checkNotifications(sbx.withExtendedSettings(ar2)); var highest = ctx.notifications.findHighestAlarm(); highest.level.should.equal(ctx.notifications.levels.WARN); - highest.title.should.equal('Warning, LOW BG predicted w/raw'); + highest.title.should.equal('Warning, LOW predicted w/raw'); done(); }); @@ -122,7 +122,7 @@ describe('ar2', function ( ) { ar2.checkNotifications(sbx.withExtendedSettings(ar2)); var highest = ctx.notifications.findHighestAlarm(); highest.level.should.equal(ctx.notifications.levels.WARN); - highest.title.should.equal('Warning, HIGH BG predicted w/raw'); + highest.title.should.equal('Warning, HIGH predicted w/raw'); done(); }); From f8b86de8268f568d09074f1c5df9846eb56b58de Mon Sep 17 00:00:00 2001 From: Fokko Driesprong Date: Mon, 29 Jun 2015 10:10:04 +0200 Subject: [PATCH 236/937] Updated the permissions, still running as non-root --- Dockerfile | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/Dockerfile b/Dockerfile index 847f2391b2d..328e21dddbe 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,19 +8,23 @@ RUN apt-get update && apt-get install -y python-software-properties python g++ m # Upgrade RUN apt-get upgrade -y +# We need to change user for security. # https://github.com/jspm/jspm-cli/issues/865 -ENV user node - -RUN groupadd --system $user && useradd --system --create-home --gid $user $user - -COPY . /home/$user/ -WORKDIR /home/$user - -# We don't wat to run in root. -RUN chown $user --recursive . -USER $user - -RUN npm install . +# http://stackoverflow.com/questions/24308760/running-app-inside-docker-as-non-root-user + +RUN useradd -ms /bin/bash node +# copy the nice dotfiles that dockerfile/ubuntu gives us: +RUN cd && cp -R .bashrc .profile /home/node + +ADD . /home/node/app +RUN chown -R node:node /home/node + +USER node +ENV HOME /home/node + +WORKDIR /home/node/app + +RUN npm install # Expose the default port, although this does not matter at it will be exposed as an arbitrary port by the Docker network driver. EXPOSE 1337 From aea42ea8a133e4c4b077c2da0931172832cdefe2 Mon Sep 17 00:00:00 2001 From: Fokko Driesprong Date: Mon, 29 Jun 2015 15:54:49 +0200 Subject: [PATCH 237/937] ok --- docker-build.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-build.sh b/docker-build.sh index 05ed0164c72..8e5675e49f3 100644 --- a/docker-build.sh +++ b/docker-build.sh @@ -1,4 +1,4 @@ #!/bin/sh -sudo docker build -t local/nightscout . -sudo docker run -t local/nightscout \ No newline at end of file +sudo docker build -t nightscout . +sudo docker run local/nightscout \ No newline at end of file From 21f1edcd7c72601fe0b23d818343de2ac66d6c5a Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 29 Jun 2015 12:36:48 -0700 Subject: [PATCH 238/937] snooze links seem to be getting called, maybe by maker or twitter, need to do that a different way --- lib/pushnotify.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/pushnotify.js b/lib/pushnotify.js index 734321b839d..19001388cba 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -131,21 +131,21 @@ function init(env, ctx) { function sendMakerEvent (notify) { var snoozeLink = ''; if (notify.level >= ctx.notifications.levels.WARN) { - //add the key to the cache before sending, but with a short TTL - var now = Date.now(); - var sig = ctx.notifications.sign(1, TIME_30_MINS_MS, Date.now()); - if (sig) { - snoozeLink = '\nSnooze(30m): ' + - env.baseUrl + '/api/v1/notifications/snooze?level=1&lengthMills=' + - TIME_30_MINS_MS + '&t=' + now + '&sig=' + sig; - } +// var now = Date.now(); +// var sig = ctx.notifications.sign(1, TIME_30_MINS_MS, Date.now()); +// if (sig) { +// snoozeLink = '\nSnooze(30m): ' + +// env.baseUrl + '/api/v1/notifications/snooze?level=1&lengthMills=' + +// TIME_30_MINS_MS + '&t=' + now + '&sig=' + sig; +// } } var event = { name: notify.eventName || notify.plugin.name , level: ctx.notifications.levels.toLowerCase(notify.level) , value1: notify.title , value2: notify.message && '\n' + notify.message - , value3: snoozeLink + //snooze links seem to be getting called + //, value3: snoozeLink }; ctx.maker.sendEvent(event, function makerCallback (err) { if (err) { From 7ba4e6113ce41ea32f9e71675d8b2e99faa207b7 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 29 Jun 2015 16:08:54 -0700 Subject: [PATCH 239/937] rip out notifications snooze api; too dangerous without full auth and new interaction in place --- lib/api/notifications-api.js | 22 ++++--------- lib/notifications.js | 27 --------------- lib/pushnotify.js | 12 ------- static/result/index.html | 64 ------------------------------------ 4 files changed, 7 insertions(+), 118 deletions(-) delete mode 100644 static/result/index.html diff --git a/lib/api/notifications-api.js b/lib/api/notifications-api.js index 933396db187..21f158d007e 100644 --- a/lib/api/notifications-api.js +++ b/lib/api/notifications-api.js @@ -1,25 +1,17 @@ 'use strict'; function configure (app, wares, ctx) { - var express = require('express'), - notifications = express.Router( ) + var express = require('express') + , notifications = express.Router( ) ; - notifications.get('/notifications/snooze', function (req, res) { - console.info('GOT web notification snooze', req.query); - var result = ctx.notifications.secureAck( - req.query.level - , req.query.lengthMills - , req.query.t - , req.query.sig - ); - res.redirect(302, '/result/?ok=' + result); - }); - notifications.post('/notifications/pushovercallback', function (req, res) { console.info('GOT Pushover callback', req.body); - var result = ctx.pushnotify.pushoverAck(req.body); - res.redirect(302, '/result/?ok=' + result); + if (ctx.pushnotify.pushoverAck(req.body)) { + res.sendStatus(200); + } else { + res.sendStatus(500); + } }); return notifications; diff --git a/lib/notifications.js b/lib/notifications.js index 6645343ceeb..66ab3d63721 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -187,33 +187,6 @@ function init (env, ctx) { }; - notifications.secureAck = function secureAck (level, lenghtMills, t, sig) { - var expected = notifications.sign(level, lenghtMills, t); - - if (expected && expected == sig) { - notifications.ack(level, lenghtMills, true); - return true - } else { - console.info(expected, ' != ', sig); - return false; - } - }; - - notifications.sign = function sign (level, lenghtMills, t) { - - if (env.api_secret) { - var shasum = crypto.createHash('sha1'); - shasum.update(env.api_secret); - shasum.update(level.toString()); - shasum.update(lenghtMills.toString()); - shasum.update(t.toString()); - return shasum.digest('hex'); - } else { - return false; - } - - }; - return notifications(); } diff --git a/lib/pushnotify.js b/lib/pushnotify.js index 19001388cba..fd502cc2fe2 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -129,23 +129,11 @@ function init(env, ctx) { } function sendMakerEvent (notify) { - var snoozeLink = ''; - if (notify.level >= ctx.notifications.levels.WARN) { -// var now = Date.now(); -// var sig = ctx.notifications.sign(1, TIME_30_MINS_MS, Date.now()); -// if (sig) { -// snoozeLink = '\nSnooze(30m): ' + -// env.baseUrl + '/api/v1/notifications/snooze?level=1&lengthMills=' + -// TIME_30_MINS_MS + '&t=' + now + '&sig=' + sig; -// } - } var event = { name: notify.eventName || notify.plugin.name , level: ctx.notifications.levels.toLowerCase(notify.level) , value1: notify.title , value2: notify.message && '\n' + notify.message - //snooze links seem to be getting called - //, value3: snoozeLink }; ctx.maker.sendEvent(event, function makerCallback (err) { if (err) { diff --git a/static/result/index.html b/static/result/index.html deleted file mode 100644 index b0f5969aca0..00000000000 --- a/static/result/index.html +++ /dev/null @@ -1,64 +0,0 @@ - - - - - Notifications - - - - - - - - - - -

    Success

    - -

    - You can close this page this page now. -

    - - - - - - - - - - From ad8a5519e4ccd36ddf356cec7a31b15b5e0d38e5 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 29 Jun 2015 17:35:52 -0700 Subject: [PATCH 240/937] fix some issues reported by codacy --- lib/maker.js | 19 +++++++++---------- lib/notifications.js | 2 +- lib/pushnotify.js | 8 ++++---- lib/sandbox.js | 4 ++-- tests/maker.test.js | 6 +++++- tests/sandbox.test.js | 14 ++++++++++++++ 6 files changed, 35 insertions(+), 18 deletions(-) diff --git a/lib/maker.js b/lib/maker.js index 44734ba37ef..33b8ecc125f 100644 --- a/lib/maker.js +++ b/lib/maker.js @@ -1,6 +1,5 @@ 'use strict'; -var _ = require('lodash'); var async = require('async'); var request = require('request'); @@ -23,25 +22,25 @@ function init (env) { if (err) { lastAllClear = 0; callback(err); - } else { - callback && callback(null, {sent: true}); + } else if (callback) { + callback(null, {sent: true}); } }); - } else { - callback && callback(null, {sent: false}); + } else if (callback) { + callback(null, {sent: false}); } }; maker.sendEvent = function sendEvent (event, callback) { if (!event || !event.name) { - callback('No event name found'); + if (callback) { callback('No event name found'); } } else { maker.makeRequests(event, function sendCallback (err, response) { if (err) { - callback(err); + if (callback) { callback(err); } } else { lastAllClear = 0; - callback && callback(null, response); + if (callback) { callback(null, response); } } }); } @@ -73,10 +72,10 @@ function init (env) { request .get(url) .on('response', function (response) { - callback(null, response); + if (callback) { callback(null, response); } }) .on('error', function (err) { - callback(err); + if (callback) { callback(err); } }); }; diff --git a/lib/notifications.js b/lib/notifications.js index 66ab3d63721..af61bf5d6b8 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -1,7 +1,6 @@ 'use strict'; var _ = require('lodash'); -var crypto = require('crypto'); var THIRTY_MINUTES = 30 * 60 * 1000; @@ -58,6 +57,7 @@ function init (env, ctx) { } return alarm; } + //should only be used when auto acking the alarms after going back in range or when an error corrects //setting the silence time to 1ms so the alarm will be retriggered as soon as the condition changes //since this wasn't ack'd by a user action diff --git a/lib/pushnotify.js b/lib/pushnotify.js index fd502cc2fe2..1b76c475f07 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -24,8 +24,8 @@ function init(env, ctx) { pushnotify.emitNotification = function emitNotification (notify) { if (notify.clear) { console.info('got a notify clear'); - if (ctx.pushover) cancelPushoverNotifications(); - if (ctx.maker) sendMakerAllClear(); + if (ctx.pushover) { cancelPushoverNotifications(); } + if (ctx.maker) { sendMakerAllClear(); } return; } @@ -49,8 +49,8 @@ function init(env, ctx) { recentlySent.set(key, notify, 30); - if (ctx.pushover) sendPushoverNotifications(notify); - if (ctx.maker) sendMakerEvent(notify); + if (ctx.pushover) { sendPushoverNotifications(notify); } + if (ctx.maker) { sendMakerEvent(notify); } }; diff --git a/lib/sandbox.js b/lib/sandbox.js index 141794addbe..9e2ac851c39 100644 --- a/lib/sandbox.js +++ b/lib/sandbox.js @@ -121,9 +121,9 @@ function init ( ) { }; function displayBg (bg) { - if (Number(bg) == 39) { + if (Number(bg) === 39) { return 'LOW'; - } else if (Number(bg) == 401) { + } else if (Number(bg) === 401) { return 'HIGH'; } else { return scaleBg(bg); diff --git a/tests/maker.test.js b/tests/maker.test.js index f4088e902a4..1417a5274e5 100644 --- a/tests/maker.test.js +++ b/tests/maker.test.js @@ -22,7 +22,11 @@ describe('maker', function ( ) { }); it('send a allclear, but only once', function (done) { - maker.makeRequest = function mockedToTestSingleDone (event, eventName, callback) { callback(); done(); }; + function mockedToTestSingleDone (event, eventName, callback) { + callback(); done(); + } + + maker.makeRequest = mockedToTestSingleDone; maker.sendAllClear(function sendCallback (err, result) { should.not.exist(err); result.sent.should.equal(true); diff --git a/tests/sandbox.test.js b/tests/sandbox.test.js index 60ee9b91e93..58326a17c94 100644 --- a/tests/sandbox.test.js +++ b/tests/sandbox.test.js @@ -46,4 +46,18 @@ describe('sandbox', function ( ) { done(); }); + it('display 39 as LOW and 401 as HIGH', function () { + var env = require('../env')(); + var ctx = {}; + ctx.data = require('../lib/data')(env, ctx); + ctx.notifications = require('../lib/notifications')(env, ctx); + + var sbx = sandbox.serverInit(env, ctx); + + sbx.displayBg(39).should.equal('LOW'); + sbx.displayBg('39').should.equal('LOW'); + sbx.displayBg(401).should.equal('HIGH'); + sbx.displayBg('401').should.equal('HIGH'); + }); + }); From 6093f9ee12d0f46401d79aacb628b072711a8830 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 29 Jun 2015 20:05:18 -0700 Subject: [PATCH 241/937] fixes and improvements to the all clear notification --- lib/maker.js | 13 +++++++++++-- lib/notifications.js | 10 +++++++--- lib/pushnotify.js | 8 +++----- lib/websocket.js | 2 +- server.js | 12 ++++++++++++ tests/maker.test.js | 10 +++++++--- 6 files changed, 41 insertions(+), 14 deletions(-) diff --git a/lib/maker.js b/lib/maker.js index 33b8ecc125f..34e6ee6bef4 100644 --- a/lib/maker.js +++ b/lib/maker.js @@ -15,10 +15,18 @@ function init (env) { var lastAllClear = 0; - maker.sendAllClear = function sendAllClear (callback) { + maker.sendAllClear = function sendAllClear (notify, callback) { if (Date.now() - lastAllClear > TIME_30_MINS_MS) { lastAllClear = Date.now(); - maker.makeRequest({}, 'allclear', function allClearCallback (err) { + + //can be used to prevent maker/twitter deduping (add to IFTTT tweet text) + var shortTimestamp = Math.round(Date.now() / 1000 / 60); + + maker.makeRequest({ + value1: (notify && notify.title) || 'All Clear' + , value2: notify && notify.message && '\n' + notify.message + , value3: '\n' + shortTimestamp + }, 'ns-allclear', function allClearCallback (err) { if (err) { lastAllClear = 0; callback(err); @@ -72,6 +80,7 @@ function init (env) { request .get(url) .on('response', function (response) { + console.info('sent maker request: ', url); if (callback) { callback(null, response); } }) .on('error', function (err) { diff --git a/lib/notifications.js b/lib/notifications.js index af61bf5d6b8..ab0e675f06b 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -59,7 +59,7 @@ function init (env, ctx) { } //should only be used when auto acking the alarms after going back in range or when an error corrects - //setting the silence time to 1ms so the alarm will be retriggered as soon as the condition changes + //setting the silence time to 1ms so the alarm will be re-triggered as soon as the condition changes //since this wasn't ack'd by a user action function autoAckAlarms() { @@ -75,7 +75,7 @@ function init (env, ctx) { } if (sendClear) { - ctx.bus.emit('notification', {clear: true}); + ctx.bus.emit('notification', {clear: true, title: 'All Clear', message: 'Auto ack\'d alarm(s)'}); console.info('emitted notification clear'); } } @@ -182,7 +182,11 @@ function init (env, ctx) { } if (sendClear) { - ctx.bus.emit('notification', {clear: true}); + ctx.bus.emit('notification', { + clear: true + , title: 'All Clear' + , message: notifications.levels.toString(level) + ' was ack\'d' + }); } }; diff --git a/lib/pushnotify.js b/lib/pushnotify.js index 1b76c475f07..142467dab00 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -23,10 +23,8 @@ function init(env, ctx) { pushnotify.emitNotification = function emitNotification (notify) { if (notify.clear) { - console.info('got a notify clear'); if (ctx.pushover) { cancelPushoverNotifications(); } - if (ctx.maker) { sendMakerAllClear(); } - + if (ctx.maker) { sendMakerAllClear(notify); } return; } @@ -118,8 +116,8 @@ function init(env, ctx) { }); } - function sendMakerAllClear ( ) { - ctx.maker.sendAllClear(function makerCallback (err, result) { + function sendMakerAllClear (notify) { + ctx.maker.sendAllClear(notify, function makerCallback (err, result) { if (err) { console.error('unable to send maker allclear', err); } else if (result && result.sent) { diff --git a/lib/websocket.js b/lib/websocket.js index ef6b0c83896..0c1e116b4a6 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -40,7 +40,7 @@ function init (env, ctx, server) { io.emit('clients', ++watchers); socket.on('ack', function(alarmType, silenceTime) { var level = alarmType == 'urgent_alarm' ? 2 : 1; - ctx.notifications.ack(level, silenceTime); + ctx.notifications.ack(level, silenceTime, true); }); socket.on('disconnect', function () { io.emit('clients', --watchers); diff --git a/server.js b/server.js index 2621cf10b81..aa7bfe0d486 100644 --- a/server.js +++ b/server.js @@ -66,4 +66,16 @@ require('./lib/bootevent')(env).boot(function booted (ctx) { ctx.bus.on('notification', function(info) { websocket.emitNotification(info); }); + + //after startup if there are no alarms send all clear + setTimeout(function sendStartupAllClear () { + var alarm = ctx.notifications.findHighestAlarm(); + if (!alarm) { + ctx.bus.emit('notification', { + clear: true + , title: 'All Clear' + , message: 'Server started without alarms' + }); + } + }, 20000); }); \ No newline at end of file diff --git a/tests/maker.test.js b/tests/maker.test.js index 1417a5274e5..2d94c33da4d 100644 --- a/tests/maker.test.js +++ b/tests/maker.test.js @@ -4,7 +4,11 @@ describe('maker', function ( ) { var maker = require('../lib/maker')({extendedSettings: {maker: {key: '12345'}}}); //prevent any calls to iftt - maker.makeRequest = function noOpMakeRequest (event, eventName, callback) { callback && callback()}; + function noOpMakeRequest (event, eventName, callback) { + if (callback) { callback(); } + } + + maker.makeRequest = noOpMakeRequest; it('turn values to a query', function (done) { maker.valuesToQuery({ @@ -27,13 +31,13 @@ describe('maker', function ( ) { } maker.makeRequest = mockedToTestSingleDone; - maker.sendAllClear(function sendCallback (err, result) { + maker.sendAllClear({}, function sendCallback (err, result) { should.not.exist(err); result.sent.should.equal(true); }); //send again, if done is called again test will fail - maker.sendAllClear(function sendCallback (err, result) { + maker.sendAllClear({}, function sendCallback (err, result) { should.not.exist(err); result.sent.should.equal(false); }); From 578805bfb6f118d38fd26d888968695d3f6faa5b Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 29 Jun 2015 20:16:41 -0700 Subject: [PATCH 242/937] more fixes for issues reported by codacy --- lib/notifications.js | 4 ++-- lib/pushnotify.js | 4 ++-- lib/sandbox.js | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/notifications.js b/lib/notifications.js index ab0e675f06b..0e951cc496f 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -154,7 +154,7 @@ function init (env, ctx) { var snoozedBy = notifications.snoozedBy(highestAlarm); if (snoozedBy) { console.log('snoozing: ', highestAlarm, ' with: ', snoozedBy); - notifications.ack(snoozedBy.level, snoozedBy.lengthMills) + notifications.ack(snoozedBy.level, snoozedBy.lengthMills); } emitNotification(highestAlarm); @@ -164,7 +164,7 @@ function init (env, ctx) { notifications.findInfos().forEach(function eachInfo (info) { emitNotification(info); - }) + }); }; notifications.ack = function ack (level, time, sendClear) { diff --git a/lib/pushnotify.js b/lib/pushnotify.js index 142467dab00..32bcf4d2354 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -3,7 +3,7 @@ var _ = require('lodash'); var crypto = require('crypto'); var units = require('./units')(); -var NodeCache = require( "node-cache" ); +var NodeCache = require('node-cache'); function init(env, ctx) { @@ -58,7 +58,7 @@ function init(env, ctx) { var notify = receipts.get(response.receipt); console.info('push ack, response: ', response, ', notify: ', notify); if (notify) { - ctx.notifications.ack(notify.level, TIME_30_MINS_MS, true) + ctx.notifications.ack(notify.level, TIME_30_MINS_MS, true); } return !!notify; }; diff --git a/lib/sandbox.js b/lib/sandbox.js index 9e2ac851c39..c62586a8440 100644 --- a/lib/sandbox.js +++ b/lib/sandbox.js @@ -162,7 +162,7 @@ function init ( ) { function unitsLabel ( ) { if (sbx.units == 'mmol') return 'mmol/L'; - return "mg/dl"; + return 'mg/dl'; } function roundBGToDisplayFormat (bg) { From 3548081cf0efa9c99b651387eefe0389c724fd37 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 29 Jun 2015 23:23:39 -0700 Subject: [PATCH 243/937] Update readme to include information IFTTT Maker --- README.md | 41 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9d4d99a4f49..872d6a96a84 100644 --- a/README.md +++ b/README.md @@ -102,8 +102,6 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * `BG_LOW` (`55`) - must be set using mg/dl units; the low BG outside the target range that is considered urgent * `ALARM_TYPES` (`simple` if any `BG_`* ENV's are set, otherwise `predict`) - currently 2 alarm types are supported, and can be used independently or combined. The `simple` alarm type only compares the current BG to `BG_` thresholds above, the `predict` alarm type uses highly tuned formula that forecasts where the BG is going based on it's trend. `predict` **DOES NOT** currently use any of the `BG_`* ENV's * `BASE_URL` - Used for building links to your sites api, ie pushover callbacks, usually the URL of your Nightscout site you may want https instead of http - * `PUSHOVER_API_TOKEN` - Used to enable pushover notifications, this token is specific to the application you create from in [Pushover](https://pushover.net/), ***[additional pushover information](#pushover)*** below. - * `PUSHOVER_USER_KEY` - Your Pushover user key, can be found in the top left of the [Pushover](https://pushover.net/) site, this can also be a pushover delivery group key to send to a group rather than just a single user. #### Core @@ -159,6 +157,9 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * `errorcodes` (CGM Error Codes) - Generates alarms for CGM codes `9` (hourglass) and `10` (???). **Enabled by default.** * `treatmentnotify` (Treatment Notifications) - Generates notifications when a treatment has been entered and snoozes alarms minutes after a treatment. Default snooze is 10 minutes, and can be set using the `TREATMENTNOTIFY_SNOOZE_MINS` [extended setting](#extended-settings). * `basal` (Basal Profile) - Adds the Basal pill visualization to display the basal rate for the current time. Also enables the `bwp` plugin to calculate correction temp basal suggestions. Uses the `basal` field from the [treatment profile](#treatment-profile). + + Also see [Pushover](#pushover) and [IFTTT Maker](#ifttt-maker). + #### 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. @@ -252,7 +253,41 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. You’ll need to [Create a Pushover Application](https://pushover.net/apps/build). You only need to set the Application name, you can ignore all the other settings, but setting an Icon is a nice touch. Maybe you'd like to use [this one](https://raw.githubusercontent.com/nightscout/cgm-remote-monitor/master/static/images/large.png)? - Pushover is configured using the `PUSHOVER_API_TOKEN`, `PUSHOVER_USER_KEY`, `BASE_URL`, and `API_SECRET` environment variables. For acknowledgment callbacks to work `BASE_URL` and `API_SECRET` must be set and `BASE_URL` must be publicly accessible. For testing/devlopment try [localtunnel](http://localtunnel.me/). + Pushover is configured using the following Environment Variables: + + * `ENABLE` - `pushover` should be added to the list of plugin, for example: `ENABLE="pushover"`. + * `PUSHOVER_API_TOKEN` - Used to enable pushover notifications, this token is specific to the application you create from in [Pushover](https://pushover.net/), ***[additional pushover information](#pushover)*** below. + * `PUSHOVER_USER_KEY` - Your Pushover user key, can be found in the top left of the [Pushover](https://pushover.net/) site, this can also be a pushover delivery group key to send to a group rather than just a single user. + * `BASE_URL` - Used for pushover callbacks, usually the URL of your Nightscout site, use https when possible. + * `API_SECRET` - Used for signing the pushover callback request for acknowledgments. + + For testing/devlopment try [localtunnel](http://localtunnel.me/). + +### IFTTT Maker + In addition to the normal web based alarms, and pushover, there is also integration for [IFTTT Maker](https://ifttt.com/maker). + + With Maker you are able to integrate with all the other [IFTTT Channels](https://ifttt.com/channels). For example you can send a tweet when there is an alarm, change the color of hue light, send an email, send and sms, and so much more. + + 1. Setup IFTTT account: [login](https://ifttt.com/login) or [create an account](https://ifttt.com/join) + 2. Find your secret key on the [maker page](https://ifttt.com/maker) + 3. Configure Nightscout by setting these environment variables: + * `ENABLE` - `maker` should be added to the list of plugin, for example: `ENABLE="maker"`. + * `MAKER_KEY` - Set this to your secret key that you located in step 2, for example: `MAKER_KEY="abcMyExampleabc123defjt1DeNSiftttmak-XQb69p"` + 4. [Create a recipe](https://ifttt.com/myrecipes/personal/new) or see [more detailed instructions](lib/plugins/maker-setup.md) + + Plugins can create custom events, but all events sent to maker will be prefixed with `ns-`. The core events are: + * `ns-event` - This event is sent to the maker service for all alarms and notifications. This is good catch all event for general logging. + * `ns-allclear` - This event is sent to the maker service when an alarm has been ack'd or when the server starts up without triggering any alarms. For example, you could use this event to turn a light to green. + * `ns-info` - Plugins that generate notifications at the info level will cause this event to also be triggered. It will be sent in addition to `ns-event`. + * `ns-warning` - Alarms at the warning level with cause this event to also be triggered. It will be sent in addition to `ns-event`. + * `ns-urgent` - Alarms at the urgent level with cause this event to also be triggered. It will be sent in addition to `ns-event`. + * `ns-warning-high` - Alarms at the warning level with cause this event to also be triggered. It will be sent in addition to `ns-event` and `ns-warning`. + * `ns-urgent-high` - Alarms at the urgent level with cause this event to also be triggered. It will be sent in addition to `ns-event` and `ns-urgent`. + * `ns-warning-low` - Alarms at the warning level with cause this event to also be triggered. It will be sent in addition to `ns-event` and `ns-warning`. + * `ns-urgent-low` - Alarms at the urgent level with cause this event to also be triggered. It will be sent in addition to `ns-event` and `ns-urgent`. + * `ns-info-treatmentnotify` - When a treatment is entered into the care portal this event is triggered. It will be sent in addition to `ns-event` and `ns-info`. + * `ns-warning-bwp` - When the BWP plugin generates a warning alarm. It will be sent in addition to `ns-event` and `ns-warning`. + * `ns-urgent-bwp` - When the BWP plugin generates an urgent alarm. It will be sent in addition to `ns-event` and `ns-urget`. ## Setting environment variables Easy to emulate on the commandline: From 95d4c0c342e61f7e3f1c47dbf0e18d42d9259235 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 29 Jun 2015 23:34:51 -0700 Subject: [PATCH 244/937] moved maker and pushover to plugins; initial maker setup docs --- lib/bootevent.js | 6 +++-- lib/plugins/maker-setup.md | 43 +++++++++++++++++++++++++++++++++++ lib/{ => plugins}/maker.js | 0 lib/{ => plugins}/pushover.js | 0 tests/maker.test.js | 2 +- 5 files changed, 48 insertions(+), 3 deletions(-) create mode 100644 lib/plugins/maker-setup.md rename lib/{ => plugins}/maker.js (100%) rename lib/{ => plugins}/pushover.js (100%) diff --git a/lib/bootevent.js b/lib/bootevent.js index 1865077230f..c842f9d5466 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -17,9 +17,11 @@ function boot (env) { // api and json object variables /////////////////////////////////////////////////// ctx.plugins = require('./plugins')().registerServerDefaults().init(env); - ctx.pushover = require('./pushover')(env); - ctx.maker = require('./maker')(env); + + ctx.pushover = require('./plugins/pushover')(env); + ctx.maker = require('./plugins/maker')(env); ctx.pushnotify = require('./pushnotify')(env, ctx); + ctx.entries = require('./entries')(env, ctx); ctx.treatments = require('./treatments')(env, ctx); ctx.devicestatus = require('./devicestatus')(env.devicestatus_collection, ctx); diff --git a/lib/plugins/maker-setup.md b/lib/plugins/maker-setup.md new file mode 100644 index 00000000000..1aa8b9ba731 --- /dev/null +++ b/lib/plugins/maker-setup.md @@ -0,0 +1,43 @@ +Nightscout/IFTTT Maker +====================================== + +## Overview + + In addition to the normal web based alarms, and pushover, there is also integration for [IFTTT Maker](https://ifttt.com/maker). + + With Maker you are able to integrate with all the other [IFTTT Channels](https://ifttt.com/channels). For example you can send a tweet when there is an alarm, change the color of hue light, send an email, send and sms, and so much more. + + Plugins can create custom events, but all events sent to maker will be prefixed with `ns-`. The core events are: + * `ns-event` - This event is sent to the maker service for all alarms and notifications. This is good catch all event for general logging. + * `ns-allclear` - This event is sent to the maker service when an alarm has been ack'd or when the server starts up without triggering any alarms. For example, you could use this event to turn a light to green. + * `ns-info` - Plugins that generate notifications at the info level will cause this event to also be triggered. It will be sent in addition to `ns-event`. + * `ns-warning` - Alarms at the warning level with cause this event to also be triggered. It will be sent in addition to `ns-event`. + * `ns-urgent` - Alarms at the urgent level with cause this event to also be triggered. It will be sent in addition to `ns-event`. + * `ns-warning-high` - Alarms at the warning level with cause this event to also be triggered. It will be sent in addition to `ns-event` and `ns-warning`. + * `ns-urgent-high` - Alarms at the urgent level with cause this event to also be triggered. It will be sent in addition to `ns-event` and `ns-urgent`. + * `ns-warning-low` - Alarms at the warning level with cause this event to also be triggered. It will be sent in addition to `ns-event` and `ns-warning`. + * `ns-urgent-low` - Alarms at the urgent level with cause this event to also be triggered. It will be sent in addition to `ns-event` and `ns-urgent`. + * `ns-info-treatmentnotify` - When a treatment is entered into the care portal this event is triggered. It will be sent in addition to `ns-event` and `ns-info`. + * `ns-warning-bwp` - When the BWP plugin generates a warning alarm. It will be sent in addition to `ns-event` and `ns-warning`. + * `ns-urgent-bwp` - When the BWP plugin generates an urgent alarm. It will be sent in addition to `ns-event` and `ns-urget`. + +## Configuration + + 1. Setup IFTTT account: [login](https://ifttt.com/login) or [create an account](https://ifttt.com/join) + 2. Find your secret key on the [maker page](https://ifttt.com/maker) + 3. Configure Nightscout by setting these environment variables: + * `ENABLE` - `maker` should be added to the list of plugin, for example: `ENABLE="maker"`. + * `MAKER_KEY` - Set this to your secret key that you located in step 2, for example: `MAKER_KEY="abcMyExampleabc123defjt1DeNSiftttmak-XQb69p"` + +## Create a recipe + + Start [creating a recipe](https://ifttt.com/myrecipes/personal/new) + + 1. Choose a Trigger Channel + 2. Choose a Trigger + 3. Complete Trigger Fields + 4. That + 5. Choose an Action + 6. Complete Action Fields + 7. Create and Connect + diff --git a/lib/maker.js b/lib/plugins/maker.js similarity index 100% rename from lib/maker.js rename to lib/plugins/maker.js diff --git a/lib/pushover.js b/lib/plugins/pushover.js similarity index 100% rename from lib/pushover.js rename to lib/plugins/pushover.js diff --git a/tests/maker.test.js b/tests/maker.test.js index 2d94c33da4d..6b00ab5ccb4 100644 --- a/tests/maker.test.js +++ b/tests/maker.test.js @@ -1,7 +1,7 @@ var should = require('should'); describe('maker', function ( ) { - var maker = require('../lib/maker')({extendedSettings: {maker: {key: '12345'}}}); + var maker = require('../lib/plugins/maker')({extendedSettings: {maker: {key: '12345'}}}); //prevent any calls to iftt function noOpMakeRequest (event, eventName, callback) { From fd8ad2f03eb343f20c2e685c7822da7bc2125ede Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 29 Jun 2015 23:51:49 -0700 Subject: [PATCH 245/937] try adding images to maker doc --- lib/plugins/maker-setup.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/plugins/maker-setup.md b/lib/plugins/maker-setup.md index 1aa8b9ba731..7ca03e5a38b 100644 --- a/lib/plugins/maker-setup.md +++ b/lib/plugins/maker-setup.md @@ -32,12 +32,30 @@ Nightscout/IFTTT Maker ## Create a recipe Start [creating a recipe](https://ifttt.com/myrecipes/personal/new) - + ![screen shot 2015-06-29 at 10 58 48 pm](https://cloud.githubusercontent.com/assets/751143/8425240/bab51986-1eb8-11e5-88fb-5aed311896be.png) + 1. Choose a Trigger Channel + ![screen shot 2015-06-29 at 10 59 01 pm](https://cloud.githubusercontent.com/assets/751143/8425243/c007ace6-1eb8-11e5-96d1-b13f9c3d071f.png) + 2. Choose a Trigger + ![screen shot 2015-06-29 at 10 59 18 pm](https://cloud.githubusercontent.com/assets/751143/8425246/c77c5a4e-1eb8-11e5-9084-32ae40518ee0.png) + 3. Complete Trigger Fields + ![screen shot 2015-06-29 at 10 59 33 pm](https://cloud.githubusercontent.com/assets/751143/8425249/ced7b450-1eb8-11e5-95a3-730f6b9b2925.png) + 4. That + ![screen shot 2015-06-29 at 10 59 46 pm](https://cloud.githubusercontent.com/assets/751143/8425251/d46e1dc8-1eb8-11e5-91be-8dc731e308b2.png) + 5. Choose an Action + ![screen shot 2015-06-29 at 11 00 12 pm](https://cloud.githubusercontent.com/assets/751143/8425254/de634844-1eb8-11e5-8f09-cd43c41ccf3f.png) + 6. Complete Action Fields + ![screen shot 2015-06-29 at 11 02 14 pm](https://cloud.githubusercontent.com/assets/751143/8425267/f2da6dd4-1eb8-11e5-8e4d-cad2590d111f.png) + ![screen shot 2015-06-29 at 11 02 21 pm](https://cloud.githubusercontent.com/assets/751143/8425272/f83ceb62-1eb8-11e5-8ea2-afd4dcbd391f.png) + 7. Create and Connect + ![screen shot 2015-06-29 at 11 02 43 pm](https://cloud.githubusercontent.com/assets/751143/8425277/fe52f618-1eb8-11e5-8d7f-e0b34eebe29a.png) + + + From 889a16f1f1194851e075f6e3b4f2541a37738dde Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 30 Jun 2015 00:06:19 -0700 Subject: [PATCH 246/937] add toc's to docs, some clean/organization --- CONTRIBUTING.md | 16 +++++ README.md | 139 +++++++++++++++++++++---------------- Release.md | 15 ---- lib/plugins/maker-setup.md | 65 +++++++++++------ 4 files changed, 140 insertions(+), 95 deletions(-) delete mode 100644 Release.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1dd0ba4d427..6169a7cbc4a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,3 +1,19 @@ + + +**Table of Contents** + +- [Contributing to cgm-remote-monitor](#contributing-to-cgm-remote-monitor) + - [Design](#design) + - [Develop on `dev`](#develop-on-dev) + - [Style Guide](#style-guide) + - [Create a prototype](#create-a-prototype) + - [Submit a pull request](#submit-a-pull-request) + - [Comments and issues](#comments-and-issues) + - [Co-ordination](#co-ordination) + - [Other Dev Tips](#other-dev-tips) + + + # Contributing to cgm-remote-monitor diff --git a/README.md b/README.md index 872d6a96a84..32701b7b0a2 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,31 @@ + + +**Table of Contents** + +- [Nightscout Web Monitor (a.k.a. cgm-remote-monitor)](#nightscout-web-monitor-aka-cgm-remote-monitor) +- [[#WeAreNotWaiting](https://twitter.com/hashtag/wearenotwaiting?src=hash&vertical=default&f=images) and [this](https://vimeo.com/109767890) is why.](##wearenotwaitinghttpstwittercomhashtagwearenotwaitingsrchash&verticaldefault&fimages-and-thishttpsvimeocom109767890-is-why) +- [Install](#install) +- [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) + - [Environment](#environment) + - [Required](#required) + - [Features/Labs](#featureslabs) + - [Core](#core) + - [Predefined values for your browser settings (optional)](#predefined-values-for-your-browser-settings-optional) + - [Plugins](#plugins) + - [Extended Settings](#extended-settings) + - [Pushover](#pushover) + - [IFTTT Maker](#ifttt-maker) + - [Treatment Profile](#treatment-profile) + - [Setting environment variables](#setting-environment-variables) + - [Vagrant install](#vagrant-install) + - [More questions?](#more-questions) + - [License](#license) + + + Nightscout Web Monitor (a.k.a. cgm-remote-monitor) ====================================== @@ -19,7 +47,7 @@ and blood glucose values are predicted 0.5 hours ahead using an autoregressive second order model. Alarms are generated for high and low values, which can be cleared by any watcher of the data. -#[#WeAreNotWaiting](https://twitter.com/hashtag/wearenotwaiting?src=hash&vertical=default&f=images) and [this](https://vimeo.com/109767890) is why. +# [#WeAreNotWaiting](https://twitter.com/hashtag/wearenotwaiting?src=hash&vertical=default&f=images) and [this](https://vimeo.com/109767890) is why. Community maintained fork of the [original cgm-remote-monitor][original]. @@ -39,8 +67,7 @@ Community maintained fork of the [heroku-url]: https://heroku.com/deploy [original]: https://github.com/rnpenguin/cgm-remote-monitor -Install ---------------- +# Install Requirements: @@ -52,8 +79,7 @@ Clone this repo then install dependencies into the root of the project: $ npm install ``` -Usage ---------------- +#Usage The data being uploaded from the server to the client is from a MongoDB server such as [mongolab][mongodb]. In order to access the @@ -68,31 +94,32 @@ ready, just host your web app on your service of choice. [mongostring]: http://nightscout.github.io/pages/mongostring/ [update-fork]: http://nightscout.github.io/pages/update-fork/ -### Updating my version? +## Updating my version? The easiest way to update your version of cgm-remote-monitor to our latest recommended version is to use the [update my fork tool][update-fork]. It even gives out stars if you are up to date. -### What is my mongo string? +## What is my mongo string? Try the [what is my mongo string tool][mongostring] to get a good idea of your mongo string. You can copy and paste the text in the gray box into your `MONGO_CONNECTION` environment variable. -### Configure my uploader to match +## Configure my uploader to match Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. -### Environment +## Environment `VARIABLE` (default) - description -#### Required +### Required * `MONGO_CONNECTION` - Your mongo uri, for example: `mongodb://sally:sallypass@ds099999.mongolab.com:99999/nightscout` + * `DISPLAY_UNITS` (`mg/dl`) - Choices: `mg/dl` and `mmol`. Setting to `mmol` puts the entire server into `mmol` mode by default, no further settings needed. -#### Features/Labs +### Features/Labs * `ENABLE` - Used to enable optional features, expects a space delimited list such as: `careportal rawbg iob`, see [plugins](#plugins) below * `API_SECRET` - A secret passphrase that must be at least 12 characters long, required to enable `POST` and `PUT`; also required for the Care Portal @@ -104,9 +131,8 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * `BASE_URL` - Used for building links to your sites api, ie pushover callbacks, usually the URL of your Nightscout site you may want https instead of http -#### Core +### Core - * `DISPLAY_UNITS` (`mg/dl`) - Choices: `mg/dl` and `mmol`. Setting to `mmol` puts the entire server into `mmol` mode by default, no further settings needed. * `MONGO_COLLECTION` (`entries`) - The collection used to store SGV, MBG, and CAL records from your CGM device * `MONGO_TREATMENTS_COLLECTION` (`treatments`) -The collection used to store treatments entered in the Care Portal, see the `ENABLE` env var above * `MONGO_DEVICESTATUS_COLLECTION`(`devicestatus`) - The collection used to store device status information such as uploader battery @@ -116,7 +142,7 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * `SSL_CA` - Path to your ssl ca file, so that ssl(https) can be enabled directly in node.js -#### Predefined values for your browser settings (optional) +### Predefined values for your browser settings (optional) * `TIME_FORMAT` (`12`)- possible values `12` or `24` * `NIGHT_MODE` (`off`) - possible values `on` or `off` * `SHOW_RAWBG` (`never`) - possible values `always`, `never` or `noise` @@ -166,6 +192,46 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. Plugins only have access to their own extended settings, all the extended settings of client plugins will be sent to the browser. +#### Pushover + In addition to the normal web based alarms, there is also support for [Pushover](https://pushover.net/) based alarms and notifications. + + To get started install the Pushover application on your iOS or Android device and create an account. + + Using that account login to [Pushover](https://pushover.net/), in the top left you’ll see your User Key, you’ll need this plus an application API Token/Key to complete this setup. + + You’ll need to [Create a Pushover Application](https://pushover.net/apps/build). You only need to set the Application name, you can ignore all the other settings, but setting an Icon is a nice touch. Maybe you'd like to use [this one](https://raw.githubusercontent.com/nightscout/cgm-remote-monitor/master/static/images/large.png)? + + Pushover is configured using the following Environment Variables: + + * `ENABLE` - `pushover` should be added to the list of plugin, for example: `ENABLE="pushover"`. + * `PUSHOVER_API_TOKEN` - Used to enable pushover notifications, this token is specific to the application you create from in [Pushover](https://pushover.net/), ***[additional pushover information](#pushover)*** below. + * `PUSHOVER_USER_KEY` - Your Pushover user key, can be found in the top left of the [Pushover](https://pushover.net/) site, this can also be a pushover delivery group key to send to a group rather than just a single user. + * `BASE_URL` - Used for pushover callbacks, usually the URL of your Nightscout site, use https when possible. + * `API_SECRET` - Used for signing the pushover callback request for acknowledgments. + + For testing/devlopment try [localtunnel](http://localtunnel.me/). + +#### IFTTT Maker + In addition to the normal web based alarms, and pushover, there is also integration for [IFTTT Maker](https://ifttt.com/maker). + + With Maker you are able to integrate with all the other [IFTTT Channels](https://ifttt.com/channels). For example you can send a tweet when there is an alarm, change the color of hue light, send an email, send and sms, and so much more. + + 1. Setup IFTTT account: [login](https://ifttt.com/login) or [create an account](https://ifttt.com/join) + 2. Find your secret key on the [maker page](https://ifttt.com/maker) + 3. Configure Nightscout by setting these environment variables: + * `ENABLE` - `maker` should be added to the list of plugin, for example: `ENABLE="maker"`. + * `MAKER_KEY` - Set this to your secret key that you located in step 2, for example: `MAKER_KEY="abcMyExampleabc123defjt1DeNSiftttmak-XQb69p"` + 4. [Create a recipe](https://ifttt.com/myrecipes/personal/new) or see [more detailed instructions](lib/plugins/maker-setup.md#create-a-recipe) + + Plugins can create custom events, but all events sent to maker will be prefixed with `ns-`. The core events are: + * `ns-event` - This event is sent to the maker service for all alarms and notifications. This is good catch all event for general logging. + * `ns-allclear` - This event is sent to the maker service when an alarm has been ack'd or when the server starts up without triggering any alarms. For example, you could use this event to turn a light to green. + * `ns-info` - Plugins that generate notifications at the info level will cause this event to also be triggered. It will be sent in addition to `ns-event`. + * `ns-warning` - Alarms at the warning level with cause this event to also be triggered. It will be sent in addition to `ns-event`. + * `ns-urgent` - Alarms at the urgent level with cause this event to also be triggered. It will be sent in addition to `ns-event`. + * see the [full list of events](lib/plugins/maker-setup.md#events) + + ### Treatment Profile Some of the [plugins](#plugins) make use of a treatment profile that is stored in Mongo. To use those plugins there should only be a single doc in the `profile` collection. A simple example (change it to fit you): @@ -244,51 +310,6 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. Additional information can be found [here](http://www.nightscout.info/wiki/labs/the-nightscout-iob-cob-website). -### Pushover - In addition to the normal web based alarms, there is also support for [Pushover](https://pushover.net/) based alarms and notifications. - - To get started install the Pushover application on your iOS or Android device and create an account. - - Using that account login to [Pushover](https://pushover.net/), in the top left you’ll see your User Key, you’ll need this plus an application API Token/Key to complete this setup. - - You’ll need to [Create a Pushover Application](https://pushover.net/apps/build). You only need to set the Application name, you can ignore all the other settings, but setting an Icon is a nice touch. Maybe you'd like to use [this one](https://raw.githubusercontent.com/nightscout/cgm-remote-monitor/master/static/images/large.png)? - - Pushover is configured using the following Environment Variables: - - * `ENABLE` - `pushover` should be added to the list of plugin, for example: `ENABLE="pushover"`. - * `PUSHOVER_API_TOKEN` - Used to enable pushover notifications, this token is specific to the application you create from in [Pushover](https://pushover.net/), ***[additional pushover information](#pushover)*** below. - * `PUSHOVER_USER_KEY` - Your Pushover user key, can be found in the top left of the [Pushover](https://pushover.net/) site, this can also be a pushover delivery group key to send to a group rather than just a single user. - * `BASE_URL` - Used for pushover callbacks, usually the URL of your Nightscout site, use https when possible. - * `API_SECRET` - Used for signing the pushover callback request for acknowledgments. - - For testing/devlopment try [localtunnel](http://localtunnel.me/). - -### IFTTT Maker - In addition to the normal web based alarms, and pushover, there is also integration for [IFTTT Maker](https://ifttt.com/maker). - - With Maker you are able to integrate with all the other [IFTTT Channels](https://ifttt.com/channels). For example you can send a tweet when there is an alarm, change the color of hue light, send an email, send and sms, and so much more. - - 1. Setup IFTTT account: [login](https://ifttt.com/login) or [create an account](https://ifttt.com/join) - 2. Find your secret key on the [maker page](https://ifttt.com/maker) - 3. Configure Nightscout by setting these environment variables: - * `ENABLE` - `maker` should be added to the list of plugin, for example: `ENABLE="maker"`. - * `MAKER_KEY` - Set this to your secret key that you located in step 2, for example: `MAKER_KEY="abcMyExampleabc123defjt1DeNSiftttmak-XQb69p"` - 4. [Create a recipe](https://ifttt.com/myrecipes/personal/new) or see [more detailed instructions](lib/plugins/maker-setup.md) - - Plugins can create custom events, but all events sent to maker will be prefixed with `ns-`. The core events are: - * `ns-event` - This event is sent to the maker service for all alarms and notifications. This is good catch all event for general logging. - * `ns-allclear` - This event is sent to the maker service when an alarm has been ack'd or when the server starts up without triggering any alarms. For example, you could use this event to turn a light to green. - * `ns-info` - Plugins that generate notifications at the info level will cause this event to also be triggered. It will be sent in addition to `ns-event`. - * `ns-warning` - Alarms at the warning level with cause this event to also be triggered. It will be sent in addition to `ns-event`. - * `ns-urgent` - Alarms at the urgent level with cause this event to also be triggered. It will be sent in addition to `ns-event`. - * `ns-warning-high` - Alarms at the warning level with cause this event to also be triggered. It will be sent in addition to `ns-event` and `ns-warning`. - * `ns-urgent-high` - Alarms at the urgent level with cause this event to also be triggered. It will be sent in addition to `ns-event` and `ns-urgent`. - * `ns-warning-low` - Alarms at the warning level with cause this event to also be triggered. It will be sent in addition to `ns-event` and `ns-warning`. - * `ns-urgent-low` - Alarms at the urgent level with cause this event to also be triggered. It will be sent in addition to `ns-event` and `ns-urgent`. - * `ns-info-treatmentnotify` - When a treatment is entered into the care portal this event is triggered. It will be sent in addition to `ns-event` and `ns-info`. - * `ns-warning-bwp` - When the BWP plugin generates a warning alarm. It will be sent in addition to `ns-event` and `ns-warning`. - * `ns-urgent-bwp` - When the BWP plugin generates an urgent alarm. It will be sent in addition to `ns-event` and `ns-urget`. - ## Setting environment variables Easy to emulate on the commandline: diff --git a/Release.md b/Release.md deleted file mode 100644 index 8e9ba512d0d..00000000000 --- a/Release.md +++ /dev/null @@ -1,15 +0,0 @@ - -v0.4.1 / 2014-09-12 -================== - - * quick hack to prevent mbg records from crashing pebble - * add script to prep release branch - * tweak toolbar and button size/placement - * Merge pull request #128 from nightscout/wip/mbg - * Merge pull request #166 from nightscout/wip/id-rev - * Merge pull request #165 from nightscout/hotfix/pebble-sgv-string - * convert sgv to string - * add ability to easily id git rev-parse HEAD - * Merge branch 'release/0.4.0' into dev - * hack: only consider 'grey' sgv records - * Added searching for MBG data from mongo query. diff --git a/lib/plugins/maker-setup.md b/lib/plugins/maker-setup.md index 7ca03e5a38b..4bca6fa7202 100644 --- a/lib/plugins/maker-setup.md +++ b/lib/plugins/maker-setup.md @@ -1,3 +1,23 @@ + + +**Table of Contents** + +- [Nightscout/IFTTT Maker](#nightscoutifttt-maker) + - [Overview](#overview) + - [Events](#events) + - [Configuration](#configuration) + - [Create a recipe](#create-a-recipe) + - [Start [creating a recipe](https://ifttt.com/myrecipes/personal/new)](#start-creating-a-recipehttpsiftttcommyrecipespersonalnew) + - [1. Choose a Trigger Channel](#1-choose-a-trigger-channel) + - [2. Choose a Trigger](#2-choose-a-trigger) + - [3. Complete Trigger Fields](#3-complete-trigger-fields) + - [4. That](#4-that) + - [5. Choose an Action](#5-choose-an-action) + - [6. Complete Action Fields](#6-complete-action-fields) + - [7. Create and Connect](#7-create-and-connect) + + + Nightscout/IFTTT Maker ====================================== @@ -7,7 +27,10 @@ Nightscout/IFTTT Maker With Maker you are able to integrate with all the other [IFTTT Channels](https://ifttt.com/channels). For example you can send a tweet when there is an alarm, change the color of hue light, send an email, send and sms, and so much more. +## Events + Plugins can create custom events, but all events sent to maker will be prefixed with `ns-`. The core events are: + * `ns-event` - This event is sent to the maker service for all alarms and notifications. This is good catch all event for general logging. * `ns-allclear` - This event is sent to the maker service when an alarm has been ack'd or when the server starts up without triggering any alarms. For example, you could use this event to turn a light to green. * `ns-info` - Plugins that generate notifications at the info level will cause this event to also be triggered. It will be sent in addition to `ns-event`. @@ -31,30 +54,30 @@ Nightscout/IFTTT Maker ## Create a recipe - Start [creating a recipe](https://ifttt.com/myrecipes/personal/new) - ![screen shot 2015-06-29 at 10 58 48 pm](https://cloud.githubusercontent.com/assets/751143/8425240/bab51986-1eb8-11e5-88fb-5aed311896be.png) +### Start [creating a recipe](https://ifttt.com/myrecipes/personal/new) +![screen shot 2015-06-29 at 10 58 48 pm](https://cloud.githubusercontent.com/assets/751143/8425240/bab51986-1eb8-11e5-88fb-5aed311896be.png) + +### 1. Choose a Trigger Channel + ![screen shot 2015-06-29 at 10 59 01 pm](https://cloud.githubusercontent.com/assets/751143/8425243/c007ace6-1eb8-11e5-96d1-b13f9c3d071f.png) + +### 2. Choose a Trigger + ![screen shot 2015-06-29 at 10 59 18 pm](https://cloud.githubusercontent.com/assets/751143/8425246/c77c5a4e-1eb8-11e5-9084-32ae40518ee0.png) - 1. Choose a Trigger Channel - ![screen shot 2015-06-29 at 10 59 01 pm](https://cloud.githubusercontent.com/assets/751143/8425243/c007ace6-1eb8-11e5-96d1-b13f9c3d071f.png) +### 3. Complete Trigger Fields + ![screen shot 2015-06-29 at 10 59 33 pm](https://cloud.githubusercontent.com/assets/751143/8425249/ced7b450-1eb8-11e5-95a3-730f6b9b2925.png) + +### 4. That + ![screen shot 2015-06-29 at 10 59 46 pm](https://cloud.githubusercontent.com/assets/751143/8425251/d46e1dc8-1eb8-11e5-91be-8dc731e308b2.png) - 2. Choose a Trigger - ![screen shot 2015-06-29 at 10 59 18 pm](https://cloud.githubusercontent.com/assets/751143/8425246/c77c5a4e-1eb8-11e5-9084-32ae40518ee0.png) +### 5. Choose an Action + ![screen shot 2015-06-29 at 11 00 12 pm](https://cloud.githubusercontent.com/assets/751143/8425254/de634844-1eb8-11e5-8f09-cd43c41ccf3f.png) - 3. Complete Trigger Fields - ![screen shot 2015-06-29 at 10 59 33 pm](https://cloud.githubusercontent.com/assets/751143/8425249/ced7b450-1eb8-11e5-95a3-730f6b9b2925.png) - - 4. That - ![screen shot 2015-06-29 at 10 59 46 pm](https://cloud.githubusercontent.com/assets/751143/8425251/d46e1dc8-1eb8-11e5-91be-8dc731e308b2.png) - - 5. Choose an Action - ![screen shot 2015-06-29 at 11 00 12 pm](https://cloud.githubusercontent.com/assets/751143/8425254/de634844-1eb8-11e5-8f09-cd43c41ccf3f.png) - - 6. Complete Action Fields - ![screen shot 2015-06-29 at 11 02 14 pm](https://cloud.githubusercontent.com/assets/751143/8425267/f2da6dd4-1eb8-11e5-8e4d-cad2590d111f.png) - ![screen shot 2015-06-29 at 11 02 21 pm](https://cloud.githubusercontent.com/assets/751143/8425272/f83ceb62-1eb8-11e5-8ea2-afd4dcbd391f.png) - - 7. Create and Connect - ![screen shot 2015-06-29 at 11 02 43 pm](https://cloud.githubusercontent.com/assets/751143/8425277/fe52f618-1eb8-11e5-8d7f-e0b34eebe29a.png) +### 6. Complete Action Fields + ![screen shot 2015-06-29 at 11 02 14 pm](https://cloud.githubusercontent.com/assets/751143/8425267/f2da6dd4-1eb8-11e5-8e4d-cad2590d111f.png) + ![screen shot 2015-06-29 at 11 02 21 pm](https://cloud.githubusercontent.com/assets/751143/8425272/f83ceb62-1eb8-11e5-8ea2-afd4dcbd391f.png) + +### 7. Create and Connect + ![screen shot 2015-06-29 at 11 02 43 pm](https://cloud.githubusercontent.com/assets/751143/8425277/fe52f618-1eb8-11e5-8d7f-e0b34eebe29a.png) From 6b06a5788342272565cde91f1e8ca598e553977b Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 30 Jun 2015 00:10:04 -0700 Subject: [PATCH 247/937] move toc down in readme --- README.md | 54 ++++++++++++++++++++++++++---------------------------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 32701b7b0a2..f97537aab77 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,3 @@ - - -**Table of Contents** - -- [Nightscout Web Monitor (a.k.a. cgm-remote-monitor)](#nightscout-web-monitor-aka-cgm-remote-monitor) -- [[#WeAreNotWaiting](https://twitter.com/hashtag/wearenotwaiting?src=hash&vertical=default&f=images) and [this](https://vimeo.com/109767890) is why.](##wearenotwaitinghttpstwittercomhashtagwearenotwaitingsrchash&verticaldefault&fimages-and-thishttpsvimeocom109767890-is-why) -- [Install](#install) -- [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) - - [Environment](#environment) - - [Required](#required) - - [Features/Labs](#featureslabs) - - [Core](#core) - - [Predefined values for your browser settings (optional)](#predefined-values-for-your-browser-settings-optional) - - [Plugins](#plugins) - - [Extended Settings](#extended-settings) - - [Pushover](#pushover) - - [IFTTT Maker](#ifttt-maker) - - [Treatment Profile](#treatment-profile) - - [Setting environment variables](#setting-environment-variables) - - [Vagrant install](#vagrant-install) - - [More questions?](#more-questions) - - [License](#license) - - - Nightscout Web Monitor (a.k.a. cgm-remote-monitor) ====================================== @@ -67,6 +39,32 @@ Community maintained fork of the [heroku-url]: https://heroku.com/deploy [original]: https://github.com/rnpenguin/cgm-remote-monitor + + +**Table of Contents** + +- [Install](#install) +- [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) + - [Environment](#environment) + - [Required](#required) + - [Features/Labs](#featureslabs) + - [Core](#core) + - [Predefined values for your browser settings (optional)](#predefined-values-for-your-browser-settings-optional) + - [Plugins](#plugins) + - [Extended Settings](#extended-settings) + - [Pushover](#pushover) + - [IFTTT Maker](#ifttt-maker) + - [Treatment Profile](#treatment-profile) + - [Setting environment variables](#setting-environment-variables) + - [Vagrant install](#vagrant-install) + - [More questions?](#more-questions) + - [License](#license) + + + # Install Requirements: From 08529c4175c2ae53f10699a8ff0e42d54d11b1fe Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 30 Jun 2015 00:12:23 -0700 Subject: [PATCH 248/937] added logo to readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index f97537aab77..0cc3c1d0551 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ Nightscout Web Monitor (a.k.a. cgm-remote-monitor) ====================================== +![nightscout horizontal](https://cloud.githubusercontent.com/assets/751143/8425633/93c94dc0-1ebc-11e5-99e7-71a8f464caac.png) + [![Build Status][build-img]][build-url] [![Dependency Status][dependency-img]][dependency-url] [![Coverage Status][coverage-img]][coverage-url] From 4c7846f6e9aa28bbdf5bac16229b589283c33ae3 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 30 Jun 2015 00:36:50 -0700 Subject: [PATCH 249/937] more doc updates --- README.md | 2 +- lib/plugins/maker-setup.md | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0cc3c1d0551..c037be74481 100644 --- a/README.md +++ b/README.md @@ -163,7 +163,7 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. Plugins are used extend the way information is displayed, how notifications are sent, alarms are triggered, and more. - The built-in/example plugins that are available by default are listed below. The plugins may still need to be enabled by adding the to the `ENABLE` environment variable. + The built-in/example plugins that are available by default are listed below. The plugins may still need to be enabled by adding to the `ENABLE` environment variable. **Built-in/Example Plugins:** diff --git a/lib/plugins/maker-setup.md b/lib/plugins/maker-setup.md index 4bca6fa7202..261599abaff 100644 --- a/lib/plugins/maker-setup.md +++ b/lib/plugins/maker-setup.md @@ -78,7 +78,8 @@ Nightscout/IFTTT Maker ### 7. Create and Connect ![screen shot 2015-06-29 at 11 02 43 pm](https://cloud.githubusercontent.com/assets/751143/8425277/fe52f618-1eb8-11e5-8d7f-e0b34eebe29a.png) - - + +### Result + ![cinpiqkumaa33u7](https://cloud.githubusercontent.com/assets/751143/8425925/e7d08d2c-1ebf-11e5-853c-cdc5381c4186.png) From 93ee9b7ecf9e36ae7691b0d1f68c0d932b97a6bf Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 30 Jun 2015 00:37:46 -0700 Subject: [PATCH 250/937] update toc --- lib/plugins/maker-setup.md | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/plugins/maker-setup.md b/lib/plugins/maker-setup.md index 261599abaff..1d01df7b75b 100644 --- a/lib/plugins/maker-setup.md +++ b/lib/plugins/maker-setup.md @@ -15,6 +15,7 @@ - [5. Choose an Action](#5-choose-an-action) - [6. Complete Action Fields](#6-complete-action-fields) - [7. Create and Connect](#7-create-and-connect) + - [Result](#result) From 52c8bc617e217e2aeed18ce12c275ef21d3b8477 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 30 Jun 2015 00:40:06 -0700 Subject: [PATCH 251/937] added example tweet test to doc --- lib/plugins/maker-setup.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/plugins/maker-setup.md b/lib/plugins/maker-setup.md index 1d01df7b75b..a7d89084376 100644 --- a/lib/plugins/maker-setup.md +++ b/lib/plugins/maker-setup.md @@ -74,6 +74,8 @@ Nightscout/IFTTT Maker ![screen shot 2015-06-29 at 11 00 12 pm](https://cloud.githubusercontent.com/assets/751143/8425254/de634844-1eb8-11e5-8f09-cd43c41ccf3f.png) ### 6. Complete Action Fields + **Example:** `Nightscout: {{Value1}} {{Value2}} {{Value3}}` + ![screen shot 2015-06-29 at 11 02 14 pm](https://cloud.githubusercontent.com/assets/751143/8425267/f2da6dd4-1eb8-11e5-8e4d-cad2590d111f.png) ![screen shot 2015-06-29 at 11 02 21 pm](https://cloud.githubusercontent.com/assets/751143/8425272/f83ceb62-1eb8-11e5-8ea2-afd4dcbd391f.png) From 6f55a86d1104c9bd357f1183be174f7e3195894d Mon Sep 17 00:00:00 2001 From: Fokko Driesprong Date: Tue, 30 Jun 2015 18:03:34 +0200 Subject: [PATCH 252/937] Fixed the issues as described by Codacy --- tests/storage.test.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/storage.test.js b/tests/storage.test.js index a4a5d8152a5..641b710d920 100644 --- a/tests/storage.test.js +++ b/tests/storage.test.js @@ -1,9 +1,7 @@ 'use strict'; -var request = require('supertest'); var should = require('should'); var assert = require('assert'); -var load = require('./fixtures/load'); describe('STORAGE', function () { var env = require('../env')(); @@ -14,7 +12,7 @@ describe('STORAGE', function () { }); it('The storage class should be OK.', function (done) { - require('../lib/storage').should.be.ok; + should(require('../lib/storage')).be.ok; done(); }); @@ -25,7 +23,7 @@ describe('STORAGE', function () { store(env, function (err2, db2) { should.not.exist(err2); - assert(db1.db, db2.db, 'Check if the handlers are the same.') + assert(db1.db, db2.db, 'Check if the handlers are the same.'); done(); }); From 853a052d57933e7b6b5a7060f5064e0e640f5cd6 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 30 Jun 2015 18:11:51 -0700 Subject: [PATCH 253/937] adjust example profile, to prevent copy and pastes from seeing a high BWP --- README.md | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index c037be74481..c05ecb40f0f 100644 --- a/README.md +++ b/README.md @@ -233,36 +233,38 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. ### Treatment Profile - Some of the [plugins](#plugins) make use of a treatment profile that is stored in Mongo. To use those plugins there should only be a single doc in the `profile` collection. A simple example (change it to fit you): + Some of the [plugins](#plugins) make use of a treatment profile that is stored in Mongo. To use those plugins there should only be a single doc in the `profile` collection. + + Example Profile (change it to fit you): ```json { - "dia": 4, - "carbs_hr": 30, - "carbratio": 7.5, - "sens": 35, - "basal": 1.00 - "target_low": 95, + "dia": 3, + "carbs_hr": 20, + "carbratio": 30, + "sens": 100, + "basal": 0.125, + "target_low": 100, "target_high": 120 } ``` - Profiles can also use time periods for any field, for example: + Profile can also use time periods for any field, for example: ```json { "carbratio": [ { "time": "00:00", - "value": 16 + "value": 30 }, { "time": "06:00", - "value": 15 + "value": 25 }, { "time": "14:00", - "value": 16 + "value": 28 } ], "basal": [ @@ -280,7 +282,7 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. }, { "time": "08:00", - "value": 0.1 + "value": 0.100 }, { "time": "14:00", @@ -288,11 +290,11 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. }, { "time": "20:00", - "value": 0.3 + "value": 0.175 }, { "time": "22:00", - "value": 0.225 + "value": 0.200 } ] } From e68fce5e55ca10881be20a9a446e46b5646bee9c Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 30 Jun 2015 18:39:58 -0700 Subject: [PATCH 254/937] minor formatting, while testing --- lib/storage.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/storage.js b/lib/storage.js index ac8f745c40c..77c2fa33a30 100644 --- a/lib/storage.js +++ b/lib/storage.js @@ -4,11 +4,11 @@ var mongodb = require('mongodb'); var connection = null; -function init(env, cb, forceNewConnection) { +function init (env, cb, forceNewConnection) { var MongoClient = mongodb.MongoClient; var mongo = {}; - function maybe_connect(cb) { + function maybe_connect (cb) { if (connection != null && !forceNewConnection) { console.log('Reusing MongoDB connection handler'); @@ -43,24 +43,24 @@ function init(env, cb, forceNewConnection) { } } - mongo.collection = function get_collection(name) { + mongo.collection = function get_collection (name) { return connection.collection(name); }; - mongo.with_collection = function with_collection(name) { + mongo.with_collection = function with_collection (name) { return function use_collection(fn) { fn(null, connection.collection(name)); }; }; - mongo.limit = function limit(opts) { + mongo.limit = function limit (opts) { if (opts && opts.count) { return this.limit(parseInt(opts.count)); } return this; }; - mongo.ensureIndexes = function (collection, fields) { + mongo.ensureIndexes = function ensureIndexes (collection, fields) { fields.forEach(function (field) { console.info('ensuring index for: ' + field); collection.ensureIndex(field, function (err) { From 264597ba83cbb2bc6cfd5144779a1259fd11247b Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 30 Jun 2015 19:37:18 -0700 Subject: [PATCH 255/937] send coverage to codacy --- Makefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Makefile b/Makefile index c7fa740dcbb..def09f31ccc 100644 --- a/Makefile +++ b/Makefile @@ -22,6 +22,7 @@ MOCHA=./node_modules/mocha/bin/_mocha # Pinned from dependency list. ISTANBUL=./node_modules/.bin/istanbul ANALYZED=./coverage/lcov.info +export CODACY_REPO_TOKEN=a033cfbe4e184d6f925bab97a21ed2d0 all: test @@ -36,6 +37,9 @@ report: test -f ${ANALYZED} && \ (npm install codecov.io && cat ${ANALYZED} | \ ./node_modules/codecov.io/bin/codecov.io.js) || echo "NO COVERAGE" + test -f ${ANALYZED} && \ + (npm install codacy-coverage && cat ${ANALYZED} | \ + YOURPACKAGE_COVERAGE=1 ./node_modules/codacy-coverage/bin/codacy-coverage.js) || echo "NO COVERAGE" test: ${MONGO_SETTINGS} ${MOCHA} -R tap ${TESTS} From 21c2679672d82088e50d213344ab39d1f1d3d01d Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 30 Jun 2015 20:07:05 -0700 Subject: [PATCH 256/937] new key to try getting things going again --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index def09f31ccc..2b60d26001b 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ MOCHA=./node_modules/mocha/bin/_mocha # Pinned from dependency list. ISTANBUL=./node_modules/.bin/istanbul ANALYZED=./coverage/lcov.info -export CODACY_REPO_TOKEN=a033cfbe4e184d6f925bab97a21ed2d0 +export CODACY_REPO_TOKEN=e29ae5cf671f4f918912d9864316207c all: test From fd585720b8693cdec31f41b38fcc4f23157cd535 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 30 Jun 2015 23:34:59 -0700 Subject: [PATCH 257/937] fix lots of little issues reported by codacy --- app.js | 12 +- env.js | 20 +-- lib/api/devicestatus/index.js | 73 +++++----- lib/api/index.js | 4 +- lib/api/status.js | 6 +- lib/api/treatments/index.js | 69 ++++----- lib/bootevent.js | 2 +- lib/bus.js | 2 +- lib/data.js | 47 +++--- lib/devicestatus.js | 65 ++++----- lib/entries.js | 15 +- lib/middleware/send-json-status.js | 17 ++- lib/mqtt.js | 220 ++++++++++++++--------------- lib/notifications.js | 2 +- lib/pebble.js | 18 +-- lib/plugins/ar2.js | 4 +- lib/plugins/basalprofile.js | 4 +- lib/plugins/boluswizardpreview.js | 10 +- lib/plugins/cannulaage.js | 2 +- lib/plugins/cob.js | 8 +- lib/plugins/iob.js | 6 +- lib/plugins/pluginbase.js | 2 +- lib/plugins/treatmentnotify.js | 4 +- lib/profilefunctions.js | 24 ++-- lib/pushnotify.js | 5 +- lib/sandbox.js | 8 +- lib/storage.js | 3 +- lib/treatments.js | 8 +- lib/units.js | 2 + lib/utils.js | 28 ++-- lib/websocket.js | 5 +- static/js/client.js | 70 ++++----- static/js/ui-utils.js | 8 +- testing/convert-treatments.js | 28 ++-- testing/make_high_data.js | 18 +-- testing/populate.js | 35 +++-- testing/populate_rest.js | 55 ++++---- testing/util.js | 107 +++++++------- tests/api.entries.test.js | 8 +- tests/api.status.test.js | 5 +- tests/cannulaage.test.js | 4 +- tests/cob.test.js | 34 ++--- tests/data.test.js | 7 +- tests/delta.test.js | 2 +- tests/iob.test.js | 12 +- tests/mqtt.test.js | 8 +- tests/pebble.test.js | 2 +- tests/profile.test.js | 98 +++++++------ tests/pushnotify.test.js | 4 +- tests/rawbg.test.js | 2 +- tests/security.test.js | 1 - tests/units.test.js | 4 +- tests/upbat.test.js | 2 +- tests/utils.test.js | 4 +- 54 files changed, 621 insertions(+), 592 deletions(-) diff --git a/app.js b/app.js index c15b5e1a6df..d6c63bea4ba 100644 --- a/app.js +++ b/app.js @@ -12,13 +12,11 @@ function create (env, ctx) { app.set('title', appInfo); app.enable('trust proxy'); // Allows req.secure test on heroku https connections. - app.use(compression({filter: shouldCompress})); - - function shouldCompress(req, res) { - //TODO: return false here if we find a condition where we don't want to compress - // fallback to standard filter function - return compression.filter(req, res); - } + app.use(compression({filter: function shouldCompress(req, res) { + //TODO: return false here if we find a condition where we don't want to compress + // fallback to standard filter function + return compression.filter(req, res); + }})); //if (env.api_secret) { // console.log("API_SECRET", env.api_secret); diff --git a/env.js b/env.js index f879572221f..9cc0220d6c6 100644 --- a/env.js +++ b/env.js @@ -12,9 +12,9 @@ function config ( ) { * First inspect a bunch of environment variables: * * PORT - serve http on this port * * MONGO_CONNECTION, CUSTOMCONNSTR_mongo - mongodb://... uri - * * CUSTOMCONNSTR_mongo_collection - name of mongo collection with "sgv" documents + * * CUSTOMCONNSTR_mongo_collection - name of mongo collection with `sgv` documents * * API_SECRET - if defined, this passphrase is fed to a sha1 hash digest, the hex output is used to create a single-use token for API authorization - * * NIGHTSCOUT_STATIC_FILES - the "base directory" to use for serving + * * NIGHTSCOUT_STATIC_FILES - the `base directory` to use for serving * static files over http. Default value is the included `static` * directory. */ @@ -23,10 +23,10 @@ function config ( ) { if (readENV('APPSETTING_ScmType') == readENV('ScmType') && readENV('ScmType') == 'GitHub') { env.head = require('./scm-commit-id.json'); - console.log("SCM COMMIT ID", env.head); + console.log('SCM COMMIT ID', env.head); } else { git.short(function record_git_head (head) { - console.log("GIT HEAD", head); + console.log('GIT HEAD', head); env.head = head || readENV('SCM_COMMIT_ID') || readENV('COMMIT_HASH', ''); }); } @@ -54,7 +54,7 @@ function config ( ) { env.profile_collection = readENV('MONGO_PROFILE_COLLECTION', 'profile'); env.devicestatus_collection = readENV('MONGO_DEVICESTATUS_COLLECTION', 'devicestatus'); - env.enable = readENV('ENABLE', ""); + env.enable = readENV('ENABLE', ''); env.defaults = { // currently supported keys must defined be here 'units': 'mg/dL' @@ -127,7 +127,7 @@ function config ( ) { // if a passphrase was provided, get the hex digest to mint a single token if (useSecret) { if (readENV('API_SECRET').length < consts.MIN_PASSPHRASE_LENGTH) { - var msg = ["API_SECRET should be at least", consts.MIN_PASSPHRASE_LENGTH, "characters"]; + var msg = ['API_SECRET should be at least', consts.MIN_PASSPHRASE_LENGTH, 'characters']; var err = new Error(msg.join(' ')); // console.error(err); throw err; @@ -170,9 +170,9 @@ function config ( ) { console.warn('BG_HIGH is now ' + env.thresholds.bg_high); } - //if any of the BG_* thresholds are set, default to "simple" otherwise default to "predict" to preserve current behavior + //if any of the BG_* thresholds are set, default to `simple` otherwise default to `predict` to preserve current behavior var thresholdsSet = readIntENV('BG_HIGH') || readIntENV('BG_TARGET_TOP') || readIntENV('BG_TARGET_BOTTOM') || readIntENV('BG_LOW'); - env.alarm_types = readENV('ALARM_TYPES') || (thresholdsSet ? "simple" : "predict"); + env.alarm_types = readENV('ALARM_TYPES') || (thresholdsSet ? 'simple' : 'predict'); //TODO: maybe get rid of ALARM_TYPES and only use enable? if (env.alarm_types.indexOf('simple') > -1) { @@ -223,8 +223,8 @@ function readENV(varName, defaultValue) { || process.env[varName] || process.env[varName.toLowerCase()]; - if (typeof value === 'string' && value.toLowerCase() == 'on') value = true; - if (typeof value === 'string' && value.toLowerCase() == 'off') value = false; + if (typeof value === 'string' && value.toLowerCase() == 'on') { value = true; } + if (typeof value === 'string' && value.toLowerCase() == 'off') { value = false; } return value != null ? value : defaultValue; } diff --git a/lib/api/devicestatus/index.js b/lib/api/devicestatus/index.js index 05980ffbe36..49e1ac36d8b 100644 --- a/lib/api/devicestatus/index.js +++ b/lib/api/devicestatus/index.js @@ -3,48 +3,49 @@ var consts = require('../../constants'); function configure (app, wares, ctx) { - var express = require('express'), - api = express.Router( ); - - // invoke common middleware - api.use(wares.sendJSONStatus); - // text body types get handled as raw buffer stream - api.use(wares.bodyParser.raw( )); - // json body types get handled as parsed json - api.use(wares.bodyParser.json( )); - // also support url-encoded content-type - api.use(wares.bodyParser.urlencoded({ extended: true })); - - // List settings available - api.get('/devicestatus/', function(req, res) { - var q = req.query; - if (!q.count) { - q.count = 10; - } - ctx.devicestatus.list(q, function (err, results) { - return res.json(results); - }); + var express = require('express'), + api = express.Router( ); + + // invoke common middleware + api.use(wares.sendJSONStatus); + // text body types get handled as raw buffer stream + api.use(wares.bodyParser.raw( )); + // json body types get handled as parsed json + api.use(wares.bodyParser.json( )); + // also support url-encoded content-type + api.use(wares.bodyParser.urlencoded({ extended: true })); + + // List settings available + api.get('/devicestatus/', function(req, res) { + var q = req.query; + if (!q.count) { + q.count = 10; + } + ctx.devicestatus.list(q, function (err, results) { + return res.json(results); }); + }); - function config_authed (app, api, wares, ctx) { + function config_authed (app, api, wares, ctx) { - api.post('/devicestatus/', /*TODO: auth disabled for quick UI testing... wares.verifyAuthorization, */ function(req, res) { - var obj = req.body; - ctx.devicestatus.create(obj, function (err, created) { - if (err) - res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', err); - else - res.json(created); - }); - }); + api.post('/devicestatus/', /*TODO: auth disabled for quick UI testing... wares.verifyAuthorization, */ function(req, res) { + var obj = req.body; + ctx.devicestatus.create(obj, function (err, created) { + if (err) { + res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', err); + } else { + res.json(created); + } + }); + }); - } + } - if (app.enabled('api') || true /*TODO: auth disabled for quick UI testing...*/) { - config_authed(app, api, wares, ctx); - } + if (app.enabled('api') || true /*TODO: auth disabled for quick UI testing...*/) { + config_authed(app, api, wares, ctx); + } - return api; + return api; } module.exports = configure; diff --git a/lib/api/index.js b/lib/api/index.js index 6e28946db46..c2552069b72 100644 --- a/lib/api/index.js +++ b/lib/api/index.js @@ -19,7 +19,7 @@ function create (env, ctx) { // Only allow access to the API if API_SECRET is set on the server. app.disable('api'); if (env.api_secret) { - console.log("API_SECRET", env.api_secret); + console.log('API_SECRET', env.api_secret); app.enable('api'); } @@ -28,7 +28,7 @@ function create (env, ctx) { app.extendedClientSettings = ctx.plugins && ctx.plugins.extendedClientSettings ? ctx.plugins.extendedClientSettings(env.extendedSettings) : {}; env.enable.toLowerCase().split(' ').forEach(function (value) { var enable = value.trim(); - console.info("enabling feature:", enable); + console.info('enabling feature:', enable); app.enable(enable); }); } diff --git a/lib/api/status.js b/lib/api/status.js index eca642ce0ab..8b1de9ed21d 100644 --- a/lib/api/status.js +++ b/lib/api/status.js @@ -9,7 +9,7 @@ function configure (app, wares) { 'json', 'svg', 'csv', 'txt', 'png', 'html', 'js' ])); // Status badge/text/json - api.get('/status', function (req, res, next) { + api.get('/status', function (req, res) { var info = { status: 'ok' , apiEnabled: app.enabled('api') , careportalEnabled: app.enabled('api') && app.enabled('careportal') @@ -34,13 +34,13 @@ function configure (app, wares) { res.redirect(302, badge + '.svg'); }, js: function ( ) { - var head = "this.serverSettings ="; + var head = 'this.serverSettings ='; var body = JSON.stringify(info); var tail = ';'; res.send([head, body, tail].join(' ')); }, text: function ( ) { - res.send("STATUS OK"); + res.send('STATUS OK'); }, json: function ( ) { res.json(info); diff --git a/lib/api/treatments/index.js b/lib/api/treatments/index.js index eb093d2b4cb..e1e3a1c29b2 100644 --- a/lib/api/treatments/index.js +++ b/lib/api/treatments/index.js @@ -3,44 +3,45 @@ var consts = require('../../constants'); function configure (app, wares, ctx) { - var express = require('express'), - api = express.Router( ); - - // invoke common middleware - api.use(wares.sendJSONStatus); - // text body types get handled as raw buffer stream - api.use(wares.bodyParser.raw( )); - // json body types get handled as parsed json - api.use(wares.bodyParser.json( )); - // also support url-encoded content-type - api.use(wares.bodyParser.urlencoded({ extended: true })); - - // List treatments available - api.get('/treatments/', function(req, res) { - ctx.treatments.list({find: req.params}, function (err, results) { - return res.json(results); - }); + var express = require('express'), + api = express.Router( ); + + // invoke common middleware + api.use(wares.sendJSONStatus); + // text body types get handled as raw buffer stream + api.use(wares.bodyParser.raw( )); + // json body types get handled as parsed json + api.use(wares.bodyParser.json( )); + // also support url-encoded content-type + api.use(wares.bodyParser.urlencoded({ extended: true })); + + // List treatments available + api.get('/treatments/', function(req, res) { + ctx.treatments.list({find: req.params}, function (err, results) { + return res.json(results); + }); + }); + + function config_authed (app, api, wares, ctx) { + + api.post('/treatments/', /*TODO: auth disabled for now, need to get login figured out... wares.verifyAuthorization, */ function(req, res) { + var treatment = req.body; + ctx.treatments.create(treatment, function (err, created) { + if (err) { + res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', err); + } else { + res.json(created); + } + }); }); - function config_authed (app, api, wares, ctx) { - - api.post('/treatments/', /*TODO: auth disabled for now, need to get login figured out... wares.verifyAuthorization, */ function(req, res) { - var treatment = req.body; - ctx.treatments.create(treatment, function (err, created) { - if (err) - res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', err); - else - res.json(created); - }); - }); - - } + } - if (app.enabled('api') && app.enabled('careportal')) { - config_authed(app, api, wares, ctx); - } + if (app.enabled('api') && app.enabled('careportal')) { + config_authed(app, api, wares, ctx); + } - return api; + return api; } module.exports = configure; diff --git a/lib/bootevent.js b/lib/bootevent.js index c842f9d5466..634710f7641 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -35,7 +35,7 @@ function boot (env) { } function ensureIndexes (ctx, next) { - console.info("Ensuring indexes"); + console.info('Ensuring indexes'); ctx.store.ensureIndexes(ctx.entries( ), ctx.entries.indexedFields); ctx.store.ensureIndexes(ctx.treatments( ), ctx.treatments.indexedFields); ctx.store.ensureIndexes(ctx.devicestatus( ), ctx.devicestatus.indexedFields); diff --git a/lib/bus.js b/lib/bus.js index 687f31cb6ee..107fe5d036f 100644 --- a/lib/bus.js +++ b/lib/bus.js @@ -25,7 +25,7 @@ function init (env, ctx) { } function ender ( ) { - if (id) cancelInterval(id); + if (id) { cancelInterval(id); } stream.emit('end'); } diff --git a/lib/data.js b/lib/data.js index cc7025a4d25..dedcfe4805f 100644 --- a/lib/data.js +++ b/lib/data.js @@ -2,7 +2,6 @@ var _ = require('lodash'); var async = require('async'); -var utils = require('./utils')(); var ObjectID = require('mongodb').ObjectID; function uniq(a) { @@ -71,7 +70,7 @@ function init(env, ctx) { async.parallel({ entries: function (callback) { - var q = {find: {"date": {"$gte": earliest_data}}}; + var q = {find: {date: {$gte: earliest_data}}}; ctx.entries.list(q, function (err, results) { if (!err && results) { var mbgs = []; @@ -99,7 +98,7 @@ function init(env, ctx) { }) }, cal: function (callback) { //FIXME: date $gte????? - var cq = {count: 1, find: {"type": "cal"}}; + var cq = {count: 1, find: {type: 'cal'}}; ctx.entries.list(cq, function (err, results) { if (!err && results) { var cals = []; @@ -115,7 +114,7 @@ function init(env, ctx) { callback(); }); }, treatments: function (callback) { - var tq = {find: {"created_at": {"$gte": new Date(treatment_earliest_data).toISOString()}}}; + var tq = {find: {created_at: {$gte: new Date(treatment_earliest_data).toISOString()}}}; ctx.treatments.list(tq, function (err, results) { if (!err && results) { var treatments = results.map(function (treatment) { @@ -140,7 +139,7 @@ function init(env, ctx) { if (!err && results) { // There should be only one document in the profile collection with a DIA. If there are multiple, use the last one. var profiles = []; - results.forEach(function (element, index, array) { + results.forEach(function (element) { if (element) { if (element.dia) { profiles[0] = element; @@ -173,7 +172,7 @@ function init(env, ctx) { var changesFound = false; // if there's no updates done so far, just return the full set - if (!oldData.sgvs) return newData; + if (!oldData.sgvs) { return newData; } function nsArrayDiff(oldArray, newArray) { var seen = {}; @@ -203,23 +202,25 @@ function init(env, ctx) { var compressibleArrays = ['sgvs', 'treatments', 'mbgs', 'cals']; for (var array in compressibleArrays) { - var a = compressibleArrays[array]; - if (newData.hasOwnProperty(a)) { + if (compressibleArrays.hasOwnProperty(array)) { + var a = compressibleArrays[array]; + if (newData.hasOwnProperty(a)) { + + // if previous data doesn't have the property (first time delta?), just assign data over + if (!oldData.hasOwnProperty(a)) { + delta[a] = newData[a]; + changesFound = true; + continue; + } - // if previous data doesn't have the property (first time delta?), just assign data over - if (!oldData.hasOwnProperty(a)) { - delta[a] = newData[a]; - changesFound = true; - continue; - } - - // Calculate delta and assign delta over if changes were found - var deltaData = nsArrayDiff(oldData[a], newData[a]); - if (deltaData.length > 0) { - console.log('delta changes found on', a); - changesFound = true; - sort(deltaData); - delta[a] = deltaData; + // Calculate delta and assign delta over if changes were found + var deltaData = nsArrayDiff(oldData[a], newData[a]); + if (deltaData.length > 0) { + console.log('delta changes found on', a); + changesFound = true; + sort(deltaData); + delta[a] = deltaData; + } } } } @@ -238,7 +239,7 @@ function init(env, ctx) { } } - if (changesFound) return delta; + if (changesFound) { return delta; } return newData; }; diff --git a/lib/devicestatus.js b/lib/devicestatus.js index ace6a6e3b32..1357138c1b7 100644 --- a/lib/devicestatus.js +++ b/lib/devicestatus.js @@ -2,39 +2,40 @@ function storage (collection, ctx) { - function create(obj, fn) { - if (! obj.hasOwnProperty("created_at")){ - obj.created_at = (new Date()).toISOString(); - } - api().insert(obj, function (err, doc) { - fn(null, doc); - }); - } - - function create_date_included(obj, fn) { - api().insert(obj, function (err, doc) { - fn(null, doc); - }); - - } - - function last(fn) { - return api().find({}).sort({created_at: -1}).limit(1).toArray(function (err, entries) { - if (entries && entries.length > 0) - fn(err, entries[0]); - else - fn(err, null); - }); - } - - function list(opts, fn) { - var q = opts && opts.find ? opts.find : { }; - return ctx.store.limit.call(api().find(q).sort({created_at: -1}), opts).toArray(fn); - } - - function api() { - return ctx.store.db.collection(collection); + function create(obj, fn) { + if (! obj.hasOwnProperty('created_at')){ + obj.created_at = (new Date()).toISOString(); } + api().insert(obj, function (err, doc) { + fn(null, doc); + }); + } + + function create_date_included(obj, fn) { + api().insert(obj, function (err, doc) { + fn(null, doc); + }); + + } + + function last(fn) { + return api().find({}).sort({created_at: -1}).limit(1).toArray(function (err, entries) { + if (entries && entries.length > 0) { + fn(err, entries[0]); + } else { + fn(err, null); + } + }); + } + + function list(opts, fn) { + var q = opts && opts.find ? opts.find : { }; + return ctx.store.limit.call(api().find(q).sort({created_at: -1}), opts).toArray(fn); + } + + function api() { + return ctx.store.db.collection(collection); + } api.list = list; diff --git a/lib/entries.js b/lib/entries.js index 07e336b91b8..8eba532180d 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -2,7 +2,6 @@ var es = require('event-stream'); var sgvdata = require('sgvdata'); -var units = require('./units')(); var ObjectID = require('mongodb').ObjectID; /**********\ @@ -86,7 +85,7 @@ function storage(env, ctx) { // receives entire list at end of stream function done (err, result) { // report any errors - if (err) return fn(err, result); + if (err) { return fn(err, result); } // batch insert a list of records create(result, fn); } @@ -127,15 +126,17 @@ function storage(env, ctx) { function getEntry(id, fn) { with_collection(function(err, collection) { - if (err) + if (err) { fn(err); - else - collection.findOne({"_id": ObjectID(id)}, function (err, entry) { - if (err) + } else { + collection.findOne({_id: ObjectID(id)}, function (err, entry) { + if (err) { fn(err); - else + } else { fn(null, entry); + } }); + } }); } diff --git a/lib/middleware/send-json-status.js b/lib/middleware/send-json-status.js index 974fdc70af0..fa1e7183d09 100644 --- a/lib/middleware/send-json-status.js +++ b/lib/middleware/send-json-status.js @@ -2,17 +2,16 @@ // Craft a JSON friendly status (or error) message. function sendJSONStatus(res, status, title, description, warning) { - var json = { - status: status, - message: title, - description: description - }; + var json = { + status: status, + message: title, + description: description + }; - // Add optional warning message. - if (warning) - json.warning = warning; + // Add optional warning message. + if (warning) { json.warning = warning; } - res.status(status).json(json); + res.status(status).json(json); } function configure ( ) { diff --git a/lib/mqtt.js b/lib/mqtt.js index daa4a86be6b..9ac47f9f6bb 100644 --- a/lib/mqtt.js +++ b/lib/mqtt.js @@ -7,34 +7,34 @@ var direction = require('sgvdata/lib/utils').direction; var mqtt = require('mqtt'); var moment = require('moment'); -function process(client) { - var stream = es.through( - function _write(data) { - this.push(data); - } - ); - return stream; +function process ( ) { + var stream = es.through( + function _write(data) { + this.push(data); + } + ); + return stream; } -function every(storage) { - function iter(item, next) { - storage.create(item, next); - } +function every (storage) { + function iter(item, next) { + storage.create(item, next); + } - return es.map(iter); + return es.map(iter); } -function downloader() { - var opts = { - model: decoders.models.G4Download - , json: function (o) { - return o; - } - , payload: function (o) { - return o; - } - }; - return decoders(opts); +function downloader () { + var opts = { + model: decoders.models.G4Download + , json: function (o) { + return o; + } + , payload: function (o) { + return o; + } + }; + return decoders(opts); } function toSGV (proto, vars) { @@ -71,9 +71,9 @@ function toTimestamp (proto, receiver_time, download_time) { var record_offset = receiver_time - proto.sys_timestamp_sec; var record_time = download_time.clone( ).subtract(record_offset, 'second'); var obj = { - device: 'dexcom' - , date: record_time.unix() * 1000 - , dateString: record_time.format( ) + device: 'dexcom' + , date: record_time.unix() * 1000 + , dateString: record_time.format( ) }; return obj; } @@ -121,7 +121,7 @@ function sgvSensorMerge(packet) { } else { console.info('mismatch or missing, sgv: ', sgv, ' sensor: ', sensor); //timestamps aren't close enough so add both - if (sgv) merged.push(sgv); + if (sgv) { merged.push(sgv); } //but the sensor will become and sgv now if (sensor) { sensor.type = 'sgv'; @@ -159,99 +159,99 @@ function iter_mqtt_record_stream (packet, prop, sync) { var stream = es.readArray(list || [ ]); var receiver_time = packet.receiver_system_time_sec; var download_time = moment(packet.download_timestamp); - function map(item, next) { - var timestamped = toTimestamp(item, receiver_time, download_time.clone( )); - var r = sync(item, timestamped); - if (!('type' in r)) { - r.type = prop; - } - console.log("ITEM", item, "TO", prop, r); - next(null, r); + function map(item, next) { + var timestamped = toTimestamp(item, receiver_time, download_time.clone( )); + var r = sync(item, timestamped); + if (!('type' in r)) { + r.type = prop; } - return stream.pipe(es.map(map)); + console.log('ITEM', item, 'TO', prop, r); + next(null, r); + } + return stream.pipe(es.map(map)); } function configure(env, ctx) { - var uri = env['MQTT_MONITOR']; - var opts = { - encoding: 'binary', - clean: false, - clientId: env.mqtt_client_id - }; - var client = mqtt.connect(uri, opts); - var downloads = downloader(); - client.subscribe('sgvs'); - client.subscribe('published'); - client.subscribe('/downloads/protobuf', {qos: 2}, granted); - client.subscribe('/uploader', granted); - client.subscribe('/entries/sgv', granted); - function granted() { - console.log('granted', arguments); - } + var uri = env['MQTT_MONITOR']; + var opts = { + encoding: 'binary', + clean: false, + clientId: env.mqtt_client_id + }; + var client = mqtt.connect(uri, opts); + var downloads = downloader(); + client.subscribe('sgvs'); + client.subscribe('published'); + client.subscribe('/downloads/protobuf', {qos: 2}, granted); + client.subscribe('/uploader', granted); + client.subscribe('/entries/sgv', granted); + function granted() { + console.log('granted', arguments); + } - client.on('message', function (topic, msg) { - console.log('topic', topic); - console.log(topic, 'on message', 'msg', msg.length); - switch (topic) { - case '/uploader': - console.log({type: topic, msg: msg.toString()}); - break; - case '/downloads/protobuf': - var b = new Buffer(msg, 'binary'); - console.log("BINARY", b.length, b.toString('hex')); - try { - var packet = downloads.parse(b); - if (!packet.type) { - packet.type = topic; - } - } catch (e) { - console.log("DID NOT PARSE", e); - break; - } - console.log('DOWNLOAD msg', msg.length, packet); - console.log('download SGV', packet.sgv[0]); - console.log('download_timestamp', packet.download_timestamp, new Date(Date.parse(packet.download_timestamp))); - console.log("WRITE TO MONGO"); - var download_timestamp = moment(packet.download_timestamp); - if (packet.download_status === 0) { - es.readArray(sgvSensorMerge(packet)).pipe(ctx.entries.persist(function empty(err, result) { - console.log("DONE WRITING MERGED SGV TO MONGO", err, result); - })); + client.on('message', function (topic, msg) { + console.log('topic', topic); + console.log(topic, 'on message', 'msg', msg.length); + switch (topic) { + case '/uploader': + console.log({type: topic, msg: msg.toString()}); + break; + case '/downloads/protobuf': + var b = new Buffer(msg, 'binary'); + console.log('BINARY', b.length, b.toString('hex')); + try { + var packet = downloads.parse(b); + if (!packet.type) { + packet.type = topic; + } + } catch (e) { + console.log('DID NOT PARSE', e); + break; + } + console.log('DOWNLOAD msg', msg.length, packet); + console.log('download SGV', packet.sgv[0]); + console.log('download_timestamp', packet.download_timestamp, new Date(Date.parse(packet.download_timestamp))); + console.log('WRITE TO MONGO'); + var download_timestamp = moment(packet.download_timestamp); + if (packet.download_status === 0) { + es.readArray(sgvSensorMerge(packet)).pipe(ctx.entries.persist(function empty(err, result) { + console.log('DONE WRITING MERGED SGV TO MONGO', err, result); + })); - iter_mqtt_record_stream(packet, 'cal', toCal) - .pipe(ctx.entries.persist(function empty(err, result) { - console.log("DONE WRITING Cal TO MONGO", err, result.length); - })); - iter_mqtt_record_stream(packet, 'meter', toMeter) - .pipe(ctx.entries.persist(function empty(err, result) { - console.log("DONE WRITING Meter TO MONGO", err, result.length); - })); - } - packet.type = "download"; - ctx.devicestatus.create({ - uploaderBattery: packet.uploader_battery, - created_at: download_timestamp.toISOString() - }, function empty(err, result) { - console.log("DONE WRITING TO MONGO devicestatus ", result, err); - }); + iter_mqtt_record_stream(packet, 'cal', toCal) + .pipe(ctx.entries.persist(function empty(err, result) { + console.log('DONE WRITING Cal TO MONGO', err, result.length); + })); + iter_mqtt_record_stream(packet, 'meter', toMeter) + .pipe(ctx.entries.persist(function empty(err, result) { + console.log('DONE WRITING Meter TO MONGO', err, result.length); + })); + } + packet.type = 'download'; + ctx.devicestatus.create({ + uploaderBattery: packet.uploader_battery, + created_at: download_timestamp.toISOString() + }, function empty(err, result) { + console.log('DONE WRITING TO MONGO devicestatus ', result, err); + }); - ctx.entries.create([ packet ], function empty(err, res) { - console.log("Download written to mongo: ", packet) - }); + ctx.entries.create([ packet ], function empty(err, res) { + console.log('Download written to mongo: ', packet) + }); - // ctx.entries.write(packet); - break; - default: - console.log(topic, 'on message', 'msg', msg); - // ctx.entries.write(msg); - break; - } - }); - client.entries = process(client); - client.every = every; - return client; + // ctx.entries.write(packet); + break; + default: + console.log(topic, 'on message', 'msg', msg); + // ctx.entries.write(msg); + break; + } + }); + client.entries = process(client); + client.every = every; + return client; } //expose for tests that don't need to connect diff --git a/lib/notifications.js b/lib/notifications.js index 0e951cc496f..18e141c621e 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -114,7 +114,7 @@ function init (env, ctx) { }; notifications.snoozedBy = function snoozedBy (notify) { - if (_.isEmpty(requests.snoozes)) return false; + if (_.isEmpty(requests.snoozes)) { return false; } var byLevel = _.filter(requests.snoozes, function checkSnooze (snooze) { return snooze.level >= notify.level; diff --git a/lib/pebble.js b/lib/pebble.js index b13280a9a52..6dce18ad4a3 100644 --- a/lib/pebble.js +++ b/lib/pebble.js @@ -13,10 +13,10 @@ var DIRECTIONS = { , 'RATE OUT OF RANGE': 9 }; -var iob = require("./plugins/iob")(); +var iob = require('./plugins/iob')(); var async = require('async'); var units = require('./units')(); -var profileObject = require("./profilefunctions")(); +var profileObject = require('./profilefunctions')(); function directionToTrend (direction) { var trend = 8; @@ -47,7 +47,7 @@ function pebble (req, res) { //for compatibility we're keeping battery and iob here, but they would be better somewhere else if (sgvData.length > 0) { - sgvData[0].battery = uploaderBattery ? "" + uploaderBattery : undefined; + sgvData[0].battery = uploaderBattery ? '' + uploaderBattery : undefined; if (req.iob) { sgvData[0].iob = iob.calcTotal(treatmentResults.slice(0, 20), profileResult, new Date(now)).display; } @@ -67,7 +67,7 @@ function pebble (req, res) { if (!err && value) { uploaderBattery = value.uploaderBattery; } else { - console.error("req.devicestatus.tail", err); + console.error('req.devicestatus.tail', err); } callback(); }); @@ -89,7 +89,7 @@ function pebble (req, res) { } }); } else { - console.error("pebble profile error", arguments); + console.error('pebble profile error', arguments); } callback(); }); @@ -109,7 +109,7 @@ function pebble (req, res) { } }); } else { - console.error("pebble cal error", arguments); + console.error('pebble cal error', arguments); } callback(); }); @@ -118,7 +118,7 @@ function pebble (req, res) { } } , entries: function(callback) { - var q = { count: req.count + 1, find: { "sgv": { $exists: true }} }; + var q = { count: req.count + 1, find: {sgv: { $exists: true }} }; req.ctx.entries.list(q, function(err, results) { if (!err && results) { @@ -151,7 +151,7 @@ function pebble (req, res) { } }); } else { - console.error("pebble entries error", arguments); + console.error('pebble entries error', arguments); } callback(); }); @@ -162,7 +162,7 @@ function pebble (req, res) { function loadTreatments(req, earliest_data, fn) { if (req.iob) { - var q = { find: {"created_at": {"$gte": new Date(earliest_data).toISOString()}} }; + var q = { find: {created_at: {$gte: new Date(earliest_data).toISOString()}} }; req.ctx.treatments.list(q, fn); } else { fn(null, []); diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index dae234d15ce..3ee91c1aed4 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -65,11 +65,11 @@ function init() { if (max > sbx.scaleBg(sbx.thresholds.bg_target_top)) { rangeLabel = 'HIGH'; eventName = 'high'; - if (!result.pushoverSound) result.pushoverSound = 'climb'; + if (!result.pushoverSound) { result.pushoverSound = 'climb'; } } else if (min < sbx.scaleBg(sbx.thresholds.bg_target_bottom)) { rangeLabel = 'LOW'; eventName = 'low'; - if (!result.pushoverSound) result.pushoverSound = 'falling'; + if (!result.pushoverSound) { result.pushoverSound = 'falling'; } } else { rangeLabel = 'Check BG'; } diff --git a/lib/plugins/basalprofile.js b/lib/plugins/basalprofile.js index 2d0851becbd..9919b153d52 100644 --- a/lib/plugins/basalprofile.js +++ b/lib/plugins/basalprofile.js @@ -1,7 +1,5 @@ 'use strict'; -var _ = require('lodash'); - function init() { function basal() { @@ -13,7 +11,7 @@ function init() { function hasRequiredInfo (sbx) { - if (!sbx.data.profile) return false; + if (!sbx.data.profile) { return false; } if (!sbx.data.profile.hasData()) { console.warn('For the Basal plugin to function you need a treatment profile'); diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 9d3210a4598..20cd97a4929 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -2,9 +2,7 @@ var _ = require('lodash'); - var TEN_MINS = 10 * 60 * 1000; -var FIFTEEN_MINS = 15 * 60 * 1000; function init() { @@ -17,7 +15,7 @@ function init() { function hasRequiredInfo (sbx) { - if (!sbx.data.profile) return false; + if (!sbx.data.profile) { return false; } if (!sbx.data.profile.hasData()) { console.warn('For the BolusWizardPreview plugin to function you need a treatment profile'); @@ -57,9 +55,9 @@ function init() { bwp.checkNotifications = function checkNotifications (sbx) { var results = sbx.properties.bwp; - if (results == undefined) return; + if (results == undefined) { return; } - if (results.lastSGV < sbx.data.profile.getHighBGTarget(sbx.time)) return; + if (results.lastSGV < sbx.data.profile.getHighBGTarget(sbx.time)) { return; } var snoozeBWP = Number(sbx.extendedSettings.snooze) || 0.10; var warnBWP = Number(sbx.extendedSettings.warn) || 0.50; @@ -121,7 +119,7 @@ function init() { bwp.updateVisualisation = function updateVisualisation (sbx) { var results = sbx.properties.bwp; - if (results == undefined) return; + if (results == undefined) { return; } // display text var info = [ diff --git a/lib/plugins/cannulaage.js b/lib/plugins/cannulaage.js index 50182984ccd..58e6eab291e 100644 --- a/lib/plugins/cannulaage.js +++ b/lib/plugins/cannulaage.js @@ -38,7 +38,7 @@ function init() { }); var info = [{label: 'Inserted:', value: moment(treatmentDate).format('lll')}]; - if (message != '') info.push({label: 'Notes:', value: message}); + if (message != '') { info.push({label: 'Notes:', value: message}); } sbx.pluginBase.updatePillText(cage, { value: age + 'h' diff --git a/lib/plugins/cob.js b/lib/plugins/cob.js index 8969d2e1246..599f9d60cad 100644 --- a/lib/plugins/cob.js +++ b/lib/plugins/cob.js @@ -35,7 +35,11 @@ function init() { var liverSensRatio = 1; var totalCOB = 0; var lastCarbs = null; - if (!treatments) return {}; + + if (!treatments) { + return {}; + } + if (typeof time === 'undefined') { time = new Date(); } @@ -147,7 +151,7 @@ function init() { var prop = sbx.properties.cob; - if (prop == undefined || prop.cob == undefined) return; + if (prop == undefined || prop.cob == undefined) { return; } var displayCob = Math.round(prop.cob * 10) / 10; diff --git a/lib/plugins/iob.js b/lib/plugins/iob.js index 6c571cf3fe5..5c099bcf848 100644 --- a/lib/plugins/iob.js +++ b/lib/plugins/iob.js @@ -24,7 +24,7 @@ function init() { var totalIOB = 0 , totalActivity = 0; - if (!treatments) return {}; + if (!treatments) { return {}; } if (time === undefined) { time = new Date(); @@ -38,8 +38,8 @@ function init() { if (tIOB.iobContrib > 0) { lastBolus = treatment; } - if (tIOB && tIOB.iobContrib) totalIOB += tIOB.iobContrib; - if (tIOB && tIOB.activityContrib) totalActivity += tIOB.activityContrib; + if (tIOB && tIOB.iobContrib) { totalIOB += tIOB.iobContrib; } + if (tIOB && tIOB.activityContrib) { totalActivity += tIOB.activityContrib; } } }); diff --git a/lib/plugins/pluginbase.js b/lib/plugins/pluginbase.js index 18800551b8a..3f4984ced6d 100644 --- a/lib/plugins/pluginbase.js +++ b/lib/plugins/pluginbase.js @@ -21,7 +21,7 @@ function init (majorPills, minorPills, statusPills, bgStatus, tooltip) { container = minorPills } - var pillName = "span.pill." + plugin.name; + var pillName = 'span.pill.' + plugin.name; var pill = container.find(pillName); var classes = 'pill ' + plugin.name; diff --git a/lib/plugins/treatmentnotify.js b/lib/plugins/treatmentnotify.js index c68346b152a..aa6ad0694dc 100644 --- a/lib/plugins/treatmentnotify.js +++ b/lib/plugins/treatmentnotify.js @@ -33,8 +33,8 @@ function init() { autoSnoozeAlarms(sbx); //and add some info notifications //the notification providers (push, websockets, etc) are responsible to not sending the same notifications repeatedly - if (mbgCurrent) requestMBGNotify(lastMBG, sbx); - if (treatmentCurrent) requestTreatmentNotify(lastTreatment, sbx); + if (mbgCurrent) { requestMBGNotify(lastMBG, sbx); } + if (treatmentCurrent) { requestTreatmentNotify(lastTreatment, sbx); } } }; diff --git a/lib/profilefunctions.js b/lib/profilefunctions.js index 8167bb309e3..aa1e1ab31ec 100644 --- a/lib/profilefunctions.js +++ b/lib/profilefunctions.js @@ -10,18 +10,17 @@ function init(profileData) { } profile.loadData = function loadData(profileData) { - profile.data = _.cloneDeep(profileData); - profile.preprocessProfileOnLoad(profile.data[0]); - } + profile.data = _.cloneDeep(profileData); + profile.preprocessProfileOnLoad(profile.data[0]); + }; profile.timeStringToSeconds = function timeStringToSeconds(time) { - var split = time.split(":"); + var split = time.split(':'); return parseInt(split[0])*3600 + parseInt(split[1])*60; - } + }; // preprocess the timestamps to seconds for a couple orders of magnitude faster operation - profile.preprocessProfileOnLoad = function preprocessProfileOnLoad(container) - { + profile.preprocessProfileOnLoad = function preprocessProfileOnLoad(container) { for (var key in container) { var value = container[key]; @@ -31,17 +30,16 @@ function init(profileData) { if (value.time) { var sec = profile.timeStringToSeconds(value.time); - if (!isNaN(sec)) value.timeAsSeconds = sec; + if (!isNaN(sec)) { value.timeAsSeconds = sec; } } } - } + }; - if (profileData) profile.loadData(profileData); + if (profileData) { profile.loadData(profileData); } - profile.getValueByTime = function getValueByTime(time, valueContainer) - { - if (!time) time = new Date(); + profile.getValueByTime = function getValueByTime (time, valueContainer) { + if (!time) { time = new Date(); } // If the container is an Array, assume it's a valid timestamped value container diff --git a/lib/pushnotify.js b/lib/pushnotify.js index 32bcf4d2354..6546dc592e1 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -2,7 +2,6 @@ var _ = require('lodash'); var crypto = require('crypto'); -var units = require('./units')(); var NodeCache = require('node-cache'); function init(env, ctx) { @@ -53,7 +52,7 @@ function init(env, ctx) { }; pushnotify.pushoverAck = function pushoverAck (response) { - if (!response.receipt) return false; + if (!response.receipt) { return false; } var notify = receipts.get(response.receipt); console.info('push ack, response: ', response, ', notify: ', notify); @@ -67,7 +66,7 @@ function init(env, ctx) { var receiptKeys = receipts.keys(); _.forEach(receiptKeys, function eachKey (receipt) { - ctx.pushover.cancelWithReceipt(receipt, function cancelCallback (err, response) { + ctx.pushover.cancelWithReceipt(receipt, function cancelCallback (err) { if (err) { console.error('error canceling receipt, err: ', err); } else { diff --git a/lib/sandbox.js b/lib/sandbox.js index c62586a8440..0aa57eb2a08 100644 --- a/lib/sandbox.js +++ b/lib/sandbox.js @@ -2,7 +2,6 @@ var _ = require('lodash'); var units = require('./units')(); -var utils = require('./utils'); var profile = require('./profilefunctions')(); function init ( ) { @@ -140,7 +139,9 @@ function init ( ) { function roundInsulinForDisplayFormat (insulin) { - if (insulin == 0) return '0'; + if (insulin == 0) { + return '0'; + } if (sbx.properties.roundingStyle == 'medtronic') { var denominator = 0.1; @@ -161,8 +162,7 @@ function init ( ) { } function unitsLabel ( ) { - if (sbx.units == 'mmol') return 'mmol/L'; - return 'mg/dl'; + return sbx.units == 'mmol' ? 'mmol/L' : 'mg/dl'; } function roundBGToDisplayFormat (bg) { diff --git a/lib/storage.js b/lib/storage.js index 77c2fa33a30..30bfd60b0b1 100644 --- a/lib/storage.js +++ b/lib/storage.js @@ -37,8 +37,9 @@ function init (env, cb, forceNewConnection) { mongo.db = connection; // If there is a valid callback, then invoke the function to perform the callback - if (cb && cb.call) + if (cb && cb.call) { cb(err, mongo); + } }); } } diff --git a/lib/treatments.js b/lib/treatments.js index 98821fd858a..ff9c3c989d0 100644 --- a/lib/treatments.js +++ b/lib/treatments.js @@ -23,10 +23,10 @@ function storage (env, ctx) { // clean data delete obj.eventTime; - if (!obj.carbs) delete obj.carbs; - if (!obj.insulin) delete obj.insulin; - if (!obj.notes) delete obj.notes; - if (!obj.preBolus || obj.preBolus == 0) delete obj.preBolus; + if (!obj.carbs) { delete obj.carbs; } + if (!obj.insulin) { delete obj.insulin; } + if (!obj.notes) { delete obj.notes; } + if (!obj.preBolus || obj.preBolus == 0) { delete obj.preBolus; } if (!obj.glucose) { delete obj.glucose; delete obj.glucoseType; diff --git a/lib/units.js b/lib/units.js index 031685f2280..0a86f9db06f 100644 --- a/lib/units.js +++ b/lib/units.js @@ -1,3 +1,5 @@ +'use strict'; + function mgdlToMMOL(mgdl) { return (Math.round((mgdl / 18) * 10) / 10).toFixed(1); } diff --git a/lib/utils.js b/lib/utils.js index 35761d80042..328c492b71b 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -25,15 +25,25 @@ function init() { , offset = time == -1 ? -1 : (now - time) / 1000 , parts = {}; - if (offset < MINUTE_IN_SECS * -5) parts = { value: 'in the future' }; - else if (offset == -1) parts = { label: 'time ago' }; - else if (offset <= MINUTE_IN_SECS * 2) parts = { value: 1, label: 'min ago' }; - else if (offset < (MINUTE_IN_SECS * 60)) parts = { value: Math.round(Math.abs(offset / MINUTE_IN_SECS)), label: 'mins ago' }; - else if (offset < (HOUR_IN_SECS * 2)) parts = { value: 1, label: 'hr ago' }; - else if (offset < (HOUR_IN_SECS * 24)) parts = { value: Math.round(Math.abs(offset / HOUR_IN_SECS)), label: 'hrs ago' }; - else if (offset < DAY_IN_SECS) parts = { value: 1, label: 'day ago' }; - else if (offset <= (DAY_IN_SECS * 7)) parts = { value: Math.round(Math.abs(offset / DAY_IN_SECS)), label: 'day ago' }; - else parts = { value: 'long ago' }; + if (offset < MINUTE_IN_SECS * -5) { + parts = { value: 'in the future' }; + } else if (offset == -1) { + parts = { label: 'time ago' }; + } else if (offset <= MINUTE_IN_SECS * 2) { + parts = { value: 1, label: 'min ago' }; + } else if (offset < (MINUTE_IN_SECS * 60)) { + parts = { value: Math.round(Math.abs(offset / MINUTE_IN_SECS)), label: 'mins ago' }; + } else if (offset < (HOUR_IN_SECS * 2)) { + parts = { value: 1, label: 'hr ago' }; + } else if (offset < (HOUR_IN_SECS * 24)) { + parts = { value: Math.round(Math.abs(offset / HOUR_IN_SECS)), label: 'hrs ago' }; + } else if (offset < DAY_IN_SECS) { + parts = { value: 1, label: 'day ago' }; + } else if (offset <= (DAY_IN_SECS * 7)) { + parts = { value: Math.round(Math.abs(offset / DAY_IN_SECS)), label: 'day ago' }; + } else { + parts = { value: 'long ago' }; + } if (offset > DAY_IN_SECS * 7) { parts.status = 'warn'; diff --git a/lib/websocket.js b/lib/websocket.js index 0c1e116b4a6..887dae94a5a 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -1,8 +1,5 @@ 'use strict'; -var _ = require('lodash'); -var utils = require('./utils')(); - function init (env, ctx, server) { function websocket ( ) { @@ -56,7 +53,7 @@ function init (env, ctx, server) { var delta = ctx.data.calculateDelta(lastData); if (delta.delta) { console.log('lastData full size', JSON.stringify(lastData).length,'bytes'); - if (delta.sgvs) console.log('patientData update size', JSON.stringify(delta).length,'bytes'); + if (delta.sgvs) { console.log('patientData update size', JSON.stringify(delta).length,'bytes'); } emitData(delta); } else { console.log('delta calculation indicates no new data is present'); } } diff --git a/static/js/client.js b/static/js/client.js index 42503eb9479..1ecffededfc 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -275,7 +275,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } function inRetroMode() { - if (!brush) return false; + if (!brush) { + return false; + } var time = brush.extent()[1].getTime(); @@ -470,8 +472,11 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var dotRadius = function(type) { var radius = prevChartWidth > WIDTH_BIG_DOTS ? 4 : (prevChartWidth < WIDTH_SMALL_DOTS ? 2 : 3); - if (type == 'mbg') radius *= 2; - else if (type == 'rawbg') radius = Math.min(2, radius - 1); + if (type == 'mbg') { + radius *= 2; + } else if (type == 'rawbg') { + radius = Math.min(2, radius - 1); + } return radius / focusRangeAdjustment; }; @@ -493,7 +498,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; }) .attr('fill', function (d) { return d.color; }) .attr('opacity', function (d) { return futureOpacity(d.date.getTime() - latestSGV.x); }) - .attr('stroke-width', function (d) { if (d.type == 'mbg') return 2; else return 0; }) + .attr('stroke-width', function (d) { return d.type == 'mbg' ? 2 : 0; }) .attr('stroke', function (d) { return (isDexcom(d.device) ? 'white' : '#0099ff'); }) @@ -512,33 +517,34 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; // if new circle then just display prepareFocusCircles(focusCircles.enter().append('circle')) .on('mouseover', function (d) { - if (d.type != 'sgv' && d.type != 'mbg') return; - - var bgType = (d.type == 'sgv' ? 'CGM' : (isDexcom(d.device) ? 'Calibration' : 'Meter')) - , rawbgValue = 0 - , noiseLabel = ''; - - if (d.type == 'sgv') { - if (rawbg.showRawBGs(d.y, d.noise, cal, sbx)) { - rawbgValue = scaleBg(rawbg.calc(d, cal, sbx)); + if (d.type === 'sgv' || d.type === 'mbg') { + var bgType = (d.type === 'sgv' ? 'CGM' : (isDexcom(d.device) ? 'Calibration' : 'Meter')) + , rawbgValue = 0 + , noiseLabel = ''; + + if (d.type === 'sgv') { + if (rawbg.showRawBGs(d.y, d.noise, cal, sbx)) { + rawbgValue = scaleBg(rawbg.calc(d, cal, sbx)); + } + noiseLabel = rawbg.noiseCodeToDisplay(d.y, d.noise); } - noiseLabel = rawbg.noiseCodeToDisplay(d.y, d.noise); - } - tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); - tooltip.html('' + bgType + ' BG: ' + d.sgv + - (d.type == 'mbg' ? '
    Device: ' + d.device : '') + - (rawbgValue ? '
    Raw BG: ' + rawbgValue : '') + - (noiseLabel ? '
    Noise: ' + noiseLabel : '') + - '
    Time: ' + formatTime(d.date)) - .style('left', (d3.event.pageX) + 'px') - .style('top', (d3.event.pageY + 15) + 'px'); + tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); + tooltip.html('' + bgType + ' BG: ' + d.sgv + + (d.type == 'mbg' ? '
    Device: ' + d.device : '') + + (rawbgValue ? '
    Raw BG: ' + rawbgValue : '') + + (noiseLabel ? '
    Noise: ' + noiseLabel : '') + + '
    Time: ' + formatTime(d.date)) + .style('left', (d3.event.pageX) + 'px') + .style('top', (d3.event.pageY + 15) + 'px'); + } }) .on('mouseout', function (d) { - if (d.type != 'sgv' && d.type != 'mbg') return; - tooltip.transition() - .duration(TOOLTIP_TRANS_MS) - .style('opacity', 0); + if (d.type === 'sgv' || d.type === 'mbg') { + tooltip.transition() + .duration(TOOLTIP_TRANS_MS) + .style('opacity', 0); + } }); focusCircles.exit() @@ -963,9 +969,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; }) .attr('fill', function (d) { return d.color; }) .style('opacity', function (d) { return highlightBrushPoints(d) }) - .attr('stroke-width', function (d) {if (d.type == 'mbg') return 2; else return 0; }) - .attr('stroke', function (d) { return 'white'; }) - .attr('r', function(d) { if (d.type == 'mbg') return 4; else return 2;}); + .attr('stroke-width', function (d) { return d.type == 'mbg' ? 2 : 0; }) + .attr('stroke', function ( ) { return 'white'; }) + .attr('r', function (d) { return d.type == 'mbg' ? 4 : 2; }); if (badData.length > 0) { console.warn("Bad Data: isNaN(sgv)", badData); @@ -1138,10 +1144,10 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// function drawTreatment(treatment, scale, showValues) { - if (!treatment.carbs && !treatment.insulin) return; + if (!treatment.carbs && !treatment.insulin) { return; } // don't render the treatment if it's not visible - if (Math.abs(xScale(treatment.created_at.getTime())) > window.innerWidth) return; + if (Math.abs(xScale(treatment.created_at.getTime())) > window.innerWidth) { return; } var CR = treatment.CR || 20; var carbs = treatment.carbs || CR; diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js index 5667070df51..f33182d432e 100644 --- a/static/js/ui-utils.js +++ b/static/js/ui-utils.js @@ -155,7 +155,7 @@ function closeDrawer(id, callback) { $("html, body").animate({ scrollTop: 0 }); $(id).animate({right: '-300px'}, 300, function () { $(id).css('display', 'none'); - if (callback) callback(); + if (callback) { callback(); } }); } @@ -173,7 +173,7 @@ function toggleDrawer(id, openCallback, closeCallback) { closeOpenDraw(function () { openDraw = id; $(id).css('display', 'block').animate({right: '0'}, 300, function () { - if (callback) callback(); + if (callback) { callback(); } }); }); @@ -205,8 +205,8 @@ function currentTime() { var hours = now.getHours(); var minutes = now.getMinutes(); - if (hours<10) hours = '0' + hours; - if (minutes<10) minutes = '0' + minutes; + if (hours < 10) { hours = '0' + hours; } + if (minutes < 10) { minutes = '0' + minutes; } return ''+ hours + ':' + minutes; } diff --git a/testing/convert-treatments.js b/testing/convert-treatments.js index 1ef65786be9..3c3cbf17f6e 100644 --- a/testing/convert-treatments.js +++ b/testing/convert-treatments.js @@ -1,16 +1,16 @@ db.treatments.find().forEach( - function (elem) { - db.treatments.update( - { - _id: elem._id - }, - { - $set: { - glucose: elem.glucoseValue, - insulin: elem.insulinGiven, - carbs: elem.carbsGiven - } - } - ); - } + function (elem) { + db.treatments.update( + { + _id: elem._id + }, + { + $set: { + glucose: elem.glucoseValue, + insulin: elem.insulinGiven, + carbs: elem.carbsGiven + } + } + ); + } ); diff --git a/testing/make_high_data.js b/testing/make_high_data.js index 8cbf1917859..80f48d434e3 100644 --- a/testing/make_high_data.js +++ b/testing/make_high_data.js @@ -1,6 +1,6 @@ var fs = require('fs'); -var data = "" +var data = '' var END_TIME = Date.now(); var FIVE_MINS_IN_MS = 300000; var TIME_PERIOD_HRS = 24; @@ -10,17 +10,17 @@ var currentBG = START_BG; var currentTime = END_TIME - (TIME_PERIOD_HRS * DATA_PER_HR * FIVE_MINS_IN_MS); for(var i = 0; i < TIME_PERIOD_HRS * DATA_PER_HR; i++) { - currentBG += Math.ceil(Math.cos(i)*5+.2); - currentTime += FIVE_MINS_IN_MS; - data += "1," + currentBG + ",,,,,,,,," + new Date(currentTime).toString() + ",,,,\n"; + currentBG += Math.ceil(Math.cos(i)*5+.2); + currentTime += FIVE_MINS_IN_MS; + data += '1,' + currentBG + ',,,,,,,,,' + new Date(currentTime).toString() + ',,,,\n'; } -fs.writeFile("../Dexcom.csv", data); +fs.writeFile('../Dexcom.csv', data); function makedata() { - currentBG -= 1; - currentTime += FIVE_MINS_IN_MS; - data += "1," + currentBG + ",,,,,,,,," + new Date(currentTime).toString() + ",,,,\n"; - fs.writeFile("../Dexcom.csv", data); + currentBG -= 1; + currentTime += FIVE_MINS_IN_MS; + data += '1,' + currentBG + ',,,,,,,,,' + new Date(currentTime).toString() + ',,,,\n'; + fs.writeFile('../Dexcom.csv', data); } setInterval(makedata, 1000 * 10) \ No newline at end of file diff --git a/testing/populate.js b/testing/populate.js index 67d133d7599..61bbadf0342 100644 --- a/testing/populate.js +++ b/testing/populate.js @@ -1,7 +1,6 @@ 'use strict'; var mongodb = require('mongodb'); -var software = require('./../package.json'); var env = require('./../env')(); var util = require('./helpers/util'); @@ -9,26 +8,26 @@ var util = require('./helpers/util'); main(); function main() { - var MongoClient = mongodb.MongoClient; - MongoClient.connect(env.mongo, function connected(err, db) { + var MongoClient = mongodb.MongoClient; + MongoClient.connect(env.mongo, function connected(err, db) { - console.log("Connecting to mongo..."); - if (err) { - console.log("Error occurred: ", err); - throw err; - } - populate_collection(db); - }); + console.log('Connecting to mongo...'); + if (err) { + console.log('Error occurred: ', err); + throw err; + } + populate_collection(db); + }); } function populate_collection(db) { - var cgm_collection = db.collection(env.mongo_collection); - var new_cgm_record = util.get_cgm_record(); + var cgm_collection = db.collection(env.mongo_collection); + var new_cgm_record = util.get_cgm_record(); - cgm_collection.insert(new_cgm_record, function (err, created) { - if (err) { - throw err; - } - process.exit(0); - }); + cgm_collection.insert(new_cgm_record, function (err) { + if (err) { + throw err; + } + process.exit(0); + }); } diff --git a/testing/populate_rest.js b/testing/populate_rest.js index fc8e614d7c9..703e3ea124a 100644 --- a/testing/populate_rest.js +++ b/testing/populate_rest.js @@ -1,6 +1,5 @@ 'use strict'; -var software = require('./../package.json'); var env = require('./../env')(); var http = require('http'); var util = require('./util'); @@ -8,34 +7,34 @@ var util = require('./util'); main(); function main() { - send_entry_rest(); + send_entry_rest(); } function send_entry_rest() { - var new_cgm_record = util.get_cgm_record(); - var new_cgm_record_string = JSON.stringify(new_cgm_record); - - var options = { - host: 'localhost', - port: env.PORT, - path: '/api/v1/entries/', - method: 'POST', - headers: { - 'api-secret' : env.api_secret, - 'Content-Type': 'application/json', - 'Content-Length': new_cgm_record_string.length - } - }; - - var req = http.request(options, function(res) { - console.log("Ok: ", res.statusCode); - }); - - req.on('error', function(e) { - console.error('error'); - console.error(e); - }); - - req.write(new_cgm_record_string); - req.end(); + var new_cgm_record = util.get_cgm_record(); + var new_cgm_record_string = JSON.stringify(new_cgm_record); + + var options = { + host: 'localhost', + port: env.PORT, + path: '/api/v1/entries/', + method: 'POST', + headers: { + 'api-secret' : env.api_secret, + 'Content-Type': 'application/json', + 'Content-Length': new_cgm_record_string.length + } + }; + + var req = http.request(options, function(res) { + console.log("Ok: ", res.statusCode); + }); + + req.on('error', function(e) { + console.error('error'); + console.error(e); + }); + + req.write(new_cgm_record_string); + req.end(); } \ No newline at end of file diff --git a/testing/util.js b/testing/util.js index b434fc627a3..25348e1566a 100644 --- a/testing/util.js +++ b/testing/util.js @@ -1,63 +1,72 @@ 'use strict'; exports.get_cgm_record = function() { - var dateobj = new Date(); - var datemil = dateobj.getTime(); - var datesec = datemil / 1000; - var datestr = getDateString(dateobj); - - // We put the time in a range from -1 to +1 for every thiry minute period - var range = (datesec % 1800) / 900 - 1.0; - - // The we push through a COS function and scale between 40 and 400 (so it is like a bg level) - var sgv = Math.floor(360 * (Math.cos(10.0 * range / 3.14) / 2 + 0.5)) + 40; - var dir = range > 0.0 ? "FortyFiveDown" : "FortyFiveUp"; - - console.log('Writing Record: '); - console.log('sgv = ' + sgv); - console.log('date = ' + datemil); - console.log('dir = ' + dir); - console.log('str = ' + datestr); - - return { - 'device': 'dexcom', - 'date': datemil, - 'sgv': sgv, - 'direction': dir, - 'dateString': datestr - }; -} + var dateobj = new Date(); + var datemil = dateobj.getTime(); + var datesec = datemil / 1000; + var datestr = getDateString(dateobj); + // We put the time in a range from -1 to +1 for every thiry minute period + var range = (datesec % 1800) / 900 - 1.0; + + // The we push through a COS function and scale between 40 and 400 (so it is like a bg level) + var sgv = Math.floor(360 * (Math.cos(10.0 * range / 3.14) / 2 + 0.5)) + 40; + var dir = range > 0.0 ? 'FortyFiveDown' : 'FortyFiveUp'; + + console.log('Writing Record: '); + console.log('sgv = ' + sgv); + console.log('date = ' + datemil); + console.log('dir = ' + dir); + console.log('str = ' + datestr); + + return { + 'device': 'dexcom', + 'date': datemil, + 'sgv': sgv, + 'direction': dir, + 'dateString': datestr + }; +}; + +//TODO: use moment function getDateString(d) { - // How I wish js had strftime. This would be one line of code! + // How I wish js had strftime. This would be one line of code! + + var month = d.getMonth(); + var day = d.getDay(); + var year = d.getFullYear(); + + if (month < 10) { month = '0' + month; } + if (day < 10) { day = '0' + day; } - var month = d.getMonth(); - var day = d.getDay(); - var year = d.getFullYear(); + var hour = d.getHours(); + var min = d.getMinutes(); + var sec = d.getSeconds(); - if (month < 10) month = '0' + month; - if (day < 10) day = '0' + day; + var ampm = 'PM'; + if (hour < 12) { + ampm = 'AM'; + } - var hour = d.getHours(); - var min = d.getMinutes(); - var sec = d.getSeconds(); + if (hour == 0) { + hour = 12; + } + if (hour > 12) { + hour = hour - 12; + } - var ampm = 'PM'; - if (hour < 12) { - ampm = "AM"; - } + if (hour < 10) { + hour = '0' + hour; + } - if (hour == 0) { - hour = 12; - } - if (hour > 12) { - hour = hour - 12; - } + if (min < 10) { + min = '0' + min; + } - if (hour < 10) hour = '0' + hour; - if (min < 10) min = '0' + min; - if (sec < 10) sec = '0' + sec; + if (sec < 10) { + sec = '0' + sec; + } - return month + '/' + day + '/' + year + ' ' + hour + ':' + min + ':' + sec + ' ' + ampm; + return month + '/' + day + '/' + year + ' ' + hour + ':' + min + ':' + sec + ' ' + ampm; } \ No newline at end of file diff --git a/tests/api.entries.test.js b/tests/api.entries.test.js index 39a7c435bb8..779c32823db 100644 --- a/tests/api.entries.test.js +++ b/tests/api.entries.test.js @@ -1,6 +1,8 @@ +'use strict'; + var request = require('supertest'); -var should = require('should'); var load = require('./fixtures/load'); +require('should'); describe('Entries REST api', function ( ) { var entries = require('../lib/api/entries/'); @@ -23,10 +25,6 @@ describe('Entries REST api', function ( ) { this.archive( ).remove({ }, done); }); - it('should be a module', function ( ) { - entries.should.be.ok; - }); - // keep this test pinned at or near the top in order to validate all // entries successfully uploaded. if res.body.length is short of the // expected value, it may indicate a regression in the create diff --git a/tests/api.status.test.js b/tests/api.status.test.js index 435e6d82246..cda4caecd4c 100644 --- a/tests/api.status.test.js +++ b/tests/api.status.test.js @@ -1,12 +1,13 @@ +'use strict'; var request = require('supertest'); -var should = require('should'); +require('should'); describe('Status REST api', function ( ) { var api = require('../lib/api/'); before(function (done) { var env = require('../env')( ); - env.enable = "careportal rawbg"; + env.enable = 'careportal rawbg'; env.api_secret = 'this is my long pass phrase'; this.wares = require('../lib/middleware/')(env); this.app = require('express')( ); diff --git a/tests/cannulaage.test.js b/tests/cannulaage.test.js index 2140b645391..cd4b70798bb 100644 --- a/tests/cannulaage.test.js +++ b/tests/cannulaage.test.js @@ -1,4 +1,6 @@ -var should = require('should'); +'use strict'; + +require('should'); describe('cage', function ( ) { var cage = require('../lib/plugins/cannulaage')(); diff --git a/tests/cob.test.js b/tests/cob.test.js index c34237d0823..6e383d57b0c 100644 --- a/tests/cob.test.js +++ b/tests/cob.test.js @@ -1,6 +1,6 @@ 'use strict'; -var should = require('should'); +require('should'); describe('COB', function ( ) { var cob = require('../lib/plugins/cob')(); @@ -17,18 +17,18 @@ describe('COB', function ( ) { var treatments = [ { - "carbs": "100", - "created_at": new Date("2015-05-29T02:03:48.827Z") + 'carbs': '100', + 'created_at': new Date('2015-05-29T02:03:48.827Z') }, { - "carbs": "10", - "created_at": new Date("2015-05-29T03:45:10.670Z") + 'carbs': '10', + 'created_at': new Date('2015-05-29T03:45:10.670Z') } ]; - var after100 = cob.cobTotal(treatments, profile, new Date("2015-05-29T02:03:49.827Z")); - var before10 = cob.cobTotal(treatments, profile, new Date("2015-05-29T03:45:10.670Z")); - var after10 = cob.cobTotal(treatments, profile, new Date("2015-05-29T03:45:11.670Z")); + var after100 = cob.cobTotal(treatments, profile, new Date('2015-05-29T02:03:49.827Z')); + var before10 = cob.cobTotal(treatments, profile, new Date('2015-05-29T03:45:10.670Z')); + var after10 = cob.cobTotal(treatments, profile, new Date('2015-05-29T03:45:11.670Z')); after100.cob.should.equal(100); Math.round(before10.cob).should.equal(59); @@ -39,16 +39,16 @@ describe('COB', function ( ) { var treatments = [ { - "carbs": "8", - "created_at": new Date("2015-05-29T04:40:40.174Z") + 'carbs': '8', + 'created_at': new Date('2015-05-29T04:40:40.174Z') } ]; - var rightAfterCorrection = new Date("2015-05-29T04:41:40.174Z"); - var later1 = new Date("2015-05-29T05:04:40.174Z"); - var later2 = new Date("2015-05-29T05:20:00.174Z"); - var later3 = new Date("2015-05-29T05:50:00.174Z"); - var later4 = new Date("2015-05-29T06:50:00.174Z"); + var rightAfterCorrection = new Date('2015-05-29T04:41:40.174Z'); + var later1 = new Date('2015-05-29T05:04:40.174Z'); + var later2 = new Date('2015-05-29T05:20:00.174Z'); + var later3 = new Date('2015-05-29T05:50:00.174Z'); + var later4 = new Date('2015-05-29T06:50:00.174Z'); var result1 = cob.cobTotal(treatments, profile, rightAfterCorrection); var result2 = cob.cobTotal(treatments, profile, later1); @@ -70,8 +70,8 @@ describe('COB', function ( ) { var data = { treatments: [{ - carbs: "8" - , "created_at": Date.now() - 60000 //1m ago + carbs: '8' + , 'created_at': Date.now() - 60000 //1m ago }] , profile: profile }; diff --git a/tests/data.test.js b/tests/data.test.js index 27370f1fc93..d30cc637785 100644 --- a/tests/data.test.js +++ b/tests/data.test.js @@ -1,11 +1,12 @@ -var should = require('should'); +'use strict'; + +require('should'); describe('Data', function ( ) { var env = require('../env')(); var ctx = {}; - data = require('../lib/data')(env, ctx); -// console.log(data); + var data = require('../lib/data')(env, ctx); it('should return original data if there are no changes', function() { data.sgvs = [{sgv: 100, x:100},{sgv: 100, x:99}]; diff --git a/tests/delta.test.js b/tests/delta.test.js index 788811ad01f..5d4e0790fd9 100644 --- a/tests/delta.test.js +++ b/tests/delta.test.js @@ -1,6 +1,6 @@ 'use strict'; -var should = require('should'); +require('should'); describe('Delta', function ( ) { var delta = require('../lib/plugins/delta')(); diff --git a/tests/iob.test.js b/tests/iob.test.js index 466fe87923c..3aef85ead0e 100644 --- a/tests/iob.test.js +++ b/tests/iob.test.js @@ -1,6 +1,6 @@ -var should = require('should'); +'use strict'; -var FIVE_MINS = 10 * 60 * 1000; +require('should'); describe('IOB', function ( ) { var iob = require('../lib/plugins/iob')(); @@ -11,7 +11,7 @@ describe('IOB', function ( ) { var time = new Date() , treatments = [ { created_at: time - 1, - insulin: "1.00" + insulin: '1.00' } ]; @@ -41,7 +41,7 @@ describe('IOB', function ( ) { var treatments = [{ created_at: (new Date()) - 1, - insulin: "1.00" + insulin: '1.00' }]; var rightAfterBolus = iob.calcTotal(treatments); @@ -56,7 +56,7 @@ describe('IOB', function ( ) { var treatments = [{ created_at: time, - insulin: "5.00" + insulin: '5.00' }]; var whenApproaching0 = iob.calcTotal(treatments, undefined, new Date(time + (3 * 60 * 60 * 1000) - (90 * 1000))); @@ -71,7 +71,7 @@ describe('IOB', function ( ) { var time = new Date() , treatments = [ { created_at: time - 1, - insulin: "1.00" + insulin: '1.00' } ]; diff --git a/tests/mqtt.test.js b/tests/mqtt.test.js index 76d4c3c6911..81e1ed6ea79 100644 --- a/tests/mqtt.test.js +++ b/tests/mqtt.test.js @@ -1,4 +1,6 @@ -var should = require('should'); +'use strict'; + +require('should'); var FIVE_MINS = 5 * 60 * 1000; @@ -12,8 +14,8 @@ describe('mqtt', function ( ) { ; it('setup env correctly', function (done) { - process.env.MONGO="mongodb://localhost/test_db"; - process.env.MONGO_COLLECTION="test_sgvs"; + process.env.MONGO='mongodb://localhost/test_db'; + process.env.MONGO_COLLECTION='test_sgvs'; process.env.MQTT_MONITOR = 'mqtt://user:password@m10.cloudmqtt.com:12345'; var env = require('../env')(); env.mqtt_client_id.should.equal('fSjoHx8buyCtAc474tg8Dt3'); diff --git a/tests/pebble.test.js b/tests/pebble.test.js index 369d75ea7c7..d2a1cdefa6e 100644 --- a/tests/pebble.test.js +++ b/tests/pebble.test.js @@ -166,7 +166,7 @@ describe('Pebble Endpoint with Raw', function ( ) { var pebbleRaw = require('../lib/pebble'); before(function (done) { var envRaw = require('../env')( ); - envRaw.enable = "rawbg"; + envRaw.enable = 'rawbg'; this.appRaw = require('express')( ); this.appRaw.enable('api'); this.appRaw.use('/pebble', pebbleRaw(envRaw, ctx)); diff --git a/tests/profile.test.js b/tests/profile.test.js index b49bd5cae33..ae487ef279f 100644 --- a/tests/profile.test.js +++ b/tests/profile.test.js @@ -2,8 +2,6 @@ var should = require('should'); describe('Profile', function ( ) { - var env = require('../env')(); - var profile_empty = require('../lib/profilefunctions')(); it('should say it does not have data before it has data', function() { @@ -17,8 +15,8 @@ describe('Profile', function ( ) { }); var profileDataPartial = { - "dia": 3, - "carbs_hr": 30, + 'dia': 3 + , 'carbs_hr': 30 }; var profilePartial = require('../lib/profilefunctions')([profileDataPartial]); @@ -29,12 +27,12 @@ describe('Profile', function ( ) { }); var profileData = { - "dia": 3, - "carbs_hr": 30, - "carbratio": 7, - "sens": 35, - "target_low": 95, - "target_high": 120 + 'dia': 3 + , 'carbs_hr': 30 + , 'carbratio': 7 + , 'sens': 35 + , 'target_low': 95 + , 'target_high': 120 }; var profile = require('../lib/profilefunctions')([profileData]); @@ -80,12 +78,12 @@ describe('Profile', function ( ) { it('should know how to reload data and still know what the low target is with old style profiles', function() { var profileData2 = { - "dia": 3, - "carbs_hr": 30, - "carbratio": 7, - "sens": 35, - "target_low": 50, - "target_high": 120 + 'dia': 3, + 'carbs_hr': 30, + 'carbratio': 7, + 'sens': 35, + 'target_low': 50, + 'target_high': 120 }; profile.loadData([profileData2]); @@ -95,69 +93,69 @@ describe('Profile', function ( ) { var complexProfileData = { - "sens": [ + 'sens': [ { - "time": "00:00", - "value": 10 + 'time': '00:00', + 'value': 10 }, { - "time": "02:00", - "value": 10 + 'time': '02:00', + 'value': 10 }, { - "time": "07:00", - "value": 9 + 'time': '07:00', + 'value': 9 } ], - "dia": 3, - "carbratio": [ + 'dia': 3, + 'carbratio': [ { - "time": "00:00", - "value": 16 + 'time': '00:00', + 'value': 16 }, { - "time": "06:00", - "value": 15 + 'time': '06:00', + 'value': 15 }, { - "time": "14:00", - "value": 16 + 'time': '14:00', + 'value': 16 } ], - "carbs_hr": 30, - "startDate": "2015-06-21", - "basal": [ + 'carbs_hr': 30, + 'startDate': '2015-06-21', + 'basal': [ { - "time": "00:00", - "value": 0.175 + 'time': '00:00', + 'value': 0.175 }, { - "time": "02:30", - "value": 0.125 + 'time': '02:30', + 'value': 0.125 }, { - "time": "05:00", - "value": 0.075 + 'time': '05:00', + 'value': 0.075 }, { - "time": "08:00", - "value": 0.1 + 'time': '08:00', + 'value': 0.1 }, { - "time": "14:00", - "value": 0.125 + 'time': '14:00', + 'value': 0.125 }, { - "time": "20:00", - "value": 0.3 + 'time': '20:00', + 'value': 0.3 }, { - "time": "22:00", - "value": 0.225 + 'time': '22:00', + 'value': 0.225 } ], - "target_low": 4.5, - "target_high": 8 + 'target_low': 4.5, + 'target_high': 8 }; var complexProfile = require('../lib/profilefunctions')([complexProfileData]); diff --git a/tests/pushnotify.test.js b/tests/pushnotify.test.js index 0f81a5a5755..bbafa27651d 100644 --- a/tests/pushnotify.test.js +++ b/tests/pushnotify.test.js @@ -1,4 +1,6 @@ -var should = require('should'); +'use strict'; + +require('should'); describe('pushnotify', function ( ) { diff --git a/tests/rawbg.test.js b/tests/rawbg.test.js index 7695c01c076..e4aec0e9c16 100644 --- a/tests/rawbg.test.js +++ b/tests/rawbg.test.js @@ -1,6 +1,6 @@ 'use strict'; -var should = require('should'); +require('should'); describe('Raw BG', function ( ) { var rawbg = require('../lib/plugins/rawbg')(); diff --git a/tests/security.test.js b/tests/security.test.js index d5a5b193763..d718d5ed5cf 100644 --- a/tests/security.test.js +++ b/tests/security.test.js @@ -85,7 +85,6 @@ describe('API_SECRET', function ( ) { }); it('should not work short', function ( ) { - var known = 'c1d117818a97e847bdf286aa02d9dc8e8f7148f5'; delete process.env.API_SECRET; process.env.API_SECRET = 'tooshort'; var env; diff --git a/tests/units.test.js b/tests/units.test.js index 3f51382e817..3f5793d327c 100644 --- a/tests/units.test.js +++ b/tests/units.test.js @@ -1,4 +1,6 @@ -var should = require('should'); +'use strict'; + +require('should'); describe('units', function ( ) { var units = require('../lib/units')(); diff --git a/tests/upbat.test.js b/tests/upbat.test.js index 9b71d965c21..0b49a3d790c 100644 --- a/tests/upbat.test.js +++ b/tests/upbat.test.js @@ -1,6 +1,6 @@ 'use strict'; -var should = require('should'); +require('should'); describe('Uploader Battery', function ( ) { var data = {uploaderBattery: 20}; diff --git a/tests/utils.test.js b/tests/utils.test.js index 0b79d107ade..55b0eaad4c1 100644 --- a/tests/utils.test.js +++ b/tests/utils.test.js @@ -1,4 +1,6 @@ -var should = require('should'); +'use strict'; + +require('should'); describe('utils', function ( ) { var utils = require('../lib/utils')(); From 1cfdc03dbf63f4779d3446b2029782358e6f0545 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 1 Jul 2015 00:03:23 -0700 Subject: [PATCH 258/937] added some missing sbx.scaleBg's --- lib/plugins/ar2.js | 2 +- lib/plugins/rawbg.js | 7 +++---- lib/plugins/simplealarms.js | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index dae234d15ce..69209a8b748 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -92,7 +92,7 @@ function init() { var rawbgProp = sbx.properties.rawbg; if (rawbgProp) { - lines.push(['Raw BG:', rawbgProp.value, sbx.unitsLabel, rawbgProp.noiseLabel].join(' ')); + lines.push(['Raw BG:', sbx.scaleBg(rawbgProp.value), sbx.unitsLabel, rawbgProp.noiseLabel].join(' ')); } lines.push([usingRaw ? 'Raw BG' : 'BG', '15m:', sbx.scaleBg(predicted[2]), sbx.unitsLabel].join(' ')); diff --git a/lib/plugins/rawbg.js b/lib/plugins/rawbg.js index cbd3410bc0a..7be7e78b61c 100644 --- a/lib/plugins/rawbg.js +++ b/lib/plugins/rawbg.js @@ -35,7 +35,7 @@ function init() { var options = prop && prop.sgv && rawbg.showRawBGs(prop.sgv.y, prop.sgv.noise, prop.cal, sbx) ? { hide: !prop || !prop.value - , value: prop.value + , value: sbx.scaleBg(prop.value) , label: prop.noiseLabel } : { hide: true @@ -48,7 +48,6 @@ function init() { var raw = 0 , unfiltered = parseInt(sgv.unfiltered) || 0 , filtered = parseInt(sgv.filtered) || 0 - , sgv = sgv.y , scale = parseFloat(cal.scale) || 0 , intercept = parseFloat(cal.intercept) || 0 , slope = parseFloat(cal.slope) || 0; @@ -56,10 +55,10 @@ function init() { if (slope == 0 || unfiltered == 0 || scale == 0) { raw = 0; - } else if (filtered == 0 || sgv < 40) { + } else if (filtered == 0 || sgv.y < 40) { raw = scale * (unfiltered - intercept) / slope; } else { - var ratio = scale * (filtered - intercept) / slope / sgv; + var ratio = scale * (filtered - intercept) / slope / sgv.y; raw = scale * ( unfiltered - intercept) / slope / ratio; } diff --git a/lib/plugins/simplealarms.js b/lib/plugins/simplealarms.js index 7ce39c72d79..b056ac8f087 100644 --- a/lib/plugins/simplealarms.js +++ b/lib/plugins/simplealarms.js @@ -66,7 +66,7 @@ function init() { var rawbgProp = sbx.properties.rawbg; if (rawbgProp) { - lines.push(['Raw BG:', rawbgProp.value, sbx.unitsLabel, rawbgProp.noiseLabel].join(' ')); + lines.push(['Raw BG:', sbx.scaleBg(rawbgProp.value), sbx.unitsLabel, rawbgProp.noiseLabel].join(' ')); } var bwp = sbx.properties.bwp && sbx.properties.bwp.bolusEstimateDisplay; From 320565b0cbd7e100edc04b2f1c1ead02c1e1ced6 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 1 Jul 2015 00:30:39 -0700 Subject: [PATCH 259/937] more easy cleanup --- bundle/bundle.source.js | 2 +- lib/api/status.js | 2 +- lib/data.js | 22 ++++++++++++---------- lib/entries.js | 2 +- lib/mqtt.js | 2 +- lib/plugins/ar2.js | 2 +- lib/plugins/boluswizardpreview.js | 2 +- lib/plugins/cob.js | 4 ++-- lib/plugins/iob.js | 2 +- lib/plugins/pluginbase.js | 2 +- lib/profilefunctions.js | 23 +++++++++++------------ lib/treatments.js | 4 +++- lib/units.js | 2 +- static/index.html | 10 ---------- static/js/client.js | 23 ++++++++++++++--------- static/js/experiments.js | 28 ---------------------------- static/js/ui-utils.js | 8 ++++---- testing/make_high_data.js | 4 ++-- testing/populate_rest.js | 2 +- tests/api.status.test.js | 4 ++-- tests/boluswizardpreview.test.js | 6 +++--- tests/delta.test.js | 2 +- tests/pebble.test.js | 2 +- tests/plugins.test.js | 2 +- tests/pushnotify.test.js | 4 ++-- tests/rawbg.test.js | 2 +- tests/security.test.js | 4 ++-- tests/units.test.js | 4 ++-- tests/upbat.test.js | 2 +- tests/utils.test.js | 2 +- 30 files changed, 75 insertions(+), 105 deletions(-) delete mode 100644 static/js/experiments.js diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index 81693b6e81a..ae04f5f72bf 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -11,7 +11,7 @@ sandbox: require('../lib/sandbox')() }; - console.info("Nightscout bundle ready", window.Nightscout); + console.info('Nightscout bundle ready', window.Nightscout); })(); diff --git a/lib/api/status.js b/lib/api/status.js index 8b1de9ed21d..862f5909513 100644 --- a/lib/api/status.js +++ b/lib/api/status.js @@ -25,7 +25,7 @@ function configure (app, wares) { var badge = 'http://img.shields.io/badge/Nightscout-OK-green'; return res.format({ html: function ( ) { - res.send("

    STATUS OK

    "); + res.send('

    STATUS OK

    '); }, png: function ( ) { res.redirect(302, badge + '.png'); diff --git a/lib/data.js b/lib/data.js index dedcfe4805f..d682b292b64 100644 --- a/lib/data.js +++ b/lib/data.js @@ -95,7 +95,7 @@ function init(env, ctx) { data.sgvs = sgvs; } callback(); - }) + }); }, cal: function (callback) { //FIXME: date $gte????? var cq = {count: 1, find: {type: 'cal'}}; @@ -156,7 +156,7 @@ function init(env, ctx) { data.devicestatus.uploaderBattery = result.uploaderBattery; } callback(); - }) + }); } }, done); @@ -164,7 +164,7 @@ function init(env, ctx) { data.calculateDelta = function calculateDelta(lastData) { return data.calculateDeltaBetweenDatasets(lastData,data); - } + }; data.calculateDeltaBetweenDatasets = function calculateDeltaBetweenDatasets(oldData,newData) { @@ -178,7 +178,7 @@ function init(env, ctx) { var seen = {}; var l = oldArray.length; for (var i = 0; i < l; i++) { - seen[oldArray[i].x] = true + seen[oldArray[i].x] = true; } var result = []; l = newArray.length; @@ -229,12 +229,14 @@ function init(env, ctx) { var skippableObjects = ['profiles', 'devicestatus']; for (var object in skippableObjects) { - var o = skippableObjects[object]; - if (newData.hasOwnProperty(o)) { - if (JSON.stringify(newData[o]) != JSON.stringify(oldData[o])) { - console.log('delta changes found on', o); - changesFound = true; - delta[o] = newData[o]; + if (skippableObjects.hasOwnProperty(object)) { + var o = skippableObjects[object]; + if (newData.hasOwnProperty(o)) { + if (JSON.stringify(newData[o]) != JSON.stringify(oldData[o])) { + console.log('delta changes found on', o); + changesFound = true; + delta[o] = newData[o]; + } } } } diff --git a/lib/entries.js b/lib/entries.js index 8eba532180d..c9a52a00f03 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -43,7 +43,7 @@ function storage(env, ctx) { // determine sort options function sort ( ) { - return {"date": -1}; + return {date: -1}; // return this.sort({"date": -1}); } diff --git a/lib/mqtt.js b/lib/mqtt.js index 9ac47f9f6bb..6d3414348cd 100644 --- a/lib/mqtt.js +++ b/lib/mqtt.js @@ -237,7 +237,7 @@ function configure(env, ctx) { }); ctx.entries.create([ packet ], function empty(err, res) { - console.log('Download written to mongo: ', packet) + console.log('Download written to mongo: ', packet); }); diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 3ee91c1aed4..3cc910554cc 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -31,7 +31,7 @@ function init() { var result = {}; if (lastSGVEntry && Date.now() - lastSGVEntry.x < TEN_MINUTES) { - result = ar2.forcastAndCheck(sbx.data.sgvs) + result = ar2.forcastAndCheck(sbx.data.sgvs); } var usingRaw = false; diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 20cd97a4929..819b9aaf5b5 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -70,7 +70,7 @@ function init() { level: sbx.notifications.levels.URGENT , lengthMills: snoozeLength , debug: results - }) + }); } else if (results.bolusEstimate > warnBWP) { var level = results.bolusEstimate > urgentBWP ? sbx.notifications.levels.URGENT : sbx.notifications.levels.WARN; var levelLabel = sbx.notifications.levels.toString(level); diff --git a/lib/plugins/cob.js b/lib/plugins/cob.js index 599f9d60cad..1eee45074b9 100644 --- a/lib/plugins/cob.js +++ b/lib/plugins/cob.js @@ -103,7 +103,7 @@ function init() { return { netCarbImpact: netCarbImpact, totalImpact: totalImpact - } + }; }; cob.cobCalc = function cobCalc(treatment, profile, lastDecayedBy, time) { @@ -159,7 +159,7 @@ function init() { if (prop.lastCarbs) { var when = moment(new Date(prop.lastCarbs.created_at)).format('lll'); var amount = prop.lastCarbs.carbs + 'g'; - info = [{label: 'Last Carbs', value: amount + ' @ ' + when }] + info = [{label: 'Last Carbs', value: amount + ' @ ' + when }]; } sbx.pluginBase.updatePillText(sbx, { diff --git a/lib/plugins/iob.js b/lib/plugins/iob.js index 5c099bcf848..d1b2fbe4eab 100644 --- a/lib/plugins/iob.js +++ b/lib/plugins/iob.js @@ -97,7 +97,7 @@ function init() { if (prop && prop.lastBolus) { var when = moment(new Date(prop.lastBolus.created_at)).format('lll'); var amount = sbx.roundInsulinForDisplayFormat(Number(prop.lastBolus.insulin)) + 'U'; - info = [{label: 'Last Bolus', value: amount + ' @ ' + when }] + info = [{label: 'Last Bolus', value: amount + ' @ ' + when }]; } sbx.pluginBase.updatePillText(iob, { diff --git a/lib/plugins/pluginbase.js b/lib/plugins/pluginbase.js index 3f4984ced6d..3c58145144d 100644 --- a/lib/plugins/pluginbase.js +++ b/lib/plugins/pluginbase.js @@ -18,7 +18,7 @@ function init (majorPills, minorPills, statusPills, bgStatus, tooltip) { } else if (plugin.pluginType == 'pill-alt') { container = bgStatus; } else { - container = minorPills + container = minorPills; } var pillName = 'span.pill.' + plugin.name; diff --git a/lib/profilefunctions.js b/lib/profilefunctions.js index aa1e1ab31ec..e6ac5ea0e9c 100644 --- a/lib/profilefunctions.js +++ b/lib/profilefunctions.js @@ -59,46 +59,45 @@ function init(profileData) { } return returnValue; - } + }; profile.getCurrentProfile = function getCurrentProfile() { - if (profile.hasData()) return profile.data[0]; - return {}; - } + return profile.hasData() ? profile.data[0] : {}; + }; profile.hasData = function hasData() { var rVal = false; if (profile.data) rVal = true; return (rVal); - } + }; profile.getDIA = function getDIA(time) { return profile.getValueByTime(time,profile.getCurrentProfile()['dia']); - } + }; profile.getSensitivity = function getSensitivity(time) { return profile.getValueByTime(time,profile.getCurrentProfile()['sens']); - } + }; profile.getCarbRatio = function getCarbRatio(time) { return profile.getValueByTime(time,profile.getCurrentProfile()['carbratio']); - } + }; profile.getCarbAbsorptionRate = function getCarbAbsorptionRate(time) { return profile.getValueByTime(time,profile.getCurrentProfile()['carbs_hr']); - } + }; profile.getLowBGTarget = function getLowBGTarget(time) { return profile.getValueByTime(time,profile.getCurrentProfile()['target_low']); - } + }; profile.getHighBGTarget = function getHighBGTarget(time) { return profile.getValueByTime(time,profile.getCurrentProfile()['target_high']); - } + }; profile.getBasal = function getBasal(time) { return profile.getValueByTime(time,profile.getCurrentProfile()['basal']); - } + }; return profile(); diff --git a/lib/treatments.js b/lib/treatments.js index ff9c3c989d0..ca6895f24e5 100644 --- a/lib/treatments.js +++ b/lib/treatments.js @@ -44,7 +44,9 @@ function storage (env, ctx) { carbs: preBolusCarbs }; - if (obj.notes) pbTreat.notes = obj.notes; + if (obj.notes) { + pbTreat.notes = obj.notes; + } api( ).insert(pbTreat, function() { //nothing to do here diff --git a/lib/units.js b/lib/units.js index 0a86f9db06f..cf99785ebd9 100644 --- a/lib/units.js +++ b/lib/units.js @@ -7,7 +7,7 @@ function mgdlToMMOL(mgdl) { function configure() { return { mgdlToMMOL: mgdlToMMOL - } + }; } module.exports = configure; \ No newline at end of file diff --git a/static/index.html b/static/index.html index 2b2d40ab14b..a44fca3ca42 100644 --- a/static/index.html +++ b/static/index.html @@ -141,15 +141,6 @@

    Nightscout

    Reset, and use defaults
    -
    - Experiments -
    - Hamburger?
    - - -
    -
    -
    About
    @@ -266,6 +257,5 @@

    Nightscout

    - diff --git a/static/js/client.js b/static/js/client.js index 1ecffededfc..ea7313ab7f6 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -415,7 +415,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var lookbackTime = (lookback + 2) * FIVE_MINS_IN_MS + 2 * ONE_MIN_IN_MS; nowData = nowData.filter(function(d) { return d.date.getTime() >= brushExtent[1].getTime() - TWENTY_FIVE_MINS_IN_MS - lookbackTime && - d.date.getTime() <= brushExtent[1].getTime() - TWENTY_FIVE_MINS_IN_MS + d.date.getTime() <= brushExtent[1].getTime() - TWENTY_FIVE_MINS_IN_MS; }); // sometimes nowData contains duplicates. uniq it. @@ -505,7 +505,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; .attr('r', function (d) { return dotRadius(d.type); }); if (badData.length > 0) { - console.warn("Bad Data: isNaN(sgv)", badData); + console.warn('Bad Data: isNaN(sgv)', badData); } return sel; @@ -974,7 +974,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; .attr('r', function (d) { return d.type == 'mbg' ? 4 : 2; }); if (badData.length > 0) { - console.warn("Bad Data: isNaN(sgv)", badData); + console.warn('Bad Data: isNaN(sgv)', badData); } return sel; @@ -1121,7 +1121,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; console.info('found mismatched glucose units, converting ' + treatment.units + ' into ' + browserSettings.units, treatment); if (treatment.units == 'mmol') { //BG is in mmol and display in mg/dl - treatmentGlucose = Math.round(treatment.glucose * 18) + treatmentGlucose = Math.round(treatment.glucose * 18); } else { //BG is in mg/dl and display in mmol treatmentGlucose = scaleBg(treatment.glucose); @@ -1158,7 +1158,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; R3 = R2 + 8 / scale; if (isNaN(R1) || isNaN(R3) || isNaN(R3)) { - console.warn("Bad Data: Found isNaN value in treatment", treatment); + console.warn('Bad Data: Found isNaN value in treatment', treatment); return; } @@ -1172,8 +1172,13 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; 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'; - if (treatment.insulin > 0) arc_data[3].element = Math.round(treatment.insulin * 100) / 100 + ' U'; + if (treatment.carbs > 0) { + arc_data[1].element = Math.round(treatment.carbs) + ' g'; + } + + if (treatment.insulin > 0) { + arc_data[3].element = Math.round(treatment.insulin * 100) / 100 + ' U'; + } var arc = d3.svg.arc() .innerRadius(function (d) { return 5 * d.inner; }) @@ -1205,8 +1210,8 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; }); var arcs = treatmentDots.append('path') .attr('class', 'path') - .attr('fill', function (d, i) { if (d.outlineOnly) return 'transparent'; else return d.color; }) - .attr('stroke-width', function (d) {if (d.outlineOnly) return 1; else return 0; }) + .attr('fill', function (d, i) { return d.outlineOnly ? 'transparent' : d.color; }) + .attr('stroke-width', function (d) { return d.outlineOnly ? 1 : 0; }) .attr('stroke', function (d) { return d.color; }) .attr('id', function (d, i) { return 's' + i; }) .attr('d', arc); diff --git a/static/js/experiments.js b/static/js/experiments.js deleted file mode 100644 index d0e7b8aa100..00000000000 --- a/static/js/experiments.js +++ /dev/null @@ -1,28 +0,0 @@ -$(function() { - if (querystring.experiments) { - $(".experiments").show(); - if (!querystring.drawer) { - $("#drawerToggle").click(); - } - } else { - $(".experiments").hide(); - } - - $(".glyphToggle").on("click", function(){ - var newGlyph = $(this).find("i").attr("class"); - $("#drawerToggle").find("i").prop("class", newGlyph); - event.preventDefault(); - }); - - $(".iconToggle").on("click", function(){ - var newIcon = $(this).find("img").attr("src"); - $("#favicon").prop("href", newIcon); - event.preventDefault(); - }); - - $(".toolbarIconToggle").on("click", function(){ - var newIcon = $(this).find("img").attr("src"); - $("#toolbar").css({'background-image':'url('+newIcon+')'}); - event.preventDefault(); - }); -}); diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js index f33182d432e..bff843efa45 100644 --- a/static/js/ui-utils.js +++ b/static/js/ui-utils.js @@ -152,7 +152,7 @@ function isTouch() { function closeDrawer(id, callback) { openDraw = null; - $("html, body").animate({ scrollTop: 0 }); + $('html, body').animate({ scrollTop: 0 }); $(id).animate({right: '-300px'}, 300, function () { $(id).css('display', 'none'); if (callback) { callback(); } @@ -166,7 +166,7 @@ function toggleDrawer(id, openCallback, closeCallback) { if (openDraw) { closeDrawer(openDraw, callback); } else { - callback() + callback(); } } @@ -242,7 +242,7 @@ function showNotification(note, type) { } function showLocalstorageError() { - var msg = 'Settings are disabled.

    Please enable cookies so you may customize your Nightscout site.' + var msg = 'Settings are disabled.

    Please enable cookies so you may customize your Nightscout site.'; $('.browserSettings').html('Settings'+msg+''); $('#save').hide(); } @@ -373,7 +373,7 @@ $('#notification').click(function(event) { $('#save').click(function(event) { function checkedPluginNames() { - var checkedPlugins = [] + var checkedPlugins = []; $('#show-plugins input:checked').each(function eachPluginCheckbox(index, checkbox) { checkedPlugins.push($(checkbox).val()); }); diff --git a/testing/make_high_data.js b/testing/make_high_data.js index 80f48d434e3..610b163d41a 100644 --- a/testing/make_high_data.js +++ b/testing/make_high_data.js @@ -1,6 +1,6 @@ var fs = require('fs'); -var data = '' +var data = ''; var END_TIME = Date.now(); var FIVE_MINS_IN_MS = 300000; var TIME_PERIOD_HRS = 24; @@ -23,4 +23,4 @@ function makedata() { fs.writeFile('../Dexcom.csv', data); } -setInterval(makedata, 1000 * 10) \ No newline at end of file +setInterval(makedata, 1000 * 10); \ No newline at end of file diff --git a/testing/populate_rest.js b/testing/populate_rest.js index 703e3ea124a..a3f75faddbe 100644 --- a/testing/populate_rest.js +++ b/testing/populate_rest.js @@ -27,7 +27,7 @@ function send_entry_rest() { }; var req = http.request(options, function(res) { - console.log("Ok: ", res.statusCode); + console.log('Ok: ', res.statusCode); }); req.on('error', function(e) { diff --git a/tests/api.status.test.js b/tests/api.status.test.js index cda4caecd4c..c37407048de 100644 --- a/tests/api.status.test.js +++ b/tests/api.status.test.js @@ -41,7 +41,7 @@ describe('Status REST api', function ( ) { .end(function(err, res) { res.type.should.equal('text/html'); res.statusCode.should.equal(200); - done() + done(); }); }); @@ -52,7 +52,7 @@ describe('Status REST api', function ( ) { res.type.should.equal('application/javascript'); res.statusCode.should.equal(200); res.text.should.startWith('this.serverSettings ='); - done() + done(); }); }); diff --git a/tests/boluswizardpreview.test.js b/tests/boluswizardpreview.test.js index 9677c9d2c5e..8e9244dcbb5 100644 --- a/tests/boluswizardpreview.test.js +++ b/tests/boluswizardpreview.test.js @@ -24,7 +24,7 @@ describe('boluswizardpreview', function ( ) { var sbx = require('../lib/sandbox')().serverInit(env, ctx); sbx.offerProperty('iob', function () { - return {iob: 0} + return {iob: 0}; }); boluswizardpreview.setProperties(sbx); @@ -41,7 +41,7 @@ describe('boluswizardpreview', function ( ) { var sbx = require('../lib/sandbox')().serverInit(env, ctx); sbx.offerProperty('iob', function () { - return {iob: 0} + return {iob: 0}; }); boluswizardpreview.setProperties(sbx); @@ -58,7 +58,7 @@ describe('boluswizardpreview', function ( ) { var sbx = require('../lib/sandbox')().serverInit(env, ctx); sbx.offerProperty('iob', function () { - return {iob: 0} + return {iob: 0}; }); boluswizardpreview.setProperties(sbx); diff --git a/tests/delta.test.js b/tests/delta.test.js index 5d4e0790fd9..d5c838234f0 100644 --- a/tests/delta.test.js +++ b/tests/delta.test.js @@ -21,7 +21,7 @@ describe('Delta', function ( ) { var result = setter(); result.value.should.equal(5); result.display.should.equal('+5'); - done() + done(); }; delta.setProperties(sbx); diff --git a/tests/pebble.test.js b/tests/pebble.test.js index d2a1cdefa6e..bf87f889a67 100644 --- a/tests/pebble.test.js +++ b/tests/pebble.test.js @@ -96,7 +96,7 @@ var ctx = { callback(null, {uploaderBattery: 100}); } } -} +}; describe('Pebble Endpoint without Raw', function ( ) { var pebble = require('../lib/pebble'); diff --git a/tests/plugins.test.js b/tests/plugins.test.js index c7fa5f11618..0b0fc20ac43 100644 --- a/tests/plugins.test.js +++ b/tests/plugins.test.js @@ -18,7 +18,7 @@ describe('Plugins', function ( ) { }); it('should find sever plugins, but not client only plugins', function (done) { - var plugins = require('../lib/plugins/')().registerServerDefaults() + var plugins = require('../lib/plugins/')().registerServerDefaults(); plugins('rawbg').name.should.equal('rawbg'); plugins('treatmentnotify').name.should.equal('treatmentnotify'); diff --git a/tests/pushnotify.test.js b/tests/pushnotify.test.js index bbafa27651d..4380dcd6210 100644 --- a/tests/pushnotify.test.js +++ b/tests/pushnotify.test.js @@ -26,7 +26,7 @@ describe('pushnotify', function ( ) { msg.priority.should.equal(2); msg.sound.should.equal('climb'); callback(null, JSON.stringify({receipt: 'abcd12345'})); - done() + done(); } }; @@ -60,7 +60,7 @@ describe('pushnotify', function ( ) { msg.priority.should.equal(0); msg.sound.should.equal('gamelan'); callback(null, JSON.stringify({})); - done() + done(); } }; diff --git a/tests/rawbg.test.js b/tests/rawbg.test.js index e4aec0e9c16..f7a626c3c3f 100644 --- a/tests/rawbg.test.js +++ b/tests/rawbg.test.js @@ -24,7 +24,7 @@ describe('Raw BG', function ( ) { var result = setter(); result.value.should.equal(113); result.noiseLabel.should.equal('Clean'); - done() + done(); }; rawbg.setProperties(sbx); diff --git a/tests/security.test.js b/tests/security.test.js index d718d5ed5cf..bf8d25fc68d 100644 --- a/tests/security.test.js +++ b/tests/security.test.js @@ -103,7 +103,7 @@ describe('API_SECRET', function ( ) { res.body.status.should.equal('ok'); fn( ); // console.log('err', err, 'res', res); - }) + }); } function ping_authorized_endpoint (app, fails, fn) { @@ -117,7 +117,7 @@ describe('API_SECRET', function ( ) { } fn( ); // console.log('err', err, 'res', res); - }) + }); } }); diff --git a/tests/units.test.js b/tests/units.test.js index 3f5793d327c..b6e8a9faa8f 100644 --- a/tests/units.test.js +++ b/tests/units.test.js @@ -6,11 +6,11 @@ describe('units', function ( ) { var units = require('../lib/units')(); it('should convert 99 to 5.5', function () { - units.mgdlToMMOL(99).should.equal('5.5') + units.mgdlToMMOL(99).should.equal('5.5'); }); it('should convert 180 to 10.0', function () { - units.mgdlToMMOL(180).should.equal('10.0') + units.mgdlToMMOL(180).should.equal('10.0'); }); }); diff --git a/tests/upbat.test.js b/tests/upbat.test.js index 0b49a3d790c..668e3c9cfc4 100644 --- a/tests/upbat.test.js +++ b/tests/upbat.test.js @@ -18,7 +18,7 @@ describe('Uploader Battery', function ( ) { result.display.should.equal('20%'); result.status.should.equal('urgent'); result.level.should.equal(25); - done() + done(); }; var upbat = require('../lib/plugins/upbat')(); diff --git a/tests/utils.test.js b/tests/utils.test.js index 55b0eaad4c1..3dab434df78 100644 --- a/tests/utils.test.js +++ b/tests/utils.test.js @@ -11,7 +11,7 @@ describe('utils', function ( ) { }; it('format numbers', function () { - utils.toFixed(5.499999999).should.equal('5.50') + utils.toFixed(5.499999999).should.equal('5.50'); }); it('show format recent times to 1 minute', function () { From 61d235953b365b8a58d162a832ce57b60668070d Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 1 Jul 2015 00:03:23 -0700 Subject: [PATCH 260/937] added some missing sbx.scaleBg's --- lib/plugins/ar2.js | 2 +- lib/plugins/rawbg.js | 7 +++---- lib/plugins/simplealarms.js | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 3cc910554cc..855dd35164b 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -92,7 +92,7 @@ function init() { var rawbgProp = sbx.properties.rawbg; if (rawbgProp) { - lines.push(['Raw BG:', rawbgProp.value, sbx.unitsLabel, rawbgProp.noiseLabel].join(' ')); + lines.push(['Raw BG:', sbx.scaleBg(rawbgProp.value), sbx.unitsLabel, rawbgProp.noiseLabel].join(' ')); } lines.push([usingRaw ? 'Raw BG' : 'BG', '15m:', sbx.scaleBg(predicted[2]), sbx.unitsLabel].join(' ')); diff --git a/lib/plugins/rawbg.js b/lib/plugins/rawbg.js index cbd3410bc0a..7be7e78b61c 100644 --- a/lib/plugins/rawbg.js +++ b/lib/plugins/rawbg.js @@ -35,7 +35,7 @@ function init() { var options = prop && prop.sgv && rawbg.showRawBGs(prop.sgv.y, prop.sgv.noise, prop.cal, sbx) ? { hide: !prop || !prop.value - , value: prop.value + , value: sbx.scaleBg(prop.value) , label: prop.noiseLabel } : { hide: true @@ -48,7 +48,6 @@ function init() { var raw = 0 , unfiltered = parseInt(sgv.unfiltered) || 0 , filtered = parseInt(sgv.filtered) || 0 - , sgv = sgv.y , scale = parseFloat(cal.scale) || 0 , intercept = parseFloat(cal.intercept) || 0 , slope = parseFloat(cal.slope) || 0; @@ -56,10 +55,10 @@ function init() { if (slope == 0 || unfiltered == 0 || scale == 0) { raw = 0; - } else if (filtered == 0 || sgv < 40) { + } else if (filtered == 0 || sgv.y < 40) { raw = scale * (unfiltered - intercept) / slope; } else { - var ratio = scale * (filtered - intercept) / slope / sgv; + var ratio = scale * (filtered - intercept) / slope / sgv.y; raw = scale * ( unfiltered - intercept) / slope / ratio; } diff --git a/lib/plugins/simplealarms.js b/lib/plugins/simplealarms.js index 7ce39c72d79..b056ac8f087 100644 --- a/lib/plugins/simplealarms.js +++ b/lib/plugins/simplealarms.js @@ -66,7 +66,7 @@ function init() { var rawbgProp = sbx.properties.rawbg; if (rawbgProp) { - lines.push(['Raw BG:', rawbgProp.value, sbx.unitsLabel, rawbgProp.noiseLabel].join(' ')); + lines.push(['Raw BG:', sbx.scaleBg(rawbgProp.value), sbx.unitsLabel, rawbgProp.noiseLabel].join(' ')); } var bwp = sbx.properties.bwp && sbx.properties.bwp.bolusEstimateDisplay; From beeeebdf182d1b83e50f18509db26fbacb9810d9 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 1 Jul 2015 00:54:46 -0700 Subject: [PATCH 261/937] more cleanup --- lib/plugins/boluswizardpreview.js | 8 ++++---- lib/plugins/index.js | 18 ++++++++++++------ lib/plugins/rawbg.js | 8 ++++---- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 819b9aaf5b5..8b2027241a1 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -55,7 +55,7 @@ function init() { bwp.checkNotifications = function checkNotifications (sbx) { var results = sbx.properties.bwp; - if (results == undefined) { return; } + if (results === undefined) { return; } if (results.lastSGV < sbx.data.profile.getHighBGTarget(sbx.time)) { return; } @@ -74,7 +74,7 @@ function init() { } else if (results.bolusEstimate > warnBWP) { var level = results.bolusEstimate > urgentBWP ? sbx.notifications.levels.URGENT : sbx.notifications.levels.WARN; var levelLabel = sbx.notifications.levels.toString(level); - var sound = level == sbx.notifications.levels.URGENT ? 'updown' : 'bike'; + var sound = level === sbx.notifications.levels.URGENT ? 'updown' : 'bike'; var lines = ['BG Now: ' + results.displaySGV]; @@ -119,7 +119,7 @@ function init() { bwp.updateVisualisation = function updateVisualisation (sbx) { var results = sbx.properties.bwp; - if (results == undefined) { return; } + if (results === undefined) { return; } // display text var info = [ @@ -189,7 +189,7 @@ function init() { results.bolusEstimate = delta / sens * -1; } - if (results.bolusEstimate != 0 && sbx.data.profile.getBasal()) { + if (results.bolusEstimate !== 0 && sbx.data.profile.getBasal()) { // Basal profile exists, calculate % change var basal = sbx.data.profile.getBasal(sbx.time); diff --git a/lib/plugins/index.js b/lib/plugins/index.js index 2f45802de78..d92c37b0b8e 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -56,7 +56,7 @@ function init() { }); }; - plugins.init = function init(envOrApp) { + plugins.init = function initPlugins (envOrApp) { enabledPlugins = []; function isEnabled(plugin) { //TODO: unify client/server env/app @@ -96,25 +96,31 @@ function init() { plugins.hasShownType = function hasShownType(pluginType, sbx) { return _.find(plugins.shownPlugins(sbx), function findWithType(plugin) { - return plugin.pluginType == pluginType; - }) != undefined; + return plugin.pluginType === pluginType; + }) !== undefined; }; plugins.setProperties = function setProperties(sbx) { plugins.eachEnabledPlugin( function eachPlugin (plugin) { - plugin.setProperties && plugin.setProperties(sbx.withExtendedSettings(plugin)); + if (plugin.setProperties) { + plugin.setProperties(sbx.withExtendedSettings(plugin)); + } }); }; plugins.checkNotifications = function checkNotifications(sbx) { plugins.eachEnabledPlugin( function eachPlugin (plugin) { - plugin.checkNotifications && plugin.checkNotifications(sbx.withExtendedSettings(plugin)); + if (plugin.checkNotifications) { + plugin.checkNotifications(sbx.withExtendedSettings(plugin)); + } }); }; plugins.updateVisualisations = function updateVisualisations(sbx) { plugins.eachShownPlugins(sbx, function eachPlugin(plugin) { - plugin.updateVisualisation && plugin.updateVisualisation(sbx.withExtendedSettings(plugin)); + if (plugin.updateVisualisation) { + plugin.updateVisualisation(sbx.withExtendedSettings(plugin)); + } }); }; diff --git a/lib/plugins/rawbg.js b/lib/plugins/rawbg.js index 7be7e78b61c..a3635017805 100644 --- a/lib/plugins/rawbg.js +++ b/lib/plugins/rawbg.js @@ -53,9 +53,9 @@ function init() { , slope = parseFloat(cal.slope) || 0; - if (slope == 0 || unfiltered == 0 || scale == 0) { + if (slope === 0 || unfiltered === 0 || scale === 0) { raw = 0; - } else if (filtered == 0 || sgv.y < 40) { + } else if (filtered === 0 || sgv.y < 40) { raw = scale * (unfiltered - intercept) / slope; } else { var ratio = scale * (filtered - intercept) / slope / sgv.y; @@ -72,8 +72,8 @@ function init() { rawbg.showRawBGs = function showRawBGs(sgv, noise, cal, sbx) { return cal && rawbg.isEnabled(sbx) - && (sbx.defaults.showRawbg == 'always' - || (sbx.defaults.showRawbg == 'noise' && (noise >= 2 || sgv < 40)) + && (sbx.defaults.showRawbg === 'always' + || (sbx.defaults.showRawbg === 'noise' && (noise >= 2 || sgv < 40)) ); }; From 48c2a2a3856258b0e0752a0264573c9ce8f83504 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 1 Jul 2015 09:26:31 -0700 Subject: [PATCH 262/937] mqtt fixes for issues reported by codacy --- lib/mqtt.js | 67 +++++++++++++++++++++++++---------------------------- 1 file changed, 32 insertions(+), 35 deletions(-) diff --git a/lib/mqtt.js b/lib/mqtt.js index 6d3414348cd..078d93eb681 100644 --- a/lib/mqtt.js +++ b/lib/mqtt.js @@ -105,7 +105,7 @@ function sgvSensorMerge(packet) { , sgvsLength = sgvs.length , sensorsLength = sensors.length; - if (sgvsLength >= 0 && sensorsLength == 0) { + if (sgvsLength >= 0 && sensorsLength === 0) { merged = sgvs; } else { var smallerLength = Math.min(sgvsLength, sensorsLength); @@ -204,45 +204,42 @@ function configure(env, ctx) { var packet = downloads.parse(b); if (!packet.type) { packet.type = topic; - } - } catch (e) { - console.log('DID NOT PARSE', e); - break; - } - console.log('DOWNLOAD msg', msg.length, packet); - console.log('download SGV', packet.sgv[0]); - console.log('download_timestamp', packet.download_timestamp, new Date(Date.parse(packet.download_timestamp))); - console.log('WRITE TO MONGO'); - var download_timestamp = moment(packet.download_timestamp); - if (packet.download_status === 0) { - es.readArray(sgvSensorMerge(packet)).pipe(ctx.entries.persist(function empty(err, result) { - console.log('DONE WRITING MERGED SGV TO MONGO', err, result); - })); - iter_mqtt_record_stream(packet, 'cal', toCal) - .pipe(ctx.entries.persist(function empty(err, result) { - console.log('DONE WRITING Cal TO MONGO', err, result.length); - })); - iter_mqtt_record_stream(packet, 'meter', toMeter) - .pipe(ctx.entries.persist(function empty(err, result) { - console.log('DONE WRITING Meter TO MONGO', err, result.length); + } + console.log('DOWNLOAD msg', msg.length, packet); + console.log('download SGV', packet.sgv[0]); + console.log('download_timestamp', packet.download_timestamp, new Date(Date.parse(packet.download_timestamp))); + console.log('WRITE TO MONGO'); + var download_timestamp = moment(packet.download_timestamp); + if (packet.download_status === 0) { + es.readArray(sgvSensorMerge(packet)).pipe(ctx.entries.persist(function empty(err, result) { + console.log('DONE WRITING MERGED SGV TO MONGO', err, result); })); - } - packet.type = 'download'; - ctx.devicestatus.create({ - uploaderBattery: packet.uploader_battery, - created_at: download_timestamp.toISOString() - }, function empty(err, result) { - console.log('DONE WRITING TO MONGO devicestatus ', result, err); - }); - ctx.entries.create([ packet ], function empty(err, res) { - console.log('Download written to mongo: ', packet); - }); + iter_mqtt_record_stream(packet, 'cal', toCal) + .pipe(ctx.entries.persist(function empty(err, result) { + console.log('DONE WRITING Cal TO MONGO', err, result.length); + })); + iter_mqtt_record_stream(packet, 'meter', toMeter) + .pipe(ctx.entries.persist(function empty(err, result) { + console.log('DONE WRITING Meter TO MONGO', err, result.length); + })); + } + packet.type = 'download'; + ctx.devicestatus.create({ + uploaderBattery: packet.uploader_battery, + created_at: download_timestamp.toISOString() + }, function empty(err, result) { + console.log('DONE WRITING TO MONGO devicestatus ', result, err); + }); + ctx.entries.create([ packet ], function empty(err, res) { + console.log('Download written to mongo: ', packet); + }); + } catch (e) { + console.log('DID NOT PARSE', e); + } - // ctx.entries.write(packet); - break; default: console.log(topic, 'on message', 'msg', msg); // ctx.entries.write(msg); From bd9759a489983b523c492028088d7aa7ed7baa98 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 1 Jul 2015 09:34:31 -0700 Subject: [PATCH 263/937] added back break that was lost --- lib/mqtt.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/mqtt.js b/lib/mqtt.js index 078d93eb681..e3beafc061d 100644 --- a/lib/mqtt.js +++ b/lib/mqtt.js @@ -240,6 +240,8 @@ function configure(env, ctx) { console.log('DID NOT PARSE', e); } + break; + default: console.log(topic, 'on message', 'msg', msg); // ctx.entries.write(msg); From 853fb60c21225b81f7bda76d8624f0b9d7bbee82 Mon Sep 17 00:00:00 2001 From: Paul Andrel Date: Wed, 1 Jul 2015 13:15:25 -0400 Subject: [PATCH 264/937] Notifications generated by BWP were missing a sbx.scaleBg() around raw values --- lib/plugins/boluswizardpreview.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 9d3210a4598..87109384768 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -88,7 +88,7 @@ function init() { var rawbgProp = sbx.properties.rawbg; if (rawbgProp) { - lines.push(['Raw BG:', rawbgProp.value, sbx.unitsLabel, rawbgProp.noiseLabel].join(' ')); + lines.push(['Raw BG:', sbx.scaleBg(rawbgProp.value), sbx.unitsLabel, rawbgProp.noiseLabel].join(' ')); } lines.push(['BWP:', results.bolusEstimateDisplay, 'U'].join(' ')); From 87893bf6dce6aaca8bb6e788b01c3363558d7542 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 1 Jul 2015 17:59:48 -0700 Subject: [PATCH 265/937] more == vs === and other issues reported by codacy --- env.js | 12 ++++++------ lib/api/entries/index.js | 6 +++--- lib/api/experiments/index.js | 6 ++---- lib/bus.js | 7 +------ lib/data.js | 2 +- lib/devicestatus.js | 12 +----------- lib/entries.js | 20 ++++++++------------ lib/mqtt.js | 8 ++++++-- lib/notifications.js | 2 +- lib/plugins/cannulaage.js | 4 ++-- lib/plugins/cob.js | 2 +- lib/plugins/pluginbase.js | 8 ++++---- lib/plugins/treatmentnotify.js | 4 ++-- lib/poller.js | 18 ------------------ lib/profile.js | 5 +---- lib/pushnotify.js | 2 +- lib/sandbox.js | 13 +++++-------- lib/treatments.js | 6 ++---- lib/utils.js | 6 +++--- lib/websocket.js | 6 +++--- testing/util.js | 2 +- tests/api.status.test.js | 6 +----- tests/notifications.test.js | 2 +- tests/pebble.test.js | 10 +--------- tests/profile.test.js | 7 ------- tests/security.test.js | 8 ++++---- 26 files changed, 61 insertions(+), 123 deletions(-) delete mode 100644 lib/poller.js diff --git a/env.js b/env.js index 9cc0220d6c6..1c212aa9709 100644 --- a/env.js +++ b/env.js @@ -21,7 +21,7 @@ function config ( ) { var software = require('./package.json'); var git = require('git-rev'); - if (readENV('APPSETTING_ScmType') == readENV('ScmType') && readENV('ScmType') == 'GitHub') { + if (readENV('APPSETTING_ScmType') === readENV('ScmType') && readENV('ScmType') === 'GitHub') { env.head = require('./scm-commit-id.json'); console.log('SCM COMMIT ID', env.head); } else { @@ -44,7 +44,7 @@ function config ( ) { //some MQTT servers only allow the client id to be 23 chars env.mqtt_client_id = mongoHash.digest('base64').substring(0, 23); console.info('Using Mongo host/db/collection to create the default MQTT client_id', hostDbCollection); - if (env.MQTT_MONITOR.indexOf('?clientId=') == -1) { + if (env.MQTT_MONITOR.indexOf('?clientId=') === -1) { console.info('Set MQTT client_id to: ', env.mqtt_client_id); } else { console.info('MQTT configured to use a custom client id, it will override the default: ', env.mqtt_client_id); @@ -94,9 +94,9 @@ function config ( ) { env.defaults.showPlugins = readENV('SHOW_PLUGINS', ''); //TODO: figure out something for some plugins to have them shown by default - if (env.defaults.showPlugins != '') { + if (env.defaults.showPlugins !== '') { env.defaults.showPlugins += ' delta upbat'; - if (env.defaults.showRawbg == 'always' || env.defaults.showRawbg == 'noise') { + if (env.defaults.showRawbg === 'always' || env.defaults.showRawbg === 'noise') { env.defaults.showPlugins += 'rawbg'; } } @@ -223,8 +223,8 @@ function readENV(varName, defaultValue) { || process.env[varName] || process.env[varName.toLowerCase()]; - if (typeof value === 'string' && value.toLowerCase() == 'on') { value = true; } - if (typeof value === 'string' && value.toLowerCase() == 'off') { value = false; } + if (typeof value === 'string' && value.toLowerCase() === 'on') { value = true; } + if (typeof value === 'string' && value.toLowerCase() === 'off') { value = false; } return value != null ? value : defaultValue; } diff --git a/lib/api/entries/index.js b/lib/api/entries/index.js index 0e5e6a4c1fe..1ad6e90d499 100644 --- a/lib/api/entries/index.js +++ b/lib/api/entries/index.js @@ -29,7 +29,7 @@ function configure (app, wares, ctx) { function force_typed_data (opts) { function sync (data, next) { - if (data.type != opts.type) { + if (data.type !== opts.type) { console.warn('BAD DATA TYPE, setting', data.type, 'to', opts.type); data.type = opts.type; } @@ -39,10 +39,10 @@ function configure (app, wares, ctx) { } // Middleware to format any response involving entries. - function format_entries (req, res, next) { + function format_entries (req, res) { var type_params = { type: (req.query && req.query.find && req.query.find.type - && req.query.find.type != req.params.model) + && req.query.find.type !== req.params.model) ? req.query.find.type : req.params.model }; var output = es.readArray(res.entries || [ ]); diff --git a/lib/api/experiments/index.js b/lib/api/experiments/index.js index b0dca1ed408..4ccd13ff596 100644 --- a/lib/api/experiments/index.js +++ b/lib/api/experiments/index.js @@ -1,7 +1,5 @@ 'use strict'; -var consts = require('../../constants'); - function configure (app, wares) { var express = require('express'), api = express.Router( ) @@ -9,11 +7,11 @@ function configure (app, wares) { if (app.enabled('api')) { api.use(wares.sendJSONStatus); - api.get('/:secret/test', wares.verifyAuthorization, function (req, res, next) { + api.get('/:secret/test', wares.verifyAuthorization, function (req, res) { return res.json({status: 'ok'}); }); - api.get('/test', wares.verifyAuthorization, function (req, res, next) { + api.get('/test', wares.verifyAuthorization, function (req, res) { return res.json({status: 'ok'}); }); } diff --git a/lib/bus.js b/lib/bus.js index 107fe5d036f..6ec88a5a2a2 100644 --- a/lib/bus.js +++ b/lib/bus.js @@ -1,6 +1,6 @@ var Stream = require('stream'); -function init (env, ctx) { +function init (env) { var beats = 0; var started = new Date( ); var id; @@ -24,11 +24,6 @@ function init (env, ctx) { stream.emit('tick', ictus( )); } - function ender ( ) { - if (id) { cancelInterval(id); } - stream.emit('end'); - } - stream.readable = true; stream.uptime = repeat; id = setInterval(repeat, interval); diff --git a/lib/data.js b/lib/data.js index d682b292b64..a5c04eafc81 100644 --- a/lib/data.js +++ b/lib/data.js @@ -232,7 +232,7 @@ function init(env, ctx) { if (skippableObjects.hasOwnProperty(object)) { var o = skippableObjects[object]; if (newData.hasOwnProperty(o)) { - if (JSON.stringify(newData[o]) != JSON.stringify(oldData[o])) { + if (JSON.stringify(newData[o]) !== JSON.stringify(oldData[o])) { console.log('delta changes found on', o); changesFound = true; delta[o] = newData[o]; diff --git a/lib/devicestatus.js b/lib/devicestatus.js index 1357138c1b7..87652001279 100644 --- a/lib/devicestatus.js +++ b/lib/devicestatus.js @@ -11,13 +11,6 @@ function storage (collection, ctx) { }); } - function create_date_included(obj, fn) { - api().insert(obj, function (err, doc) { - fn(null, doc); - }); - - } - function last(fn) { return api().find({}).sort({created_at: -1}).limit(1).toArray(function (err, entries) { if (entries && entries.length > 0) { @@ -37,14 +30,11 @@ function storage (collection, ctx) { return ctx.store.db.collection(collection); } - api.list = list; api.create = create; api.last = last; - api.indexedFields = indexedFields; + api.indexedFields = ['created_at']; return api; } -var indexedFields = ['created_at']; - module.exports = storage; diff --git a/lib/entries.js b/lib/entries.js index c9a52a00f03..7c9ac75f08e 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -93,13 +93,12 @@ function storage(env, ctx) { return es.pipeline(map( ), es.writeArray(done)); } - function update (fn) { - // TODO: implement - } - - function remove (fn) { - // TODO: implement - } + //TODO: implement + //function update (fn) { + //} + // + //function remove (fn) { + //} // store new documents using the storage mechanism function create (docs, fn) { @@ -112,7 +111,7 @@ function storage(env, ctx) { docs.forEach(function(doc) { var query = (doc.sysTime && doc.type) ? {sysTime: doc.sysTime, type: doc.type} : doc; - collection.update(query, doc, {upsert: true}, function (err, created) { + collection.update(query, doc, {upsert: true}, function (err) { firstErr = firstErr || err; if (++totalCreated === numDocs) { //TODO: this is triggering a read from Mongo, we can do better @@ -154,13 +153,10 @@ function storage(env, ctx) { api.create = create; api.persist = persist; api.getEntry = getEntry; - api.indexedFields = indexedFields; + api.indexedFields = [ 'date', 'type', 'sgv', 'sysTime' ]; return api; } -var indexedFields = [ 'date', 'type', 'sgv', 'sysTime' ]; -storage.indexedFields = indexedFields; - // expose module storage.storage = storage; module.exports = storage; diff --git a/lib/mqtt.js b/lib/mqtt.js index e3beafc061d..50ebca5d648 100644 --- a/lib/mqtt.js +++ b/lib/mqtt.js @@ -233,8 +233,12 @@ function configure(env, ctx) { console.log('DONE WRITING TO MONGO devicestatus ', result, err); }); - ctx.entries.create([ packet ], function empty(err, res) { - console.log('Download written to mongo: ', packet); + ctx.entries.create([ packet ], function empty(err) { + if (err) { + console.log('Error writting to mongo: ', err); + } else { + console.log('Download written to mongo: ', packet); + } }); } catch (e) { console.log('DID NOT PARSE', e); diff --git a/lib/notifications.js b/lib/notifications.js index 18e141c621e..cc983e72679 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -177,7 +177,7 @@ function init (env, ctx) { alarm.silenceTime = time ? time : THIRTY_MINUTES; delete alarm.lastEmitTime; - if (level == 2) { + if (level === 2) { notifications.ack(1, time); } diff --git a/lib/plugins/cannulaage.js b/lib/plugins/cannulaage.js index 58e6eab291e..37b0913a1c8 100644 --- a/lib/plugins/cannulaage.js +++ b/lib/plugins/cannulaage.js @@ -19,7 +19,7 @@ function init() { var message = ''; _.forEach(sbx.data.treatments, function eachTreatment (treatment) { - if (treatment.eventType == 'Site Change') { + if (treatment.eventType === 'Site Change') { treatmentDate = new Date(treatment.created_at); var hours = Math.round(Math.abs(sbx.time - treatmentDate) / 36e5); @@ -38,7 +38,7 @@ function init() { }); var info = [{label: 'Inserted:', value: moment(treatmentDate).format('lll')}]; - if (message != '') { info.push({label: 'Notes:', value: message}); } + if (message !== '') { info.push({label: 'Notes:', value: message}); } sbx.pluginBase.updatePillText(cage, { value: age + 'h' diff --git a/lib/plugins/cob.js b/lib/plugins/cob.js index 1eee45074b9..8f8c9f5c68b 100644 --- a/lib/plugins/cob.js +++ b/lib/plugins/cob.js @@ -151,7 +151,7 @@ function init() { var prop = sbx.properties.cob; - if (prop == undefined || prop.cob == undefined) { return; } + if (prop === undefined || prop.cob === undefined) { return; } var displayCob = Math.round(prop.cob * 10) / 10; diff --git a/lib/plugins/pluginbase.js b/lib/plugins/pluginbase.js index 3c58145144d..ac876edc9a4 100644 --- a/lib/plugins/pluginbase.js +++ b/lib/plugins/pluginbase.js @@ -11,11 +11,11 @@ function init (majorPills, minorPills, statusPills, bgStatus, tooltip) { function findOrCreatePill (plugin) { var container = null; - if (plugin.pluginType == 'pill-major') { + if (plugin.pluginType === 'pill-major') { container = majorPills; - } else if (plugin.pluginType == 'pill-status') { + } else if (plugin.pluginType === 'pill-status') { container = statusPills; - } else if (plugin.pluginType == 'pill-alt') { + } else if (plugin.pluginType === 'pill-alt') { container = bgStatus; } else { container = minorPills; @@ -26,7 +26,7 @@ function init (majorPills, minorPills, statusPills, bgStatus, tooltip) { var classes = 'pill ' + plugin.name; - if (!pill || pill.length == 0) { + if (!pill || pill.length === 0) { pill = $(''); var pillLabel = $(''); var pillValue = $(''); diff --git a/lib/plugins/treatmentnotify.js b/lib/plugins/treatmentnotify.js index aa6ad0694dc..04d041aa3e9 100644 --- a/lib/plugins/treatmentnotify.js +++ b/lib/plugins/treatmentnotify.js @@ -23,11 +23,11 @@ function init() { //TODO: figure out why date is x here #CleanUpDataModel var lastMBGTime = lastMBG ? lastMBG.x : 0; var mbgAgo = (lastMBGTime && lastMBGTime <= now) ? now - lastMBGTime : -1; - var mbgCurrent = mbgAgo != -1 && mbgAgo < TIME_10_MINS_MS; + var mbgCurrent = mbgAgo !== -1 && mbgAgo < TIME_10_MINS_MS; var lastTreatmentTime = lastTreatment ? new Date(lastTreatment.created_at).getTime() : 0; var treatmentAgo = (lastTreatmentTime && lastTreatmentTime <= now) ? now - lastTreatmentTime : -1; - var treatmentCurrent = treatmentAgo != -1 && treatmentAgo < TIME_10_MINS_MS; + var treatmentCurrent = treatmentAgo !== -1 && treatmentAgo < TIME_10_MINS_MS; if (mbgCurrent || treatmentCurrent) { autoSnoozeAlarms(sbx); diff --git a/lib/poller.js b/lib/poller.js deleted file mode 100644 index fd78327a1b2..00000000000 --- a/lib/poller.js +++ /dev/null @@ -1,18 +0,0 @@ - -var es = require('event-stream'); - -function create (core) { - function heartbeat (ev) { - - } - core.inputs.on('heartbeat', heartbeat); - function make (beat) { - var poll = {heart: beat}; - return poll; - } - function writer (data) { - } - var poller = es.through(writer); -} - -module.exports = create; diff --git a/lib/profile.js b/lib/profile.js index ca97ff6d841..060845210d7 100644 --- a/lib/profile.js +++ b/lib/profile.js @@ -1,6 +1,5 @@ 'use strict'; - function storage (collection, ctx) { var ObjectID = require('mongodb').ObjectID; @@ -35,10 +34,8 @@ function storage (collection, ctx) { api.create = create; api.save = save; api.last = last; - api.indexedFields = indexedFields; + api.indexedFields = ['validfrom']; return api; } -var indexedFields = ['validfrom']; - module.exports = storage; diff --git a/lib/pushnotify.js b/lib/pushnotify.js index 6546dc592e1..dfba17eb98c 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -90,7 +90,7 @@ function init(env, ctx) { if (notify.level >= ctx.notifications.levels.WARN) { //ADJUST RETRY TIME based on WARN or URGENT - msg.retry = notify.level == ctx.notifications.levels.URGENT ? TIME_2_MINS_S : TIME_15_MINS_S; + msg.retry = notify.level === ctx.notifications.levels.URGENT ? TIME_2_MINS_S : TIME_15_MINS_S; if (env.baseUrl) { msg.callback = env.baseUrl + '/api/v1/notifications/pushovercallback'; } diff --git a/lib/sandbox.js b/lib/sandbox.js index 0aa57eb2a08..21015a9ce27 100644 --- a/lib/sandbox.js +++ b/lib/sandbox.js @@ -130,7 +130,7 @@ function init ( ) { } function scaleBg (bg) { - if (sbx.units == 'mmol' && bg) { + if (sbx.units === 'mmol' && bg) { return Number(units.mgdlToMMOL(bg)); } else { return Number(bg); @@ -139,11 +139,11 @@ function init ( ) { function roundInsulinForDisplayFormat (insulin) { - if (insulin == 0) { + if (insulin === 0) { return '0'; } - if (sbx.properties.roundingStyle == 'medtronic') { + if (sbx.properties.roundingStyle === 'medtronic') { var denominator = 0.1; var digits = 1; if (insulin > 0.5 && iob < 1) { @@ -162,14 +162,11 @@ function init ( ) { } function unitsLabel ( ) { - return sbx.units == 'mmol' ? 'mmol/L' : 'mg/dl'; + return sbx.units === 'mmol' ? 'mmol/L' : 'mg/dl'; } function roundBGToDisplayFormat (bg) { - if (sbx.units == 'mmol') { - return Math.round(bg * 10) / 10; - } - return Math.round(bg); + return sbx.units === 'mmol' ? Math.round(bg * 10) / 10 : Math.round(bg); } diff --git a/lib/treatments.js b/lib/treatments.js index ca6895f24e5..7804142bfa6 100644 --- a/lib/treatments.js +++ b/lib/treatments.js @@ -26,7 +26,7 @@ function storage (env, ctx) { if (!obj.carbs) { delete obj.carbs; } if (!obj.insulin) { delete obj.insulin; } if (!obj.notes) { delete obj.notes; } - if (!obj.preBolus || obj.preBolus == 0) { delete obj.preBolus; } + if (!obj.preBolus || obj.preBolus === 0) { delete obj.preBolus; } if (!obj.glucose) { delete obj.glucose; delete obj.glucoseType; @@ -73,11 +73,9 @@ function storage (env, ctx) { api.list = list; api.create = create; - api.indexedFields = indexedFields; + api.indexedFields = ['created_at', 'eventType']; return api; } -var indexedFields = ['created_at', 'eventType']; - module.exports = storage; diff --git a/lib/utils.js b/lib/utils.js index 328c492b71b..86476956a32 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -15,19 +15,19 @@ function init() { return '0'; } else { var fixed = value.toFixed(2); - return fixed == '-0.00' ? '0.00' : fixed; + return fixed === '-0.00' ? '0.00' : fixed; } }; utils.timeAgo = function timeAgo(time, clientSettings) { var now = Date.now() - , offset = time == -1 ? -1 : (now - time) / 1000 + , offset = time === -1 ? -1 : (now - time) / 1000 , parts = {}; if (offset < MINUTE_IN_SECS * -5) { parts = { value: 'in the future' }; - } else if (offset == -1) { + } else if (offset === -1) { parts = { label: 'time ago' }; } else if (offset <= MINUTE_IN_SECS * 2) { parts = { value: 1, label: 'min ago' }; diff --git a/lib/websocket.js b/lib/websocket.js index 887dae94a5a..a6e5745f200 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -65,12 +65,12 @@ function init (env, ctx, server) { if (notify.clear) { io.emit('clear_alarm', true); console.info('emitted clear_alarm to all clients'); - } else if (notify.level == levels.INFO) { + } else if (notify.level === levels.INFO) { //client doesn't know how to display info notifications yet, ignoring - } else if (notify.level == levels.WARN) { + } else if (notify.level === levels.WARN) { io.emit('alarm', notify); console.info('emitted alarm to all clients'); - } else if (notify.level == levels.URGENT) { + } else if (notify.level === levels.URGENT) { io.emit('urgent_alarm', notify); console.info('emitted urgent_alarm to all clients'); } diff --git a/testing/util.js b/testing/util.js index 25348e1566a..eb433429da8 100644 --- a/testing/util.js +++ b/testing/util.js @@ -49,7 +49,7 @@ function getDateString(d) { ampm = 'AM'; } - if (hour == 0) { + if (hour === 0) { hour = 12; } if (hour > 12) { diff --git a/tests/api.status.test.js b/tests/api.status.test.js index c37407048de..8447d560b93 100644 --- a/tests/api.status.test.js +++ b/tests/api.status.test.js @@ -19,10 +19,6 @@ describe('Status REST api', function ( ) { }); }); - it('should be a module', function ( ) { - api.should.be.ok; - }); - it('/status.json', function (done) { request(this.app) .get('/api/status.json') @@ -62,7 +58,7 @@ describe('Status REST api', function ( ) { .end(function(err, res) { res.headers.location.should.equal('http://img.shields.io/badge/Nightscout-OK-green.png'); res.statusCode.should.equal(302); - done() + done(); }); }); diff --git a/tests/notifications.test.js b/tests/notifications.test.js index df386cbf098..1c6d55e994f 100644 --- a/tests/notifications.test.js +++ b/tests/notifications.test.js @@ -13,7 +13,7 @@ describe('notifications', function ( ) { var notifications = require('../lib/notifications')(env, ctx); - var examplePlugin = function examplePlugin () {}; + function examplePlugin () {}; var exampleInfo = { title: 'test' diff --git a/tests/pebble.test.js b/tests/pebble.test.js index bf87f889a67..dd1e18ee714 100644 --- a/tests/pebble.test.js +++ b/tests/pebble.test.js @@ -79,7 +79,7 @@ var ctx = { if (opts && opts.find && opts.find.sgv) { callback(null, sgvs.slice(0, count)); - } else if (opts && opts.find && opts.find.type == 'cal') { + } else if (opts && opts.find && opts.find.type === 'cal') { callback(null, cals.slice(0, count)); } } @@ -108,10 +108,6 @@ describe('Pebble Endpoint without Raw', function ( ) { done(); }); - it('should be a module', function ( ) { - pebble.should.be.ok; - }); - it('/pebble default(1) count', function (done) { request(this.app) .get('/pebble') @@ -173,10 +169,6 @@ describe('Pebble Endpoint with Raw', function ( ) { done(); }); - it('should be a module', function ( ) { - pebbleRaw.should.be.ok; - }); - it('/pebble', function (done) { request(this.appRaw) .get('/pebble?count=2') diff --git a/tests/profile.test.js b/tests/profile.test.js index ae487ef279f..7a3156d2c5d 100644 --- a/tests/profile.test.js +++ b/tests/profile.test.js @@ -14,13 +14,6 @@ describe('Profile', function ( ) { should.not.exist(dia); }); - var profileDataPartial = { - 'dia': 3 - , 'carbs_hr': 30 - }; - - var profilePartial = require('../lib/profilefunctions')([profileDataPartial]); - it('should return undefined if asking for missing keys', function() { var sens = profile_empty.getSensitivity(now); should.not.exist(sens); diff --git a/tests/security.test.js b/tests/security.test.js index bf8d25fc68d..7f692b6a31f 100644 --- a/tests/security.test.js +++ b/tests/security.test.js @@ -6,7 +6,6 @@ var load = require('./fixtures/load'); describe('API_SECRET', function ( ) { var api = require('../lib/api/'); - api.should.be.ok; var scope = this; function setup_app (env, fn) { @@ -34,7 +33,8 @@ describe('API_SECRET', function ( ) { var env = require('../env')( ); should.not.exist(env.api_secret); setup_app(env, function (ctx) { - ctx.app.enabled('api').should.be.false; + + ctx.app.enabled('api').should.equal(false); ping_status(ctx.app, again); function again ( ) { ping_authorized_endpoint(ctx.app, 404, done); @@ -51,7 +51,7 @@ describe('API_SECRET', function ( ) { env.api_secret.should.equal(known); setup_app(env, function (ctx) { // console.log(this.app.enabled('api')); - ctx.app.enabled('api').should.be.true; + ctx.app.enabled('api').should.equal(true); // ping_status(ctx.app, done); // ping_authorized_endpoint(ctx.app, 200, done); ping_status(ctx.app, again); @@ -72,7 +72,7 @@ describe('API_SECRET', function ( ) { env.api_secret.should.equal(known); setup_app(env, function (ctx) { // console.log(this.app.enabled('api')); - ctx.app.enabled('api').should.be.true; + ctx.app.enabled('api').should.equal(true); // ping_status(ctx.app, done); // ping_authorized_endpoint(ctx.app, 200, done); ping_status(ctx.app, again); From c306a3aefb194249a717a69cbee513f935601ccd Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 1 Jul 2015 19:12:33 -0700 Subject: [PATCH 266/937] maybe the last of the easy js clean up? --- lib/middleware/verify-token.js | 2 +- lib/profilefunctions.js | 10 ++-- lib/websocket.js | 2 +- static/js/client.js | 98 ++++++++++++++++++---------------- static/js/ui-utils.js | 14 ++--- 5 files changed, 63 insertions(+), 63 deletions(-) diff --git a/lib/middleware/verify-token.js b/lib/middleware/verify-token.js index f7f875fa83a..714201c5ac2 100644 --- a/lib/middleware/verify-token.js +++ b/lib/middleware/verify-token.js @@ -8,7 +8,7 @@ function configure (env) { var secret = req.params.secret ? req.params.secret : req.header('api-secret'); // Return an error message if the authorization fails. - var unauthorized = (typeof api_secret === 'undefined' || secret != api_secret); + var unauthorized = (typeof api_secret === 'undefined' || secret !== api_secret); if (unauthorized) { res.sendJSONStatus(res, consts.HTTP_UNAUTHORIZED, 'Unauthorized', 'api-secret Request Header is incorrect or missing.'); } else { diff --git a/lib/profilefunctions.js b/lib/profilefunctions.js index e6ac5ea0e9c..a937b1de501 100644 --- a/lib/profilefunctions.js +++ b/lib/profilefunctions.js @@ -1,7 +1,6 @@ 'use strict'; var _ = require('lodash'); -var moment = require('moment'); function init(profileData) { @@ -50,12 +49,11 @@ function init(profileData) { var timeAsDate = new Date(time); var timeAsSecondsFromMidnight = timeAsDate.getHours()*3600 + timeAsDate.getMinutes()*60; - for (var t in valueContainer) { - var value = valueContainer[t]; + _.forEach(valueContainer, function eachValue (value) { if (timeAsSecondsFromMidnight >= value.timeAsSeconds) { returnValue = value.value; } - } + }); } return returnValue; @@ -66,9 +64,7 @@ function init(profileData) { }; profile.hasData = function hasData() { - var rVal = false; - if (profile.data) rVal = true; - return (rVal); + return profile.data ? true : false; }; profile.getDIA = function getDIA(time) { diff --git a/lib/websocket.js b/lib/websocket.js index a6e5745f200..4d1fa74ac95 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -36,7 +36,7 @@ function init (env, ctx, server) { socket.emit('dataUpdate',lastData); io.emit('clients', ++watchers); socket.on('ack', function(alarmType, silenceTime) { - var level = alarmType == 'urgent_alarm' ? 2 : 1; + var level = alarmType === 'urgent_alarm' ? 2 : 1; ctx.notifications.ack(level, silenceTime, true); }); socket.on('disconnect', function () { diff --git a/static/js/client.js b/static/js/client.js index ea7313ab7f6..b669164d117 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -39,7 +39,6 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; , data = [] , foucusRangeMS = THREE_HOURS_MS , clientAlarms = {} - , audio = document.getElementById('audio') , alarmInProgress = false , currentAlarmType = null , alarmSound = 'alarm.mp3' @@ -80,7 +79,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } function isTimeFormat24() { - return browserSettings && browserSettings.timeFormat && parseInt(browserSettings.timeFormat) == 24; + return browserSettings && browserSettings.timeFormat && parseInt(browserSettings.timeFormat) === 24; } function getTimeFormat(isForScale, compact) { @@ -96,7 +95,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; // lixgbg: Convert mg/dL BG value to metric mmol function scaleBg(bg) { - if (browserSettings.units == 'mmol') { + if (browserSettings.units === 'mmol') { return Nightscout.units.mgdlToMMOL(bg); } else { return bg; @@ -170,8 +169,8 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; [':%S', function(d) { return d.getSeconds(); }], ['%I:%M', function(d) { return d.getMinutes(); }], [isTimeFormat24() ? '%H:%M' : '%-I %p', function(d) { return d.getHours(); }], - ['%a %d', function(d) { return d.getDay() && d.getDate() != 1; }], - ['%b %d', function(d) { return d.getDate() != 1; }], + ['%a %d', function(d) { return d.getDay() && d.getDate() !== 1; }], + ['%b %d', function(d) { return d.getDate() !== 1; }], ['%B', function(d) { return d.getMonth(); }], ['%Y', function() { return true; }] ]); @@ -224,7 +223,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; // This shouldn't need to be generated and can be fixed by using xScale.domain([x0,x1]) function with // 2 days before now as x0 and 30 minutes from now for x1 for context plot, but this will be // required to happen when 'now' event is sent from websocket.js every minute. When fixed, - // remove all 'color != 'none'' code + // remove this code and all references to `type: 'server-forecast'` var lastTime = data.length > 0 ? data[data.length - 1].date.getTime() : Date.now(); var n = Math.ceil(12 * (1 / 2 + (now - lastTime) / SIXTY_MINS_IN_MS)) + 1; for (var i = 1; i <= n; i++) { @@ -260,7 +259,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; // update the opacity of the context data points to brush extent context.selectAll('circle') .data(data) - .style('opacity', function (d) { return 1; }); + .style('opacity', 1); } function brushEnded() { @@ -317,7 +316,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var brushExtent = brush.extent(); // ensure that brush extent is fixed at 3.5 hours - if (brushExtent[1].getTime() - brushExtent[0].getTime() != foucusRangeMS) { + if (brushExtent[1].getTime() - brushExtent[0].getTime() !== foucusRangeMS) { // ensure that brush updating is with the time range if (brushExtent[0].getTime() + foucusRangeMS > d3.extent(data, dateFn)[1].getTime()) { @@ -340,8 +339,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; , majorPills = $('.bgStatus .majorPills') , minorPills = $('.bgStatus .minorPills') , statusPills = $('.status .statusPills') - , lastEntry = $('#lastEntry'); - + ; function updateCurrentSGV(entry) { var value, time, ago, isCurrent; @@ -350,7 +348,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; ago = timeAgo(time, browserSettings); isCurrent = ago.status === 'current'; - if (value == 9) { + if (value === 9) { currentBG.text(''); } else if (value < 39) { currentBG.html(errorCodeToDisplay(value)); @@ -370,9 +368,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } } - currentBG.toggleClass('icon-hourglass', value == 9); + currentBG.toggleClass('icon-hourglass', value === 9); currentBG.toggleClass('error-code', value < 39); - currentBG.toggleClass('bg-limit', value == 39 || value > 400); + currentBG.toggleClass('bg-limit', value === 39 || value > 400); $('.container').removeClass('loading'); @@ -405,7 +403,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var lookback = 2; var nowData = data.filter(function(d) { - return d.type == 'sgv'; + return d.type === 'sgv'; }); if (inRetroMode()) { @@ -468,13 +466,13 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; // selects all our data into data and uses date function to get current max date var focusCircles = focus.selectAll('circle').data(focusData, dateFn); - var focusRangeAdjustment = foucusRangeMS == THREE_HOURS_MS ? 1 : 1 + ((foucusRangeMS - THREE_HOURS_MS) / THREE_HOURS_MS / 8); + var focusRangeAdjustment = foucusRangeMS === THREE_HOURS_MS ? 1 : 1 + ((foucusRangeMS - THREE_HOURS_MS) / THREE_HOURS_MS / 8); var dotRadius = function(type) { var radius = prevChartWidth > WIDTH_BIG_DOTS ? 4 : (prevChartWidth < WIDTH_SMALL_DOTS ? 2 : 3); - if (type == 'mbg') { + if (type === 'mbg') { radius *= 2; - } else if (type == 'rawbg') { + } else if (type === 'rawbg') { radius = Math.min(2, radius - 1); } @@ -482,7 +480,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; }; function isDexcom(device) { - return device && device.toLowerCase().indexOf('dexcom') == 0; + return device && device.toLowerCase().indexOf('dexcom') === 0; } function prepareFocusCircles(sel) { @@ -498,7 +496,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; }) .attr('fill', function (d) { return d.color; }) .attr('opacity', function (d) { return futureOpacity(d.date.getTime() - latestSGV.x); }) - .attr('stroke-width', function (d) { return d.type == 'mbg' ? 2 : 0; }) + .attr('stroke-width', function (d) { return d.type === 'mbg' ? 2 : 0; }) .attr('stroke', function (d) { return (isDexcom(d.device) ? 'white' : '#0099ff'); }) @@ -531,7 +529,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); tooltip.html('' + bgType + ' BG: ' + d.sgv + - (d.type == 'mbg' ? '
    Device: ' + d.device : '') + + (d.type === 'mbg' ? '
    Device: ' + d.device : '') + (rawbgValue ? '
    Raw BG: ' + rawbgValue : '') + (noiseLabel ? '
    Noise: ' + noiseLabel : '') + '
    Time: ' + formatTime(d.date)) @@ -647,7 +645,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } // called for initial update and updates for resize - var updateChart = _.debounce(function updateChart(init) { + var updateChart = _.debounce(function debouncedUpdateChart(init) { if (documentHidden && !init) { console.info('Document Hidden, not updating - ' + (new Date())); @@ -671,7 +669,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var currentBrushExtent = brush.extent(); // only redraw chart if chart size has changed - if ((prevChartWidth != chartWidth) || (prevChartHeight != chartHeight)) { + if ((prevChartWidth !== chartWidth) || (prevChartHeight !== chartHeight)) { prevChartWidth = chartWidth; prevChartHeight = chartHeight; @@ -969,9 +967,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; }) .attr('fill', function (d) { return d.color; }) .style('opacity', function (d) { return highlightBrushPoints(d) }) - .attr('stroke-width', function (d) { return d.type == 'mbg' ? 2 : 0; }) + .attr('stroke-width', function (d) { return d.type === 'mbg' ? 2 : 0; }) .attr('stroke', function ( ) { return 'white'; }) - .attr('r', function (d) { return d.type == 'mbg' ? 4 : 2; }); + .attr('r', function (d) { return d.type === 'mbg' ? 4 : 2; }); if (badData.length > 0) { console.warn('Bad Data: isNaN(sgv)', badData); @@ -998,7 +996,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; function sgvToColor(sgv) { var color = 'grey'; - if (browserSettings.theme == 'colors') { + if (browserSettings.theme === 'colors') { if (sgv > app.thresholds.bg_high) { color = 'red'; } else if (sgv > app.thresholds.bg_target_top) { @@ -1018,7 +1016,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; function sgvToColoredRange(sgv) { var range = ''; - if (browserSettings.theme == 'colors') { + if (browserSettings.theme === 'colors') { if (sgv > app.thresholds.bg_high) { range = 'urgent'; } else if (sgv > app.thresholds.bg_target_top) { @@ -1039,18 +1037,18 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; function generateAlarm(file) { alarmInProgress = true; var selector = '.audio.alarms audio.' + file; - d3.select(selector).each(function (d, i) { + d3.select(selector).each(function () { var audio = this; playAlarm(audio); $(this).addClass('playing'); }); - $('.bgButton').addClass(file == urgentAlarmSound ? 'urgent' : 'warning'); + $('.bgButton').addClass(file === urgentAlarmSound ? 'urgent' : 'warning'); $('#container').addClass('alarming'); } function playAlarm(audio) { // ?mute=true disables alarms to testers. - if (querystring.mute != 'true') { + if (querystring.mute !== 'true') { audio.play(); } else { showNotification('Alarm was muted (?mute=true)'); @@ -1089,7 +1087,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; function calcBGByTime(time) { var withBGs = _.filter(data, function(d) { - return d.y > 39 && d.type == 'sgv'; + return d.y > 39 && d.type === 'sgv'; }); var beforeTreatment = _.findLast(withBGs, function (d) { @@ -1117,9 +1115,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; console.warn('found an invalid glucose value', treatment); } else { if (treatment.glucose && treatment.units && browserSettings.units) { - if (treatment.units != browserSettings.units) { + if (treatment.units !== browserSettings.units) { console.info('found mismatched glucose units, converting ' + treatment.units + ' into ' + browserSettings.units, treatment); - if (treatment.units == 'mmol') { + if (treatment.units === 'mmol') { //BG is in mmol and display in mg/dl treatmentGlucose = Math.round(treatment.glucose * 18); } else { @@ -1210,7 +1208,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; }); var arcs = treatmentDots.append('path') .attr('class', 'path') - .attr('fill', function (d, i) { return d.outlineOnly ? 'transparent' : d.color; }) + .attr('fill', function (d) { return d.outlineOnly ? 'transparent' : d.color; }) .attr('stroke-width', function (d) { return d.outlineOnly ? 1 : 0; }) .attr('stroke', function (d) { return d.color; }) .attr('id', function (d, i) { return 's' + i; }) @@ -1249,13 +1247,16 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var BG_MAX = scaleBg(400); function roundByUnits(value) { - if (browserSettings.units == 'mmol') { + if (browserSettings.units === 'mmol') { return value.toFixed(1); } else { return Math.round(value); } } + //TODO: clean when moving the ar2 plugin + var y; + // these are the one sigma limits for the first 13 prediction interval uncertainties (65 minutes) var CONE = [0.020, 0.041, 0.061, 0.081, 0.099, 0.116, 0.132, 0.146, 0.159, 0.171, 0.182, 0.192, 0.201]; // these are modified to make the cone much blunter @@ -1263,7 +1264,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; // for testing //var CONE = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; if (actual.length < lookback+1) { - var y = [Math.log(actual[actual.length-1].sgv / BG_REF), Math.log(actual[actual.length-1].sgv / BG_REF)]; + y = [Math.log(actual[actual.length-1].sgv / BG_REF), Math.log(actual[actual.length-1].sgv / BG_REF)]; } else { var elapsedMins = (actual[actual.length-1].date - actual[actual.length-1-lookback].date) / ONE_MINUTE; // construct a '5m ago' sgv offset from current sgv by the average change over the lookback interval @@ -1281,7 +1282,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var AR = [-0.723, 1.716]; var dt = actual[lookback].date.getTime(); var predictedColor = 'blue'; - if (browserSettings.theme == 'colors') { + if (browserSettings.theme === 'colors') { predictedColor = 'cyan'; } for (var i = 0; i < CONE.length; i++) { @@ -1301,9 +1302,10 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; }; predicted.forEach(function (d) { d.type = 'forecast'; - if (d.sgv < BG_MIN) + if (d.sgv < BG_MIN) { d.color = 'transparent'; - }) + } + }); } return predicted; } @@ -1318,7 +1320,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; // Dim the screen by reducing the opacity when at nighttime if (browserSettings.nightMode) { var dateTime = new Date(); - if (opacity.current != opacity.NIGHT && (dateTime.getHours() > 21 || dateTime.getHours() < 7)) { + if (opacity.current !== opacity.NIGHT && (dateTime.getHours() > 21 || dateTime.getHours() < 7)) { $('body').css({ 'opacity': opacity.NIGHT }); } else { $('body').css({ 'opacity': opacity.DAY }); @@ -1327,7 +1329,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } function updateClockDisplay() { - if (inRetroMode()) return; + if (inRetroMode()) { + return; + } now = Date.now(); var dateTime = new Date(now); $('#currentTime').text(formatTime(dateTime, true)).css('text-decoration', ''); @@ -1343,7 +1347,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } function isTimeAgoAlarmType(alarmType) { - return alarmType == 'warnTimeAgo' || alarmType == 'urgentTimeAgo'; + return alarmType === 'warnTimeAgo' || alarmType === 'urgentTimeAgo'; } function checkTimeAgoAlarm(ago) { @@ -1354,7 +1358,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; currentAlarmType = alarm.type; console.info('generating timeAgoAlarm', alarm.type); $('#container').addClass('alarming-timeago'); - if (level == 'warn') { + if (level === 'warn') { generateAlarm(alarmSound); } else { generateAlarm(urgentAlarmSound); @@ -1376,12 +1380,12 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } if ( - (browserSettings.alarmTimeAgoWarn && ago.status == 'warn') - || (browserSettings.alarmTimeAgoUrgent && ago.status == 'urgent')) { + (browserSettings.alarmTimeAgoWarn && ago.status === 'warn') + || (browserSettings.alarmTimeAgoUrgent && ago.status === 'urgent')) { checkTimeAgoAlarm(ago); } - if (alarmingNow() && ago.status == 'current' && isTimeAgoAlarmType(currentAlarmType)) { + if (alarmingNow() && ago.status === 'current' && isTimeAgoAlarmType(currentAlarmType)) { $('#container').removeClass('alarming-timeago'); stopAlarm(true, ONE_MIN_IN_MS); } @@ -1408,7 +1412,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; .style('opacity', 0); // Tick Values - if (browserSettings.units == 'mmol') { + if (browserSettings.units === 'mmol') { tickValues = [ 2.0 , Math.round(scaleBg(app.thresholds.bg_low)) @@ -1573,7 +1577,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } else { return null; } - }).filter(function(entry) { return entry != null; }); + }).filter(function(entry) { return entry !== null; }); } var temp2 = SGVdata.map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), direction: obj.direction, color: sgvToColor(obj.y), type: 'sgv', noise: obj.noise, filtered: obj.filtered, unfiltered: obj.unfiltered}; diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js index bff843efa45..a9aa1ec3b9c 100644 --- a/static/js/ui-utils.js +++ b/static/js/ui-utils.js @@ -10,7 +10,7 @@ function getBrowserSettings(storage) { var json = {}; function scaleBg(bg) { - if (json.units == 'mmol') { + if (json.units === 'mmol') { return Nightscout.units.mgdlToMMOL(bg); } else { return bg; @@ -18,7 +18,7 @@ function getBrowserSettings(storage) { } function appendThresholdValue(threshold) { - return app.alarm_types.indexOf('simple') == -1 ? '' : ' (' + scaleBg(threshold) + ')'; + return app.alarm_types.indexOf('simple') === -1 ? '' : ' (' + scaleBg(threshold) + ')'; } try { @@ -42,7 +42,7 @@ function getBrowserSettings(storage) { // Default browser units to server units if undefined. json.units = setDefault(json.units, app.units); - if (json.units == 'mmol') { + if (json.units === 'mmol') { $('#mmol-browser').prop('checked', true); } else { $('#mgdl-browser').prop('checked', true); @@ -82,7 +82,7 @@ function getBrowserSettings(storage) { $('input#customTitle').prop('value', json.customTitle); json.theme = setDefault(json.theme, app.defaults.theme); - if (json.theme == 'colors') { + if (json.theme === 'colors') { $('#theme-colors-browser').prop('checked', true); } else { $('#theme-default-browser').prop('checked', true); @@ -90,7 +90,7 @@ function getBrowserSettings(storage) { json.timeFormat = setDefault(json.timeFormat, app.defaults.timeFormat); - if (json.timeFormat == '24') { + if (json.timeFormat === '24') { $('#24-browser').prop('checked', true); } else { $('#12-browser').prop('checked', true); @@ -179,7 +179,7 @@ function toggleDrawer(id, openCallback, closeCallback) { } - if (openDraw == id) { + if (openDraw === id) { closeDrawer(id, closeCallback); } else { openDrawer(id, openCallback); @@ -278,7 +278,7 @@ function treatmentSubmit(event) { window.alert(errors.join('\n')); } else { var eventTimeDisplay = ''; - if ($('#treatment-form input[name=nowOrOther]:checked').val() != 'now') { + if ($('#treatment-form input[name=nowOrOther]:checked').val() !== 'now') { var value = $('#eventTimeValue').val(); var eventTimeParts = value.split(':'); data.eventTime = new Date(); From 0f5a12b827419617d047287c009746d1e75888d5 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 1 Jul 2015 19:46:15 -0700 Subject: [PATCH 267/937] there are always more codacy issues to fix... --- lib/profilefunctions.js | 7 ++----- static/js/client.js | 45 ++++++++++++++++++++++++++--------------- static/js/ui-utils.js | 2 +- 3 files changed, 32 insertions(+), 22 deletions(-) diff --git a/lib/profilefunctions.js b/lib/profilefunctions.js index a937b1de501..5604bd500b5 100644 --- a/lib/profilefunctions.js +++ b/lib/profilefunctions.js @@ -20,9 +20,7 @@ function init(profileData) { // preprocess the timestamps to seconds for a couple orders of magnitude faster operation profile.preprocessProfileOnLoad = function preprocessProfileOnLoad(container) { - for (var key in container) { - var value = container[key]; - + _.forEach(container, function eachValue (value) { if( Object.prototype.toString.call(value) === '[object Array]' ) { profile.preprocessProfileOnLoad(value); } @@ -31,8 +29,7 @@ function init(profileData) { var sec = profile.timeStringToSeconds(value.time); if (!isNaN(sec)) { value.timeAsSeconds = sec; } } - - } + }); }; if (profileData) { profile.loadData(profileData); } diff --git a/static/js/client.js b/static/js/client.js index b669164d117..5ce392a4ded 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1206,7 +1206,8 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; .duration(TOOLTIP_TRANS_MS) .style('opacity', 0); }); - var arcs = treatmentDots.append('path') + + treatmentDots.append('path') .attr('class', 'path') .attr('fill', function (d) { return d.outlineOnly ? 'transparent' : d.color; }) .attr('stroke-width', function (d) { return d.outlineOnly ? 1 : 0; }) @@ -1285,6 +1286,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; if (browserSettings.theme === 'colors') { predictedColor = 'cyan'; } + for (var i = 0; i < CONE.length; i++) { y = [y[1], AR[0] * y[0] + AR[1] * y[1]]; dt = dt + FIVE_MINUTES; @@ -1300,13 +1302,15 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; sgv: Math.max(BG_MIN, Math.min(BG_MAX, roundByUnits(BG_REF * Math.exp((y[1] + 2 * CONE[i]))))), color: predictedColor }; - predicted.forEach(function (d) { - d.type = 'forecast'; - if (d.sgv < BG_MIN) { - d.color = 'transparent'; - } - }); } + + predicted.forEach(function (d) { + d.type = 'forecast'; + if (d.sgv < BG_MIN) { + d.color = 'transparent'; + } + }); + return predicted; } @@ -1492,7 +1496,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var silenceDropdown = new Dropdown('.dropdown-menu'); $('.bgButton').click(function (e) { - if (alarmingNow()) silenceDropdown.open(e); + if (alarmingNow()) { + silenceDropdown.open(e); + } }); $('#silenceBtn').find('a').click(function (e) { @@ -1527,10 +1533,14 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } // If there was no delta data, just return the original data - if (!receivedDataArray) return cachedDataArray; + if (!receivedDataArray) { + return cachedDataArray; + } // If this is not a delta update, replace all data - if (!isDelta) return receivedDataArray; + if (!isDelta) { + return receivedDataArray; + } // If this is delta, calculate the difference, merge and sort var diff = nsArrayDiff(cachedDataArray,receivedDataArray); @@ -1541,19 +1551,23 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; socket.on('dataUpdate', function receivedSGV(d) { - if (!d) return; + if (!d) { + return; + } // Calculate the diff to existing data and replace as needed SGVdata = mergeDataUpdate(d.delta, SGVdata, d.sgvs); MBGdata = mergeDataUpdate(d.delta,MBGdata, d.mbgs); treatments = mergeDataUpdate(d.delta,treatments, d.treatments); + if (d.profiles) { profile = d.profiles[0]; Nightscout.profile.loadData(d.profiles); } - if (d.cals) cal = d.cals[d.cals.length-1]; - if (d.devicestatus) devicestatusData = d.devicestatus; + + if (d.cals) { cal = d.cals[d.cals.length-1]; } + if (d.devicestatus) { devicestatusData = d.devicestatus; } // Do some reporting on the console console.log('Total SGV data size', SGVdata.length); @@ -1590,8 +1604,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; data = data.concat(MBGdata.map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), color: 'red', type: 'mbg', device: obj.device } })); data.forEach(function (d) { - if (d.y < 39) - d.color = 'transparent'; + if (d.y < 39) { d.color = 'transparent'; } }); // OPTIMIZATION: precalculate treatment location in timeline @@ -1616,7 +1629,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; // Alarms and Text handling //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// socket.on('connect', function () { - console.log('Client connected to server.') + console.log('Client connected to server.'); }); diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js index a9aa1ec3b9c..f461981e33d 100644 --- a/static/js/ui-utils.js +++ b/static/js/ui-utils.js @@ -22,7 +22,7 @@ function getBrowserSettings(storage) { } try { - var json = { + json = { 'units': storage.get('units'), 'alarmUrgentHigh': storage.get('alarmUrgentHigh'), 'alarmHigh': storage.get('alarmHigh'), From 6c84eac2e8ab366c17385af08fbc75fcecf2684a Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 1 Jul 2015 23:22:08 -0700 Subject: [PATCH 268/937] Added Codacy badge --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index c05ecb40f0f..5ff52f8c910 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ Nightscout Web Monitor (a.k.a. cgm-remote-monitor) [![Build Status][build-img]][build-url] [![Dependency Status][dependency-img]][dependency-url] [![Coverage Status][coverage-img]][coverage-url] +[![Codacy Badge][codacy-img]][codacy-url] [![Gitter chat][gitter-img]][gitter-url] [![Stories in Ready][ready-img]][waffle] [![Stories in Progress][progress-img]][waffle] @@ -32,6 +33,8 @@ Community maintained fork of the [dependency-url]: https://david-dm.org/nightscout/cgm-remote-monitor [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 +[codacy-img]: https://www.codacy.com/project/badge/f79327216860472dad9afda07de39d3b +[codacy-url]: https://www.codacy.com/app/Nightscout/cgm-remote-monitor [gitter-img]: https://img.shields.io/badge/Gitter-Join%20Chat%20%E2%86%92-1dce73.svg [gitter-url]: https://gitter.im/nightscout/public [ready-img]: https://badge.waffle.io/nightscout/cgm-remote-monitor.svg?label=ready&title=Ready From 4649296bfd2c146f1f5243cec19eb0b8e03bd092 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 1 Jul 2015 23:55:22 -0700 Subject: [PATCH 269/937] added test to reproduce #664, and fixed the bug --- lib/plugins/ar2.js | 16 +++++++++++----- tests/ar2.test.js | 13 +++++++++++++ 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 855dd35164b..577563bb07e 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -39,10 +39,12 @@ function init() { var cal = _.last(sbx.data.cals); if (cal) { var rawSGVs = _.map(_.takeRight(sbx.data.sgvs, 2), function eachSGV (sgv) { - return { - x: sgv.x - , y: Math.max(rawbg.calc(sgv, cal), BG_MIN) //stay above BG_MIN - }; + var rawResult = rawbg.calc(sgv, cal); + + //ignore when raw is 0, and use BG_MIN if raw is lower + var rawY = rawResult === 0 ? 0 : Math.max(rawResult, BG_MIN); + + return { x: sgv.x, y: rawY }; }); result = ar2.forcastAndCheck(rawSGVs, true); usingRaw = true; @@ -158,10 +160,14 @@ function init() { , avgLoss: 0 }; + if (lastIndex < 1) { + return result; + } + var current = sgvs[lastIndex].y; var prev = sgvs[lastIndex - 1].y; - if (lastIndex > 0 && current >= BG_MIN && sgvs[lastIndex - 1].y >= BG_MIN) { + if (current >= BG_MIN && sgvs[lastIndex - 1].y >= BG_MIN) { // predict using AR model var lastValidReadingTime = sgvs[lastIndex].x; var elapsedMins = (sgvs[lastIndex].x - sgvs[lastIndex - 1].x) / ONE_MINUTE; diff --git a/tests/ar2.test.js b/tests/ar2.test.js index f560e5937b4..28ca4e8e6f1 100644 --- a/tests/ar2.test.js +++ b/tests/ar2.test.js @@ -99,6 +99,19 @@ describe('ar2', function ( ) { return require('../lib/sandbox')().serverInit(envRaw, ctx); } + it('should not trigger an alarm when raw is missing or 0', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{unfiltered: 0, filtered: 0, y: 100, x: before, noise: 1}, {unfiltered: 0, filtered: 0, y: 100, x: now, noise: 1}]; + ctx.data.cals = [{scale: 1, intercept: 25717.82377004309, slope: 766.895601715918}]; + + var sbx = rawSandbox(ctx); + ar2.checkNotifications(sbx.withExtendedSettings(ar2)); + should.not.exist(ctx.notifications.findHighestAlarm()); + + done(); + }); + + it('should trigger a warning (no urgent for raw) when raw is falling really fast, but sgv is steady', function (done) { ctx.notifications.initRequests(); ctx.data.sgvs = [{unfiltered: 113680, filtered: 111232, y: 100, x: before, noise: 1}, {unfiltered: 43680, filtered: 111232, y: 100, x: now, noise: 1}]; From 5ea7b3e83ef1d888f07137e121268830a4d1238b Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 2 Jul 2015 18:26:20 -0700 Subject: [PATCH 270/937] remove some duplication when formatting messages --- lib/plugins/ar2.js | 34 +++++---------------------- lib/plugins/boluswizardpreview.js | 30 ++---------------------- lib/plugins/cob.js | 5 ++-- lib/plugins/iob.js | 10 ++++---- lib/plugins/rawbg.js | 1 + lib/plugins/simplealarms.js | 32 +------------------------- lib/sandbox.js | 36 +++++++++++++++++++++++++++++ tests/ar2.test.js | 5 +++- tests/sandbox.test.js | 38 +++++++++++++++++++++++-------- 9 files changed, 88 insertions(+), 103 deletions(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 577563bb07e..f4e11fe643a 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -84,35 +84,13 @@ function init() { title += ' w/raw'; } - var lines = ['BG Now: ' + sbx.displayBg(sbx.data.lastSGV())]; + var lines = sbx.prepareDefaultLines(sbx.data.lastSGV()); - var delta = sbx.properties.delta && sbx.properties.delta.display; - if (delta) { - lines[0] += ' ' + delta; - } - lines[0] += ' ' + sbx.unitsLabel; - - var rawbgProp = sbx.properties.rawbg; - if (rawbgProp) { - lines.push(['Raw BG:', sbx.scaleBg(rawbgProp.value), sbx.unitsLabel, rawbgProp.noiseLabel].join(' ')); - } - - lines.push([usingRaw ? 'Raw BG' : 'BG', '15m:', sbx.scaleBg(predicted[2]), sbx.unitsLabel].join(' ')); - - var bwp = sbx.properties.bwp && sbx.properties.bwp.bolusEstimateDisplay; - if (bwp && bwp > 0) { - lines.push(['BWP:', bwp, 'U'].join(' ')); - } - - var iob = sbx.properties.iob && sbx.properties.iob.display; - if (iob) { - lines.push(['IOB:', iob, 'U'].join(' ')); - } - - var cob = sbx.properties.cob && sbx.properties.cob.display; - if (cob) { - lines.push(['COB:', cob, 'g'].join(' ')); - } + //insert prediction after the first line + lines.splice(1 + , 0 + , [usingRaw ? 'Raw BG' : 'BG', '15m:', sbx.scaleBg(predicted[2]), sbx.unitsLabel].join(' ') + ); var message = lines.join('\n'); diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 6ece9487671..9013d62d7be 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -76,37 +76,10 @@ function init() { var levelLabel = sbx.notifications.levels.toString(level); var sound = level === sbx.notifications.levels.URGENT ? 'updown' : 'bike'; - var lines = ['BG Now: ' + results.displaySGV]; - - var delta = sbx.properties.delta && sbx.properties.delta.display; - if (delta) { - lines[0] += ' ' + delta; - } - lines[0] += ' ' + sbx.unitsLabel; - - var rawbgProp = sbx.properties.rawbg; - if (rawbgProp) { - lines.push(['Raw BG:', sbx.scaleBg(rawbgProp.value), sbx.unitsLabel, rawbgProp.noiseLabel].join(' ')); - } - - lines.push(['BWP:', results.bolusEstimateDisplay, 'U'].join(' ')); - - var iob = sbx.properties.iob && sbx.properties.iob.display; - if (iob) { - lines.push(['IOB:', iob, 'U'].join(' ')); - } - - var cob = sbx.properties.cob && sbx.properties.cob.display; - if (cob) { - lines.push(['COB:', cob, 'g'].join(' ')); - } - - var message = lines.join('\n'); - sbx.notifications.requestNotify({ level: level , title: levelLabel + ', Check BG, time to bolus?' - , message: message + , message: sbx.buildDefaultMessage(results.displaySGV) , eventName: 'bwp' , pushoverSound: sound , plugin: bwp @@ -205,6 +178,7 @@ function init() { results.outcomeDisplay = sbx.roundBGToDisplayFormat(results.outcome); results.displayIOB = sbx.roundInsulinForDisplayFormat(results.iob); results.effectDisplay = sbx.roundBGToDisplayFormat(results.effect); + results.displayLine = 'BWP: ' + results.bolusEstimateDisplay + 'U'; return results; }; diff --git a/lib/plugins/cob.js b/lib/plugins/cob.js index 8f8c9f5c68b..0bb076dafba 100644 --- a/lib/plugins/cob.js +++ b/lib/plugins/cob.js @@ -81,14 +81,15 @@ function init() { }); var rawCarbImpact = isDecaying * profile.getSensitivity(time) / profile.getCarbRatio(time) * profile.getCarbAbsorptionRate(time) / 60; - + var display = Math.round(totalCOB * 10) / 10; return { decayedBy: lastDecayedBy , isDecaying: isDecaying , carbs_hr: profile.getCarbAbsorptionRate(time) , rawCarbImpact: rawCarbImpact , cob: totalCOB - , display: Math.round(totalCOB * 10) / 10 + , display: display + , displayLine: 'COB: ' + display + 'g' , lastCarbs: lastCarbs }; }; diff --git a/lib/plugins/iob.js b/lib/plugins/iob.js index d1b2fbe4eab..dec67f44ab0 100644 --- a/lib/plugins/iob.js +++ b/lib/plugins/iob.js @@ -43,11 +43,13 @@ function init() { } }); + var display = utils.toFixed(totalIOB); return { - iob: totalIOB, - display: utils.toFixed(totalIOB), - activity: totalActivity, - lastBolus: lastBolus + iob: totalIOB + , display: display + , displayLine: 'IOB: ' + display + 'U' + , activity: totalActivity + , lastBolus: lastBolus }; }; diff --git a/lib/plugins/rawbg.js b/lib/plugins/rawbg.js index a3635017805..9619bf21a4a 100644 --- a/lib/plugins/rawbg.js +++ b/lib/plugins/rawbg.js @@ -24,6 +24,7 @@ function init() { result.noiseLabel = rawbg.noiseCodeToDisplay(currentSGV.y, currentSGV.noise); result.sgv = currentSGV; result.cal = currentCal; + result.displayLine = ['Raw BG:', sbx.scaleBg(result.value), sbx.unitsLabel, result.noiseLabel].join(' '); } return result; diff --git a/lib/plugins/simplealarms.js b/lib/plugins/simplealarms.js index b056ac8f087..f0edf4ecf9d 100644 --- a/lib/plugins/simplealarms.js +++ b/lib/plugins/simplealarms.js @@ -56,41 +56,11 @@ function init() { console.info(title + ': ' + (lastSGV + ' < ' + sbx.scaleBg(sbx.thresholds.bg_target_bottom))); } - var lines = ['BG Now: ' + displaySGV]; - - var delta = sbx.properties.delta && sbx.properties.delta.display; - if (delta) { - lines[0] += ' ' + delta; - } - lines[0] += ' ' + sbx.unitsLabel; - - var rawbgProp = sbx.properties.rawbg; - if (rawbgProp) { - lines.push(['Raw BG:', sbx.scaleBg(rawbgProp.value), sbx.unitsLabel, rawbgProp.noiseLabel].join(' ')); - } - - var bwp = sbx.properties.bwp && sbx.properties.bwp.bolusEstimateDisplay; - if (bwp && bwp > 0) { - lines.push(['BWP:', bwp, 'U'].join(' ')); - } - - var iob = sbx.properties.iob && sbx.properties.iob.display; - if (iob) { - lines.push(['IOB:', iob, 'U'].join(' ')); - } - - var cob = sbx.properties.cob && sbx.properties.cob.display; - if (cob) { - lines.push(['COB:', cob, 'g'].join(' ')); - } - - var message = lines.join('\n'); - if (trigger) { sbx.notifications.requestNotify({ level: level , title: title - , message: message + , message: sbx.buildDefaultMessage(displaySGV) , eventName: eventName , plugin: simplealarms , pushoverSound: pushoverSound diff --git a/lib/sandbox.js b/lib/sandbox.js index 21015a9ce27..83703370bad 100644 --- a/lib/sandbox.js +++ b/lib/sandbox.js @@ -119,6 +119,42 @@ function init ( ) { } }; + sbx.buildBGNowLine = function buildBGNowLine (displaySGV) { + var line = 'BG Now: ' + displaySGV; + var delta = sbx.properties.delta && sbx.properties.delta.display; + if (delta) { + line += ' ' + delta; + } + line += ' ' + sbx.unitsLabel; + + return line; + }; + + sbx.appendPropertyLine = function appendPropertyLine (propertyName, lines) { + lines = lines || []; + + var displayLine = sbx.properties[propertyName] && sbx.properties[propertyName].displayLine; + if (displayLine) { + lines.push(displayLine); + } + + return lines; + }; + + sbx.prepareDefaultLines = function prepareDefaultLines(displaySGV) { + var lines = [sbx.buildBGNowLine(displaySGV)]; + sbx.appendPropertyLine('rawbg', lines); + sbx.appendPropertyLine('bwp', lines); + sbx.appendPropertyLine('iob', lines); + sbx.appendPropertyLine('cob', lines); + + return lines; + }; + + sbx.buildDefaultMessage = function buildDefaultMessage(displaySGV) { + return sbx.prepareDefaultLines(displaySGV).join('\n'); + }; + function displayBg (bg) { if (Number(bg) === 39) { return 'LOW'; diff --git a/tests/ar2.test.js b/tests/ar2.test.js index 28ca4e8e6f1..20661ba0e92 100644 --- a/tests/ar2.test.js +++ b/tests/ar2.test.js @@ -32,11 +32,14 @@ describe('ar2', function ( ) { var sbx = require('../lib/sandbox')().serverInit(env, ctx); delta.setProperties(sbx); + sbx.offerProperty('iob', function setFakeIOB() { + return {displayLine: 'IOB: 1.25U'}; + }); ar2.checkNotifications(sbx); var highest = ctx.notifications.findHighestAlarm(); highest.level.should.equal(ctx.notifications.levels.WARN); highest.title.should.equal('Warning, HIGH predicted'); - highest.message.should.startWith('BG Now: 170 +20 mg/dl'); + highest.message.should.equal('BG Now: 170 +20 mg/dl\nBG 15m: 206 mg/dl\nIOB: 1.25U'); done(); }); diff --git a/tests/sandbox.test.js b/tests/sandbox.test.js index 58326a17c94..8eafaebf29d 100644 --- a/tests/sandbox.test.js +++ b/tests/sandbox.test.js @@ -29,14 +29,18 @@ describe('sandbox', function ( ) { done(); }); - it('init on server', function (done) { + function createServerSandbox() { var env = require('../env')(); var ctx = {}; ctx.data = require('../lib/data')(env, ctx); - ctx.data.sgvs = [{sgv: 100}]; ctx.notifications = require('../lib/notifications')(env, ctx); - var sbx = sandbox.serverInit(env, ctx); + return sandbox.serverInit(env, ctx); + } + + it('init on server', function (done) { + var sbx = createServerSandbox(); + sbx.data.sgvs = [{sgv: 100}]; should.exist(sbx.notifications.requestNotify); should.not.exist(sbx.notifications.process); @@ -47,12 +51,7 @@ describe('sandbox', function ( ) { }); it('display 39 as LOW and 401 as HIGH', function () { - var env = require('../env')(); - var ctx = {}; - ctx.data = require('../lib/data')(env, ctx); - ctx.notifications = require('../lib/notifications')(env, ctx); - - var sbx = sandbox.serverInit(env, ctx); + var sbx = createServerSandbox(); sbx.displayBg(39).should.equal('LOW'); sbx.displayBg('39').should.equal('LOW'); @@ -60,4 +59,25 @@ describe('sandbox', function ( ) { sbx.displayBg('401').should.equal('HIGH'); }); + it('build BG Now line using properties', function ( ) { + var sbx = createServerSandbox(); + sbx.properties = { delta: {display: '+5' } }; + + sbx.buildBGNowLine(99).should.equal('BG Now: 99 +5 mg/dl'); + + }); + + it('build default message using properties', function ( ) { + var sbx = createServerSandbox(); + sbx.properties = { + delta: {display: '+5' } + , rawbg: {displayLine: 'Raw BG: 100 mg/dl'} + , iob: {displayLine: 'IOB: 1.25U'} + , cob: {displayLine: 'COB: 15g'} + }; + + sbx.buildDefaultMessage(99).should.equal('BG Now: 99 +5 mg/dl\nRaw BG: 100 mg/dl\nIOB: 1.25U\nCOB: 15g'); + + }); + }); From 853a58c94e5dfb8825835b3692235660e1180b09 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 3 Jul 2015 01:01:57 -0700 Subject: [PATCH 271/937] converted the direction to a plugin less code in client.js and using it for messages too --- README.md | 1 + env.js | 4 +- lib/data.js | 19 +------- lib/plugins/direction.js | 72 ++++++++++++++++++++++++++++ lib/plugins/index.js | 4 +- lib/plugins/pluginbase.js | 16 ++++--- lib/plugins/rawbg.js | 2 +- lib/sandbox.js | 7 +++ static/css/main.css | 23 ++++----- static/index.html | 2 +- static/js/client.js | 28 +---------- tests/ar2.test.js | 5 +- tests/direction.test.js | 98 +++++++++++++++++++++++++++++++++++++++ tests/sandbox.test.js | 7 +-- 14 files changed, 218 insertions(+), 70 deletions(-) create mode 100644 lib/plugins/direction.js create mode 100644 tests/direction.test.js diff --git a/README.md b/README.md index 5ff52f8c910..72a1e09029c 100644 --- a/README.md +++ b/README.md @@ -180,6 +180,7 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * `BWP_SNOOZE` - (`0.10`) If BG is higher then the `target_high` and `BWP` < `BWP_SNOOZE` alarms will be snoozed for `BWP_SNOOZE_MINS`. * `cage` (Cannula Age) - Calculates the number of hours since the last `Site Change` treatment that was recorded. * `delta` (BG Delta) - Calculates and displays the change between the last 2 BG values. **Enabled by default.** + * `direction` (BG Direction) - Displays the trend direction. **Enabled by default.** * `upbat` (Uploader Battery) - Displays the most recent battery status from the uploader phone. **Enabled by default.** * `ar2` ([Forcasting using AR2 algorithm](https://github.com/nightscout/nightscout.github.io/wiki/Forecasting)) - Generates alarms based on forecasted values. **Enabled by default.** Use [extended setting](#extended-settings) `AR2_USE_RAW=true` to forecast using `rawbg` values. * `simplealarms` (Simple BG Alarms) - Uses `BG_HIGH`, `BG_TARGET_TOP`, `BG_TARGET_BOTTOM`, `BG_LOW` settings to generate alarms. diff --git a/env.js b/env.js index 1c212aa9709..cb1b0365666 100644 --- a/env.js +++ b/env.js @@ -95,7 +95,7 @@ function config ( ) { //TODO: figure out something for some plugins to have them shown by default if (env.defaults.showPlugins !== '') { - env.defaults.showPlugins += ' delta upbat'; + env.defaults.showPlugins += ' delta direction upbat'; if (env.defaults.showRawbg === 'always' || env.defaults.showRawbg === 'noise') { env.defaults.showPlugins += 'rawbg'; } @@ -193,7 +193,7 @@ function config ( ) { } //TODO: figure out something for default plugins, how can they be disabled? - env.enable += ' delta upbat errorcodes'; + env.enable += ' delta direction upbat errorcodes'; env.extendedSettings = findExtendedSettings(env.enable, process.env); diff --git a/lib/data.js b/lib/data.js index a5c04eafc81..802523ba755 100644 --- a/lib/data.js +++ b/lib/data.js @@ -27,23 +27,6 @@ function init(env, ctx) { , TWO_DAYS = 172800000; - var dir2Char = { - 'NONE': '⇼', - 'DoubleUp': '⇈', - 'SingleUp': '↑', - 'FortyFiveUp': '↗', - 'Flat': '→', - 'FortyFiveDown': '↘', - 'SingleDown': '↓', - 'DoubleDown': '⇊', - 'NOT COMPUTABLE': '-', - 'RATE OUT OF RANGE': '↮' - }; - - function directionToChar(direction) { - return dir2Char[direction] || '-'; - } - data.clone = function clone() { return _.cloneDeep(data, function (value) { //special handling of mongo ObjectID's @@ -83,7 +66,7 @@ function init(env, ctx) { }); } else if (element.sgv) { sgvs.push({ - y: element.sgv, x: element.date, device: element.device, direction: directionToChar(element.direction), filtered: element.filtered, unfiltered: element.unfiltered, noise: element.noise, rssi: element.rssi + y: element.sgv, x: element.date, device: element.device, direction: element.direction, filtered: element.filtered, unfiltered: element.unfiltered, noise: element.noise, rssi: element.rssi }); } } diff --git a/lib/plugins/direction.js b/lib/plugins/direction.js new file mode 100644 index 00000000000..0a282f47dfd --- /dev/null +++ b/lib/plugins/direction.js @@ -0,0 +1,72 @@ +'use strict'; + +var _ = require('lodash'); + +function init() { + + function direction() { + return direction; + } + + direction.label = 'BG direction'; + direction.pluginType = 'bg-status'; + + direction.setProperties = function setProperties (sbx) { + sbx.offerProperty('direction', function setDirection ( ) { + return direction.info(_.last(sbx.data.sgvs)); + }); + }; + + direction.updateVisualisation = function updateVisualisation (sbx) { + var prop = sbx.properties.direction; + + var lastSGV = _.last(sbx.data.sgvs); + if (lastSGV && lastSGV.y < 39) { + prop.value = 'CGM ERROR'; + prop.label = '✖'; + } + + sbx.pluginBase.updatePillText(direction, { + label: prop.label + , directText: true + }); + }; + + direction.info = function info(sgv) { + var result = { display: null }; + + if (!sgv) { return result; } + + result.value = sgv.direction; + result.label = directionToChar(result.value); + result.entity = charToEntity(result.label); + + return result; + }; + + var dir2Char = { + NONE: '⇼' + , DoubleUp: '⇈' + , SingleUp: '↑' + , FortyFiveUp: '↗' + , Flat: '→' + , FortyFiveDown: '↘' + , SingleDown: '↓' + , DoubleDown: '⇊' + , 'NOT COMPUTABLE': '-' + , 'RATE OUT OF RANGE': '⇕' + }; + + function charToEntity(char) { + return char && char.length && '&#' + char.charCodeAt(0) + ';'; + } + + function directionToChar(direction) { + return dir2Char[direction] || '-'; + } + + return direction(); + +} + +module.exports = init; \ No newline at end of file diff --git a/lib/plugins/index.js b/lib/plugins/index.js index d92c37b0b8e..03c1ca8eaef 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -20,6 +20,7 @@ function init() { var clientDefaultPlugins = [ require('./rawbg')() , require('./delta')() + , require('./direction')() , require('./iob')() , require('./cob')() , require('./boluswizardpreview')() @@ -31,6 +32,7 @@ function init() { var serverDefaultPlugins = [ require('./rawbg')() , require('./delta')() + , require('./direction')() , require('./ar2')() , require('./simplealarms')() , require('./errorcodes')() @@ -82,7 +84,7 @@ function init() { }; //these plugins are either always on or have custom settings - plugins.specialPlugins = 'delta upbat rawbg'; + plugins.specialPlugins = 'delta direction upbat rawbg'; plugins.shownPlugins = function(sbx) { return _.filter(enabledPlugins, function filterPlugins(plugin) { diff --git a/lib/plugins/pluginbase.js b/lib/plugins/pluginbase.js index ac876edc9a4..a06b24625c9 100644 --- a/lib/plugins/pluginbase.js +++ b/lib/plugins/pluginbase.js @@ -15,7 +15,7 @@ function init (majorPills, minorPills, statusPills, bgStatus, tooltip) { container = majorPills; } else if (plugin.pluginType === 'pill-status') { container = statusPills; - } else if (plugin.pluginType === 'pill-alt') { + } else if (plugin.pluginType === 'bg-status') { container = bgStatus; } else { container = minorPills; @@ -59,13 +59,17 @@ function init (majorPills, minorPills, statusPills, bgStatus, tooltip) { pill.addClass(options.pillClass); - pill.find('label').attr('class', options.labelClass).text(options.label); + if (options.directText) { + pill.text(options.label); + } else { + pill.find('label').attr('class', options.labelClass).text(options.label); - pill.find('em') - .attr('class', options.valueClass) - .toggle(options.value != null) - .text(options.value) + pill.find('em') + .attr('class', options.valueClass) + .toggle(options.value != null) + .text(options.value) ; + } if (options.info) { var html = _.map(options.info, function mapInfo (i) { diff --git a/lib/plugins/rawbg.js b/lib/plugins/rawbg.js index 9619bf21a4a..233e3883216 100644 --- a/lib/plugins/rawbg.js +++ b/lib/plugins/rawbg.js @@ -9,7 +9,7 @@ function init() { } rawbg.label = 'Raw BG'; - rawbg.pluginType = 'pill-alt'; + rawbg.pluginType = 'bg-status'; rawbg.pillFlip = true; rawbg.setProperties = function setProperties (sbx) { diff --git a/lib/sandbox.js b/lib/sandbox.js index 83703370bad..071b7b8153b 100644 --- a/lib/sandbox.js +++ b/lib/sandbox.js @@ -121,10 +121,17 @@ function init ( ) { sbx.buildBGNowLine = function buildBGNowLine (displaySGV) { var line = 'BG Now: ' + displaySGV; + var delta = sbx.properties.delta && sbx.properties.delta.display; if (delta) { line += ' ' + delta; } + + var trend = sbx.properties.trend && sbx.properties.trend.label; + if (trend) { + line += ' ' + trend; + } + line += ' ' + sbx.unitsLabel; return line; diff --git a/static/css/main.css b/static/css/main.css index bb17a8fda3a..e5324398406 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -68,12 +68,13 @@ body { font-size: 90px; } -.bgStatus .currentDirection { - font-weight: normal; - text-decoration: none; - font-size: 90px; - line-height: 90px; - vertical-align: middle; +.bgStatus .pill.direction { + font-weight: normal; + text-decoration: none; + font-size: 90px; + line-height: 90px; + vertical-align: middle; + border: none; } .bgStatus .majorPills { @@ -360,7 +361,7 @@ div.tooltip { font-size: 70px; } - .bgStatus .currentDirection { + .bgStatus .pill.direction { font-size: 70px; line-height: 70px; } @@ -442,7 +443,7 @@ div.tooltip { font-size: 50px; } - .bgStatus .currentDirection { + .bgStatus .pill.direction { font-size: 50px; line-height: 50px; } @@ -500,7 +501,7 @@ div.tooltip { padding-left: 20px; } - .bgStatus .currentDirection { + .bgStatus .pill.direction { font-size: 60px; line-height: 60px; } @@ -626,7 +627,7 @@ div.tooltip { font-size: 70px; } - .bgStatus .currentDirection { + .bgStatus .pill.direction { font-size: 50px; line-height: 50px; } @@ -673,7 +674,7 @@ div.tooltip { font-size: 60px; } - .bgStatus .currentDirection { + .bgStatus .pill.direction { font-size: 50px; line-height: 50px; } diff --git a/static/index.html b/static/index.html index a44fca3ca42..38d8fb23c84 100644 --- a/static/index.html +++ b/static/index.html @@ -49,7 +49,7 @@

    Nightscout

    --- - - + -
    @@ -256,6 +257,7 @@

    Nightscout

    + diff --git a/static/js/treatment.js b/static/js/treatment.js new file mode 100644 index 00000000000..2beec340066 --- /dev/null +++ b/static/js/treatment.js @@ -0,0 +1,116 @@ +'use strict'; + +function initTreatmentDrawer() { + $('#eventType').val('BG Check'); + $('#glucoseValue').val('').attr('placeholder', 'Value in ' + browserSettings.units); + $('#meter').prop('checked', true); + $('#carbsGiven').val(''); + $('#insulinGiven').val(''); + $('#preBolus').val(0); + $('#notes').val(''); + $('#enteredBy').val(browserStorage.get('enteredBy') || ''); + $('#nowtime').prop('checked', true); + $('#eventTimeValue').val(new Date().toTimeString().slice(0,5)); + $('#eventDateValue').val(new Date().toDateInputValue()); +} + +function treatmentSubmit(event) { + + var data = {}; + data.enteredBy = $('#enteredBy').val(); + data.eventType = $('#eventType').val(); + data.glucose = $('#glucoseValue').val(); + data.glucoseType = $('#treatment-form input[name=glucoseType]:checked').val(); + data.carbs = $('#carbsGiven').val(); + data.insulin = $('#insulinGiven').val(); + data.preBolus = parseInt($('#preBolus').val()); + data.notes = $('#notes').val(); + data.units = browserSettings.units; + + var errors = []; + if (isNaN(data.glucose)) { + errors.push('Blood glucose must be a number'); + } + + if (isNaN(data.carbs)) { + errors.push('Carbs must be a number'); + } + + if (isNaN(data.insulin)) { + errors.push('Insulin must be a number'); + } + + if (errors.length > 0) { + window.alert(errors.join('\n')); + } else { + var eventTimeDisplay = ''; + if ($('#othertime').is(':checked')) { + data.eventTime = mergeInputTime('#eventTimeValue','#eventDateValue'); + eventTimeDisplay = data.eventTime.toLocaleString(); + } + + var dataJson = JSON.stringify(data, null, ' '); + + var ok = window.confirm( + 'Please verify that the data entered is correct: ' + + '\nEvent type: ' + data.eventType + + ( data.glucose ? '\nBlood glucose: ' + data.glucose + + '\nMethod: ' + data.glucoseType : '' ) + + ( data.carbs ? '\nCarbs Given: ' + data.carbs : '' ) + + ( data.insulin ? '\nInsulin Given: ' + data.insulin : '' ) + + ( data.preBolus ? '\nPre Bolus: ' + data.preBolus : '' ) + + ( data.notes ? '\nNotes: ' + data.notes : '' ) + + ( data.enteredBy ? '\nEntered By: ' + data.enteredBy : '') + + (eventTimeDisplay ? '\nEvent Time: ' + eventTimeDisplay: '' ) + ); + + if (ok) { + var xhr = new XMLHttpRequest(); + xhr.open('POST', '/api/v1/treatments/', true); + xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); + xhr.send(dataJson); + + browserStorage.set('enteredBy', data.enteredBy); + + closeDrawer('#treatmentDrawer'); + } + } + + if (event) { + event.preventDefault(); + } +} + +$('#treatmentDrawerToggle').click(function(event) { + toggleDrawer('#treatmentDrawer', initTreatmentDrawer); + event.preventDefault(); +}); + +$('#treatmentDrawer').find('button').click(treatmentSubmit); + +$('#eventTime input:radio').change(function (event){ + if ($('#othertime').is(':checked')) { + $('#eventTimeValue').focus(); + } + event.preventDefault(); +}); + +$('.eventtimeinput').focus(function (event) { + $('#othertime').prop('checked', true); + var time = mergeInputTime('#eventTimeValue','#eventDateValue'); + $(this).attr('oldminutes',time.getMinutes()); + $(this).attr('oldhours',time.getHours()); + event.preventDefault(); +}); + +$('.eventtimeinput').change(function (event) { + $('#othertime').prop('checked', true); + var time = mergeInputTime('#eventTimeValue','#eventDateValue'); + if ($(this).attr('oldminutes')==59 && time.getMinutes()==0) time.addHours(1); + if ($(this).attr('oldminutes')==0 && time.getMinutes()==59) time.addHours(-1); + $('#eventTimeValue').val(time.toTimeString().slice(0,5)); + $('#eventDateValue').val(time.toDateInputValue()); + $(this).attr('oldminutes',time.getMinutes()); + $(this).attr('oldhours',time.getHours()); + event.preventDefault(); +}); diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js index 0e144b81a56..770e51c7f81 100644 --- a/static/js/ui-utils.js +++ b/static/js/ui-utils.js @@ -187,19 +187,6 @@ function toggleDrawer(id, openCallback, closeCallback) { } -function initTreatmentDrawer() { - $('#eventType').val('BG Check'); - $('#glucoseValue').val('').attr('placeholder', 'Value in ' + browserSettings.units); - $('#meter').prop('checked', true); - $('#carbsGiven').val(''); - $('#insulinGiven').val(''); - $('#preBolus').val(0); - $('#notes').val(''); - $('#enteredBy').val(browserStorage.get('enteredBy') || ''); - $('#nowtime').prop('checked', true); - $('#eventTimeValue').val(currentTime()); -} - function currentTime() { var now = new Date(); var hours = now.getHours(); @@ -248,79 +235,6 @@ function showLocalstorageError() { } -function treatmentSubmit(event) { - - var data = {}; - data.enteredBy = $('#enteredBy').val(); - data.eventType = $('#eventType').val(); - data.glucose = $('#glucoseValue').val(); - data.glucoseType = $('#treatment-form input[name=glucoseType]:checked').val(); - data.carbs = $('#carbsGiven').val(); - data.insulin = $('#insulinGiven').val(); - data.preBolus = $('#preBolus').val(); - data.notes = $('#notes').val(); - data.units = browserSettings.units; - - var errors = []; - if (isNaN(data.glucose)) { - errors.push('Blood glucose must be a number'); - } - - if (isNaN(data.carbs)) { - errors.push('Carbs must be a number'); - } - - if (isNaN(data.insulin)) { - errors.push('Insulin must be a number'); - } - - if (errors.length > 0) { - window.alert(errors.join('\n')); - } else { - var eventTimeDisplay = ''; - if ($('#treatment-form input[name=nowOrOther]:checked').val() !== 'now') { - var value = $('#eventTimeValue').val(); - var eventTimeParts = value.split(':'); - data.eventTime = new Date(); - data.eventTime.setHours(eventTimeParts[0]); - data.eventTime.setMinutes(eventTimeParts[1]); - data.eventTime.setSeconds(0); - data.eventTime.setMilliseconds(0); - eventTimeDisplay = formatTime(data.eventTime); - } - - var dataJson = JSON.stringify(data, null, ' '); - - var ok = window.confirm( - 'Please verify that the data entered is correct: ' + - '\nEvent type: ' + data.eventType + - '\nBlood glucose: ' + data.glucose + - '\nMethod: ' + data.glucoseType + - '\nCarbs Given: ' + data.carbs + - '\nInsulin Given: ' + data.insulin + - '\nPre Bolus: ' + data.preBolus + - '\nNotes: ' + data.notes + - '\nEntered By: ' + data.enteredBy + - '\nEvent Time: ' + eventTimeDisplay); - - if (ok) { - var xhr = new XMLHttpRequest(); - xhr.open('POST', '/api/v1/treatments/', true); - xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); - xhr.send(dataJson); - - browserStorage.set('enteredBy', data.enteredBy); - - closeDrawer('#treatmentDrawer'); - } - } - - if (event) { - event.preventDefault(); - } -} - - var querystring = getQueryParms(); function Dropdown(el) { @@ -349,23 +263,6 @@ $('#drawerToggle').click(function(event) { event.preventDefault(); }); -$('#treatmentDrawerToggle').click(function(event) { - toggleDrawer('#treatmentDrawer', initTreatmentDrawer); - event.preventDefault(); -}); - -$('#treatmentDrawer').find('button').click(treatmentSubmit); - -$('#eventTime input:radio').change(function (){ - if ($('#othertime').attr('checked')) { - $('#eventTimeValue').focus(); - } -}); - -$('#eventTimeValue').focus(function () { - $('#othertime').attr('checked', 'checked'); -}); - $('#notification').click(function(event) { closeNotification(); event.preventDefault(); @@ -444,3 +341,37 @@ $(function() { openDrawer('#drawer'); } }); + +// some helpers for input "date" +function mergeInputTime(timeid,dateid) { + var value = $(timeid).val(); + var eventTimeParts = value.split(':'); + var eventTime = new Date($(dateid).val()); + eventTime.setHours(eventTimeParts[0]); + eventTime.setMinutes(eventTimeParts[1]); + eventTime.setSeconds(0); + eventTime.setMilliseconds(0); + return eventTime; +} + +Date.prototype.toDateInputValue = function toDateInputValue() { + var local = new Date(this); + local.setMinutes(this.getMinutes() - this.getTimezoneOffset()); + return local.toJSON().slice(0,10); +} + +Date.prototype.addMinutes = function addMinutes(h) { + this.setTime(this.getTime() + (h*60*1000)); + return this; +} + +Date.prototype.addHours = function addHours(h) { + this.setTime(this.getTime() + (h*60*60*1000)); + return this; +} + +Date.prototype.addDays = function addDays(h) { + this.setTime(this.getTime() + (h*24*60*60*1000)); + return this; +} + From 0be552c43cf27f2e0642da78a3851e0f47c7fedf Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 13 Jul 2015 14:10:39 -0700 Subject: [PATCH 389/937] bind pushPoint to sbx --- lib/plugins/ar2.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 606f5010e67..778433761a2 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -113,7 +113,7 @@ function init() { //fold left, each time update the accumulator result.predicted = _.reduce( new Array(6) //only 6 points are used for calculating avgLoss - , pushPoint + , pushPoint.bind(null, sbx) , initAR2(sgvs, sbx) ).points; @@ -148,7 +148,7 @@ function init() { var coneFactor = getConeFactor(sbx); function pushConePoints(result, step) { - var next = incrementAR2(result); + var next = incrementAR2(result, sbx); //offset from points so they are at a unique time if (coneFactor > 0) { @@ -288,8 +288,8 @@ function incrementAR2 (result, sbx) { }; } -function pushPoint(result) { - var next = incrementAR2(result); +function pushPoint(sbx, result) { + var next = incrementAR2(result, sbx); next.points.push(ar2Point( next From 712e077ce9939005f9e34c5f2c8e95d79df35060 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Mon, 13 Jul 2015 23:50:24 +0200 Subject: [PATCH 390/937] changes for codacy --- static/js/treatment.js | 8 ++++++-- static/js/ui-utils.js | 8 ++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/static/js/treatment.js b/static/js/treatment.js index 2beec340066..7e42d65e78f 100644 --- a/static/js/treatment.js +++ b/static/js/treatment.js @@ -106,8 +106,12 @@ $('.eventtimeinput').focus(function (event) { $('.eventtimeinput').change(function (event) { $('#othertime').prop('checked', true); var time = mergeInputTime('#eventTimeValue','#eventDateValue'); - if ($(this).attr('oldminutes')==59 && time.getMinutes()==0) time.addHours(1); - if ($(this).attr('oldminutes')==0 && time.getMinutes()==59) time.addHours(-1); + if ($(this).attr('oldminutes')=='59' && time.getMinutes()=='0') { + time.addHours(1); + } + if ($(this).attr('oldminutes')=='0' && time.getMinutes()=='59') { + time.addHours(-1); + } $('#eventTimeValue').val(time.toTimeString().slice(0,5)); $('#eventDateValue').val(time.toDateInputValue()); $(this).attr('oldminutes',time.getMinutes()); diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js index 770e51c7f81..3842d3f6660 100644 --- a/static/js/ui-utils.js +++ b/static/js/ui-utils.js @@ -358,20 +358,20 @@ Date.prototype.toDateInputValue = function toDateInputValue() { var local = new Date(this); local.setMinutes(this.getMinutes() - this.getTimezoneOffset()); return local.toJSON().slice(0,10); -} +}; Date.prototype.addMinutes = function addMinutes(h) { this.setTime(this.getTime() + (h*60*1000)); return this; -} +}; Date.prototype.addHours = function addHours(h) { this.setTime(this.getTime() + (h*60*60*1000)); return this; -} +}; Date.prototype.addDays = function addDays(h) { this.setTime(this.getTime() + (h*24*60*60*1000)); return this; -} +}; From d30987966acfeede09b66f02e39194a3bf1ba70e Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 13 Jul 2015 17:14:48 -0700 Subject: [PATCH 391/937] added some debug to try tracking down the intermitant retro mode when switching back to a tab after some time --- static/js/client.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index 60b6accb3e6..055b4321d17 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -73,7 +73,6 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; , clip; var container = $('.container') - , bgButton = $('.bgButton') , bgStatus = $('.bgStatus') , currentBG = $('.bgStatus .currentBG') , majorPills = $('.bgStatus .majorPills') @@ -234,7 +233,13 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; // 2 days before now as x0 and 30 minutes from now for x1 for context plot, but this will be // required to happen when 'now' event is sent from websocket.js every minute. When fixed, // remove this code and all references to `type: 'server-forecast'` - var lastTime = data.length > 0 ? data[data.length - 1].mills : Date.now(); + var last = _.last(data); + var lastTime = last && last.mills; + if (!lastTime) { + console.error('Bad Data, last point has no mills', last); + lastTime = Date.now(); + } + var n = Math.ceil(12 * (1 / 2 + (now - lastTime) / SIXTY_MINS_IN_MS)) + 1; for (var i = 1; i <= n; i++) { data.push({ From 15c8f40e8188a8a859a8f91c0ac905b10048a5f4 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 14 Jul 2015 00:42:08 -0700 Subject: [PATCH 392/937] remove false warning --- lib/api/entries/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/api/entries/index.js b/lib/api/entries/index.js index 1ad6e90d499..048a51cc0b7 100644 --- a/lib/api/entries/index.js +++ b/lib/api/entries/index.js @@ -30,7 +30,6 @@ function configure (app, wares, ctx) { function force_typed_data (opts) { function sync (data, next) { if (data.type !== opts.type) { - console.warn('BAD DATA TYPE, setting', data.type, 'to', opts.type); data.type = opts.type; } next(null, data); From 4940e27569dee5ccd2aa1d34c599d6c8794b21a9 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 14 Jul 2015 00:43:56 -0700 Subject: [PATCH 393/937] add timestamps to notification event logging --- lib/notifications.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/notifications.js b/lib/notifications.js index 151025b6758..fd472ed00cc 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -207,20 +207,25 @@ function init (env, ctx) { function logEmitEvent(notify) { var type = notify.level >= levels.WARN ? 'ALARM' : (notify.clear ? 'ALL CLEAR' : 'NOTIFICATION'); console.info([ - 'EMITTING ' + type + ':' + logTimestamp() + '\tEMITTING ' + type + ':' , ' ' + JSON.stringify(notifyToView(notify)) ].join('\n')); } function logSnoozingEvent(highestAlarm, snoozedBy) { console.info([ - 'SNOOZING ALARM:' + logTimestamp() + '\tSNOOZING ALARM:' , ' ' + JSON.stringify(notifyToView(highestAlarm)) , ' BECAUSE:' , ' ' + JSON.stringify(snoozeToView(snoozedBy)) ].join('\n')); } + //TODO: we need a common logger, but until then... + function logTimestamp ( ) { + return (new Date).toISOString(); + } + return notifications(); } From d86a27017d5a6db6046a8d9492322062d6b68cc9 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 14 Jul 2015 00:46:05 -0700 Subject: [PATCH 394/937] simplify ar2 timing init/increment --- lib/plugins/ar2.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 778433761a2..9814c9fb30f 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -113,7 +113,7 @@ function init() { //fold left, each time update the accumulator result.predicted = _.reduce( new Array(6) //only 6 points are used for calculating avgLoss - , pushPoint.bind(null, sbx) + , pushPoint , initAR2(sgvs, sbx) ).points; @@ -148,7 +148,7 @@ function init() { var coneFactor = getConeFactor(sbx); function pushConePoints(result, step) { - var next = incrementAR2(result, sbx); + var next = incrementAR2(result); //offset from points so they are at a unique time if (coneFactor > 0) { @@ -272,24 +272,24 @@ function initAR2 (sgvs, sbx) { var mgdl5MinsAgo = delta.calc(prev, current, sbx).mgdl5MinsAgo; return { - forecastTime: sbx.lastSGVMills() + forecastTime: current.mills || Date.now() , points: [] , prev: Math.log(mgdl5MinsAgo / BG_REF) , curr: Math.log(current.mgdl / BG_REF) }; } -function incrementAR2 (result, sbx) { +function incrementAR2 (result) { return { - forecastTime: result.forecastTime ? result.forecastTime + FIVE_MINUTES : sbx.lastSGVMills() + forecastTime: result.forecastTime + FIVE_MINUTES , points: result.points || [] , prev: result.curr , curr: AR[0] * result.prev + AR[1] * result.curr }; } -function pushPoint(sbx, result) { - var next = incrementAR2(result, sbx); +function pushPoint(result) { + var next = incrementAR2(result); next.points.push(ar2Point( next From 6b2e09545b49456cc1bc6c7014222ac410157b4e Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 14 Jul 2015 00:59:41 -0700 Subject: [PATCH 395/937] new look for the forecast points --- static/js/client.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index 055b4321d17..fabdb346b33 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -443,6 +443,8 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var radius = prevChartWidth > WIDTH_BIG_DOTS ? 4 : (prevChartWidth < WIDTH_SMALL_DOTS ? 2 : 3); if (type === 'mbg') { radius *= 2; + } else if (type === 'forecast') { + radius = Math.min(3, radius - 1); } else if (type === 'rawbg') { radius = Math.min(2, radius - 1); } @@ -476,11 +478,11 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; return yScale(scaled); } }) - .attr('fill', function (d) { return d.color; }) + .attr('fill', function (d) { return d.type === 'forecast' ? 'none' : d.color; }) .attr('opacity', function (d) { return futureOpacity(d.mills - latestSGV.mills); }) - .attr('stroke-width', function (d) { return d.type === 'mbg' ? 2 : 0; }) + .attr('stroke-width', function (d) { return d.type === 'mbg' ? 2 : d.type === 'forecast' ? 1 : 0; }) .attr('stroke', function (d) { - return (isDexcom(d.device) ? 'white' : '#0099ff'); + return (isDexcom(d.device) ? 'white' : d.type === 'forecast' ? d.color : '#0099ff'); }) .attr('r', function (d) { return dotRadius(d.type); }); From 466ca382fa22776dadb1a3728e4cb3bc7cc8d069 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 14 Jul 2015 01:02:19 -0700 Subject: [PATCH 396/937] scale the thresholds for now, need to be able to set them in either units --- lib/plugins/ar2.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 9814c9fb30f..210ae6ff186 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -209,9 +209,9 @@ function selectEventType (prop, sbx) { var eventName = ''; if (in20mins !== undefined) { - if (in20mins > sbx.thresholds.bg_target_top) { + if (in20mins > sbx.scaleMgdl(sbx.thresholds.bg_target_top)) { eventName = 'high'; - } else if (in20mins < sbx.thresholds.bg_target_bottom) { + } else if (in20mins < sbx.scaleMgdl(sbx.thresholds.bg_target_bottom)) { eventName = 'low'; } } @@ -224,7 +224,7 @@ function buildTitle(prop, sbx) { var title = levels.toDisplay(prop.level) + ', ' + rangeLabel; var sgv = sbx.lastScaledSGV(); - if (sgv > sbx.thresholds.bg_target_bottom && sgv < sbx.thresholds.bg_target_top) { + if (sgv > sbx.scaleMgdl(sbx.thresholds.bg_target_bottom) && sgv < sbx.scaleMgdl(sbx.thresholds.bg_target_top)) { title += ' predicted'; } if (prop.usingRaw) { From a48ccffb0b9dfc15dd876c2f06712483e86c71ed Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Tue, 14 Jul 2015 13:19:04 +0300 Subject: [PATCH 397/937] Revert older Pebble API behavior, where bgDelta was a string --- lib/pebble.js | 2 +- tests/pebble.test.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/pebble.js b/lib/pebble.js index 5484342b6e5..c1e50f19130 100644 --- a/lib/pebble.js +++ b/lib/pebble.js @@ -63,7 +63,7 @@ function prepareSGVs (req, sbx) { //for legacy reasons we need to return a 0 for delta if it can't be calculated var deltaResult = delta.calc(prev, current, sbx); - bgs[0].bgdelta = deltaResult && deltaResult.scaled || 0; + bgs[0].bgdelta = String(deltaResult && deltaResult.scaled || 0); bgs[0].battery = data.devicestatus && data.devicestatus.uploaderBattery && data.devicestatus.uploaderBattery.toString(); if (req.iob) { diff --git a/tests/pebble.test.js b/tests/pebble.test.js index a7568784889..82abfa46be0 100644 --- a/tests/pebble.test.js +++ b/tests/pebble.test.js @@ -102,7 +102,7 @@ describe('Pebble Endpoint', function ( ) { bgs.length.should.equal(1); var bg = bgs[0]; bg.sgv.should.equal('82'); - bg.bgdelta.should.equal(-2); + bg.bgdelta.should.equal('-2'); bg.trend.should.equal(4); bg.direction.should.equal('Flat'); bg.datetime.should.equal(now); @@ -127,7 +127,7 @@ describe('Pebble Endpoint', function ( ) { bgs.length.should.equal(1); var bg = bgs[0]; bg.sgv.should.equal('4.6'); - bg.bgdelta.should.equal(-0.1); + bg.bgdelta.should.equal('-0.1'); bg.trend.should.equal(4); bg.direction.should.equal('Flat'); bg.datetime.should.equal(now); @@ -151,7 +151,7 @@ describe('Pebble Endpoint', function ( ) { bgs.length.should.equal(2); var bg = bgs[0]; bg.sgv.should.equal('82'); - bg.bgdelta.should.equal(-2); + bg.bgdelta.should.equal('-2'); bg.trend.should.equal(4); bg.direction.should.equal('Flat'); bg.datetime.should.equal(now); @@ -187,7 +187,7 @@ describe('Pebble Endpoint with Raw and IOB', function ( ) { bgs.length.should.equal(2); var bg = bgs[0]; bg.sgv.should.equal('82'); - bg.bgdelta.should.equal(-2); + bg.bgdelta.should.equal('-2'); bg.trend.should.equal(4); bg.direction.should.equal('Flat'); bg.datetime.should.equal(now); From b1b83907263ca6dcd2a474bd29c886487984e2a9 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Tue, 14 Jul 2015 13:22:47 +0300 Subject: [PATCH 398/937] Show basal calculation in case basal can be set to exact zero or +100% --- lib/plugins/boluswizardpreview.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 75276ca760f..1ca425442f5 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -225,12 +225,12 @@ function pushTempBasalAdjustments(prop, info, sbx) { info.push({label: 'Current basal', value: sbx.data.profile.getBasal(sbx.time)}); info.push({label: 'Basal change to account for ' + prop.bolusEstimateDisplay + ':', value: ''}); - if (prop.tempBasalAdjustment.thirtymin > 0 && prop.tempBasalAdjustment.thirtymin < 200) { + if (prop.tempBasalAdjustment.thirtymin >= 0 && prop.tempBasalAdjustment.thirtymin <= 200) { info.push({label: '30m temp basal', value: '' + prop.tempBasalAdjustment.thirtymin + '% (' + sign + (prop.tempBasalAdjustment.thirtymin - 100) + '%)'}); } else { info.push({label: '30m temp basal', value: carbsOrBolusMessage}); } - if (prop.tempBasalAdjustment.onehour > 0 && prop.tempBasalAdjustment.onehour < 200) { + if (prop.tempBasalAdjustment.onehour >= 0 && prop.tempBasalAdjustment.onehour <= 200) { info.push({label: '1h temp basal', value: '' + prop.tempBasalAdjustment.onehour + '% (' + sign + (prop.tempBasalAdjustment.onehour - 100) + '%)'}); } else { info.push({label: '1h temp basal', value: carbsOrBolusMessage}); From 16c85ad4f7c3234d0a7da3d5b77c58423a6cf928 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Tue, 14 Jul 2015 13:23:33 +0300 Subject: [PATCH 399/937] Calculate IOB for treatments that match the current timestamp --- lib/plugins/iob.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/iob.js b/lib/plugins/iob.js index 64ccc99374a..4307e0abd9c 100644 --- a/lib/plugins/iob.js +++ b/lib/plugins/iob.js @@ -33,7 +33,7 @@ function init() { var lastBolus = null; _.forEach(treatments, function eachTreatment(treatment) { - if (treatment.mills < time) { + if (treatment.mills <= time) { var tIOB = iob.calcTreatment(treatment, profile, time); if (tIOB.iobContrib > 0) { lastBolus = treatment; From d6d767afc2a37108951080464746f052b2e11c01 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Tue, 14 Jul 2015 13:26:06 +0300 Subject: [PATCH 400/937] More comprehensive test for BWP, including a test in MMOL --- tests/boluswizardpreview.test.js | 115 +++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/tests/boluswizardpreview.test.js b/tests/boluswizardpreview.test.js index e58e5aca752..179916723a6 100644 --- a/tests/boluswizardpreview.test.js +++ b/tests/boluswizardpreview.test.js @@ -38,6 +38,121 @@ describe('boluswizardpreview', function ( ) { , target_low: 100 }; + it('should calculate IOB results correctly with 0 IOB', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{mills: before, mgdl: 100}, {mills: now, mgdl: 100}]; + ctx.data.treatments = []; + ctx.data.profiles = [profile]; + + var sbx = prepareSandbox(); + var results = boluswizardpreview.calc(sbx); + + results.effect.should.equal(0); + results.effectDisplay.should.equal(0); + results.outcome.should.equal(100); + results.outcomeDisplay.should.equal(100); + results.bolusEstimate.should.equal(0); + results.displayLine.should.equal('BWP: 0U'); + + done(); + }); + + it('should calculate IOB results correctly with 1.0 U IOB', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{mills: before, mgdl: 100}, {mills: now, mgdl: 100}]; + ctx.data.treatments = [{mills: now, insulin: '1.0'}]; + + var profile = { + dia: 3 + , sens: 50 + , target_high: 100 + , target_low: 50 + }; + + ctx.data.profiles = [profile]; + + var sbx = prepareSandbox(); + var results = boluswizardpreview.calc(sbx); + + Math.round(results.effect).should.equal(50); + results.effectDisplay.should.equal(50); + Math.round(results.outcome).should.equal(50); + results.outcomeDisplay.should.equal(50); + results.bolusEstimate.should.equal(0); + results.displayLine.should.equal('BWP: 0U'); + + done(); + }); + + it('should calculate IOB results correctly with 1.0 U IOB resulting in going low', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{mills: before, mgdl: 100}, {mills: now, mgdl: 100}]; + ctx.data.treatments = [{mills: now, insulin: '1.0'}]; + + var profile = { + dia: 3 + , sens: 50 + , target_high: 200 + , target_low: 100 + , basal: 1 + }; + + + ctx.data.profiles = [profile]; + + var sbx = prepareSandbox(); + var results = boluswizardpreview.calc(sbx); + + Math.round(results.effect).should.equal(50); + results.effectDisplay.should.equal(50); + Math.round(results.outcome).should.equal(50); + results.outcomeDisplay.should.equal(50); + Math.round(results.bolusEstimate).should.equal(-1); + results.displayLine.should.equal('BWP: -1.00U'); + results.tempBasalAdjustment.thirtymin.should.equal(-100); + results.tempBasalAdjustment.onehour.should.equal(0); + + done(); + }); + + it('should calculate IOB results correctly with 1.0 U IOB resulting in going low in MMOL', function (done) { + + // boilerplate for client sandbox running in mmol + + var profileData = { + dia: 3 + , units: 'mmol' + , sens: 10 + , target_high: 10 + , target_low: 5.6 + , basal: 1 + }; + + var sandbox = require('../lib/sandbox')(); + var app = { }; + var pluginBase = {}; + var clientSettings = { units: 'mmol' }; + var data = {sgvs: [{mills: before, mgdl: 100}, {mills: now, mgdl: 100}]}; + data.treatments = [{mills: now, insulin: '1.0'}]; + data.profile = require('../lib/profilefunctions')([profileData]); + var sbx = sandbox.clientInit(app, clientSettings, Date.now(), pluginBase, data); + var iob = require('../lib/plugins/iob')(); + sbx.properties.iob = iob.calcTotal(data.treatments, data.profile, now); + + var results = boluswizardpreview.calc(sbx); + + results.effect.should.equal(10); + results.outcome.should.equal(-4.4); + results.bolusEstimate.should.equal(-1); + results.displayLine.should.equal('BWP: -1.00U'); + results.tempBasalAdjustment.thirtymin.should.equal(-100); + results.tempBasalAdjustment.onehour.should.equal(0); + + done(); + }); + + + it('Not trigger an alarm when in range', function (done) { ctx.notifications.initRequests(); ctx.data.sgvs = [{mills: before, mgdl: 95}, {mills: now, mgdl: 100}]; From 9fd0ebbd43cce8231fa0acefde75a6ce36ed0496 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Tue, 14 Jul 2015 14:18:47 +0300 Subject: [PATCH 401/937] One more MMOL test for BWP --- tests/boluswizardpreview.test.js | 36 ++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/boluswizardpreview.test.js b/tests/boluswizardpreview.test.js index 179916723a6..e24e130595b 100644 --- a/tests/boluswizardpreview.test.js +++ b/tests/boluswizardpreview.test.js @@ -152,6 +152,42 @@ describe('boluswizardpreview', function ( ) { }); + it('should calculate IOB results correctly with 0.45 U IOB resulting in going low in MMOL', function (done) { + + // boilerplate for client sandbox running in mmol + + var profileData = { + dia: 3 + , units: 'mmol' + , sens: 9 + , target_high: 6 + , target_low: 5 + , basal: 0.125 + }; + + var sandbox = require('../lib/sandbox')(); + var app = { }; + var pluginBase = {}; + var clientSettings = { units: 'mmol' }; + var data = {sgvs: [{mills: before, mgdl: 175}, {mills: now, mgdl: 153}]}; + data.treatments = [{mills: now, insulin: '0.45'}]; + data.profile = require('../lib/profilefunctions')([profileData]); + var sbx = sandbox.clientInit(app, clientSettings, Date.now(), pluginBase, data); + var iob = require('../lib/plugins/iob')(); + sbx.properties.iob = iob.calcTotal(data.treatments, data.profile, now); + + var results = boluswizardpreview.calc(sbx); + + results.effect.should.equal(4.05); + results.outcome.should.equal(4.45); + Math.round(results.bolusEstimate*100).should.equal(-6); + results.displayLine.should.equal('BWP: -0.07U'); + results.tempBasalAdjustment.thirtymin.should.equal(2); + results.tempBasalAdjustment.onehour.should.equal(51); + + done(); + }); + it('Not trigger an alarm when in range', function (done) { ctx.notifications.initRequests(); From 326d9d379e18098d67b468fa15943698df91e1a7 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Tue, 14 Jul 2015 15:06:55 +0300 Subject: [PATCH 402/937] Show the BG target in BWP, when above or below --- lib/plugins/boluswizardpreview.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 1ca425442f5..070fbcecc9a 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -133,6 +133,8 @@ function init() { if (results.outcome > target_high) { delta = results.outcome - target_high; results.bolusEstimate = delta / sens; + results.aimTarget= target_high; + results.aimTargetString = "above high"; } var target_low = profile.getLowBGTarget(sbx.time); @@ -140,6 +142,8 @@ function init() { if (results.outcome < target_low) { delta = Math.abs(results.outcome - target_low); results.bolusEstimate = delta / sens * -1; + results.aimTarget = target_low; + results.aimTargetString = "below low"; } if (results.bolusEstimate !== 0 && sbx.data.profile.getBasal(sbx.time)) { @@ -222,6 +226,9 @@ function pushTempBasalAdjustments(prop, info, sbx) { } info.push({label: '---------', value: ''}); + if (prop.aimTarget) { + info.push({label: 'Projection ' + prop.aimTargetString +' target', value: 'aiming at ' + prop.aimTarget + ' ' + sbx.units}); + } info.push({label: 'Current basal', value: sbx.data.profile.getBasal(sbx.time)}); info.push({label: 'Basal change to account for ' + prop.bolusEstimateDisplay + ':', value: ''}); From 84b1ab959623972b782a5007921cf4db087e9a1b Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Tue, 14 Jul 2015 18:37:52 +0200 Subject: [PATCH 403/937] Data.prototype.. converted to Nightscout.utils. treatment post split to 2 functions --- lib/utils.js | 35 +++++++++++++++++++- static/js/treatment.js | 74 +++++++++++++++++++++--------------------- static/js/ui-utils.js | 33 ------------------- 3 files changed, 71 insertions(+), 71 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index 86476956a32..7d68c3b2704 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -59,7 +59,40 @@ function init() { }; - return utils(); + // some helpers for input "date" + utils.mergeInputTime = function mergeInputTime(timeid,dateid) { + var value = $(timeid).val(); + var eventTimeParts = value.split(':'); + var eventTime = new Date($(dateid).val()); + eventTime.setHours(eventTimeParts[0]); + eventTime.setMinutes(eventTimeParts[1]); + eventTime.setSeconds(0); + eventTime.setMilliseconds(0); + return eventTime; + } + + utils.toDateInputValue = function toDateInputValue(datetime) { + var local = new Date(datetime); + local.setMinutes(datetime.getMinutes() - datetime.getTimezoneOffset()); + return local.toJSON().slice(0,10); + }; + + utils.addMinutes = function addMinutes(datetime,h) { + datetime.setTime(datetime.getTime() + (h*60*1000)); + return datetime; + }; + + utils.addHours = function addHours(datetime,h) { + datetime.setTime(datetime.getTime() + (h*60*60*1000)); + return datetime; + }; + + utils.addDays = function addDays(datetime,h) { + datetime.setTime(datetime.getTime() + (h*24*60*60*1000)); + return datetime; + }; + + return utils(); } module.exports = init; \ No newline at end of file diff --git a/static/js/treatment.js b/static/js/treatment.js index 7e42d65e78f..04cd606b558 100644 --- a/static/js/treatment.js +++ b/static/js/treatment.js @@ -11,7 +11,7 @@ function initTreatmentDrawer() { $('#enteredBy').val(browserStorage.get('enteredBy') || ''); $('#nowtime').prop('checked', true); $('#eventTimeValue').val(new Date().toTimeString().slice(0,5)); - $('#eventDateValue').val(new Date().toDateInputValue()); + $('#eventDateValue').val(Nightscout.utils.toDateInputValue(new Date())); } function treatmentSubmit(event) { @@ -43,37 +43,10 @@ function treatmentSubmit(event) { if (errors.length > 0) { window.alert(errors.join('\n')); } else { - var eventTimeDisplay = ''; if ($('#othertime').is(':checked')) { - data.eventTime = mergeInputTime('#eventTimeValue','#eventDateValue'); - eventTimeDisplay = data.eventTime.toLocaleString(); + data.eventTime = Nightscout.utils.mergeInputTime('#eventTimeValue','#eventDateValue'); } - - var dataJson = JSON.stringify(data, null, ' '); - - var ok = window.confirm( - 'Please verify that the data entered is correct: ' + - '\nEvent type: ' + data.eventType + - ( data.glucose ? '\nBlood glucose: ' + data.glucose + - '\nMethod: ' + data.glucoseType : '' ) + - ( data.carbs ? '\nCarbs Given: ' + data.carbs : '' ) + - ( data.insulin ? '\nInsulin Given: ' + data.insulin : '' ) + - ( data.preBolus ? '\nPre Bolus: ' + data.preBolus : '' ) + - ( data.notes ? '\nNotes: ' + data.notes : '' ) + - ( data.enteredBy ? '\nEntered By: ' + data.enteredBy : '') + - (eventTimeDisplay ? '\nEvent Time: ' + eventTimeDisplay: '' ) - ); - - if (ok) { - var xhr = new XMLHttpRequest(); - xhr.open('POST', '/api/v1/treatments/', true); - xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); - xhr.send(dataJson); - - browserStorage.set('enteredBy', data.enteredBy); - - closeDrawer('#treatmentDrawer'); - } + confirmPost(data); } if (event) { @@ -81,6 +54,33 @@ function treatmentSubmit(event) { } } +function confirmPost(data) { + var ok = window.confirm( + 'Please verify that the data entered is correct: ' + + '\nEvent type: ' + data.eventType + + ( data.glucose ? '\nBlood glucose: ' + data.glucose + + '\nMethod: ' + data.glucoseType : '' ) + + ( data.carbs ? '\nCarbs Given: ' + data.carbs : '' ) + + ( data.insulin ? '\nInsulin Given: ' + data.insulin : '' ) + + ( data.preBolus ? '\nPre Bolus: ' + data.preBolus : '' ) + + ( data.notes ? '\nNotes: ' + data.notes : '' ) + + ( data.enteredBy ? '\nEntered By: ' + data.enteredBy : '') + + (data.eventTime ? '\nEvent Time: ' + data.eventTime.toLocaleString(): '' ) + ); + + if (ok) { + var dataJson = JSON.stringify(data, null, ' '); + var xhr = new XMLHttpRequest(); + xhr.open('POST', '/api/v1/treatments/', true); + xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); + xhr.send(dataJson); + + browserStorage.set('enteredBy', data.enteredBy); + + closeDrawer('#treatmentDrawer'); + } +} + $('#treatmentDrawerToggle').click(function(event) { toggleDrawer('#treatmentDrawer', initTreatmentDrawer); event.preventDefault(); @@ -97,7 +97,7 @@ $('#eventTime input:radio').change(function (event){ $('.eventtimeinput').focus(function (event) { $('#othertime').prop('checked', true); - var time = mergeInputTime('#eventTimeValue','#eventDateValue'); + var time = Nightscout.utils.mergeInputTime('#eventTimeValue','#eventDateValue'); $(this).attr('oldminutes',time.getMinutes()); $(this).attr('oldhours',time.getHours()); event.preventDefault(); @@ -105,15 +105,15 @@ $('.eventtimeinput').focus(function (event) { $('.eventtimeinput').change(function (event) { $('#othertime').prop('checked', true); - var time = mergeInputTime('#eventTimeValue','#eventDateValue'); - if ($(this).attr('oldminutes')=='59' && time.getMinutes()=='0') { - time.addHours(1); + var time = Nightscout.utils.mergeInputTime('#eventTimeValue','#eventDateValue'); + if ($(this).attr('oldminutes')==='59' && time.getMinutes()===0) { + Nightscout.utils.addHours(time,1); } - if ($(this).attr('oldminutes')=='0' && time.getMinutes()=='59') { - time.addHours(-1); + if ($(this).attr('oldminutes')==='0' && time.getMinutes()===59) { + Nightscout.utils.addHours(time,-1); } $('#eventTimeValue').val(time.toTimeString().slice(0,5)); - $('#eventDateValue').val(time.toDateInputValue()); + $('#eventDateValue').val(Nightscout.utils.toDateInputValue(time)); $(this).attr('oldminutes',time.getMinutes()); $(this).attr('oldhours',time.getHours()); event.preventDefault(); diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js index 3842d3f6660..98b7a1d7d14 100644 --- a/static/js/ui-utils.js +++ b/static/js/ui-utils.js @@ -342,36 +342,3 @@ $(function() { } }); -// some helpers for input "date" -function mergeInputTime(timeid,dateid) { - var value = $(timeid).val(); - var eventTimeParts = value.split(':'); - var eventTime = new Date($(dateid).val()); - eventTime.setHours(eventTimeParts[0]); - eventTime.setMinutes(eventTimeParts[1]); - eventTime.setSeconds(0); - eventTime.setMilliseconds(0); - return eventTime; -} - -Date.prototype.toDateInputValue = function toDateInputValue() { - var local = new Date(this); - local.setMinutes(this.getMinutes() - this.getTimezoneOffset()); - return local.toJSON().slice(0,10); -}; - -Date.prototype.addMinutes = function addMinutes(h) { - this.setTime(this.getTime() + (h*60*1000)); - return this; -}; - -Date.prototype.addHours = function addHours(h) { - this.setTime(this.getTime() + (h*60*60*1000)); - return this; -}; - -Date.prototype.addDays = function addDays(h) { - this.setTime(this.getTime() + (h*24*60*60*1000)); - return this; -}; - From 7e46a08456e6a7bf3c3416c6823aa828c1eb0504 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Tue, 14 Jul 2015 22:00:15 +0200 Subject: [PATCH 404/937] removed unused code --- lib/utils.js | 2 +- static/js/treatment.js | 7 +++---- static/js/ui-utils.js | 21 --------------------- 3 files changed, 4 insertions(+), 26 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index 7d68c3b2704..f50fd89655a 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -69,7 +69,7 @@ function init() { eventTime.setSeconds(0); eventTime.setMilliseconds(0); return eventTime; - } + }; utils.toDateInputValue = function toDateInputValue(datetime) { var local = new Date(datetime); diff --git a/static/js/treatment.js b/static/js/treatment.js index 04cd606b558..35e85fa30e1 100644 --- a/static/js/treatment.js +++ b/static/js/treatment.js @@ -55,7 +55,7 @@ function treatmentSubmit(event) { } function confirmPost(data) { - var ok = window.confirm( + var confirmtext = 'Please verify that the data entered is correct: ' + '\nEvent type: ' + data.eventType + ( data.glucose ? '\nBlood glucose: ' + data.glucose + @@ -65,10 +65,9 @@ function confirmPost(data) { ( data.preBolus ? '\nPre Bolus: ' + data.preBolus : '' ) + ( data.notes ? '\nNotes: ' + data.notes : '' ) + ( data.enteredBy ? '\nEntered By: ' + data.enteredBy : '') + - (data.eventTime ? '\nEvent Time: ' + data.eventTime.toLocaleString(): '' ) - ); + ( data.eventTime ? '\nEvent Time: ' + data.eventTime.toLocaleString(): '' ); - if (ok) { + if (window.confirm(confirmtext)) { var dataJson = JSON.stringify(data, null, ' '); var xhr = new XMLHttpRequest(); xhr.open('POST', '/api/v1/treatments/', true); diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js index 98b7a1d7d14..be4cab8b2a6 100644 --- a/static/js/ui-utils.js +++ b/static/js/ui-utils.js @@ -187,27 +187,6 @@ function toggleDrawer(id, openCallback, closeCallback) { } -function currentTime() { - var now = new Date(); - var hours = now.getHours(); - var minutes = now.getMinutes(); - - if (hours < 10) { hours = '0' + hours; } - if (minutes < 10) { minutes = '0' + minutes; } - - return ''+ hours + ':' + minutes; -} - -function formatTime(date) { - var hours = date.getHours(); - var minutes = date.getMinutes(); - var ampm = hours >= 12 ? 'pm' : 'am'; - hours = hours % 12; - hours = hours ? hours : 12; // the hour '0' should be '12' - minutes = minutes < 10 ? '0' + minutes : minutes; - return hours + ':' + minutes + ' ' + ampm; -} - function closeNotification() { var notify = $('#notification'); notify.hide(); From 623ff90e1f434fdf4e1433d1bd319db7baa72643 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Tue, 14 Jul 2015 22:11:08 +0200 Subject: [PATCH 405/937] removed jquery from utils.js. lowered code complexity --- lib/utils.js | 7 +++---- static/js/treatment.js | 25 ++++++++++++------------- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index f50fd89655a..c9b061cf5d3 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -60,10 +60,9 @@ function init() { }; // some helpers for input "date" - utils.mergeInputTime = function mergeInputTime(timeid,dateid) { - var value = $(timeid).val(); - var eventTimeParts = value.split(':'); - var eventTime = new Date($(dateid).val()); + utils.mergeInputTime = function mergeInputTime(timestring,datestring) { + var eventTimeParts = timestring.split(':'); + var eventTime = new Date(datestring); eventTime.setHours(eventTimeParts[0]); eventTime.setMinutes(eventTimeParts[1]); eventTime.setSeconds(0); diff --git a/static/js/treatment.js b/static/js/treatment.js index 35e85fa30e1..a9a9e29ebc5 100644 --- a/static/js/treatment.js +++ b/static/js/treatment.js @@ -44,7 +44,7 @@ function treatmentSubmit(event) { window.alert(errors.join('\n')); } else { if ($('#othertime').is(':checked')) { - data.eventTime = Nightscout.utils.mergeInputTime('#eventTimeValue','#eventDateValue'); + data.eventTime = Nightscout.utils.mergeInputTime($('#eventTimeValue').val(),$('#eventDateValue').val()); } confirmPost(data); } @@ -56,16 +56,15 @@ function treatmentSubmit(event) { function confirmPost(data) { var confirmtext = - 'Please verify that the data entered is correct: ' + - '\nEvent type: ' + data.eventType + - ( data.glucose ? '\nBlood glucose: ' + data.glucose + - '\nMethod: ' + data.glucoseType : '' ) + - ( data.carbs ? '\nCarbs Given: ' + data.carbs : '' ) + - ( data.insulin ? '\nInsulin Given: ' + data.insulin : '' ) + - ( data.preBolus ? '\nPre Bolus: ' + data.preBolus : '' ) + - ( data.notes ? '\nNotes: ' + data.notes : '' ) + - ( data.enteredBy ? '\nEntered By: ' + data.enteredBy : '') + - ( data.eventTime ? '\nEvent Time: ' + data.eventTime.toLocaleString(): '' ); + 'Please verify that the data entered is correct: ' + + '\nEvent type: ' + data.eventType; + confirmtext += data.glucose ? '\nBlood glucose: ' + data.glucose + '\nMethod: ' + data.glucoseType : ''; + confirmtext += data.carbs ? '\nCarbs Given: ' + data.carbs : ''; + confirmtext += data.insulin ? '\nInsulin Given: ' + data.insulin : ''; + confirmtext += data.preBolus ? '\nPre Bolus: ' + data.preBolus : ''; + confirmtext += data.notes ? '\nNotes: ' + data.notes : ''; + confirmtext += data.enteredBy ? '\nEntered By: ' + data.enteredBy : ''; + confirmtext += data.eventTime ? '\nEvent Time: ' + data.eventTime.toLocaleString(): ''; if (window.confirm(confirmtext)) { var dataJson = JSON.stringify(data, null, ' '); @@ -96,7 +95,7 @@ $('#eventTime input:radio').change(function (event){ $('.eventtimeinput').focus(function (event) { $('#othertime').prop('checked', true); - var time = Nightscout.utils.mergeInputTime('#eventTimeValue','#eventDateValue'); + var time = Nightscout.utils.mergeInputTime($('#eventTimeValue').val(),$('#eventDateValue').val()); $(this).attr('oldminutes',time.getMinutes()); $(this).attr('oldhours',time.getHours()); event.preventDefault(); @@ -104,7 +103,7 @@ $('.eventtimeinput').focus(function (event) { $('.eventtimeinput').change(function (event) { $('#othertime').prop('checked', true); - var time = Nightscout.utils.mergeInputTime('#eventTimeValue','#eventDateValue'); + var time = Nightscout.utils.mergeInputTime($('#eventTimeValue').val(),$('#eventDateValue').val()); if ($(this).attr('oldminutes')==='59' && time.getMinutes()===0) { Nightscout.utils.addHours(time,1); } From cbb5c45961e1995fe96df52d72f22bff759b2c40 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 14 Jul 2015 17:33:10 -0700 Subject: [PATCH 406/937] Dockerfile needs to be in a different repo --- .dockerignore | 17 ----------------- Dockerfile | 32 -------------------------------- 2 files changed, 49 deletions(-) delete mode 100644 .dockerignore delete mode 100644 Dockerfile diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 9bd34f41610..00000000000 --- a/.dockerignore +++ /dev/null @@ -1,17 +0,0 @@ -bower_components/ -node_modules/ - -.idea/ -*.iml - -*.env -static/bower_components/ -.*.sw? -.DS_Store - -.vagrant -/iisnode - -# istanbul output -coverage/ -npm-debug.log diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index b9cf711dac0..00000000000 --- a/Dockerfile +++ /dev/null @@ -1,32 +0,0 @@ -FROM node:latest - -MAINTAINER Nightscout https://github.com/nightscout/ - -# Installing the required packages. -RUN apt-get update && apt-get install -y python-software-properties python g++ make git - -# Upgrade -RUN apt-get upgrade -y - -# We need to change user for security and for proper execution of all the NPM stages -# https://github.com/jspm/jspm-cli/issues/865 -# http://stackoverflow.com/questions/24308760/running-app-inside-docker-as-non-root-user - -RUN useradd --system -ms /bin/bash node - -RUN cd && cp -R .bashrc .profile /home/node - -ADD . /home/node/app -RUN chown -R node:node /home/node - -USER node - -ENV HOME /home/node -WORKDIR /home/node/app - -# Invoke NPM -RUN npm install - -# Expose the default port, although this does not matter at it will be exposed as an arbitrary port by the Docker network driver. -EXPOSE 1337 -CMD ["node", "server.js"] \ No newline at end of file From be3a2e7fc92cba4cf0a0bcb665db22fc8a81e1db Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 14 Jul 2015 19:01:04 -0700 Subject: [PATCH 407/937] more specific check to enable forecast using raw data --- lib/plugins/ar2.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 210ae6ff186..af859cf514b 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -80,8 +80,7 @@ function init() { function rawForecast (sbx) { var rawSGVs; - if (sbx.properties.rawbg && sbx.extendedSettings.useRaw) { - + if (sbx.properties.rawbg && sbx.extendedSettings.useRaw.toLowerCase() === 'true') { //TODO:OnlyOneCal - currently we only load the last cal, so we can't ignore future data var cal = _.last(sbx.data.cals); if (cal) { From 832fbb7644532fc142c7a25f8d6a91abb0bae0ee Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 14 Jul 2015 20:29:26 -0700 Subject: [PATCH 408/937] fixed a couple warnings --- lib/plugins/boluswizardpreview.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 070fbcecc9a..f1af1c759ab 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -133,8 +133,8 @@ function init() { if (results.outcome > target_high) { delta = results.outcome - target_high; results.bolusEstimate = delta / sens; - results.aimTarget= target_high; - results.aimTargetString = "above high"; + results.aimTarget = target_high; + results.aimTargetString = 'above high'; } var target_low = profile.getLowBGTarget(sbx.time); @@ -143,7 +143,7 @@ function init() { delta = Math.abs(results.outcome - target_low); results.bolusEstimate = delta / sens * -1; results.aimTarget = target_low; - results.aimTargetString = "below low"; + results.aimTargetString = 'below low'; } if (results.bolusEstimate !== 0 && sbx.data.profile.getBasal(sbx.time)) { From d958cd2e1945e91f7732291022f7cbe291ede95b Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 14 Jul 2015 20:36:07 -0700 Subject: [PATCH 409/937] better check of useRaw option --- lib/plugins/ar2.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index af859cf514b..c4843af8205 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -80,7 +80,7 @@ function init() { function rawForecast (sbx) { var rawSGVs; - if (sbx.properties.rawbg && sbx.extendedSettings.useRaw.toLowerCase() === 'true') { + if (useRaw(sbx)) { //TODO:OnlyOneCal - currently we only load the last cal, so we can't ignore future data var cal = _.last(sbx.data.cals); if (cal) { @@ -98,6 +98,10 @@ function init() { return ar2.forecast(rawSGVs, sbx); } + function useRaw (sbx) { + return sbx.properties.rawbg && (sbx.extendedSettings.useRaw === true || sbx.extendedSettings.useRaw.toLowerCase() === 'true') + } + ar2.forecast = function forecast (sgvs, sbx) { var result = { From 7a02ce6120387d04d67c77759e6cd48ad675b73a Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 14 Jul 2015 23:12:59 -0700 Subject: [PATCH 410/937] hopefully fix some mmol bugs with /pebble --- lib/pebble.js | 17 +++++++++++++++-- tests/pebble.test.js | 18 +++++++++--------- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/lib/pebble.js b/lib/pebble.js index c1e50f19130..6fce7df05a0 100644 --- a/lib/pebble.js +++ b/lib/pebble.js @@ -3,6 +3,7 @@ var _ = require('lodash'); var sandbox = require('./sandbox')(); +var units = require('./units')(); var iob = require('./plugins/iob')(); var delta = require('./plugins/delta')(); @@ -33,17 +34,26 @@ function reverseAndSlice (entries, req) { return reversed.slice(0, req.count); } + function prepareSGVs (req, sbx) { var bgs = []; var data = sbx.data; + function scaleMgdlAPebbleLegacyHackThatWillNotGoAway (bg) { + if (req.mmol) { + return units.mgdlToMMOL(bg); + } else { + return bg.toString(); + } + } + //for compatibility we're keeping battery and iob here, but they would be better somewhere else if (data.sgvs.length > 0) { var cal = sbx.lastEntry(sbx.data.cals); bgs = _.map(reverseAndSlice(data.sgvs, req), function transformSGV (sgv) { var transformed = { - sgv: sbx.scaleEntry(sgv).toString() + sgv: scaleMgdlAPebbleLegacyHackThatWillNotGoAway(sgv.mgdl) , trend: directionToTrend(sgv.direction) , direction: sgv.direction , datetime: sgv.mills @@ -63,7 +73,10 @@ function prepareSGVs (req, sbx) { //for legacy reasons we need to return a 0 for delta if it can't be calculated var deltaResult = delta.calc(prev, current, sbx); - bgs[0].bgdelta = String(deltaResult && deltaResult.scaled || 0); + bgs[0].bgdelta = deltaResult && deltaResult.scaled || 0; + if (req.mmol) { + bgs[0].bgdelta = bgs[0].bgdelta.toFixed(1); + } bgs[0].battery = data.devicestatus && data.devicestatus.uploaderBattery && data.devicestatus.uploaderBattery.toString(); if (req.iob) { diff --git a/tests/pebble.test.js b/tests/pebble.test.js index 82abfa46be0..4cb0ca59a49 100644 --- a/tests/pebble.test.js +++ b/tests/pebble.test.js @@ -47,7 +47,7 @@ ctx.data.sgvs = updateMills([ noise: 1 } , { device: 'dexcom', - mgdl: 84, + mgdl: 92, direction: 'Flat', type: 'sgv', filtered: 115680, @@ -56,7 +56,7 @@ ctx.data.sgvs = updateMills([ noise: 1 } , { device: 'dexcom', - mgdl: 82, + mgdl: 90, direction: 'Flat', type: 'sgv', filtered: 113984, @@ -101,8 +101,8 @@ describe('Pebble Endpoint', function ( ) { var bgs = res.body.bgs; bgs.length.should.equal(1); var bg = bgs[0]; - bg.sgv.should.equal('82'); - bg.bgdelta.should.equal('-2'); + bg.sgv.should.equal('90'); + bg.bgdelta.should.equal(-2); bg.trend.should.equal(4); bg.direction.should.equal('Flat'); bg.datetime.should.equal(now); @@ -126,7 +126,7 @@ describe('Pebble Endpoint', function ( ) { var bgs = res.body.bgs; bgs.length.should.equal(1); var bg = bgs[0]; - bg.sgv.should.equal('4.6'); + bg.sgv.should.equal('5.0'); bg.bgdelta.should.equal('-0.1'); bg.trend.should.equal(4); bg.direction.should.equal('Flat'); @@ -150,8 +150,8 @@ describe('Pebble Endpoint', function ( ) { var bgs = res.body.bgs; bgs.length.should.equal(2); var bg = bgs[0]; - bg.sgv.should.equal('82'); - bg.bgdelta.should.equal('-2'); + bg.sgv.should.equal('90'); + bg.bgdelta.should.equal(-2); bg.trend.should.equal(4); bg.direction.should.equal('Flat'); bg.datetime.should.equal(now); @@ -186,8 +186,8 @@ describe('Pebble Endpoint with Raw and IOB', function ( ) { var bgs = res.body.bgs; bgs.length.should.equal(2); var bg = bgs[0]; - bg.sgv.should.equal('82'); - bg.bgdelta.should.equal('-2'); + bg.sgv.should.equal('90'); + bg.bgdelta.should.equal(-2); bg.trend.should.equal(4); bg.direction.should.equal('Flat'); bg.datetime.should.equal(now); From 657f7d241d8d37354a5f5ea3f479319041fe45a0 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 15 Jul 2015 09:23:09 -0700 Subject: [PATCH 411/937] ; --- lib/plugins/ar2.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index c4843af8205..acbf82298e0 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -99,7 +99,7 @@ function init() { } function useRaw (sbx) { - return sbx.properties.rawbg && (sbx.extendedSettings.useRaw === true || sbx.extendedSettings.useRaw.toLowerCase() === 'true') + return sbx.properties.rawbg && (sbx.extendedSettings.useRaw === true || sbx.extendedSettings.useRaw.toLowerCase() === 'true'); } ar2.forecast = function forecast (sgvs, sbx) { From 2550ea5d477a5b3d7c149006ed4cd3a6706b1c03 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 15 Jul 2015 15:31:07 -0700 Subject: [PATCH 412/937] another check to see if we should try using raw for ar2 --- lib/plugins/ar2.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index acbf82298e0..53b33a62b03 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -99,7 +99,7 @@ function init() { } function useRaw (sbx) { - return sbx.properties.rawbg && (sbx.extendedSettings.useRaw === true || sbx.extendedSettings.useRaw.toLowerCase() === 'true'); + return sbx.properties.rawbg && sbx.extendedSettings.useRaw !== undefined && (sbx.extendedSettings.useRaw === true || sbx.extendedSettings.useRaw.toLowerCase() === 'true'); } ar2.forecast = function forecast (sgvs, sbx) { From 91d2c0c77815c1fa0490b1b036465b06bd36b6ad Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 15 Jul 2015 22:59:19 -0700 Subject: [PATCH 413/937] use moment.js for all date parsing+manipulation; add test; wrap in clousure; adjust size of inputs --- bundle/bundle.source.js | 1 + lib/utils.js | 33 +------ static/css/drawer.css | 8 ++ static/index.html | 4 +- static/js/treatment.js | 214 ++++++++++++++++++++-------------------- tests/utils.test.js | 9 ++ 6 files changed, 132 insertions(+), 137 deletions(-) diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index ae04f5f72bf..05f8c2292e7 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -1,6 +1,7 @@ (function () { window._ = require('lodash'); + window.moment = require('moment-timezone'); window.Nightscout = window.Nightscout || {}; window.Nightscout = { diff --git a/lib/utils.js b/lib/utils.js index c9b061cf5d3..f584e371e05 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,5 +1,7 @@ 'use strict'; +var moment = require('moment-timezone'); + function init() { function utils() { @@ -60,35 +62,8 @@ function init() { }; // some helpers for input "date" - utils.mergeInputTime = function mergeInputTime(timestring,datestring) { - var eventTimeParts = timestring.split(':'); - var eventTime = new Date(datestring); - eventTime.setHours(eventTimeParts[0]); - eventTime.setMinutes(eventTimeParts[1]); - eventTime.setSeconds(0); - eventTime.setMilliseconds(0); - return eventTime; - }; - - utils.toDateInputValue = function toDateInputValue(datetime) { - var local = new Date(datetime); - local.setMinutes(datetime.getMinutes() - datetime.getTimezoneOffset()); - return local.toJSON().slice(0,10); - }; - - utils.addMinutes = function addMinutes(datetime,h) { - datetime.setTime(datetime.getTime() + (h*60*1000)); - return datetime; - }; - - utils.addHours = function addHours(datetime,h) { - datetime.setTime(datetime.getTime() + (h*60*60*1000)); - return datetime; - }; - - utils.addDays = function addDays(datetime,h) { - datetime.setTime(datetime.getTime() + (h*24*60*60*1000)); - return datetime; + utils.mergeInputTime = function mergeInputTime(timestring, datestring) { + return moment(datestring + ' ' + timestring, 'YYYY-MM-D HH:mm'); }; return utils(); diff --git a/static/css/drawer.css b/static/css/drawer.css index 021e5ab3ee5..6598bf558f8 100644 --- a/static/css/drawer.css +++ b/static/css/drawer.css @@ -100,6 +100,14 @@ input[type=number]:invalid { display: inline-block; } +#treatmentDrawer .eventdate { + width: 120px; +} + +#treatmentDrawer .eventtime { + width: 110px; +} + #treatmentDrawer #glucoseValue { width: 230px; } diff --git a/static/index.html b/static/index.html index ccb8c3b5f52..1bd5516e2e4 100644 --- a/static/index.html +++ b/static/index.html @@ -234,8 +234,8 @@

    Nightscout

    Other - - + +
    diff --git a/static/js/treatment.js b/static/js/treatment.js index a9a9e29ebc5..2df54c9ab54 100644 --- a/static/js/treatment.js +++ b/static/js/treatment.js @@ -1,118 +1,120 @@ 'use strict'; -function initTreatmentDrawer() { - $('#eventType').val('BG Check'); - $('#glucoseValue').val('').attr('placeholder', 'Value in ' + browserSettings.units); - $('#meter').prop('checked', true); - $('#carbsGiven').val(''); - $('#insulinGiven').val(''); - $('#preBolus').val(0); - $('#notes').val(''); - $('#enteredBy').val(browserStorage.get('enteredBy') || ''); - $('#nowtime').prop('checked', true); - $('#eventTimeValue').val(new Date().toTimeString().slice(0,5)); - $('#eventDateValue').val(Nightscout.utils.toDateInputValue(new Date())); -} - -function treatmentSubmit(event) { - - var data = {}; - data.enteredBy = $('#enteredBy').val(); - data.eventType = $('#eventType').val(); - data.glucose = $('#glucoseValue').val(); - data.glucoseType = $('#treatment-form input[name=glucoseType]:checked').val(); - data.carbs = $('#carbsGiven').val(); - data.insulin = $('#insulinGiven').val(); - data.preBolus = parseInt($('#preBolus').val()); - data.notes = $('#notes').val(); - data.units = browserSettings.units; - - var errors = []; - if (isNaN(data.glucose)) { - errors.push('Blood glucose must be a number'); +(function () { + function initTreatmentDrawer() { + $('#eventType').val('BG Check'); + $('#glucoseValue').val('').attr('placeholder', 'Value in ' + browserSettings.units); + $('#meter').prop('checked', true); + $('#carbsGiven').val(''); + $('#insulinGiven').val(''); + $('#preBolus').val(0); + $('#notes').val(''); + $('#enteredBy').val(browserStorage.get('enteredBy') || ''); + $('#nowtime').prop('checked', true); + $('#eventTimeValue').val(moment().format('HH:mm')); + $('#eventDateValue').val(moment().format('YYYY-MM-D')); } - if (isNaN(data.carbs)) { - errors.push('Carbs must be a number'); + function treatmentSubmit(event) { + + var data = {}; + data.enteredBy = $('#enteredBy').val(); + data.eventType = $('#eventType').val(); + data.glucose = $('#glucoseValue').val(); + data.glucoseType = $('#treatment-form input[name=glucoseType]:checked').val(); + data.carbs = $('#carbsGiven').val(); + data.insulin = $('#insulinGiven').val(); + data.preBolus = parseInt($('#preBolus').val()); + data.notes = $('#notes').val(); + data.units = browserSettings.units; + + var errors = []; + if (isNaN(data.glucose)) { + errors.push('Blood glucose must be a number'); + } + + if (isNaN(data.carbs)) { + errors.push('Carbs must be a number'); + } + + if (isNaN(data.insulin)) { + errors.push('Insulin must be a number'); + } + + if (errors.length > 0) { + window.alert(errors.join('\n')); + } else { + if ($('#othertime').is(':checked')) { + data.eventTime = Nightscout.utils.mergeInputTime($('#eventTimeValue').val(), $('#eventDateValue').val()); + } + confirmPost(data); + } + + if (event) { + event.preventDefault(); + } } - if (isNaN(data.insulin)) { - errors.push('Insulin must be a number'); + function confirmPost(data) { + var confirmtext = + 'Please verify that the data entered is correct: ' + + '\nEvent type: ' + data.eventType; + confirmtext += data.glucose ? '\nBlood glucose: ' + data.glucose + '\nMethod: ' + data.glucoseType : ''; + confirmtext += data.carbs ? '\nCarbs Given: ' + data.carbs : ''; + confirmtext += data.insulin ? '\nInsulin Given: ' + data.insulin : ''; + confirmtext += data.preBolus ? '\nPre Bolus: ' + data.preBolus : ''; + confirmtext += data.notes ? '\nNotes: ' + data.notes : ''; + confirmtext += data.enteredBy ? '\nEntered By: ' + data.enteredBy : ''; + confirmtext += data.eventTime ? '\nEvent Time: ' + data.eventTime.format('LLL') : ''; + + if (window.confirm(confirmtext)) { + var dataJson = JSON.stringify(data, null, ' '); + var xhr = new XMLHttpRequest(); + xhr.open('POST', '/api/v1/treatments/', true); + xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); + xhr.send(dataJson); + + browserStorage.set('enteredBy', data.enteredBy); + + closeDrawer('#treatmentDrawer'); + } } - if (errors.length > 0) { - window.alert(errors.join('\n')); - } else { - if ($('#othertime').is(':checked')) { - data.eventTime = Nightscout.utils.mergeInputTime($('#eventTimeValue').val(),$('#eventDateValue').val()); - } - confirmPost(data); - } - - if (event) { + $('#treatmentDrawerToggle').click(function (event) { + toggleDrawer('#treatmentDrawer', initTreatmentDrawer); event.preventDefault(); - } -} - -function confirmPost(data) { - var confirmtext = - 'Please verify that the data entered is correct: ' + - '\nEvent type: ' + data.eventType; - confirmtext += data.glucose ? '\nBlood glucose: ' + data.glucose + '\nMethod: ' + data.glucoseType : ''; - confirmtext += data.carbs ? '\nCarbs Given: ' + data.carbs : ''; - confirmtext += data.insulin ? '\nInsulin Given: ' + data.insulin : ''; - confirmtext += data.preBolus ? '\nPre Bolus: ' + data.preBolus : ''; - confirmtext += data.notes ? '\nNotes: ' + data.notes : ''; - confirmtext += data.enteredBy ? '\nEntered By: ' + data.enteredBy : ''; - confirmtext += data.eventTime ? '\nEvent Time: ' + data.eventTime.toLocaleString(): ''; - - if (window.confirm(confirmtext)) { - var dataJson = JSON.stringify(data, null, ' '); - var xhr = new XMLHttpRequest(); - xhr.open('POST', '/api/v1/treatments/', true); - xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); - xhr.send(dataJson); - - browserStorage.set('enteredBy', data.enteredBy); - - closeDrawer('#treatmentDrawer'); - } -} + }); -$('#treatmentDrawerToggle').click(function(event) { - toggleDrawer('#treatmentDrawer', initTreatmentDrawer); - event.preventDefault(); -}); + $('#treatmentDrawer').find('button').click(treatmentSubmit); -$('#treatmentDrawer').find('button').click(treatmentSubmit); + $('#eventTime input:radio').change(function (event) { + if ($('#othertime').is(':checked')) { + $('#eventTimeValue').focus(); + } + event.preventDefault(); + }); -$('#eventTime input:radio').change(function (event){ - if ($('#othertime').is(':checked')) { - $('#eventTimeValue').focus(); - } - event.preventDefault(); -}); - -$('.eventtimeinput').focus(function (event) { - $('#othertime').prop('checked', true); - var time = Nightscout.utils.mergeInputTime($('#eventTimeValue').val(),$('#eventDateValue').val()); - $(this).attr('oldminutes',time.getMinutes()); - $(this).attr('oldhours',time.getHours()); - event.preventDefault(); -}); - -$('.eventtimeinput').change(function (event) { - $('#othertime').prop('checked', true); - var time = Nightscout.utils.mergeInputTime($('#eventTimeValue').val(),$('#eventDateValue').val()); - if ($(this).attr('oldminutes')==='59' && time.getMinutes()===0) { - Nightscout.utils.addHours(time,1); - } - if ($(this).attr('oldminutes')==='0' && time.getMinutes()===59) { - Nightscout.utils.addHours(time,-1); - } - $('#eventTimeValue').val(time.toTimeString().slice(0,5)); - $('#eventDateValue').val(Nightscout.utils.toDateInputValue(time)); - $(this).attr('oldminutes',time.getMinutes()); - $(this).attr('oldhours',time.getHours()); - event.preventDefault(); -}); + $('.eventinput').focus(function (event) { + $('#othertime').prop('checked', true); + var moment = Nightscout.utils.mergeInputTime($('#eventTimeValue').val(), $('#eventDateValue').val()); + $(this).attr('oldminutes', moment.minutes()); + $(this).attr('oldhours', moment.hours()); + event.preventDefault(); + }); + + $('.eventinput').change(function (event) { + $('#othertime').prop('checked', true); + var moment = Nightscout.utils.mergeInputTime($('#eventTimeValue').val(), $('#eventDateValue').val()); + if ($(this).attr('oldminutes') === ' 59' && moment.minutes() === 0) { + moment.add(1, 'hours'); + } + if ($(this).attr('oldminutes') === '0' && moment.minutes() === 59) { + moment.add(-1, 'hours'); + } + $('#eventTimeValue').val(moment.format('HH:mm')); + $('#eventDateValue').val(moment.format('YYYY-MM-D')); + $(this).attr('oldminutes', moment.minutes()); + $(this).attr('oldhours', moment.hours()); + event.preventDefault(); + }); +})(); \ No newline at end of file diff --git a/tests/utils.test.js b/tests/utils.test.js index 3dab434df78..7d21f3abf31 100644 --- a/tests/utils.test.js +++ b/tests/utils.test.js @@ -21,4 +21,13 @@ describe('utils', function ( ) { result.status.should.equal('current'); }); + it('merge date and time', function () { + var result = utils.mergeInputTime('22:35', '2015-07-14'); + result.hours().should.equal(22); + result.minutes().should.equal(35); + result.year().should.equal(2015); + result.format('MMM').should.equal('Jul'); + result.date().should.equal(14); + }); + }); From adee6c95b91a769ff8e50690fe7b8d446895c2d8 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 15 Jul 2015 23:13:13 -0700 Subject: [PATCH 414/937] refactoring to lower complexity; also a little jquery optimization --- static/js/treatment.js | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/static/js/treatment.js b/static/js/treatment.js index 2df54c9ab54..1ac25010f72 100644 --- a/static/js/treatment.js +++ b/static/js/treatment.js @@ -15,19 +15,7 @@ $('#eventDateValue').val(moment().format('YYYY-MM-D')); } - function treatmentSubmit(event) { - - var data = {}; - data.enteredBy = $('#enteredBy').val(); - data.eventType = $('#eventType').val(); - data.glucose = $('#glucoseValue').val(); - data.glucoseType = $('#treatment-form input[name=glucoseType]:checked').val(); - data.carbs = $('#carbsGiven').val(); - data.insulin = $('#insulinGiven').val(); - data.preBolus = parseInt($('#preBolus').val()); - data.notes = $('#notes').val(); - data.units = browserSettings.units; - + function checkForErrors(data) { var errors = []; if (isNaN(data.glucose)) { errors.push('Blood glucose must be a number'); @@ -40,6 +28,23 @@ if (isNaN(data.insulin)) { errors.push('Insulin must be a number'); } + return errors; + } + + function treatmentSubmit(event) { + + var data = {}; + data.enteredBy = $('#enteredBy').val(); + data.eventType = $('#eventType').val(); + data.glucose = $('#glucoseValue').val(); + data.glucoseType = $('#treatment-form').find('input[name=glucoseType]:checked').val(); + data.carbs = $('#carbsGiven').val(); + data.insulin = $('#insulinGiven').val(); + data.preBolus = parseInt($('#preBolus').val()); + data.notes = $('#notes').val(); + data.units = browserSettings.units; + + var errors = checkForErrors(data); if (errors.length > 0) { window.alert(errors.join('\n')); From 5b59afb775fad9debd283a5026984c67925b2963 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 15 Jul 2015 23:22:56 -0700 Subject: [PATCH 415/937] one more shot to try making codacy happy --- static/js/treatment.js | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/static/js/treatment.js b/static/js/treatment.js index 1ac25010f72..124840e36ee 100644 --- a/static/js/treatment.js +++ b/static/js/treatment.js @@ -31,27 +31,34 @@ return errors; } - function treatmentSubmit(event) { + function prepareData() { + var data = { + enteredBy: $('#enteredBy').val() + , eventType: $('#eventType').val() + , glucose: $('#glucoseValue').val() + , glucoseType: $('#treatment-form').find('input[name=glucoseType]:checked').val() + , carbs: $('#carbsGiven').val() + , insulin: $('#insulinGiven').val() + , preBolus: parseInt($('#preBolus').val()) + , notes: $('#notes').val() + , units: browserSettings.units + }; + + if ($('#othertime').is(':checked')) { + data.eventTime = Nightscout.utils.mergeInputTime($('#eventTimeValue').val(), $('#eventDateValue').val()); + } - var data = {}; - data.enteredBy = $('#enteredBy').val(); - data.eventType = $('#eventType').val(); - data.glucose = $('#glucoseValue').val(); - data.glucoseType = $('#treatment-form').find('input[name=glucoseType]:checked').val(); - data.carbs = $('#carbsGiven').val(); - data.insulin = $('#insulinGiven').val(); - data.preBolus = parseInt($('#preBolus').val()); - data.notes = $('#notes').val(); - data.units = browserSettings.units; + return data; + } + + function treatmentSubmit(event) { + var data = prepareData(); var errors = checkForErrors(data); if (errors.length > 0) { window.alert(errors.join('\n')); } else { - if ($('#othertime').is(':checked')) { - data.eventTime = Nightscout.utils.mergeInputTime($('#eventTimeValue').val(), $('#eventDateValue').val()); - } confirmPost(data); } From 5e9951971905075b39683aac1e1e742bc7b2f417 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 15 Jul 2015 23:48:01 -0700 Subject: [PATCH 416/937] always a little more refactoring to do --- static/js/treatment.js | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/static/js/treatment.js b/static/js/treatment.js index 124840e36ee..65acf7680ae 100644 --- a/static/js/treatment.js +++ b/static/js/treatment.js @@ -67,19 +67,22 @@ } } - function confirmPost(data) { - var confirmtext = + function buildConfirmText(data) { + var text = 'Please verify that the data entered is correct: ' + '\nEvent type: ' + data.eventType; - confirmtext += data.glucose ? '\nBlood glucose: ' + data.glucose + '\nMethod: ' + data.glucoseType : ''; - confirmtext += data.carbs ? '\nCarbs Given: ' + data.carbs : ''; - confirmtext += data.insulin ? '\nInsulin Given: ' + data.insulin : ''; - confirmtext += data.preBolus ? '\nPre Bolus: ' + data.preBolus : ''; - confirmtext += data.notes ? '\nNotes: ' + data.notes : ''; - confirmtext += data.enteredBy ? '\nEntered By: ' + data.enteredBy : ''; - confirmtext += data.eventTime ? '\nEvent Time: ' + data.eventTime.format('LLL') : ''; - - if (window.confirm(confirmtext)) { + text += data.glucose ? '\nBlood glucose: ' + data.glucose + '\nMethod: ' + data.glucoseType : ''; + text += data.carbs ? '\nCarbs Given: ' + data.carbs : ''; + text += data.insulin ? '\nInsulin Given: ' + data.insulin : ''; + text += data.preBolus ? '\nPre Bolus: ' + data.preBolus : ''; + text += data.notes ? '\nNotes: ' + data.notes : ''; + text += data.enteredBy ? '\nEntered By: ' + data.enteredBy : ''; + text += data.eventTime ? '\nEvent Time: ' + data.eventTime.format('LLL') : moment().format('LLL'); + return text; + } + + function confirmPost(data) { + if (window.confirm(buildConfirmText(data))) { var dataJson = JSON.stringify(data, null, ' '); var xhr = new XMLHttpRequest(); xhr.open('POST', '/api/v1/treatments/', true); @@ -99,7 +102,7 @@ $('#treatmentDrawer').find('button').click(treatmentSubmit); - $('#eventTime input:radio').change(function (event) { + $('#eventTime').find('input:radio').change(function (event) { if ($('#othertime').is(':checked')) { $('#eventTimeValue').focus(); } @@ -112,9 +115,8 @@ $(this).attr('oldminutes', moment.minutes()); $(this).attr('oldhours', moment.hours()); event.preventDefault(); - }); - - $('.eventinput').change(function (event) { + }) + .change(function (event) { $('#othertime').prop('checked', true); var moment = Nightscout.utils.mergeInputTime($('#eventTimeValue').val(), $('#eventDateValue').val()); if ($(this).attr('oldminutes') === ' 59' && moment.minutes() === 0) { From 0637dc76c811031d4ee20b2979d38503169b678e Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 16 Jul 2015 00:15:50 -0700 Subject: [PATCH 417/937] one more try to clear the codacy warning, seems a little cleaner anyway --- static/js/treatment.js | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/static/js/treatment.js b/static/js/treatment.js index 65acf7680ae..bdc70d06b41 100644 --- a/static/js/treatment.js +++ b/static/js/treatment.js @@ -68,17 +68,24 @@ } function buildConfirmText(data) { - var text = - 'Please verify that the data entered is correct: ' + - '\nEvent type: ' + data.eventType; - text += data.glucose ? '\nBlood glucose: ' + data.glucose + '\nMethod: ' + data.glucoseType : ''; - text += data.carbs ? '\nCarbs Given: ' + data.carbs : ''; - text += data.insulin ? '\nInsulin Given: ' + data.insulin : ''; - text += data.preBolus ? '\nPre Bolus: ' + data.preBolus : ''; - text += data.notes ? '\nNotes: ' + data.notes : ''; - text += data.enteredBy ? '\nEntered By: ' + data.enteredBy : ''; - text += data.eventTime ? '\nEvent Time: ' + data.eventTime.format('LLL') : moment().format('LLL'); - return text; + var text = [ + 'Please verify that the data entered is correct: ' + , 'Event type: ' + data.eventType + ]; + + if (data.glucose) { + text.push('Blood glucose: ' + data.glucose); + text.push('Method: ' + data.glucoseType) + } + + if (data.carbs) { text.push('Carbs Given: ' + data.carbs); } + if (data.insulin) { text.push('Insulin Given: ' + data.insulin); } + if (data.preBolus) { text.push('Insulin Given: ' + data.insulin); } + if (data.notes) { text.push('Notes: ' + data.notes); } + if (data.enteredBy) { text.push('Entered By: ' + data.enteredBy); } + + text.push('Event Time: ' + (data.eventTime ? data.eventTime.format('LLL') : moment().format('LLL'))); + return text.join('\n'); } function confirmPost(data) { From 9a29a9ddfcaeb1018c1eb79f84319d2bf5e4e47d Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 16 Jul 2015 00:19:47 -0700 Subject: [PATCH 418/937] ;... --- static/js/treatment.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/js/treatment.js b/static/js/treatment.js index bdc70d06b41..7dd0df20b2e 100644 --- a/static/js/treatment.js +++ b/static/js/treatment.js @@ -75,7 +75,7 @@ if (data.glucose) { text.push('Blood glucose: ' + data.glucose); - text.push('Method: ' + data.glucoseType) + text.push('Method: ' + data.glucoseType); } if (data.carbs) { text.push('Carbs Given: ' + data.carbs); } From 89b901a234d8186de3b49ecb8307316ff3a009c2 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Thu, 16 Jul 2015 15:56:50 +0300 Subject: [PATCH 419/937] Fixes old CAGE notes being displayed on newer site change events, as reported on https://github.com/nightscout/cgm-remote-monitor/issues/699 --- lib/plugins/cannulaage.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/plugins/cannulaage.js b/lib/plugins/cannulaage.js index 8dbd306d67f..2c2be701364 100644 --- a/lib/plugins/cannulaage.js +++ b/lib/plugins/cannulaage.js @@ -86,6 +86,8 @@ cage.findLatestTimeChange = function findLatestTimeChange(sbx) { returnValue.age = hours; if (treatment.notes) { returnValue.message = treatment.notes; + } else { + returnValue.message = ''; } } } From 0cf101837cafb4e14c5effd149f153070de6c844 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Thu, 16 Jul 2015 16:11:45 +0300 Subject: [PATCH 420/937] Added a test for the site change event notes --- tests/cannulaage.test.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/cannulaage.test.js b/tests/cannulaage.test.js index 9f9a0bce64d..efa7c642444 100644 --- a/tests/cannulaage.test.js +++ b/tests/cannulaage.test.js @@ -25,12 +25,16 @@ describe('cage', function ( ) { var clientSettings = {}; var data = { - treatments: [{eventType: 'Site Change', mills: Date.now() - 24 * 60 * 60000}] + treatments: [ + {eventType: 'Site Change', notes: 'Foo', mills: Date.now() - 48 * 60 * 60000} + , {eventType: 'Site Change', notes: 'Bar', mills: Date.now() - 24 * 60 * 60000} + ] }; var pluginBase = { updatePillText: function mockedUpdatePillText (plugin, options) { options.value.should.equal('24h'); + options.info[1].value.should.equal('Bar'); done(); } }; From 7c4092f398dfb71c7bb8278273826432318a8690 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 16 Jul 2015 23:03:24 -0700 Subject: [PATCH 421/937] add errorcodes to the special plugins list --- lib/plugins/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/index.js b/lib/plugins/index.js index b17ca89991f..ebc0ab2959b 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -87,7 +87,7 @@ function init() { }; //these plugins are either always on or have custom settings - plugins.specialPlugins = 'ar2 delta direction upbat rawbg'; + plugins.specialPlugins = 'ar2 delta direction upbat rawbg errorcodes'; plugins.shownPlugins = function(sbx) { return _.filter(enabledPlugins, function filterPlugins(plugin) { From aadc7fae6b7dc71257ce8bd671bb9eac4ffe7f19 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 16 Jul 2015 23:04:16 -0700 Subject: [PATCH 422/937] refactor env.js, auto enable treatmentnotify if careportal, pushover, or maker is enabled --- env.js | 225 ++++++++++++++++++++++++++++---------------------- lib/data.js | 4 +- lib/pebble.js | 4 +- 3 files changed, 131 insertions(+), 102 deletions(-) diff --git a/env.js b/env.js index 5de71fb2acf..4dfa33aa1ef 100644 --- a/env.js +++ b/env.js @@ -5,18 +5,74 @@ var _ = require('lodash'); var crypto = require('crypto'); var consts = require('./lib/constants'); var fs = require('fs'); + // Module to constrain all config and environment parsing to one spot. +// See the function config ( ) { /* - * First inspect a bunch of environment variables: - * * PORT - serve http on this port - * * MONGO_CONNECTION, CUSTOMCONNSTR_mongo - mongodb://... uri - * * CUSTOMCONNSTR_mongo_collection - name of mongo collection with `sgv` documents - * * API_SECRET - if defined, this passphrase is fed to a sha1 hash digest, the hex output is used to create a single-use token for API authorization - * * NIGHTSCOUT_STATIC_FILES - the `base directory` to use for serving - * static files over http. Default value is the included `static` - * directory. + * See README.md for info about all the supported ENV VARs */ + env.DISPLAY_UNITS = readENV('DISPLAY_UNITS', 'mg/dl'); + env.PORT = readENV('PORT', 1337); + env.baseUrl = readENV('BASE_URL'); + env.static_files = readENV('NIGHTSCOUT_STATIC_FILES', __dirname + '/static/'); + + setSSL(); + setAPISecret(); + setVersion(); + setMongo(); + setAlarmType(); + setEnableAndExtendedSettnigs(); + setDefaults(); + setThresholds(); + + env.isEnabled = isEnabled; + env.anyEnabled = anyEnabled; + + return env; +} + +function isEnabled (feature) { + return env.enable.indexOf(feature) > -1; +} + +function anyEnabled (features) { + return _.findIndex(features, isEnabled) > -1; +} + +function setSSL() { + env.SSL_KEY = readENV('SSL_KEY'); + env.SSL_CERT = readENV('SSL_CERT'); + env.SSL_CA = readENV('SSL_CA'); + env.ssl = false; + if (env.SSL_KEY && env.SSL_CERT) { + env.ssl = { + key: fs.readFileSync(env.SSL_KEY), cert: fs.readFileSync(env.SSL_CERT) + }; + if (env.SSL_CA) { + env.ca = fs.readFileSync(env.SSL_CA); + } + } +} + +// A little ugly, but we don't want to read the secret into a var +function setAPISecret() { + var useSecret = (readENV('API_SECRET') && readENV('API_SECRET').length > 0); + //TODO: should we clear API_SECRET from process env? + env.api_secret = null; + // if a passphrase was provided, get the hex digest to mint a single token + if (useSecret) { + if (readENV('API_SECRET').length < consts.MIN_PASSPHRASE_LENGTH) { + var msg = ['API_SECRET should be at least', consts.MIN_PASSPHRASE_LENGTH, 'characters']; + throw new Error(msg.join(' ')); + } + var shasum = crypto.createHash('sha1'); + shasum.update(readENV('API_SECRET')); + env.api_secret = shasum.digest('hex'); + } +} + +function setVersion() { var software = require('./package.json'); var git = require('git-rev'); @@ -24,20 +80,21 @@ function config ( ) { env.head = require('./scm-commit-id.json'); console.log('SCM COMMIT ID', env.head); } else { - git.short(function record_git_head (head) { + git.short(function record_git_head(head) { console.log('GIT HEAD', head); env.head = head || readENV('SCM_COMMIT_ID') || readENV('COMMIT_HASH', ''); }); } env.version = software.version; env.name = software.name; - env.DISPLAY_UNITS = readENV('DISPLAY_UNITS', 'mg/dl'); - env.PORT = readENV('PORT', 1337); +} + +function setMongo() { env.mongo = readENV('MONGO_CONNECTION') || readENV('MONGO') || readENV('MONGOLAB_URI'); env.mongo_collection = readENV('MONGO_COLLECTION', 'entries'); env.MQTT_MONITOR = readENV('MQTT_MONITOR', null); if (env.MQTT_MONITOR) { - var hostDbCollection = [env.mongo.split('mongodb://').pop().split('@').pop( ), env.mongo_collection].join('/'); + var hostDbCollection = [env.mongo.split('mongodb://').pop().split('@').pop(), env.mongo_collection].join('/'); var mongoHash = crypto.createHash('sha1'); mongoHash.update(hostDbCollection); //some MQTT servers only allow the client id to be 23 chars @@ -53,9 +110,55 @@ function config ( ) { env.profile_collection = readENV('MONGO_PROFILE_COLLECTION', 'profile'); env.devicestatus_collection = readENV('MONGO_DEVICESTATUS_COLLECTION', 'devicestatus'); + // TODO: clean up a bit + // Some people prefer to use a json configuration file instead. + // This allows a provided json config to override environment variables + var DB = require('./database_configuration.json'), + DB_URL = DB.url ? DB.url : env.mongo, + DB_COLLECTION = DB.collection ? DB.collection : env.mongo_collection; + env.mongo = DB_URL; + env.mongo_collection = DB_COLLECTION; +} + +function setAlarmType() { +//if any of the BG_* thresholds are set, default to `simple` and `predict` otherwise default to only `predict` + var thresholdsSet = readIntENV('BG_HIGH') || readIntENV('BG_TARGET_TOP') || readIntENV('BG_TARGET_BOTTOM') || readIntENV('BG_LOW'); + env.alarm_types = readENV('ALARM_TYPES') || (thresholdsSet ? 'simple predict' : 'predict'); +} + +function setEnableAndExtendedSettnigs() { env.enable = readENV('ENABLE', ''); + //TODO: maybe get rid of ALARM_TYPES and only use enable? + if (env.alarm_types.indexOf('simple') > -1) { + env.enable = 'simplealarms ' + env.enable; + } + if (env.alarm_types.indexOf('predict') > -1) { + env.enable = 'ar2 ' + env.enable; + } + + // For pushing notifications to Pushover. + //TODO: handle PUSHOVER_ as generic plugin props + env.pushover_api_token = readENV('PUSHOVER_API_TOKEN'); + env.pushover_user_key = readENV('PUSHOVER_USER_KEY') || readENV('PUSHOVER_GROUP_KEY'); + if (env.pushover_api_token && env.pushover_user_key) { + env.enable += ' pushover'; + //TODO: after config changes are documented this shouldn't be auto enabled + } - env.defaults = { // currently supported keys must defined be here + if (anyEnabled(['careportal', 'pushover', 'maker'])) { + env.enable += ' treatmentnotify'; + } + + //TODO: figure out something for default plugins, how can they be disabled? + env.enable += ' delta direction upbat errorcodes'; + + env.extendedSettings = findExtendedSettings(env.enable, process.env); +} + +function setDefaults() { + + // currently supported keys must defined be here + env.defaults = { 'units': 'mg/dL' , 'timeFormat': '12' , 'nightMode': false @@ -71,11 +174,12 @@ function config ( ) { , 'alarmTimeAgoUrgent': true , 'alarmTimeAgoUrgentMins': 30 , 'language': 'en' // not used yet - } ; + }; // add units from separate variable + //TODO: figure out where this is used, should only be in 1 spot env.defaults.units = env.DISPLAY_UNITS; - + // Highest priority per line defaults env.defaults.timeFormat = readENV('TIME_FORMAT', env.defaults.timeFormat); env.defaults.nightMode = readENV('NIGHT_MODE', env.defaults.nightMode); @@ -99,43 +203,9 @@ function config ( ) { env.defaults.showPlugins += 'rawbg'; } } +} - //console.log(JSON.stringify(env.defaults)); - - env.SSL_KEY = readENV('SSL_KEY'); - env.SSL_CERT = readENV('SSL_CERT'); - env.SSL_CA = readENV('SSL_CA'); - env.ssl = false; - if (env.SSL_KEY && env.SSL_CERT) { - env.ssl = { - key: fs.readFileSync(env.SSL_KEY) - , cert: fs.readFileSync(env.SSL_CERT) - }; - if (env.SSL_CA) { - env.ca = fs.readFileSync(env.SSL_CA); - } - } - - var shasum = crypto.createHash('sha1'); - - ///////////////////////////////////////////////////////////////// - // A little ugly, but we don't want to read the secret into a var - ///////////////////////////////////////////////////////////////// - var useSecret = (readENV('API_SECRET') && readENV('API_SECRET').length > 0); - env.api_secret = null; - // if a passphrase was provided, get the hex digest to mint a single token - if (useSecret) { - if (readENV('API_SECRET').length < consts.MIN_PASSPHRASE_LENGTH) { - var msg = ['API_SECRET should be at least', consts.MIN_PASSPHRASE_LENGTH, 'characters']; - var err = new Error(msg.join(' ')); - // console.error(err); - throw err; - process.exit(1); - } - shasum.update(readENV('API_SECRET')); - env.api_secret = shasum.digest('hex'); - } - +function setThresholds() { env.thresholds = { bg_high: readIntENV('BG_HIGH', 260) , bg_target_top: readIntENV('BG_TARGET_TOP', 180) @@ -168,59 +238,18 @@ function config ( ) { env.thresholds.bg_high = env.thresholds.bg_target_top + 1; console.warn('BG_HIGH is now ' + env.thresholds.bg_high); } - - //if any of the BG_* thresholds are set, default to `simple` otherwise default to `predict` to preserve current behavior - var thresholdsSet = readIntENV('BG_HIGH') || readIntENV('BG_TARGET_TOP') || readIntENV('BG_TARGET_BOTTOM') || readIntENV('BG_LOW'); - env.alarm_types = readENV('ALARM_TYPES') || (thresholdsSet ? 'simple' : 'predict'); - - //TODO: maybe get rid of ALARM_TYPES and only use enable? - if (env.alarm_types.indexOf('simple') > -1) { - env.enable = 'simplealarms ' + env.enable; - } - if (env.alarm_types.indexOf('predict') > -1) { - env.enable = 'ar2 ' + env.enable; - } - - // For pushing notifications to Pushover. - //TODO: handle PUSHOVER_ as generic plugin props - env.pushover_api_token = readENV('PUSHOVER_API_TOKEN'); - env.pushover_user_key = readENV('PUSHOVER_USER_KEY') || readENV('PUSHOVER_GROUP_KEY'); - if (env.pushover_api_token && env.pushover_user_key) { - env.enable += ' pushover'; - //TODO: after config changes are documented this shouldn't be auto enabled - env.enable += ' treatmentnotify'; - } - - //TODO: figure out something for default plugins, how can they be disabled? - env.enable += ' delta direction upbat errorcodes'; - - env.extendedSettings = findExtendedSettings(env.enable, process.env); - - env.baseUrl = readENV('BASE_URL'); - - // TODO: clean up a bit - // Some people prefer to use a json configuration file instead. - // This allows a provided json config to override environment variables - var DB = require('./database_configuration.json'), - DB_URL = DB.url ? DB.url : env.mongo, - DB_COLLECTION = DB.collection ? DB.collection : env.mongo_collection; - env.mongo = DB_URL; - env.mongo_collection = DB_COLLECTION; - env.static_files = readENV('NIGHTSCOUT_STATIC_FILES', __dirname + '/static/'); - - return env; } function readIntENV(varName, defaultValue) { - return parseInt(readENV(varName)) || defaultValue; + return parseInt(readENV(varName)) || defaultValue; } function readENV(varName, defaultValue) { - //for some reason Azure uses this prefix, maybe there is a good reason - var value = process.env['CUSTOMCONNSTR_' + varName] - || process.env['CUSTOMCONNSTR_' + varName.toLowerCase()] - || process.env[varName] - || process.env[varName.toLowerCase()]; + //for some reason Azure uses this prefix, maybe there is a good reason + var value = process.env['CUSTOMCONNSTR_' + varName] + || process.env['CUSTOMCONNSTR_' + varName.toLowerCase()] + || process.env[varName] + || process.env[varName.toLowerCase()]; if (typeof value === 'string' && value.toLowerCase() === 'on') { value = true; } if (typeof value === 'string' && value.toLowerCase() === 'off') { value = false; } diff --git a/lib/data.js b/lib/data.js index dfff92ebf58..6951c3ad4d4 100644 --- a/lib/data.js +++ b/lib/data.js @@ -148,7 +148,7 @@ function init(env, ctx) { function mgdlByTime() { var withBGs = _.filter(data.sgvs, function(d) { - return d.mgdl > 39 || env.enable.indexOf('rawbg') > -1; + return d.mgdl > 39 || env.isEnabled('rawbg'); }); var beforeTreatment = _.findLast(withBGs, function (d) { @@ -179,7 +179,7 @@ function init(env, ctx) { function calcRaw (entry) { var raw; - if (entry && env.enable.indexOf('rawbg') > -1) { + if (entry && env.isEnabled('rawbg')) { var cal = _.last(data.cals); if (cal) { raw = rawbg.calc(entry, cal); diff --git a/lib/pebble.js b/lib/pebble.js index 6fce7df05a0..4e1075a4a6b 100644 --- a/lib/pebble.js +++ b/lib/pebble.js @@ -127,8 +127,8 @@ function configure (env, ctx) { function middle (req, res, next) { req.env = env; req.ctx = ctx; - req.rawbg = env.enable && env.enable.indexOf('rawbg') > -1; - req.iob = env.enable && env.enable.indexOf('iob') > -1; + req.rawbg = env.enable && env.isEnabled('rawbg'); + req.iob = env.enable && env.isEnabled('iob'); req.mmol = (req.query.units || env.DISPLAY_UNITS) === 'mmol'; req.count = parseInt(req.query.count) || 1; From fe02f99d680293aec92c818d1ae2964c7dffb452 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 16 Jul 2015 23:34:15 -0700 Subject: [PATCH 423/937] some env test to cover areas not covered already --- env.js | 4 +-- tests/env.test.js | 80 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 tests/env.test.js diff --git a/env.js b/env.js index 4dfa33aa1ef..23e04210ad2 100644 --- a/env.js +++ b/env.js @@ -120,10 +120,10 @@ function setMongo() { env.mongo_collection = DB_COLLECTION; } -function setAlarmType() { //if any of the BG_* thresholds are set, default to `simple` and `predict` otherwise default to only `predict` +function setAlarmType() { var thresholdsSet = readIntENV('BG_HIGH') || readIntENV('BG_TARGET_TOP') || readIntENV('BG_TARGET_BOTTOM') || readIntENV('BG_LOW'); - env.alarm_types = readENV('ALARM_TYPES') || (thresholdsSet ? 'simple predict' : 'predict'); + env.alarm_types = readENV('ALARM_TYPES') || (thresholdsSet ? 'simple' : 'predict'); } function setEnableAndExtendedSettnigs() { diff --git a/tests/env.test.js b/tests/env.test.js new file mode 100644 index 00000000000..687ecbf0da1 --- /dev/null +++ b/tests/env.test.js @@ -0,0 +1,80 @@ +'use strict'; + +require('should'); + +describe('env', function ( ) { + + it('set thresholds', function () { + process.env.BG_HIGH = 200; + process.env.BG_TARGET_TOP = 170; + process.env.BG_TARGET_BOTTOM = 70; + process.env.BG_LOW = 60; + + var env = require('../env')(); + + env.thresholds.bg_high.should.equal(200); + env.thresholds.bg_target_top.should.equal(170); + env.thresholds.bg_target_bottom.should.equal(70); + env.thresholds.bg_low.should.equal(60); + + env.alarm_types.should.equal('simple'); + + + delete process.env.BG_HIGH; + delete process.env.BG_TARGET_TOP; + delete process.env.BG_TARGET_BOTTOM; + delete process.env.BG_LOW; + }); + + it('handle screwed up thresholds in a way that will display something that looks wrong', function () { + process.env.BG_HIGH = 89; + process.env.BG_TARGET_TOP = 90; + process.env.BG_TARGET_BOTTOM = 95; + process.env.BG_LOW = 96; + + var env = require('../env')(); + + env.thresholds.bg_high.should.equal(91); + env.thresholds.bg_target_top.should.equal(90); + env.thresholds.bg_target_bottom.should.equal(89); + env.thresholds.bg_low.should.equal(88); + + env.alarm_types.should.equal('simple'); + + + delete process.env.BG_HIGH; + delete process.env.BG_TARGET_TOP; + delete process.env.BG_TARGET_BOTTOM; + delete process.env.BG_LOW; + }); + + it('show the right plugins', function () { + process.env.SHOW_PLUGINS = 'iob'; + process.env.ENABLE = 'iob cob'; + + var env = require('../env')(); + env.defaults.showPlugins.should.containEql('iob'); + env.defaults.showPlugins.should.containEql('delta'); + env.defaults.showPlugins.should.containEql('direction'); + env.defaults.showPlugins.should.containEql('upbat'); + env.defaults.showPlugins.should.not.containEql('cob'); + + delete process.env.SHOW_PLUGINS; + delete process.env.ENABLE; + }); + + it('get extended settings', function () { + process.env.ENABLE = 'scaryplugin'; + process.env.SCARYPLUGIN_DO_THING = 'yes'; + + var env = require('../env')(); + env.isEnabled('scaryplugin').should.equal(true); + + //Note the camelCase + env.extendedSettings.scaryplugin.doThing.should.equal('yes'); + + delete process.env.ENABLE; + delete process.env.SCARYPLUGIN_DO_THING; + }); + +}); From 243d3f86e18da5be9e46c1630536d4791698617e Mon Sep 17 00:00:00 2001 From: Ben West Date: Fri, 17 Jul 2015 15:28:02 -0700 Subject: [PATCH 424/937] alias subscription to /downloads/$username/protobuf /downloads/$username/protobuf is alias for /downloads/protobuf --- lib/mqtt.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/mqtt.js b/lib/mqtt.js index 1ed2d32fa36..5066770d5c4 100644 --- a/lib/mqtt.js +++ b/lib/mqtt.js @@ -5,12 +5,17 @@ var Long = require('long'); var decoders = require('sgvdata/lib/protobuf'); var direction = require('sgvdata/lib/utils').direction; var moment = require('moment'); +var url = require('url'); function init (env, ctx) { function mqtt ( ) { return mqtt; } + var info = url.parse(env.MQTT_MONITOR); + var username = info.auth.split(':').slice(0, -1).join(''); + var shared_topic = '/downloads/' + username + '/#'; + env.mqtt_shared_topic = shared_topic; mqtt.client = connect(env); var downloads = downloader(); @@ -28,6 +33,11 @@ function init (env, ctx) { function listenForMessages ( ) { mqtt.client.on('message', function (topic, msg) { console.log('topic', topic); + // XXX: ugly hack + if (topic == shared_topic) { + topic = '/downloads/protobuf'; + } + console.log(topic, 'on message', 'msg', msg.length); switch (topic) { case '/uploader': @@ -59,6 +69,7 @@ function init (env, ctx) { function connect (env) { var uri = env.MQTT_MONITOR; + var shared_topic = env.mqtt_shared_topic; if (!uri) { return null; } @@ -75,6 +86,7 @@ function connect (env) { client.subscribe('sgvs'); client.subscribe('published'); client.subscribe('/downloads/protobuf', {qos: 2}, granted); + client.subscribe(shared_topic, {qos: 2}, granted); client.subscribe('/uploader', granted); client.subscribe('/entries/sgv', granted); From f897e0dc0f660f9298cd90b32db6743c213348e7 Mon Sep 17 00:00:00 2001 From: Ben West Date: Fri, 17 Jul 2015 15:30:55 -0700 Subject: [PATCH 425/937] common throw-away vms/docker containers needs root Allow root to run bower.... --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a974b93ab98..9bf59f340fb 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "scripts": { "start": "node server.js", "test": "make test", - "postinstall": "node node_modules/bower/bin/bower install" + "postinstall": "node node_modules/bower/bin/bower --allow-root install" }, "config": { "blanket": { From 7c44512fed4b7c51eabc0201df2bc91047257977 Mon Sep 17 00:00:00 2001 From: Ben West Date: Fri, 17 Jul 2015 15:33:43 -0700 Subject: [PATCH 426/937] fix logic error --- lib/mqtt.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/mqtt.js b/lib/mqtt.js index 5066770d5c4..11e81c9679d 100644 --- a/lib/mqtt.js +++ b/lib/mqtt.js @@ -15,6 +15,7 @@ function init (env, ctx) { var info = url.parse(env.MQTT_MONITOR); var username = info.auth.split(':').slice(0, -1).join(''); var shared_topic = '/downloads/' + username + '/#'; + var alias_topic = '/downloads/' + username + '/protobuf'; env.mqtt_shared_topic = shared_topic; mqtt.client = connect(env); @@ -34,7 +35,7 @@ function init (env, ctx) { mqtt.client.on('message', function (topic, msg) { console.log('topic', topic); // XXX: ugly hack - if (topic == shared_topic) { + if (topic == alias_topic) { topic = '/downloads/protobuf'; } From e696d85160f229e6a7b453803d07cbf5ef00f92f Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 17 Jul 2015 19:05:48 -0700 Subject: [PATCH 427/937] try to prevent the stale-switch bug; also prevent stale data alarms on mobile browsers when suspended tab is opened --- static/js/client.js | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index fabdb346b33..e4dc23eac07 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -67,6 +67,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; , contextHeight , dateFn = function (d) { return new Date(d.mills) } , documentHidden = false + , visibilityChanging = false , brush , brushTimer , brushInProgress = false @@ -233,7 +234,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; // 2 days before now as x0 and 30 minutes from now for x1 for context plot, but this will be // required to happen when 'now' event is sent from websocket.js every minute. When fixed, // remove this code and all references to `type: 'server-forecast'` - var last = _.last(data); + var last = _.findLast(data, {type: 'sgv'}); var lastTime = last && last.mills; if (!lastTime) { console.error('Bad Data, last point has no mills', last); @@ -629,7 +630,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } // called for initial update and updates for resize - var updateChart = _.debounce(function debouncedUpdateChart(init) { + var updateChart = _.debounce(function debouncedUpdateChart(init, callback) { if (documentHidden && !init) { console.info('Document Hidden, not updating - ' + (new Date())); @@ -976,6 +977,10 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; context.select('.x') .call(xAxis2); + if (callback) { + callback(); + } + }, DEBOUNCE_MS); function sgvToColor(sgv) { @@ -1237,6 +1242,11 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; , ago = timeAgo(time, browserSettings) , retroMode = inRetroMode(); + if (visibilityChanging) { + console.info('visibility is changing now, wait till next tick to check time ago'); + return; + } + lastEntry.removeClass('current warn urgent'); lastEntry.addClass(ago.status); @@ -1330,12 +1340,16 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; context.append('g') .attr('class', 'y axis'); - //updateChart is _.debounce'd - function refreshChart(updateToNow) { - if (updateToNow) { - updateBrushToNow(); - } - updateChart(false); + function refreshChart(updateToNow, callback) { + //updateChart is _.debounce'd + updateChart(false, function ( ) { + if (updateToNow) { + updateBrushToNow(); + } + if (callback) { + callback(); + } + }); } function visibilityChanged() { @@ -1344,7 +1358,10 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; if (prevHidden && !documentHidden) { console.info('Document now visible, updating - ' + (new Date())); - refreshChart(true); + visibilityChanging = true; + refreshChart(true, function ( ) { + visibilityChanging = false; + }); } } From dec5a0f9e65af4bb351f9d9fce1c5e3850b36b24 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 17 Jul 2015 19:21:24 -0700 Subject: [PATCH 428/937] fix the mqtt test so the mqtt uri is defined --- tests/mqtt.test.js | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/tests/mqtt.test.js b/tests/mqtt.test.js index 4afe5f5f8c3..44ffc8a4281 100644 --- a/tests/mqtt.test.js +++ b/tests/mqtt.test.js @@ -6,7 +6,19 @@ var FIVE_MINS = 5 * 60 * 1000; describe('mqtt', function ( ) { - var mqtt = require('../lib/mqtt')({}, {}); + var self = this; + + before(function () { + process.env.MQTT_MONITOR = 'mqtt://user:password@localhost:12345'; + process.env.MONGO='mongodb://localhost/test_db'; + process.env.MONGO_COLLECTION='test_sgvs'; + self.env = require('../env')(); + self.mqtt = require('../lib/mqtt')(self.env, {}); + }); + + after(function () { + delete process.env.MQTT_MONITOR; + }); var now = Date.now() , prev1 = now - FIVE_MINS @@ -14,11 +26,7 @@ describe('mqtt', function ( ) { ; it('setup env correctly', function (done) { - process.env.MONGO='mongodb://localhost/test_db'; - process.env.MONGO_COLLECTION='test_sgvs'; - process.env.MQTT_MONITOR = 'mqtt://user:password@m10.cloudmqtt.com:12345'; - var env = require('../env')(); - env.mqtt_client_id.should.equal('fSjoHx8buyCtAc474tg8Dt3'); + self.env.mqtt_client_id.should.equal('fSjoHx8buyCtAc474tg8Dt3'); done(); }); @@ -31,7 +39,7 @@ describe('mqtt', function ( ) { ] }; - var merged = mqtt.sgvSensorMerge(packet); + var merged = self.mqtt.sgvSensorMerge(packet); merged.length.should.equal(packet.sgv.length); @@ -53,7 +61,7 @@ describe('mqtt', function ( ) { ] }; - var merged = mqtt.sgvSensorMerge(packet); + var merged = self.mqtt.sgvSensorMerge(packet); merged.length.should.equal(packet.sgv.length); @@ -77,7 +85,7 @@ describe('mqtt', function ( ) { ] }; - var merged = mqtt.sgvSensorMerge(packet); + var merged = self.mqtt.sgvSensorMerge(packet); merged.length.should.equal(packet.sgv.length); @@ -103,7 +111,7 @@ describe('mqtt', function ( ) { ] }; - var merged = mqtt.sgvSensorMerge(packet); + var merged = self.mqtt.sgvSensorMerge(packet); merged.length.should.equal(packet.sensor.length); From 29fdaf7254fb13602909664dbb5266e1a35248a6 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 17 Jul 2015 19:22:27 -0700 Subject: [PATCH 429/937] add an = --- lib/mqtt.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mqtt.js b/lib/mqtt.js index 11e81c9679d..7876cffc912 100644 --- a/lib/mqtt.js +++ b/lib/mqtt.js @@ -35,7 +35,7 @@ function init (env, ctx) { mqtt.client.on('message', function (topic, msg) { console.log('topic', topic); // XXX: ugly hack - if (topic == alias_topic) { + if (topic === alias_topic) { topic = '/downloads/protobuf'; } From 052165242daea9886a25d6ca942b886763471dda Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 18 Jul 2015 00:21:41 -0700 Subject: [PATCH 430/937] smoother page load and tab switching --- static/js/client.js | 69 ++++++++++++++++++++++----------------------- 1 file changed, 33 insertions(+), 36 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index e4dc23eac07..073e2dcaa9e 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -8,6 +8,8 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; , DEBOUNCE_MS = 10 , TOOLTIP_TRANS_MS = 200 // milliseconds , UPDATE_TRANS_MS = 750 // milliseconds + , TWO_SEC_IN_MS = 2000 + , FIVE_SEC_IN_MS = 5000 , ONE_MIN_IN_MS = 60000 , FIVE_MINS_IN_MS = 300000 , THREE_HOURS_MS = 3 * 60 * 60 * 1000 @@ -67,7 +69,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; , contextHeight , dateFn = function (d) { return new Date(d.mills) } , documentHidden = false - , visibilityChanging = false + , visibilityChangedAt = Date.now() , brush , brushTimer , brushInProgress = false @@ -567,7 +569,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; focus.select('.now-line') .transition() - .duration(UPDATE_TRANS_MS) + .duration(UPDATE_TRANS_MS * 1.3) .attr('x1', xScale(nowDate)) .attr('y1', yScale(scaleBg(36))) .attr('x2', xScale(nowDate)) @@ -630,7 +632,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } // called for initial update and updates for resize - var updateChart = _.debounce(function debouncedUpdateChart(init, callback) { + var updateChart = _.debounce(function debouncedUpdateChart(init) { if (documentHidden && !init) { console.info('Document Hidden, not updating - ' + (new Date())); @@ -976,11 +978,6 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; // update x axis domain context.select('.x') .call(xAxis2); - - if (callback) { - callback(); - } - }, DEBOUNCE_MS); function sgvToColor(sgv) { @@ -1236,28 +1233,26 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } } - function updateTimeAgo() { + function updateTimeAgo(forceUpdate) { var lastEntry = $('#lastEntry') , time = latestSGV ? latestSGV.mills : -1 , ago = timeAgo(time, browserSettings) , retroMode = inRetroMode(); - if (visibilityChanging) { + if (Date.now() - visibilityChangedAt <= FIVE_SEC_IN_MS && !forceUpdate) { console.info('visibility is changing now, wait till next tick to check time ago'); - return; - } - - lastEntry.removeClass('current warn urgent'); - lastEntry.addClass(ago.status); + } else { + lastEntry.removeClass('current warn urgent'); + lastEntry.addClass(ago.status); - if (ago.status !== 'current') { - updateTitle(); - } + if (ago.status !== 'current') { + updateTitle(); + } - if ( - (browserSettings.alarmTimeAgoWarn && ago.status === 'warn') - || (browserSettings.alarmTimeAgoUrgent && ago.status === 'urgent')) { - checkTimeAgoAlarm(ago); + if ((browserSettings.alarmTimeAgoWarn && ago.status === 'warn') + || (browserSettings.alarmTimeAgoUrgent && ago.status === 'urgent')) { + checkTimeAgoAlarm(ago); + } } container.toggleClass('alarming-timeago', ago.status !== 'current'); @@ -1340,16 +1335,19 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; context.append('g') .attr('class', 'y axis'); - function refreshChart(updateToNow, callback) { - //updateChart is _.debounce'd - updateChart(false, function ( ) { - if (updateToNow) { - updateBrushToNow(); - } - if (callback) { - callback(); - } - }); + function updateTimeAgoSoon() { + setTimeout(function updatingTimeAgoNow() { + var forceUpdate = true; + updateTimeAgo(forceUpdate); + }, TWO_SEC_IN_MS); + } + + function refreshChart(updateToNow) { + if (updateToNow) { + updateBrushToNow(); + } + updateChart(false); + updateTimeAgoSoon(); } function visibilityChanged() { @@ -1358,10 +1356,8 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; if (prevHidden && !documentHidden) { console.info('Document now visible, updating - ' + (new Date())); - visibilityChanging = true; - refreshChart(true, function ( ) { - visibilityChanging = false; - }); + visibilityChangedAt = Date.now(); + refreshChart(true); } } @@ -1371,6 +1367,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; updateClock(); + updateTimeAgoSoon(); var silenceDropdown = new Dropdown('.dropdown-menu'); From 6b4fcd7a3ba16dc52a3f99a2b11b06bb4604361f Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 18 Jul 2015 00:33:36 -0700 Subject: [PATCH 431/937] some refactoring --- static/js/client.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index 073e2dcaa9e..432f61024e2 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1220,7 +1220,10 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var level = ago.status , alarm = getClientAlarm(level + 'TimeAgo'); - if (Date.now() >= (alarm.lastAckTime || 0) + (alarm.silenceTime || 0)) { + var isStale = browserSettings.alarmTimeAgoWarn && ago.status === 'warn' + || browserSettings.alarmTimeAgoUrgent && ago.status === 'urgent'; + + if (isStale && Date.now() >= (alarm.lastAckTime || 0) + (alarm.silenceTime || 0)) { currentAlarmType = alarm.type; console.info('generating timeAgoAlarm', alarm.type); container.addClass('alarming-timeago'); @@ -1248,11 +1251,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; if (ago.status !== 'current') { updateTitle(); } - - if ((browserSettings.alarmTimeAgoWarn && ago.status === 'warn') - || (browserSettings.alarmTimeAgoUrgent && ago.status === 'urgent')) { - checkTimeAgoAlarm(ago); - } + checkTimeAgoAlarm(ago); } container.toggleClass('alarming-timeago', ago.status !== 'current'); From 8c548632f8fa0862987fe5330ec24e8b28a22eac Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 18 Jul 2015 00:43:25 -0700 Subject: [PATCH 432/937] more refactoring, maybe codacy will like it --- static/js/client.js | 45 +++++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index 432f61024e2..31ee48abdaa 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1228,11 +1228,14 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; console.info('generating timeAgoAlarm', alarm.type); container.addClass('alarming-timeago'); var message = {'title': 'Last data received ' + [ago.value, ago.label].join(' ')}; - if (level === 'warn') { - generateAlarm(alarmSound, message); - } else { - generateAlarm(urgentAlarmSound, message); - } + var sound = level === 'warn' ? alarmSound : urgentAlarmSound; + generateAlarm(sound, message); + } + + container.toggleClass('alarming-timeago', ago.status !== 'current'); + + if (alarmingNow() && ago.status === 'current' && isTimeAgoAlarmType(currentAlarmType)) { + stopAlarm(true, ONE_MIN_IN_MS); } } @@ -1242,6 +1245,20 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; , ago = timeAgo(time, browserSettings) , retroMode = inRetroMode(); + //TODO: move this ao a plugin? + function updateTimeAgoPill() { + if (retroMode || !ago.value) { + lastEntry.find('em').hide(); + } else { + lastEntry.find('em').show().text(ago.value); + } + if (retroMode || ago.label) { + lastEntry.find('label').show().text(retroMode ? 'RETRO' : ago.label); + } else { + lastEntry.find('label').hide(); + } + } + if (Date.now() - visibilityChangedAt <= FIVE_SEC_IN_MS && !forceUpdate) { console.info('visibility is changing now, wait till next tick to check time ago'); } else { @@ -1254,23 +1271,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; checkTimeAgoAlarm(ago); } - container.toggleClass('alarming-timeago', ago.status !== 'current'); - - if (alarmingNow() && ago.status === 'current' && isTimeAgoAlarmType(currentAlarmType)) { - stopAlarm(true, ONE_MIN_IN_MS); - } - - if (retroMode || !ago.value) { - lastEntry.find('em').hide(); - } else { - lastEntry.find('em').show().text(ago.value); - } - - if (retroMode || ago.label) { - lastEntry.find('label').show().text(retroMode ? 'RETRO' : ago.label); - } else { - lastEntry.find('label').hide(); - } + updateTimeAgoPill(); } function init() { From 57204ca4da4595d26136ac7d4862a5f920c63e62 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 18 Jul 2015 02:25:36 -0700 Subject: [PATCH 433/937] some improvements for generateTitle/updateTitle --- static/js/client.js | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index 31ee48abdaa..fcf5c412d35 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -125,10 +125,6 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var time = latestSGV ? latestSGV.mills : (prevSGV ? prevSGV.mills : -1) , ago = timeAgo(time, browserSettings); - if (browserSettings.customTitle) { - $('.customTitle').text(browserSettings.customTitle); - } - if (ago && ago.status !== 'current') { bg_title = s(ago.value) + s(ago.label, ' - ') + bg_title; } else if (latestSGV) { @@ -144,20 +140,26 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; return bg_title; } - function updateTitle(skipPageTitle) { + function updateTitle() { - var bg_title = browserSettings.customTitle || ''; + var bg_title; if (alarmMessage && alarmInProgress) { - bg_title = alarmMessage + ': ' + generateTitle(); $('.customTitle').text(alarmMessage); + if (!isTimeAgoAlarmType(currentAlarmType)) { + bg_title = alarmMessage + ': ' + generateTitle(); + } + } else if (browserSettings.customTitle) { + $('.customTitle').text(browserSettings.customTitle); } else { - bg_title = generateTitle(); + $('.customTitle').text('Nightscout'); } - if (!skipPageTitle) { - $(document).attr('title', bg_title); + if (bg_title === undefined) { + bg_title = generateTitle(); } + + $(document).attr('title', bg_title); } // initial setup of chart when data is first made available @@ -1036,8 +1038,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; container.addClass('alarming').addClass(file === urgentAlarmSound ? 'urgent' : 'warning'); - var skipPageTitle = isTimeAgoAlarmType(currentAlarmType); - updateTitle(skipPageTitle); + updateTitle(); } function playAlarm(audio) { From f19774df169e7b1f605143db11da3aaf7ea5e1f3 Mon Sep 17 00:00:00 2001 From: Ben West Date: Sat, 18 Jul 2015 14:39:11 -0700 Subject: [PATCH 434/937] try to limit mongo queries in null finds --- lib/entries.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/entries.js b/lib/entries.js index e54b3f48d89..49afc353c20 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -21,6 +21,7 @@ function find_sgv_query (opts) { return opts; } +var TWO_DAYS = 172800000; function storage(env, ctx) { // TODO: Code is a little redundant. @@ -37,6 +38,13 @@ function storage(env, ctx) { // determine find options function find ( ) { var finder = find_sgv_query(opts); + var query = finder && finder.find ? finder.find : null; + if (query == null) { + query = { + date: { "$gte": Date.now( ) - ( TWO_DAYS * 2 ) } + }; + } + return query; return finder && finder.find ? finder.find : { }; // return this.find(q); } From a69eac5be438132d23dec78ea29c9cc9b04dcd12 Mon Sep 17 00:00:00 2001 From: Ben West Date: Sat, 18 Jul 2015 14:52:58 -0700 Subject: [PATCH 435/937] try to ensure all finds have a date query --- lib/entries.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/entries.js b/lib/entries.js index 49afc353c20..84ec0268021 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -38,11 +38,9 @@ function storage(env, ctx) { // determine find options function find ( ) { var finder = find_sgv_query(opts); - var query = finder && finder.find ? finder.find : null; - if (query == null) { - query = { - date: { "$gte": Date.now( ) - ( TWO_DAYS * 2 ) } - }; + var query = finder && finder.find ? finder.find : { }; + if (!query.date) { + query.date = { "$gte": Date.now( ) - ( TWO_DAYS * 2 ) } } return query; return finder && finder.find ? finder.find : { }; From 25dd834c95a4e7207fc504c4b59a6ddbb6cab129 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 18 Jul 2015 15:34:23 -0700 Subject: [PATCH 436/937] wait even longer after load/tab switch before showing time ago alarms --- static/js/client.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index fcf5c412d35..0ae33581bfb 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -8,8 +8,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; , DEBOUNCE_MS = 10 , TOOLTIP_TRANS_MS = 200 // milliseconds , UPDATE_TRANS_MS = 750 // milliseconds - , TWO_SEC_IN_MS = 2000 - , FIVE_SEC_IN_MS = 5000 + , TEN_SEC_IN_MS = 10000 , ONE_MIN_IN_MS = 60000 , FIVE_MINS_IN_MS = 300000 , THREE_HOURS_MS = 3 * 60 * 60 * 1000 @@ -1260,7 +1259,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } } - if (Date.now() - visibilityChangedAt <= FIVE_SEC_IN_MS && !forceUpdate) { + if (Date.now() - visibilityChangedAt <= TEN_SEC_IN_MS && !forceUpdate) { console.info('visibility is changing now, wait till next tick to check time ago'); } else { lastEntry.removeClass('current warn urgent'); @@ -1340,7 +1339,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; setTimeout(function updatingTimeAgoNow() { var forceUpdate = true; updateTimeAgo(forceUpdate); - }, TWO_SEC_IN_MS); + }, TEN_SEC_IN_MS); } function refreshChart(updateToNow) { From 99ea9fa58e6191aa4f347a66ec5935b83eb7fb6d Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 18 Jul 2015 16:26:08 -0700 Subject: [PATCH 437/937] throttle updateData to prevent loading way too many times --- lib/bootevent.js | 10 ++++--- lib/bus.js | 8 +++--- tests/update-throttle.test.js | 51 +++++++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 8 deletions(-) create mode 100644 tests/update-throttle.test.js diff --git a/lib/bootevent.js b/lib/bootevent.js index 634710f7641..27d62c7868c 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -1,5 +1,9 @@ 'use strict'; +var _ = require('lodash'); + +var UPDATE_THROTTLE = 1000; + function boot (env) { function setupMongo (ctx, next) { @@ -45,11 +49,11 @@ function boot (env) { } function setupListeners (ctx, next) { - function updateData ( ) { + var updateData = _.debounce(function debouncedUpdateData ( ) { ctx.data.update(function dataUpdated () { ctx.bus.emit('data-loaded'); }); - } + }, UPDATE_THROTTLE); ctx.bus.on('tick', function timedReloadData (tick) { console.info('tick', tick.now); @@ -57,7 +61,7 @@ function boot (env) { }); ctx.bus.on('data-received', function forceReloadData ( ) { - console.info('got data-received event, reloading now'); + console.info('got data-received event, requesting reload'); updateData(); }); diff --git a/lib/bus.js b/lib/bus.js index 6ec88a5a2a2..10dbad2e3d1 100644 --- a/lib/bus.js +++ b/lib/bus.js @@ -3,13 +3,12 @@ var Stream = require('stream'); function init (env) { var beats = 0; var started = new Date( ); - var id; - var interval = env.HEARTBEAT || 20000; + var interval = env.HEARTBEAT || 60000; var stream = new Stream; function ictus ( ) { - var tick = { + return { now: new Date( ) , type: 'heartbeat' , sig: 'internal://' + ['heartbeat', beats ].join('/') @@ -17,7 +16,6 @@ function init (env) { , interval: interval , started: started }; - return tick; } function repeat ( ) { @@ -26,7 +24,7 @@ function init (env) { stream.readable = true; stream.uptime = repeat; - id = setInterval(repeat, interval); + setInterval(repeat, interval); return stream; } module.exports = init; diff --git a/tests/update-throttle.test.js b/tests/update-throttle.test.js new file mode 100644 index 00000000000..a7dc8de868d --- /dev/null +++ b/tests/update-throttle.test.js @@ -0,0 +1,51 @@ +'use strict'; + +var _ = require('lodash'); +var request = require('supertest'); +require('should'); + +describe('Throttle', function ( ) { + var self = this; + + var api = require('../lib/api/'); + before(function (done) { + process.env.API_SECRET = 'this is my long pass phrase'; + self.env = require('../env')(); + this.wares = require('../lib/middleware/')(self.env); + self.app = require('express')(); + self.app.enable('api'); + require('../lib/bootevent')(self.env).boot(function booted(ctx) { + self.ctx = ctx; + self.app.use('/api', api(self.env, ctx)); + done(); + }); + }); + + after(function () { + delete process.env.API_SECRET; + }); + + it('only update once when there are multiple posts', function (done) { + + //if the data-loaded event is triggered more than once the test will fail + self.ctx.bus.on('data-loaded', function dataWasLoaded ( ) { + done() + }); + + function post () { + request(self.app) + .post('/api/entries/') + .set('api-secret', self.env.api_secret || '') + .send({type: 'sgv', sgv: 100, date: Date.now()}) + .expect(200) + .end(function(err) { + if (err) { + done(err); + } + }); + } + + _.times(10, post); + }); + +}); \ No newline at end of file From 02995a0a7f91a22411137cd5e2077e1d68168a16 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 18 Jul 2015 16:54:35 -0700 Subject: [PATCH 438/937] ; --- tests/update-throttle.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/update-throttle.test.js b/tests/update-throttle.test.js index a7dc8de868d..9b15c68fb6b 100644 --- a/tests/update-throttle.test.js +++ b/tests/update-throttle.test.js @@ -29,7 +29,7 @@ describe('Throttle', function ( ) { //if the data-loaded event is triggered more than once the test will fail self.ctx.bus.on('data-loaded', function dataWasLoaded ( ) { - done() + done(); }); function post () { From d00f87d1364fdc6789e5595a84e16b1e45860a6b Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 18 Jul 2015 17:34:33 -0700 Subject: [PATCH 439/937] also check for dateString --- lib/entries.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/entries.js b/lib/entries.js index 84ec0268021..a6ed19c9955 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -39,7 +39,7 @@ function storage(env, ctx) { function find ( ) { var finder = find_sgv_query(opts); var query = finder && finder.find ? finder.find : { }; - if (!query.date) { + if (!query.date && !query.dateString) { query.date = { "$gte": Date.now( ) - ( TWO_DAYS * 2 ) } } return query; From 19b5004d4a91da427346c1bfebf3b702a9439525 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 18 Jul 2015 17:35:20 -0700 Subject: [PATCH 440/937] updated entries api test to use date filters for retro data and added current entry --- tests/api.entries.test.js | 10 +++++-- tests/fixtures/example.json | 60 ++++++++++++++++++------------------- 2 files changed, 37 insertions(+), 33 deletions(-) diff --git a/tests/api.entries.test.js b/tests/api.entries.test.js index 779c32823db..e47c633771a 100644 --- a/tests/api.entries.test.js +++ b/tests/api.entries.test.js @@ -17,7 +17,10 @@ describe('Entries REST api', function ( ) { require('../lib/bootevent')(env).boot(function booted (ctx) { self.app.use('/', entries(self.app, self.wares, ctx)); self.archive = require('../lib/entries')(env, ctx); - self.archive.create(load('json'), done); + + var creating = load('json'); + creating.push({type: 'sgv', sgv: 100, date: Date.now()}); + self.archive.create(creating, done); }); }); @@ -32,7 +35,7 @@ describe('Entries REST api', function ( ) { it('gets requested number of entries', function (done) { var count = 30; request(this.app) - .get('/entries.json?count=' + count) + .get('/entries.json?find[dateString][$gte]=2014-07-19&count=' + count) .expect(200) .end(function (err, res) { res.body.should.be.instanceof(Array).and.have.lengthOf(count); @@ -43,7 +46,7 @@ describe('Entries REST api', function ( ) { it('gets default number of entries', function (done) { var defaultCount = 10; request(this.app) - .get('/entries.json') + .get('/entries/sgv.json?find[dateString][$gte]=2014-07-19&find[dateString][$lte]=2014-07-20') .expect(200) .end(function (err, res) { res.body.should.be.instanceof(Array).and.have.lengthOf(defaultCount); @@ -57,6 +60,7 @@ describe('Entries REST api', function ( ) { .expect(200) .end(function (err, res) { res.body.should.be.instanceof(Array).and.have.lengthOf(1); + res.body[0].sgv.should.equal(100); done(); }); }); diff --git a/tests/fixtures/example.json b/tests/fixtures/example.json index 0f3b6802c41..f2c3e6de02f 100644 --- a/tests/fixtures/example.json +++ b/tests/fixtures/example.json @@ -2,7 +2,7 @@ { "type": "sgv", "sgv": "5", - "dateString": "07\/19\/2014 10:49:15 AM", + "dateString": "2014-07-19T10:49:15.000-07:00", "date": 1405792155000, "device": "dexcom", "direction": "NOT COMPUTABLE" @@ -10,7 +10,7 @@ { "type": "sgv", "sgv": "5", - "dateString": "07\/19\/2014 10:44:15 AM", + "dateString": "2014-07-19T10:44:15.000-07:00", "date": 1405791855000, "device": "dexcom", "direction": "NOT COMPUTABLE" @@ -18,7 +18,7 @@ { "type": "sgv", "sgv": "5", - "dateString": "07\/19\/2014 10:39:15 AM", + "dateString": "2014-07-19T10:39:15.000-07:00", "date": 1405791555000, "device": "dexcom", "direction": "NOT COMPUTABLE" @@ -26,7 +26,7 @@ { "type": "sgv", "sgv": "5", - "dateString": "07\/19\/2014 10:34:15 AM", + "dateString": "2014-07-19T10:34:15.000-07:00", "date": 1405791255000, "device": "dexcom", "direction": "NOT COMPUTABLE" @@ -34,7 +34,7 @@ { "type": "sgv", "sgv": "5", - "dateString": "07\/19\/2014 10:29:15 AM", + "dateString": "2014-07-19T10:29:15.000-07:00", "date": 1405790955000, "device": "dexcom", "direction": "NOT COMPUTABLE" @@ -42,7 +42,7 @@ { "type": "sgv", "sgv": "5", - "dateString": "07\/19\/2014 10:24:15 AM", + "dateString": "2014-07-19T10:24:15.000-07:00", "date": 1405790655000, "device": "dexcom", "direction": "NOT COMPUTABLE" @@ -50,7 +50,7 @@ { "type": "sgv", "sgv": "5", - "dateString": "07\/19\/2014 10:19:15 AM", + "dateString": "2014-07-19T10:19:15.000-07:00", "date": 1405790355000, "device": "dexcom", "direction": "NOT COMPUTABLE" @@ -58,7 +58,7 @@ { "type": "sgv", "sgv": "5", - "dateString": "07\/19\/2014 10:14:15 AM", + "dateString": "2014-07-19T10:14:15.000-07:00", "date": 1405790055000, "device": "dexcom", "direction": "NOT COMPUTABLE" @@ -66,7 +66,7 @@ { "type": "sgv", "sgv": "5", - "dateString": "07\/19\/2014 10:09:15 AM", + "dateString": "2014-07-19T10:09:15.000-07:00", "date": 1405789755000, "device": "dexcom", "direction": "NOT COMPUTABLE" @@ -74,7 +74,7 @@ { "type": "sgv", "sgv": "5", - "dateString": "07\/19\/2014 10:04:15 AM", + "dateString": "2014-07-19T10:04:15.000-07:00", "date": 1405789455000, "device": "dexcom", "direction": "NOT COMPUTABLE" @@ -82,7 +82,7 @@ { "type": "sgv", "sgv": "5", - "dateString": "07\/19\/2014 09:59:15 AM", + "dateString": "2014-07-19T09:59:15.000-07:00", "date": 1405789155000, "device": "dexcom", "direction": "NOT COMPUTABLE" @@ -90,7 +90,7 @@ { "type": "sgv", "sgv": "5", - "dateString": "07\/19\/2014 09:54:15 AM", + "dateString": "2014-07-19T09:54:15.000-07:00", "date": 1405788855000, "device": "dexcom", "direction": "NOT COMPUTABLE" @@ -98,7 +98,7 @@ { "type": "sgv", "sgv": "178", - "dateString": "07\/19\/2014 03:59:15 AM", + "dateString": "2014-07-19T03:59:15.000-07:00", "date": 1405767555000, "device": "dexcom", "direction": "Flat" @@ -106,7 +106,7 @@ { "type": "sgv", "sgv": "179", - "dateString": "07\/19\/2014 03:54:15 AM", + "dateString": "2014-07-19T03:54:15.000-07:00", "date": 1405767255000, "device": "dexcom", "direction": "Flat" @@ -114,7 +114,7 @@ { "type": "sgv", "sgv": "178", - "dateString": "07\/19\/2014 03:49:15 AM", + "dateString": "2014-07-19T03:49:15.000-07:00", "date": 1405766955000, "device": "dexcom", "direction": "Flat" @@ -122,7 +122,7 @@ { "type": "sgv", "sgv": "177", - "dateString": "07\/19\/2014 03:44:15 AM", + "dateString": "2014-07-19T03:44:15.000-07:00", "date": 1405766655000, "device": "dexcom", "direction": "Flat" @@ -130,7 +130,7 @@ { "type": "sgv", "sgv": "176", - "dateString": "07\/19\/2014 03:39:15 AM", + "dateString": "2014-07-19T03:39:15.000-07:00", "date": 1405766355000, "device": "dexcom", "direction": "Flat" @@ -138,7 +138,7 @@ { "type": "sgv", "sgv": "176", - "dateString": "07\/19\/2014 03:34:15 AM", + "dateString": "2014-07-19T03:34:15.000-07:00", "date": 1405766055000, "device": "dexcom", "direction": "Flat" @@ -146,7 +146,7 @@ { "type": "sgv", "sgv": "175", - "dateString": "07\/19\/2014 03:29:16 AM", + "dateString": "2014-07-19T03:29:16.000-07:00", "date": 1405765756000, "device": "dexcom", "direction": "Flat" @@ -154,7 +154,7 @@ { "type": "sgv", "sgv": "174", - "dateString": "07\/19\/2014 03:24:15 AM", + "dateString": "2014-07-19T03:24:15.000-07:00", "date": 1405765455000, "device": "dexcom", "direction": "Flat" @@ -162,7 +162,7 @@ { "type": "sgv", "sgv": "174", - "dateString": "07\/19\/2014 03:19:15 AM", + "dateString": "2014-07-19T03:19:15.000-07:00", "date": 1405765155000, "device": "dexcom", "direction": "Flat" @@ -170,7 +170,7 @@ { "type": "sgv", "sgv": "175", - "dateString": "07\/19\/2014 03:14:15 AM", + "dateString": "2014-07-19T03:14:15.000-07:00", "date": 1405764855000, "device": "dexcom", "direction": "Flat" @@ -178,7 +178,7 @@ { "type": "sgv", "sgv": "176", - "dateString": "07\/19\/2014 03:09:15 AM", + "dateString": "2014-07-19T03:09:15.000-07:00", "date": 1405764555000, "device": "dexcom", "direction": "Flat" @@ -186,7 +186,7 @@ { "type": "sgv", "sgv": "176", - "dateString": "07\/19\/2014 03:04:15 AM", + "dateString": "2014-07-19T03:04:15.000-07:00", "date": 1405764255000, "device": "dexcom", "direction": "Flat" @@ -194,7 +194,7 @@ { "type": "sgv", "sgv": "173", - "dateString": "07\/19\/2014 02:59:15 AM", + "dateString": "2014-07-19T02:59:15.000-07:00", "date": 1405763955000, "device": "dexcom", "direction": "Flat" @@ -202,7 +202,7 @@ { "type": "sgv", "sgv": "171", - "dateString": "07\/19\/2014 02:54:15 AM", + "dateString": "2014-07-19T02:54:15.000-07:00", "date": 1405763655000, "device": "dexcom", "direction": "Flat" @@ -210,7 +210,7 @@ { "type": "sgv", "sgv": "170", - "dateString": "07\/19\/2014 02:49:15 AM", + "dateString": "2014-07-19T02:49:15.000-07:00", "date": 1405763355000, "device": "dexcom", "direction": "Flat" @@ -218,7 +218,7 @@ { "type": "sgv", "sgv": "171", - "dateString": "07\/19\/2014 02:44:15 AM", + "dateString": "2014-07-19T02:44:15.000-07:00", "date": 1405763055000, "device": "dexcom", "direction": "Flat" @@ -226,7 +226,7 @@ { "type": "sgv", "sgv": "169", - "dateString": "07\/19\/2014 02:39:15 AM", + "dateString": "2014-07-19T02:39:15.000-07:00", "date": 1405762755000, "device": "dexcom", "direction": "Flat" @@ -234,7 +234,7 @@ { "type": "sgv", "sgv": "169", - "dateString": "07\/19\/2014 02:34:15 AM", + "dateString": "2014-07-19T02:34:15.000-07:00", "date": 1405762455000, "device": "dexcom", "direction": "Flat" From 224dfb22a6dba908d0f03d78a61ec3296ef9bd3b Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 18 Jul 2015 17:36:17 -0700 Subject: [PATCH 441/937] quotes --- lib/entries.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/entries.js b/lib/entries.js index a6ed19c9955..1fc956bce5b 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -40,7 +40,7 @@ function storage(env, ctx) { var finder = find_sgv_query(opts); var query = finder && finder.find ? finder.find : { }; if (!query.date && !query.dateString) { - query.date = { "$gte": Date.now( ) - ( TWO_DAYS * 2 ) } + query.date = { $gte: Date.now( ) - ( TWO_DAYS * 2 ) } } return query; return finder && finder.find ? finder.find : { }; From 9643ccef81b4392b8710339b85beb8b8fc3c2d0a Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 18 Jul 2015 17:38:14 -0700 Subject: [PATCH 442/937] more clean up --- lib/entries.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/entries.js b/lib/entries.js index 1fc956bce5b..47df1ab103a 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -40,11 +40,9 @@ function storage(env, ctx) { var finder = find_sgv_query(opts); var query = finder && finder.find ? finder.find : { }; if (!query.date && !query.dateString) { - query.date = { $gte: Date.now( ) - ( TWO_DAYS * 2 ) } + query.date = { $gte: Date.now( ) - ( TWO_DAYS * 2 ) }; } return query; - return finder && finder.find ? finder.find : { }; - // return this.find(q); } // determine sort options From c96035b57e6efca8b214e2dea0c5471f861103d3 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 18 Jul 2015 19:31:06 -0700 Subject: [PATCH 443/937] added some basic tests for the treatment api and loading --- tests/api.treatments.test.js | 67 ++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 tests/api.treatments.test.js diff --git a/tests/api.treatments.test.js b/tests/api.treatments.test.js new file mode 100644 index 00000000000..715010740eb --- /dev/null +++ b/tests/api.treatments.test.js @@ -0,0 +1,67 @@ +'use strict'; + +var _ = require('lodash'); +var request = require('supertest'); +require('should'); + +describe('Treatment API', function ( ) { + var self = this; + + var api = require('../lib/api/'); + before(function (done) { + process.env.API_SECRET = 'this is my long pass phrase'; + self.env = require('../env')(); + self.env.enable = 'careportal'; + this.wares = require('../lib/middleware/')(self.env); + self.app = require('express')(); + self.app.enable('api'); + require('../lib/bootevent')(self.env).boot(function booted(ctx) { + self.ctx = ctx; + self.app.use('/api', api(self.env, ctx)); + done(); + }); + }); + + after(function () { + delete process.env.API_SECRET; + }); + + it('post a some treatments', function (done) { + self.ctx.bus.on('data-loaded', function dataWasLoaded ( ) { + self.ctx.data.treatments.length.should.equal(3); + self.ctx.data.treatments[0].mgdl.should.equal(100); + + self.ctx.data.treatments[1].mgdl.should.equal(100); + self.ctx.data.treatments[1].insulin.should.equal('2.00'); + self.ctx.data.treatments[2].carbs.should.equal('30'); + + done(); + }); + + self.ctx.treatments().remove({ }, function ( ) { + request(self.app) + .post('/api/treatments/') + .set('api-secret', self.env.api_secret || '') + .send({eventType: 'BG Check', glucose: 100, glucoseType: 'Finger', units: 'mg/dl'}) + .expect(200) + .end(function (err) { + if (err) { + done(err); + } + }); + + request(self.app) + .post('/api/treatments/') + .set('api-secret', self.env.api_secret || '') + .send({eventType: 'Meal Bolus', carbs: '30', insulin: '2.00', preBolus: 15, glucose: 100, glucoseType: 'Finger', units: 'mg/dl'}) + .expect(200) + .end(function (err) { + if (err) { + done(err); + } + }); + + }); + }); + +}); \ No newline at end of file From f37da2ca3ee938dc82eee629f94890db3237feda Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 18 Jul 2015 19:45:21 -0700 Subject: [PATCH 444/937] removed unused _ --- tests/api.treatments.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/api.treatments.test.js b/tests/api.treatments.test.js index 715010740eb..ff52e1766ff 100644 --- a/tests/api.treatments.test.js +++ b/tests/api.treatments.test.js @@ -1,6 +1,5 @@ 'use strict'; -var _ = require('lodash'); var request = require('supertest'); require('should'); From 413e7c98618405e841693c45e699d3dff38951aa Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 18 Jul 2015 23:28:28 -0700 Subject: [PATCH 445/937] use jsdom to be able to create a test for pluginbase using jquery --- bower.json | 2 +- bundle/bundle.source.js | 1 + package.json | 4 +++- static/index.html | 1 - tests/pluginbase.test.js | 47 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 tests/pluginbase.test.js diff --git a/bower.json b/bower.json index da22cab72e3..c4169354c7d 100644 --- a/bower.json +++ b/bower.json @@ -5,7 +5,7 @@ "angularjs": "1.3.0-beta.19", "bootstrap": "~3.2.0", "d3": "~3.5.5", - "jquery": "2.1.0", + "jquery": "2.1.4", "jQuery-Storage-API": "~1.7.2", "jsSHA": "~1.5.0", "tipsy-jmalonzo": "~1.0.1" diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index 05f8c2292e7..759d9d592cd 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -1,6 +1,7 @@ (function () { window._ = require('lodash'); + window.$ = window.jQuery = require('jquery'); window.moment = require('moment-timezone'); window.Nightscout = window.Nightscout || {}; diff --git a/package.json b/package.json index a974b93ab98..3baaaa1a59e 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "express-extension-to-accept": "0.0.2", "forever": "~0.13.0", "git-rev": "git://github.com/bewest/git-rev.git", + "jquery": "^2.1.4", "lodash": "^3.9.1", "long": "~2.2.3", "moment": "2.8.1", @@ -74,6 +75,7 @@ "istanbul": "~0.3.5", "mocha": "~1.20.1", "should": "~4.0.4", - "supertest": "~0.13.0" + "supertest": "~0.13.0", + "jsdom": "^3.1.2" } } diff --git a/static/index.html b/static/index.html index a7a03c4d3dd..ed6bdaf8ec2 100644 --- a/static/index.html +++ b/static/index.html @@ -253,7 +253,6 @@ - diff --git a/tests/pluginbase.test.js b/tests/pluginbase.test.js new file mode 100644 index 00000000000..7cb83fc2caa --- /dev/null +++ b/tests/pluginbase.test.js @@ -0,0 +1,47 @@ +'use strict'; + +require('should'); + +describe('pluginbase', function ( ) { + + //jsdom(); + + var jsdom = require("jsdom").jsdom; + var doc = jsdom("hello world"); + var window = doc.parentWindow; + + + global.$ = global.jQuery = require('jquery')(window); + + function div (clazz) { + return $('
    '); + } + + var container = div('container') + , bgStatus = div('bgStatus').appendTo(container) + , currentBG = div('currentBG').appendTo(bgStatus) + , majorPills = div('majorPills').appendTo(bgStatus) + , minorPills = div('minorPills').appendTo(bgStatus) + , statusPills = div('statusPills').appendTo(bgStatus) + , tooltip = div('tooltip').appendTo(container) + ; + + var fake = { + name: 'fake' + , label: 'Insulin-on-Board' + , pluginType: 'pill-major' + }; + + it('does stuff', function() { + var pluginbase = require('../lib/plugins/pluginbase')(majorPills, minorPills, statusPills, bgStatus, tooltip); + + pluginbase.updatePillText(fake, { + value: '123' + , label: 'TEST' + , info: [{label: 'Label', value: 'Value'}] + }); + + majorPills.length.should.equal(1); + }); + +}); \ No newline at end of file From 89929631f3fcb2319af3872b7c2019b1e8944c5c Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 18 Jul 2015 23:36:24 -0700 Subject: [PATCH 446/937] revert bower change --- bower.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bower.json b/bower.json index c4169354c7d..da22cab72e3 100644 --- a/bower.json +++ b/bower.json @@ -5,7 +5,7 @@ "angularjs": "1.3.0-beta.19", "bootstrap": "~3.2.0", "d3": "~3.5.5", - "jquery": "2.1.4", + "jquery": "2.1.0", "jQuery-Storage-API": "~1.7.2", "jsSHA": "~1.5.0", "tipsy-jmalonzo": "~1.0.1" From 003ea6817c0b3a9fc614bc246bdd298b21df2d2c Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 18 Jul 2015 23:56:04 -0700 Subject: [PATCH 447/937] codacy clean up --- tests/pluginbase.test.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tests/pluginbase.test.js b/tests/pluginbase.test.js index 7cb83fc2caa..0751a53104c 100644 --- a/tests/pluginbase.test.js +++ b/tests/pluginbase.test.js @@ -4,10 +4,8 @@ require('should'); describe('pluginbase', function ( ) { - //jsdom(); - - var jsdom = require("jsdom").jsdom; - var doc = jsdom("hello world"); + var jsdom = require('jsdom').jsdom; + var doc = jsdom(''); var window = doc.parentWindow; @@ -19,7 +17,6 @@ describe('pluginbase', function ( ) { var container = div('container') , bgStatus = div('bgStatus').appendTo(container) - , currentBG = div('currentBG').appendTo(bgStatus) , majorPills = div('majorPills').appendTo(bgStatus) , minorPills = div('minorPills').appendTo(bgStatus) , statusPills = div('statusPills').appendTo(bgStatus) From 2672c6be2b53cf1ea444a448adf672991bdc6cb4 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Mon, 20 Jul 2015 20:36:51 +0200 Subject: [PATCH 448/937] date field too short on eurpean style of date --- static/css/drawer.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/css/drawer.css b/static/css/drawer.css index e5383f25423..0f95b6b1fc9 100644 --- a/static/css/drawer.css +++ b/static/css/drawer.css @@ -101,7 +101,7 @@ input[type=number]:invalid { } #treatmentDrawer .eventdate { - width: 120px; + width: 130px; } #treatmentDrawer .eventtime { From 1bf4e4237cbf7e1646d4b327d12285fee574bd09 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Mon, 20 Jul 2015 22:29:02 +0200 Subject: [PATCH 449/937] selecting now resets time to current --- static/js/treatment.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/static/js/treatment.js b/static/js/treatment.js index 7dd0df20b2e..22f4742441f 100644 --- a/static/js/treatment.js +++ b/static/js/treatment.js @@ -112,6 +112,9 @@ $('#eventTime').find('input:radio').change(function (event) { if ($('#othertime').is(':checked')) { $('#eventTimeValue').focus(); + } else { + $('#eventTimeValue').val(moment().format('HH:mm')); + $('#eventDateValue').val(moment().format('YYYY-MM-D')); } event.preventDefault(); }); From 1a3a59b7dba45914b931886051d17e4b10db2ff5 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Tue, 21 Jul 2015 17:50:37 +0200 Subject: [PATCH 450/937] basic translation module --- README.md | 1 + bundle/bundle.source.js | 1 + env.js | 3 +- lib/language.js | 857 ++++++++++++++++++++++++++++++++++++++++ server.js | 2 +- static/index.html | 2 +- static/js/client.js | 1 + 7 files changed, 864 insertions(+), 3 deletions(-) create mode 100644 lib/language.js diff --git a/README.md b/README.md index 8cac0c4a525..9a873f2bea4 100644 --- a/README.md +++ b/README.md @@ -160,6 +160,7 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * `ALARM_TIMEAGO_URGENT` (`on`) - possible values `on` or `off` * `ALARM_TIMEAGO_URGENT_MINS` (`30`) - minutes since the last reading to trigger a urgent alarm * `SHOW_PLUGINS` - enabled plugins that should have their visualizations shown, defaults to all enabled + * `LANGUAGE` (`en`) - language of Nighscout. If not available english is used ### Plugins diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index 759d9d592cd..5bf8e7902bc 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -9,6 +9,7 @@ units: require('../lib/units')(), utils: require('../lib/utils')(), profile: require('../lib/profilefunctions')(), + language: require('../lib/language')(serverSettings.defaults.language), plugins: require('../lib/plugins/')().registerClientDefaults(), sandbox: require('../lib/sandbox')() }; diff --git a/env.js b/env.js index 23e04210ad2..618777968bb 100644 --- a/env.js +++ b/env.js @@ -173,7 +173,7 @@ function setDefaults() { , 'alarmTimeAgoWarnMins': 15 , 'alarmTimeAgoUrgent': true , 'alarmTimeAgoUrgentMins': 30 - , 'language': 'en' // not used yet + , 'language': 'en' }; // add units from separate variable @@ -195,6 +195,7 @@ function setDefaults() { env.defaults.alarmTimeAgoUrgent = readENV('ALARM_TIMEAGO_URGENT', env.defaults.alarmTimeAgoUrgent); env.defaults.alarmTimeAgoUrgentMins = readENV('ALARM_TIMEAGO_URGENT_MINS', env.defaults.alarmTimeAgoUrgentMins); env.defaults.showPlugins = readENV('SHOW_PLUGINS', ''); + env.defaults.language = readENV('LANGUAGE', env.defaults.language); //TODO: figure out something for some plugins to have them shown by default if (env.defaults.showPlugins !== '') { diff --git a/lib/language.js b/lib/language.js new file mode 100644 index 00000000000..049913e9f26 --- /dev/null +++ b/lib/language.js @@ -0,0 +1,857 @@ +'use strict'; + +var _ = require('lodash'); + +function init(lang) { + + function language() { + return language; + } + + var translations = { + // Client + ,'Mo' : { + cs: 'Po' + ,de: 'Mo' + } + ,'Tu' : { + cs: 'Út' + ,de: 'Di' + }, + ',We' : { + cs: 'St' + ,de: 'Mi' + } + ,'Th' : { + cs: 'Čt' + ,de: 'Do' + } + ,'Fr' : { + cs: 'Pá' + ,de: 'Fr' + } + ,'Sa' : { + cs: 'So' + ,de: 'Sa' + } + ,'Su' : { + cs: 'Ne' + ,de: 'So' + } + ,'Monday' : { + cs: 'Pondělí' + ,de: 'Montag' + } + ,'Tuesday' : { + cs: 'Úterý' + ,de: 'Dienstag' + } + ,'Wednesday' : { + cs: 'Středa' + ,de: 'Mittwoch' + } + ,'Thursday' : { + cs: 'Čtvrtek' + ,de: 'Donnerstag' + } + ,'Friday' : { + cs: 'Pátek' + ,de: 'Freitag' + } + ,'Saturday' : { + cs: 'Sobota' + ,de: 'Samstag' + } + ,'Sunday' : { + cs: 'Neděle' + ,de: 'Sonntag' + } + ,'Category' : { + cs: 'Kategorie' + ,de: 'Kategorie' + } + ,'Subcategory' : { + cs: 'Podkategorie' + ,de: 'Unterkategorie' + } + ,'Name' : { + cs: 'Jméno' + ,de: 'Name' + } + ,'Today' : { + cs: 'Dnes' + ,de: 'Heute' + } + ,'Last 2 days' : { + cs: 'Poslední 2 dny' + ,de: 'letzte 2 Tage' + } + ,'Last 3 days' : { + cs: 'Poslední 3 dny' + ,de: 'letzte 3 Tage' + } + ,'Last week' : { + cs: 'Poslední týden' + ,de: 'letzte Woche' + } + ,'Last 2 weeks' : { + cs: 'Poslední 2 týdny' + ,de: 'letzte 2 Wochen' + } + ,'Last month' : { + cs: 'Poslední měsíc' + ,de: 'letzter Monat' + } + ,'Last 3 months' : { + cs: 'Poslední 3 měsíce' + ,de: 'letzte 3 Monate' + } + ,'From' : { + cs: 'Od' + ,de: 'Von' + } + ,'To' : { + cs: 'Do' + ,de: 'Bis' + } + ,'Notes' : { + cs: 'Poznámky' + ,de: 'Notiz' + } + ,'Food' : { + cs: 'Jídlo' + ,de: 'Essen' + } + ,'Insulin' : { + cs: 'Inzulín' + ,de: 'Insulin' + } + ,'Carbs' : { + cs: 'Sacharidy' + ,de: 'Kohlenhydrate' + } + ,'Notes contain' : { + cs: 'Poznámky obsahují' + ,de: 'Notizen beinhalten' + } + ,'Event type contains' : { + cs: 'Typ události obsahuje' + ,de: 'Ereignis-Typ beinhaltet' + } + ,'Target bg range bottom' : { + cs: 'Cílová glykémie spodní' + ,de: 'Untergrenze des Blutzuckerzielbereichs' + } + ,'top' : { + cs: 'horní' + ,de: 'oben' + } + ,'Show' : { + cs: 'Zobraz' + ,de: 'Zeige' + } + ,'Display' : { + cs: 'Zobraz' + ,de: 'Zeige' + } + ,'Loading' : { + cs: 'Nahrávám' + ,de: 'Laden' + } + ,'Loading profile' : { + cs: 'Nahrávám profil' + ,de: 'Lade Profil' + } + ,'Loading status' : { + cs: 'Nahrávám status' + ,de: 'Lade Status' + } + ,'Loading food database' : { + cs: 'Nahrávám databázi jídel' + ,de: 'Lade Essensdatenbank' + } + ,'not displayed' : { + cs: 'není zobrazeno' + ,de: 'nicht angezeigt' + } + ,'Loading CGM data of' : { + cs: 'Nahrávám CGM data' + ,de: 'Lade CGM-Daten von' + } + ,'Loading treatments data of' : { + cs: 'Nahrávám data ošetření' + ,de: 'Lade Behandlungsdaten von' + } + ,'Processing data of' : { + cs: 'Zpracovávám data' + ,de: 'Verarbeite Daten von' + } + ,'Portion' : { + cs: 'Porce' + ,de: 'Portion' + } + ,'Size' : { + cs: 'Rozměr' + ,de: 'Größe' + } + ,'(none)' : { + cs: '(Prázdný)' + ,de: '(nichts)' + } + ,'Result is empty' : { + cs: 'Prázdný výsledek' + ,de: 'Leeres Ergebnis' + } +// ported reporting + ,'Day to day' : { + cs: 'Den po dni' + } + ,'Daily Stats' : { + cs: 'Denní statistiky' + } + ,'Percentile Chart' : { + cs: 'Percentil' + } + ,'Distribution' : { + cs: 'Rozložení' + } + ,'Hourly stats' : { + cs: 'Statistika po hodinách' + } + ,'Weekly success' : { + cs: 'Statistika po týdnech' + } + ,'No data available' : { + cs: 'Žádná dostupná data' + } + ,'Low' : { + cs: 'Nízká' + } + ,'In Range' : { + cs: 'V rozsahu' + } + ,'Period' : { + cs: 'Období' + } + ,'High' : { + cs: 'Vysoká' + } + ,'Average' : { + cs: 'Průměrná' + } + ,'Low Quartile' : { + cs: 'Nízký kvartil' + } + ,'Upper Quartile' : { + cs: 'Vysoký kvartil' + } + ,'Quartile' : { + cs: 'Kvartil' + } + ,'Date' : { + cs: 'Datum' + } + ,'Normal' : { + cs: 'Normální' + } + ,'Median' : { + cs: 'Medián' + } + ,'Readings' : { + cs: 'Záznamů' + } + ,'StDev' : { + cs: 'St. odchylka' + } + ,'Daily stats report' : { + cs: 'Denní statistiky' + } + ,'Glucose Percentile report' : { + cs: 'Tabulka percentil glykémií' + } + ,'Glucose distribution' : { + cs: 'Rozložení glykémií' + } + ,'days total' : { + cs: 'dní celkem' + } + ,'Overall' : { + cs: 'Celkem' + } + ,'Range' : { + cs: 'Rozsah' + } + ,'% of Readings' : { + cs: '% záznamů' + } + ,'# of Readings' : { + cs: 'počet záznamů' + } + ,'Mean' : { + cs: 'Střední hodnota' + } + ,'Standard Deviation' : { + cs: 'Standardní odchylka' + } + ,'Max' : { + cs: 'Max' + } + ,'Min' : { + cs: 'Min' + } + ,'A1c estimation*' : { + cs: 'Předpokládané HBA1c*' + } + ,'Weekly Success' : { + cs: 'Týdenní úspěšnost' + } + ,'There is not sufficient data to run this report. Select more days.' : { + cs: 'Není dostatek dat. Vyberte delší časové období.' + } +// food editor + ,'Using stored API secret hash' : { + cs: 'Používám uložený hash API hesla' + } + ,'No API secret hash stored yet. You need to enter API secret.' : { + cs: 'Není uložený žádný hash API hesla. Musíte zadat API heslo.' + } + ,'Database loaded' : { + cs: 'Databáze načtena' + } + ,'Error: Database failed to load' : { + cs: 'Chyba při načítání databáze' + } + ,'Create new record' : { + cs: 'Vytvořit nový záznam' + } + ,'Save record' : { + cs: 'Uložit záznam' + } + ,'Portions' : { + cs: 'Porcí' + } + ,'Unit' : { + cs: 'Jedn' + } + ,'GI' : { + cs: 'GI' + } + ,'Edit record' : { + cs: 'Upravit záznam' + } + ,'Delete record' : { + cs: 'Smazat záznam' + } + ,'Move to the top' : { + cs: 'Přesuň na začátek' + } + ,'Hidden' : { + cs: 'Skrytý' + } + ,'Hide after use' : { + cs: 'Skryj po použití' + } + ,'Your API secret must be at least 12 characters long' : { + cs: 'Vaše API heslo musí mít alespoň 12 znaků' + } + ,'Bad API secret' : { + cs: 'Chybné API heslo' + } + ,'API secret hash stored' : { + cs: 'Hash API hesla uložen' + } + ,'Status' : { + cs: 'Status' + } + ,'Not loaded' : { + cs: 'Nenačtený' + } + ,'Food editor' : { + cs: 'Editor jídel' + } + ,'Your database' : { + cs: 'Vaše databáze' + } + ,'Filter' : { + cs: 'Filtr' + } + ,'Save' : { + cs: 'Ulož' + } + ,'Clear' : { + cs: 'Vymaž' + } + ,'Record' : { + cs: 'Záznam' + } + ,'Quick picks' : { + cs: 'Rychlý výběr' + } + ,'Show hidden' : { + cs: 'Zobraz skryté' + } + ,'Your API secret' : { + cs: 'Vaše API heslo' + } + ,'Store hash on this computer (Use only on private computers)' : { + cs: 'Ulož hash na tomto počítači (používejte pouze na soukromých počítačích)' + } + ,'Treatments' : { + cs: 'Ošetření' + } + ,'Time' : { + cs: 'Čas' + } + ,'Event Type' : { + cs: 'Typ události' + } + ,'Blood Glucose' : { + cs: 'Glykémie' + } + ,'Entered By' : { + cs: 'Zadal' + } + ,'Delete this treatment?' : { + cs: 'Vymazat toto ošetření?' + } + ,'Carbs Given' : { + cs: 'Sacharidů' + } + ,'Inzulin Given' : { + cs: 'Inzulínu' + } + ,'Event Time' : { + cs: 'Čas události' + } + ,'Please verify that the data entered is correct' : { + cs: 'Prosím zkontrolujte, zda jsou údaje zadány správně' + } + ,'BG' : { + cs: 'Glykémie' + } + ,'Use BG correction in calculation' : { + cs: 'Použij korekci na glykémii' + } + ,'BG from CGM (autoupdated)' : { + cs: 'Glykémie z CGM (automaticky aktualizovaná)' + } + ,'BG from meter' : { + cs: 'Glykémie z glukoměru' + } + ,'Manual BG' : { + cs: 'Ručně zadaná glykémie' + } + ,'Quickpick' : { + cs: 'Rychlý výběr' + } + ,'or' : { + cs: 'nebo' + } + ,'Add from database' : { + cs: 'Přidat z databáze' + } + ,'Use carbs correction in calculation' : { + cs: 'Použij korekci na sacharidy' + } + ,'Use COB correction in calculation' : { + cs: 'Použij korekci na COB' + } + ,'Use IOB in calculation' : { + cs: 'Použij IOB ve výpočtu' + } + ,'Other correction' : { + cs: 'Jiná korekce' + } + ,'Rounding' : { + cs: 'Zaokrouhlení' + } + ,'Enter insulin correction in treatment' : { + cs: 'Zahrň inzulín do záznamu ošetření' + } + ,'Insulin needed' : { + cs: 'Potřebný inzulín' + } + ,'Carbs needed' : { + cs: 'Potřebné sach' + } + ,'Carbs needed if Insulin total is negative value' : { + cs: 'Chybějící sacharidy v případě, že výsledek je záporný' + } + ,'Basal rate' : { + cs: 'Bazál' + } + ,'Eating' : { + cs: 'Jídlo' + } + ,'60 minutes before' : { + cs: '60 min předem' + } + ,'45 minutes before' : { + cs: '45 min předem' + } + ,'30 minutes before' : { + cs: '30 min předem' + } + ,'20 minutes before' : { + cs: '20 min předem' + } + ,'15 minutes before' : { + cs: '15 min předem' + } + ,'Time in minutes' : { + cs: 'Čas v minutách' + } + ,'15 minutes after' : { + cs: '15 min po' + } + ,'20 minutes after' : { + cs: '20 min po' + } + ,'30 minutes after' : { + cs: '30 min po' + } + ,'45 minutes after' : { + cs: '45 min po' + } + ,'60 minutes after' : { + cs: '60 min po' + } + ,'Additional Notes, Comments:' : { + cs: 'Dalši poznámky, komentáře:' + } + ,'RETRO MODE' : { + cs: 'V MINULOSTI' + } + ,'Now' : { + cs: 'Nyní' + } + ,'Other' : { + cs: 'Jiný' + } + ,'Submit Form' : { + cs: 'Odeslat formulář' + } + ,'Profile editor' : { + cs: 'Editor profilu' + } + ,'Reporting tool' : { + cs: 'Výkazy' + } + ,'Add food from your database' : { + cs: 'Přidat jidlo z Vaší databáze' + } + ,'Reload database' : { + cs: 'Znovu nahraj databázi' + } + ,'Add' : { + cs: 'Přidej' + } + ,'Unauthorized' : { + cs: 'Neautorizováno' + } + ,'Entering record failed' : { + cs: 'Vložení záznamu selhalo' + } + ,'Device authenticated' : { + cs: 'Zařízení ověřeno' + } + ,'Device not authenticated' : { + cs: 'Zařízení není ověřeno' + } + ,'Authentication status' : { + cs: 'Stav ověření' + } + ,'Authenticate' : { + cs: 'Ověřit' + } + ,'Remove' : { + cs: 'Vymazat' + } + ,'Your device is not authenticated yet' : { + cs: 'Toto zařízení nebylo dosud ověřeno' + } + ,'Sensor' : { + cs: 'Senzor' + } + ,'Finger' : { + cs: 'Glukoměr' + } + ,'Manual' : { + cs: 'Ručně' + } + ,'Scale' : { + cs: 'Měřítko' + } + ,'Linear' : { + cs: 'lineární' + } + ,'Logarithmic' : { + cs: 'logaritmické' + } + ,'Silence for 30 minutes' : { + cs: 'Ztlumit na 30 minut' + } + ,'Silence for 60 minutes' : { + cs: 'Ztlumit na 60 minut' + } + ,'Silence for 90 minutes' : { + cs: 'Ztlumit na 90 minut' + } + ,'Silence for 120 minutes' : { + cs: 'Ztlumit na 120 minut' + } + ,'3HR' : { + cs: '3hod' + } + ,'6HR' : { + cs: '6hod' + } + ,'12HR' : { + cs: '12hod' + } + ,'24HR' : { + cs: '24hod' + } + ,'Sttings' : { + cs: 'Nastavení' + } + ,'Units' : { + cs: 'Jednotky' + } + ,'Date format' : { + cs: 'Formát datumu' + } + ,'12 hours' : { + cs: '12 hodin' + } + ,'24 hours' : { + cs: '24 hodin' + } + ,'Log a Treatment' : { + cs: 'Záznam ošetření' + } + ,'BG Check' : { + cs: 'Kontrola glykémie' + } + ,'Meal Bolus' : { + cs: 'Bolus na jídlo' + } + ,'Snack Bolus' : { + cs: 'Bolus na svačinu' + } + ,'Correction Bolus' : { + cs: 'Bolus na glykémii' + } + ,'Carb Correction' : { + cs: 'Přídavek sacharidů' + } + ,'Note' : { + cs: 'Poznámka' + } + ,'Question' : { + cs: 'Otázka' + } + ,'Exercise' : { + cs: 'Cvičení' + } + ,'Pump Site Change' : { + cs: 'Přepíchnutí kanyly' + } + ,'Sensor Start' : { + cs: 'Spuštění sensoru' + } + ,'Sensor Change' : { + cs: 'Výměna sensoru' + } + ,'Dexcom Sensor Start' : { + cs: 'Spuštění sensoru' + } + ,'Dexcom Sensor Change' : { + cs: 'Výměna sensoru' + } + ,'Insulin Cartridge Change' : { + cs: 'Výměna inzulínu' + } + ,'D.A.D. Alert' : { + cs: 'D.A.D. Alert' + } + ,'Glucose Reading' : { + cs: 'Hodnota glykémie' + } + ,'Measurement Method' : { + cs: 'Metoda měření' + } + ,'Meter' : { + cs: 'Glukoměr' + } + ,'Insulin Given' : { + cs: 'Inzulín' + } + ,'Amount in grams' : { + cs: 'Množství v gramech' + } + ,'Amount in units' : { + cs: 'Množství v jednotkách' + } + ,'View all treatments' : { + cs: 'Zobraz všechny ošetření' + } + ,'Enable Alarms' : { + cs: 'Povolit alarmy' + } + ,'When enabled an alarm may sound.' : { + cs: 'Při povoleném alarmu zní zvuk' + } + ,'Urgent High Alarm' : { + cs: 'Urgentní vysoká glykémie' + } + ,'High Alarm' : { + cs: 'Vysoká glykémie' + } + ,'Low Alarm' : { + cs: 'Nízká glykémie' + } + ,'Urgent Low Alarm' : { + cs: 'Urgentní nízká glykémie' + } + ,'Stale Data: Warn' : { + cs: 'Zastaralá data' + } + ,'Stale Data: Urgent' : { + cs: 'Zastaralá data urgentní' + } + ,'mins' : { + cs: 'min' + } + ,'Night Mode' : { + cs: 'Noční mód' + } + ,'When enabled the page will be dimmed from 10pm - 6am.' : { + cs: 'Když je povoleno, obrazovka je ztlumena 22:00 - 6:00' + } + ,'Enable' : { + cs: 'Povoleno' + } + ,'Settings' : { + cs: 'Nastavení' + } + ,'Show Raw BG Data' : { + cs: 'Zobraz RAW data' + } + ,'Never' : { + cs: 'Nikdy' + } + ,'Always' : { + cs: 'Vždy' + } + ,'When there is noise' : { + cs: 'Při šumu' + } + ,'When enabled small white dots will be disaplyed for raw BG data' : { + cs: 'Když je povoleno, malé tečky budou zobrazeny pro RAW data' + } + ,'Custom Title' : { + cs: 'Vlastní název stránky' + } + ,'Theme' : { + cs: 'Téma' + } + ,'Default' : { + cs: 'Výchozí' + } + ,'Colors' : { + cs: 'Barevné' + } + ,'Reset, and use defaults' : { + cs: 'Vymaž a nastav výchozí hodnoty' + } + ,'Calibrations' : { + cs: 'Kalibrace' + } + ,'Alarm Test / Smartphone Enable' : { + cs: 'Test alarmu' + } + ,'Bolus Wizard' : { + cs: 'Bolusový kalkulátor' + } + ,'in the future' : { + cs: 'v budoucnosti' + } + ,'time ago' : { + cs: 'min zpět' + } + ,'hr ago' : { + cs: 'hod zpět' + } + ,'hrs ago' : { + cs: 'hod zpět' + } + ,'min ago' : { + cs: 'min zpět' + } + ,'mins ago' : { + cs: 'min zpět' + } + ,'day ago' : { + cs: 'den zpět' + } + ,'days ago' : { + cs: 'dnů zpět' + } + ,'long ago' : { + cs: 'dlouho zpět' + } + ,'Clean' : { + cs: 'Čistý' + } + ,'Light' : { + cs: 'Lehký' + } + ,'Medium' : { + cs: 'Střední' + } + ,'Heavy' : { + cs: 'Velký' + } + ,'Treatment type' : { + cs: 'Typ ošetření' + } + ,'Raw BG' : { + cs: 'Glykémie z RAW dat' + } + ,'Device' : { + cs: 'Zařízení' + } + ,'Noise' : { + cs: 'Šum' + } + ,'Calibration' : { + cs: 'Kalibrace' + } + ,'1' : { + cs: '1' + } + + }; + + language.translate = function translate(text) { + if (translations[text] && translations[text][lang]) + return translations[text][lang]; + return text; + } + + if (typeof window !== 'undefined') { + // do translation of static text on load + $('.translate').each(function (s) { + $(this).text(language.translate($(this).text())); + }); + $('.titletranslate').each(function (s) { + $(this).attr('title',language.translate($(this).attr('title'))); + $(this).attr('original-title',language.translate($(this).attr('original-title'))); + $(this).attr('placeholder',language.translate($(this).attr('placeholder'))); + }); + } + return language(); +} + +module.exports = init; \ No newline at end of file diff --git a/server.js b/server.js index db868d242a9..846524a61a2 100644 --- a/server.js +++ b/server.js @@ -27,7 +27,7 @@ /////////////////////////////////////////////////// var env = require('./env')( ); - +var translate = require('./lib/language')(env.defaults.language).translate; /////////////////////////////////////////////////// // setup http server diff --git a/static/index.html b/static/index.html index ed6bdaf8ec2..594fbe9157c 100644 --- a/static/index.html +++ b/static/index.html @@ -249,9 +249,9 @@
    + - diff --git a/static/js/client.js b/static/js/client.js index fabdb346b33..25469507f26 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -50,6 +50,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; , delta = Nightscout.plugins('delta') , direction = Nightscout.plugins('direction') , errorcodes = Nightscout.plugins('errorcodes') + , translate = Nightscout.language.translate , timeAgo = Nightscout.utils.timeAgo; var jqWindow From 4464e296099dad291c2cd2b0f4321b03a28589f5 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Tue, 21 Jul 2015 18:19:50 +0200 Subject: [PATCH 451/937] date locale fix --- static/js/treatment.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/js/treatment.js b/static/js/treatment.js index 22f4742441f..770f90205ac 100644 --- a/static/js/treatment.js +++ b/static/js/treatment.js @@ -84,7 +84,7 @@ if (data.notes) { text.push('Notes: ' + data.notes); } if (data.enteredBy) { text.push('Entered By: ' + data.enteredBy); } - text.push('Event Time: ' + (data.eventTime ? data.eventTime.format('LLL') : moment().format('LLL'))); + text.push('Event Time: ' + (data.eventTime ? data.eventTime.toDate().toLocaleString() : new Date().toLocaleString())); return text.join('\n'); } From a3d6fccd446fef7e04a160e17efecd74f2141ed6 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Tue, 21 Jul 2015 18:25:57 +0200 Subject: [PATCH 452/937] codacy fix 1 --- lib/language.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/language.js b/lib/language.js index 049913e9f26..c88c9546d5a 100644 --- a/lib/language.js +++ b/lib/language.js @@ -1,7 +1,5 @@ 'use strict'; -var _ = require('lodash'); - function init(lang) { function language() { @@ -10,7 +8,7 @@ function init(lang) { var translations = { // Client - ,'Mo' : { + 'Mo' : { cs: 'Po' ,de: 'Mo' } From 540b578660cab8f3bd8ff95a34288a079f41c4f1 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Tue, 21 Jul 2015 18:32:26 +0200 Subject: [PATCH 453/937] codacy fix 2 --- lib/language.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/language.js b/lib/language.js index c88c9546d5a..1cca4010071 100644 --- a/lib/language.js +++ b/lib/language.js @@ -833,17 +833,18 @@ function init(lang) { }; language.translate = function translate(text) { - if (translations[text] && translations[text][lang]) + if (translations[text] && translations[text][lang]) { return translations[text][lang]; + } return text; } if (typeof window !== 'undefined') { // do translation of static text on load - $('.translate').each(function (s) { + $('.translate').each(function () { $(this).text(language.translate($(this).text())); }); - $('.titletranslate').each(function (s) { + $('.titletranslate').each(function () { $(this).attr('title',language.translate($(this).attr('title'))); $(this).attr('original-title',language.translate($(this).attr('original-title'))); $(this).attr('placeholder',language.translate($(this).attr('placeholder'))); From e4b0e82840a015a38edd6ceb7ad8dc0159729699 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Tue, 21 Jul 2015 18:35:03 +0200 Subject: [PATCH 454/937] codacy fix 3 --- lib/language.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/language.js b/lib/language.js index 1cca4010071..d8cb49e09b1 100644 --- a/lib/language.js +++ b/lib/language.js @@ -837,7 +837,7 @@ function init(lang) { return translations[text][lang]; } return text; - } + }; if (typeof window !== 'undefined') { // do translation of static text on load From 047cfa649e9fa48a83b89d8b7122e1bbf3b50223 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Tue, 21 Jul 2015 19:09:27 +0200 Subject: [PATCH 455/937] some examples added --- lib/language.js | 6 +++++- server.js | 2 +- static/index.html | 10 +++++----- static/js/client.js | 22 +++++++++++----------- 4 files changed, 22 insertions(+), 18 deletions(-) diff --git a/lib/language.js b/lib/language.js index d8cb49e09b1..6cf1f8bb161 100644 --- a/lib/language.js +++ b/lib/language.js @@ -7,8 +7,12 @@ function init(lang) { } var translations = { + // Server + 'Listening on port' : { + cs: 'Poslouchám na portu' + } // Client - 'Mo' : { + ,'Mo' : { cs: 'Po' ,de: 'Mo' } diff --git a/server.js b/server.js index 846524a61a2..a2478b1dfb5 100644 --- a/server.js +++ b/server.js @@ -46,7 +46,7 @@ function create (app) { require('./lib/bootevent')(env).boot(function booted (ctx) { var app = require('./app')(env, ctx); var server = create(app).listen(PORT); - console.log('listening', PORT); + console.log(translate('Listening on port'), PORT); if (env.MQTT_MONITOR) { ctx.mqtt = require('./lib/mqtt')(env, ctx); diff --git a/static/index.html b/static/index.html index 594fbe9157c..9d2ab4b79ea 100644 --- a/static/index.html +++ b/static/index.html @@ -70,10 +70,10 @@
      -
    • 3HR
    • -
    • 6HR
    • -
    • 12HR
    • -
    • 24HR
    • +
    • 3HR
    • +
    • 6HR
    • +
    • 12HR
    • +
    • 24HR
    @@ -96,7 +96,7 @@
    -
    Enable Alarms
    +
    Enable Alarms
    diff --git a/static/js/client.js b/static/js/client.js index 25469507f26..a5268de6440 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -611,11 +611,11 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; prepareTreatCircles(treatCircles.enter().append('circle')) .on('mouseover', function (d) { tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); - tooltip.html('Time: ' + formatTime(new Date(d.mills)) + '
    ' + - (d.eventType ? 'Treatment type: ' + d.eventType + '
    ' : '') + - (d.glucose ? 'BG: ' + d.glucose + (d.glucoseType ? ' (' + d.glucoseType + ')': '') + '
    ' : '') + - (d.enteredBy ? 'Entered by: ' + d.enteredBy + '
    ' : '') + - (d.notes ? 'Notes: ' + d.notes : '') + tooltip.html(''+translate('Time')+': ' + formatTime(new Date(d.mills)) + '
    ' + + (d.eventType ? ''+translate('Treatment type')+': ' + d.eventType + '
    ' : '') + + (d.glucose ? ''+translate('BG')+': ' + d.glucose + (d.glucoseType ? ' (' + translate(d.glucoseType) + ')': '') + '
    ' : '') + + (d.enteredBy ? ''+translate('Entered by')+': ' + d.enteredBy + '
    ' : '') + + (d.notes ? ''+translate('Notes')+': ' + d.notes : '') ) .style('left', (d3.event.pageX) + 'px') .style('top', (d3.event.pageY + 15) + 'px'); @@ -1131,12 +1131,12 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; .attr('transform', 'translate(' + xScale(new Date(treatment.mills)) + ', ' + yScale(sbx.scaleEntry(treatment)) + ')') .on('mouseover', function () { tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); - tooltip.html('Time: ' + formatTime(new Date(treatment.mills)) + '
    ' + 'Treatment type: ' + treatment.eventType + '
    ' + - (treatment.carbs ? 'Carbs: ' + treatment.carbs + '
    ' : '') + - (treatment.insulin ? 'Insulin: ' + treatment.insulin + '
    ' : '') + - (treatment.glucose ? 'BG: ' + treatment.glucose + (treatment.glucoseType ? ' (' + treatment.glucoseType + ')': '') + '
    ' : '') + - (treatment.enteredBy ? 'Entered by: ' + treatment.enteredBy + '
    ' : '') + - (treatment.notes ? 'Notes: ' + treatment.notes : '') + tooltip.html(''+translate('Time')+': ' + formatTime(new Date(treatment.mills)) + '
    ' + ''+translate('Treatment type')+': ' + translate(treatment.eventType) + '
    ' + + (treatment.carbs ? ''+translate('Carbs')+': ' + treatment.carbs + '
    ' : '') + + (treatment.insulin ? ''+translate('Insulin')+': ' + treatment.insulin + '
    ' : '') + + (treatment.glucose ? ''+translate('BG')+': ' + treatment.glucose + (treatment.glucoseType ? ' (' + translate(treatment.glucoseType) + ')': '') + '
    ' : '') + + (treatment.enteredBy ? ''+translate('Entered by')+': ' + treatment.enteredBy + '
    ' : '') + + (treatment.notes ? ''+translate('Notes')+': ' + treatment.notes : '') ) .style('left', (d3.event.pageX) + 'px') .style('top', (d3.event.pageY + 15) + 'px'); From 9205bca9158e7d01d619a41214040764817fb804 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Tue, 21 Jul 2015 19:49:08 +0200 Subject: [PATCH 456/937] move serverSettings outside bundle --- bundle/bundle.source.js | 2 +- lib/language.js | 11 +++++++++-- server.js | 3 ++- static/js/client.js | 2 ++ 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index 5bf8e7902bc..f0e43488b6f 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -9,7 +9,7 @@ units: require('../lib/units')(), utils: require('../lib/utils')(), profile: require('../lib/profilefunctions')(), - language: require('../lib/language')(serverSettings.defaults.language), + language: require('../lib/language')(), plugins: require('../lib/plugins/')().registerClientDefaults(), sandbox: require('../lib/sandbox')() }; diff --git a/lib/language.js b/lib/language.js index 6cf1f8bb161..b7197a65770 100644 --- a/lib/language.js +++ b/lib/language.js @@ -1,6 +1,7 @@ 'use strict'; -function init(lang) { +function init() { + var lang; function language() { return language; @@ -843,7 +844,7 @@ function init(lang) { return text; }; - if (typeof window !== 'undefined') { + language.DOMtranslate = function DOMtranslate() { // do translation of static text on load $('.translate').each(function () { $(this).text(language.translate($(this).text())); @@ -854,6 +855,12 @@ function init(lang) { $(this).attr('placeholder',language.translate($(this).attr('placeholder'))); }); } + + language.set = function set(newlang) { + lang = newlang; + return language(); + } + return language(); } diff --git a/server.js b/server.js index a2478b1dfb5..95ac7176945 100644 --- a/server.js +++ b/server.js @@ -27,7 +27,8 @@ /////////////////////////////////////////////////// var env = require('./env')( ); -var translate = require('./lib/language')(env.defaults.language).translate; +var language = require('./lib/language')(); +var translate = language.set(env.defaults.language).translate; /////////////////////////////////////////////////// // setup http server diff --git a/static/js/client.js b/static/js/client.js index a5268de6440..2e03c6a9c1a 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -81,6 +81,8 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; , statusPills = $('.status .statusPills') ; + Nightscout.language.set(serverSettings.defaults.language).DOMtranslate(); + function formatTime(time, compact) { var timeFormat = getTimeFormat(false, compact); time = d3.time.format(timeFormat)(time); From e0e6643e91a6cdad6cca5e820257f6b5c364053f Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Tue, 21 Jul 2015 20:50:03 +0200 Subject: [PATCH 457/937] codacy fix 4 --- lib/language.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/language.js b/lib/language.js index b7197a65770..369a17c253d 100644 --- a/lib/language.js +++ b/lib/language.js @@ -854,12 +854,12 @@ function init() { $(this).attr('original-title',language.translate($(this).attr('original-title'))); $(this).attr('placeholder',language.translate($(this).attr('placeholder'))); }); - } + }; language.set = function set(newlang) { lang = newlang; return language(); - } + }; return language(); } From 1daf2f90875fe3834f6a1f7b45e1dd87d086b496 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 21 Jul 2015 23:23:56 -0700 Subject: [PATCH 458/937] use the status.js that was already loaded instead of getting the same with status.json again --- static/js/client.js | 142 ++++++++++++++++++------------------------ static/js/ui-utils.js | 42 ++++++------- 2 files changed, 83 insertions(+), 101 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index 2e03c6a9c1a..7f5c0a4018f 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1,5 +1,5 @@ //TODO: clean up -var app = {}, browserSettings = {}, browserStorage = $.localStorage; +var browserSettings = {}, browserStorage = $.localStorage; (function () { 'use strict'; @@ -365,7 +365,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var pluginBase = Nightscout.plugins.base(majorPills, minorPills, statusPills, bgStatus, tooltip); - sbx = Nightscout.sandbox.clientInit(app, browserSettings, time, pluginBase, { + sbx = Nightscout.sandbox.clientInit(serverSettings, browserSettings, time, pluginBase, { sgvs: sgvs , cals: [cal] , treatments: treatments @@ -721,9 +721,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; focus.append('line') .attr('class', 'high-line') .attr('x1', xScale(dataRange[0])) - .attr('y1', yScale(scaleBg(app.thresholds.bg_high))) + .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_high))) .attr('x2', xScale(dataRange[1])) - .attr('y2', yScale(scaleBg(app.thresholds.bg_high))) + .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_high))) .style('stroke-dasharray', ('1, 6')) .attr('stroke', '#777'); @@ -731,9 +731,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; focus.append('line') .attr('class', 'target-top-line') .attr('x1', xScale(dataRange[0])) - .attr('y1', yScale(scaleBg(app.thresholds.bg_target_top))) + .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_target_top))) .attr('x2', xScale(dataRange[1])) - .attr('y2', yScale(scaleBg(app.thresholds.bg_target_top))) + .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_target_top))) .style('stroke-dasharray', ('3, 3')) .attr('stroke', 'grey'); @@ -741,9 +741,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; focus.append('line') .attr('class', 'target-bottom-line') .attr('x1', xScale(dataRange[0])) - .attr('y1', yScale(scaleBg(app.thresholds.bg_target_bottom))) + .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_target_bottom))) .attr('x2', xScale(dataRange[1])) - .attr('y2', yScale(scaleBg(app.thresholds.bg_target_bottom))) + .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_target_bottom))) .style('stroke-dasharray', ('3, 3')) .attr('stroke', 'grey'); @@ -751,9 +751,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; focus.append('line') .attr('class', 'low-line') .attr('x1', xScale(dataRange[0])) - .attr('y1', yScale(scaleBg(app.thresholds.bg_low))) + .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_low))) .attr('x2', xScale(dataRange[1])) - .attr('y2', yScale(scaleBg(app.thresholds.bg_low))) + .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_low))) .style('stroke-dasharray', ('1, 6')) .attr('stroke', '#777'); @@ -787,9 +787,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; context.append('line') .attr('class', 'high-line') .attr('x1', xScale(dataRange[0])) - .attr('y1', yScale2(scaleBg(app.thresholds.bg_target_top))) + .attr('y1', yScale2(scaleBg(serverSettings.thresholds.bg_target_top))) .attr('x2', xScale(dataRange[1])) - .attr('y2', yScale2(scaleBg(app.thresholds.bg_target_top))) + .attr('y2', yScale2(scaleBg(serverSettings.thresholds.bg_target_top))) .style('stroke-dasharray', ('3, 3')) .attr('stroke', 'grey'); @@ -797,9 +797,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; context.append('line') .attr('class', 'low-line') .attr('x1', xScale(dataRange[0])) - .attr('y1', yScale2(scaleBg(app.thresholds.bg_target_bottom))) + .attr('y1', yScale2(scaleBg(serverSettings.thresholds.bg_target_bottom))) .attr('x2', xScale(dataRange[1])) - .attr('y2', yScale2(scaleBg(app.thresholds.bg_target_bottom))) + .attr('y2', yScale2(scaleBg(serverSettings.thresholds.bg_target_bottom))) .style('stroke-dasharray', ('3, 3')) .attr('stroke', 'grey'); @@ -846,33 +846,33 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; .transition() .duration(UPDATE_TRANS_MS) .attr('x1', xScale(currentBrushExtent[0])) - .attr('y1', yScale(scaleBg(app.thresholds.bg_high))) + .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_high))) .attr('x2', xScale(currentBrushExtent[1])) - .attr('y2', yScale(scaleBg(app.thresholds.bg_high))); + .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_high))); focus.select('.target-top-line') .transition() .duration(UPDATE_TRANS_MS) .attr('x1', xScale(currentBrushExtent[0])) - .attr('y1', yScale(scaleBg(app.thresholds.bg_target_top))) + .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_target_top))) .attr('x2', xScale(currentBrushExtent[1])) - .attr('y2', yScale(scaleBg(app.thresholds.bg_target_top))); + .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_target_top))); focus.select('.target-bottom-line') .transition() .duration(UPDATE_TRANS_MS) .attr('x1', xScale(currentBrushExtent[0])) - .attr('y1', yScale(scaleBg(app.thresholds.bg_target_bottom))) + .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_target_bottom))) .attr('x2', xScale(currentBrushExtent[1])) - .attr('y2', yScale(scaleBg(app.thresholds.bg_target_bottom))); + .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_target_bottom))); focus.select('.low-line') .transition() .duration(UPDATE_TRANS_MS) .attr('x1', xScale(currentBrushExtent[0])) - .attr('y1', yScale(scaleBg(app.thresholds.bg_low))) + .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_low))) .attr('x2', xScale(currentBrushExtent[1])) - .attr('y2', yScale(scaleBg(app.thresholds.bg_low))); + .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_low))); // transition open-top line to correct location focus.select('.open-top') @@ -906,18 +906,18 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; .transition() .duration(UPDATE_TRANS_MS) .attr('x1', xScale2(dataRange[0])) - .attr('y1', yScale2(scaleBg(app.thresholds.bg_target_top))) + .attr('y1', yScale2(scaleBg(serverSettings.thresholds.bg_target_top))) .attr('x2', xScale2(dataRange[1])) - .attr('y2', yScale2(scaleBg(app.thresholds.bg_target_top))); + .attr('y2', yScale2(scaleBg(serverSettings.thresholds.bg_target_top))); // transition low line to correct location context.select('.low-line') .transition() .duration(UPDATE_TRANS_MS) .attr('x1', xScale2(dataRange[0])) - .attr('y1', yScale2(scaleBg(app.thresholds.bg_target_bottom))) + .attr('y1', yScale2(scaleBg(serverSettings.thresholds.bg_target_bottom))) .attr('x2', xScale2(dataRange[1])) - .attr('y2', yScale2(scaleBg(app.thresholds.bg_target_bottom))); + .attr('y2', yScale2(scaleBg(serverSettings.thresholds.bg_target_bottom))); } } @@ -985,15 +985,15 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var color = 'grey'; if (browserSettings.theme === 'colors') { - if (sgv > app.thresholds.bg_high) { + if (sgv > serverSettings.thresholds.bg_high) { color = 'red'; - } else if (sgv > app.thresholds.bg_target_top) { + } else if (sgv > serverSettings.thresholds.bg_target_top) { color = 'yellow'; - } else if (sgv >= app.thresholds.bg_target_bottom && sgv <= app.thresholds.bg_target_top) { + } else if (sgv >= serverSettings.thresholds.bg_target_bottom && sgv <= serverSettings.thresholds.bg_target_top) { color = '#4cff00'; - } else if (sgv < app.thresholds.bg_low) { + } else if (sgv < serverSettings.thresholds.bg_low) { color = 'red'; - } else if (sgv < app.thresholds.bg_target_bottom) { + } else if (sgv < serverSettings.thresholds.bg_target_bottom) { color = 'yellow'; } } @@ -1005,15 +1005,15 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var range = ''; if (browserSettings.theme === 'colors') { - if (sgv > app.thresholds.bg_high) { + if (sgv > serverSettings.thresholds.bg_high) { range = 'urgent'; - } else if (sgv > app.thresholds.bg_target_top) { + } else if (sgv > serverSettings.thresholds.bg_target_top) { range = 'warning'; - } else if (sgv >= app.thresholds.bg_target_bottom && sgv <= app.thresholds.bg_target_top) { + } else if (sgv >= serverSettings.thresholds.bg_target_bottom && sgv <= serverSettings.thresholds.bg_target_top) { range = 'inrange'; - } else if (sgv < app.thresholds.bg_low) { + } else if (sgv < serverSettings.thresholds.bg_low) { range = 'urgent'; - } else if (sgv < app.thresholds.bg_target_bottom) { + } else if (sgv < serverSettings.thresholds.bg_target_bottom) { range = 'warning'; } } @@ -1284,21 +1284,21 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; if (browserSettings.units === 'mmol') { tickValues = [ 2.0 - , Math.round(scaleBg(app.thresholds.bg_low)) - , Math.round(scaleBg(app.thresholds.bg_target_bottom)) + , Math.round(scaleBg(serverSettings.thresholds.bg_low)) + , Math.round(scaleBg(serverSettings.thresholds.bg_target_bottom)) , 6.0 - , Math.round(scaleBg(app.thresholds.bg_target_top)) - , Math.round(scaleBg(app.thresholds.bg_high)) + , Math.round(scaleBg(serverSettings.thresholds.bg_target_top)) + , Math.round(scaleBg(serverSettings.thresholds.bg_high)) , 22.0 ]; } else { tickValues = [ 40 - , app.thresholds.bg_low - , app.thresholds.bg_target_bottom + , serverSettings.thresholds.bg_low + , serverSettings.thresholds.bg_target_bottom , 120 - , app.thresholds.bg_target_top - , app.thresholds.bg_high + , serverSettings.thresholds.bg_target_top + , serverSettings.thresholds.bg_high , 400 ]; } @@ -1494,13 +1494,13 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; //with predicted alarms, latestSGV may still be in target so to see if the alarm // is for a HIGH we can only check if it's >= the bottom of the target function isAlarmForHigh() { - return latestSGV.mgdl >= app.thresholds.bg_target_bottom; + return latestSGV.mgdl >= serverSettings.thresholds.bg_target_bottom; } //with predicted alarms, latestSGV may still be in target so to see if the alarm // is for a LOW we can only check if it's <= the top of the target function isAlarmForLow() { - return latestSGV.mgdl <= app.thresholds.bg_target_top; + return latestSGV.mgdl <= serverSettings.thresholds.bg_target_top; } socket.on('alarm', function (notify) { @@ -1552,39 +1552,21 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; }); } - $.ajax('/api/v1/status.json', { - success: function (xhr) { - app = { name: xhr.name - , version: xhr.version - , head: xhr.head - , apiEnabled: xhr.apiEnabled - , enabledOptions: xhr.enabledOptions || '' - , extendedSettings: xhr.extendedSettings - , thresholds: xhr.thresholds - , alarm_types: xhr.alarm_types - , units: xhr.units - , careportalEnabled: xhr.careportalEnabled - , defaults: xhr.defaults - }; - - //TODO: currently we need the ar2 plugin for the cone - if (app.enabledOptions.indexOf('ar2') < 0) { - app.enabledOptions += ' ar2'; - } - } - }).done(function() { - $('.appName').text(app.name); - $('.version').text(app.version); - $('.head').text(app.head); - if (app.apiEnabled) { - $('.serverSettings').show(); - } - $('#treatmentDrawerToggle').toggle(app.careportalEnabled); - Nightscout.plugins.init(app); - browserSettings = getBrowserSettings(browserStorage); - sbx = Nightscout.sandbox.clientInit(app, browserSettings, Date.now()); - $('.container').toggleClass('has-minor-pills', Nightscout.plugins.hasShownType('pill-minor', browserSettings)); - init(); - }); + if (serverSettings.enabledOptions.indexOf('ar2') < 0) { + serverSettings.enabledOptions += ' ar2'; + } + + $('.appName').text(serverSettings.name); + $('.version').text(serverSettings.version); + $('.head').text(serverSettings.head); + if (serverSettings.apiEnabled) { + $('.serverSettings').show(); + } + $('#treatmentDrawerToggle').toggle(serverSettings.careportalEnabled); + Nightscout.plugins.init(serverSettings); + browserSettings = getBrowserSettings(browserStorage); + sbx = Nightscout.sandbox.clientInit(serverSettings, browserSettings, Date.now()); + $('.container').toggleClass('has-minor-pills', Nightscout.plugins.hasShownType('pill-minor', browserSettings)); + init(); })(); diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js index 4fcc6b85a4a..93651c0cc22 100644 --- a/static/js/ui-utils.js +++ b/static/js/ui-utils.js @@ -3,7 +3,7 @@ var openDraw = null; function rawBGsEnabled() { - return app.enabledOptions && app.enabledOptions.indexOf('rawbg') > -1; + return serverSettings.enabledOptions && serverSettings.enabledOptions.indexOf('rawbg') > -1; } function getBrowserSettings(storage) { @@ -18,7 +18,7 @@ function getBrowserSettings(storage) { } function appendThresholdValue(threshold) { - return app.alarm_types.indexOf('simple') === -1 ? '' : ' (' + scaleBg(threshold) + ')'; + return serverSettings.alarm_types.indexOf('simple') === -1 ? '' : ' (' + scaleBg(threshold) + ')'; } try { @@ -41,54 +41,54 @@ function getBrowserSettings(storage) { }; // Default browser units to server units if undefined. - json.units = setDefault(json.units, app.units); + json.units = setDefault(json.units, serverSettings.units); if (json.units === 'mmol') { $('#mmol-browser').prop('checked', true); } else { $('#mgdl-browser').prop('checked', true); } - json.alarmUrgentHigh = setDefault(json.alarmUrgentHigh, app.defaults.alarmUrgentHigh); - json.alarmHigh = setDefault(json.alarmHigh, app.defaults.alarmHigh); - json.alarmLow = setDefault(json.alarmLow, app.defaults.alarmLow); - json.alarmUrgentLow = setDefault(json.alarmUrgentLow, app.defaults.alarmUrgentLow); - json.alarmTimeAgoWarn = setDefault(json.alarmTimeAgoWarn, app.defaults.alarmTimeAgoWarn); - json.alarmTimeAgoWarnMins = setDefault(json.alarmTimeAgoWarnMins, app.defaults.alarmTimeAgoWarnMins); - json.alarmTimeAgoUrgent = setDefault(json.alarmTimeAgoUrgent, app.defaults.alarmTimeAgoUrgent); - json.alarmTimeAgoUrgentMins = setDefault(json.alarmTimeAgoUrgentMins, app.defaults.alarmTimeAgoUrgentMins); - $('#alarm-urgenthigh-browser').prop('checked', json.alarmUrgentHigh).next().text('Urgent High Alarm' + appendThresholdValue(app.thresholds.bg_high)); - $('#alarm-high-browser').prop('checked', json.alarmHigh).next().text('High Alarm' + appendThresholdValue(app.thresholds.bg_target_top)); - $('#alarm-low-browser').prop('checked', json.alarmLow).next().text('Low Alarm' + appendThresholdValue(app.thresholds.bg_target_bottom)); - $('#alarm-urgentlow-browser').prop('checked', json.alarmUrgentLow).next().text('Urgent Low Alarm' + appendThresholdValue(app.thresholds.bg_low)); + json.alarmUrgentHigh = setDefault(json.alarmUrgentHigh, serverSettings.defaults.alarmUrgentHigh); + json.alarmHigh = setDefault(json.alarmHigh, serverSettings.defaults.alarmHigh); + json.alarmLow = setDefault(json.alarmLow, serverSettings.defaults.alarmLow); + json.alarmUrgentLow = setDefault(json.alarmUrgentLow, serverSettings.defaults.alarmUrgentLow); + json.alarmTimeAgoWarn = setDefault(json.alarmTimeAgoWarn, serverSettings.defaults.alarmTimeAgoWarn); + json.alarmTimeAgoWarnMins = setDefault(json.alarmTimeAgoWarnMins, serverSettings.defaults.alarmTimeAgoWarnMins); + json.alarmTimeAgoUrgent = setDefault(json.alarmTimeAgoUrgent, serverSettings.defaults.alarmTimeAgoUrgent); + json.alarmTimeAgoUrgentMins = setDefault(json.alarmTimeAgoUrgentMins, serverSettings.defaults.alarmTimeAgoUrgentMins); + $('#alarm-urgenthigh-browser').prop('checked', json.alarmUrgentHigh).next().text('Urgent High Alarm' + appendThresholdValue(serverSettings.thresholds.bg_high)); + $('#alarm-high-browser').prop('checked', json.alarmHigh).next().text('High Alarm' + appendThresholdValue(serverSettings.thresholds.bg_target_top)); + $('#alarm-low-browser').prop('checked', json.alarmLow).next().text('Low Alarm' + appendThresholdValue(serverSettings.thresholds.bg_target_bottom)); + $('#alarm-urgentlow-browser').prop('checked', json.alarmUrgentLow).next().text('Urgent Low Alarm' + appendThresholdValue(serverSettings.thresholds.bg_low)); $('#alarm-timeagowarn-browser').prop('checked', json.alarmTimeAgoWarn); $('#alarm-timeagowarnmins-browser').val(json.alarmTimeAgoWarnMins); $('#alarm-timeagourgent-browser').prop('checked', json.alarmTimeAgoUrgent); $('#alarm-timeagourgentmins-browser').val(json.alarmTimeAgoUrgentMins); - json.nightMode = setDefault(json.nightMode, app.defaults.nightMode); + json.nightMode = setDefault(json.nightMode, serverSettings.defaults.nightMode); $('#nightmode-browser').prop('checked', json.nightMode); if (rawBGsEnabled()) { $('#show-rawbg-option').show(); - json.showRawbg = setDefault(json.showRawbg, app.defaults.showRawbg); + json.showRawbg = setDefault(json.showRawbg, serverSettings.defaults.showRawbg); $('#show-rawbg-' + json.showRawbg).prop('checked', true); } else { json.showRawbg = 'never'; $('#show-rawbg-option').hide(); } - json.customTitle = setDefault(json.customTitle, app.defaults.customTitle); + json.customTitle = setDefault(json.customTitle, serverSettings.defaults.customTitle); $('h1.customTitle').text(json.customTitle); $('input#customTitle').prop('value', json.customTitle); - json.theme = setDefault(json.theme, app.defaults.theme); + json.theme = setDefault(json.theme, serverSettings.defaults.theme); if (json.theme === 'colors') { $('#theme-colors-browser').prop('checked', true); } else { $('#theme-default-browser').prop('checked', true); } - json.timeFormat = setDefault(json.timeFormat, app.defaults.timeFormat); + json.timeFormat = setDefault(json.timeFormat, serverSettings.defaults.timeFormat); if (json.timeFormat === '24') { $('#24-browser').prop('checked', true); @@ -96,7 +96,7 @@ function getBrowserSettings(storage) { $('#12-browser').prop('checked', true); } - json.showPlugins = setDefault(json.showPlugins, app.defaults.showPlugins || Nightscout.plugins.enabledPluginNames()); + json.showPlugins = setDefault(json.showPlugins, serverSettings.defaults.showPlugins || Nightscout.plugins.enabledPluginNames()); var showPluginsSettings = $('#show-plugins'); Nightscout.plugins.eachEnabledPlugin(function each(plugin) { if (Nightscout.plugins.specialPlugins.indexOf(plugin.name) > -1) { From de952454f74f3f0500ef238f99d4f406f4d1eecc Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 21 Jul 2015 23:26:00 -0700 Subject: [PATCH 459/937] move more to init() --- static/js/client.js | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index 7f5c0a4018f..76059967aa3 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1550,23 +1550,25 @@ var browserSettings = {}, browserStorage = $.localStorage; }); event.preventDefault(); }); - } - if (serverSettings.enabledOptions.indexOf('ar2') < 0) { - serverSettings.enabledOptions += ' ar2'; - } + if (serverSettings.enabledOptions.indexOf('ar2') < 0) { + serverSettings.enabledOptions += ' ar2'; + } + + $('.appName').text(serverSettings.name); + $('.version').text(serverSettings.version); + $('.head').text(serverSettings.head); + if (serverSettings.apiEnabled) { + $('.serverSettings').show(); + } + $('#treatmentDrawerToggle').toggle(serverSettings.careportalEnabled); + Nightscout.plugins.init(serverSettings); + browserSettings = getBrowserSettings(browserStorage); + sbx = Nightscout.sandbox.clientInit(serverSettings, browserSettings, Date.now()); + $('.container').toggleClass('has-minor-pills', Nightscout.plugins.hasShownType('pill-minor', browserSettings)); - $('.appName').text(serverSettings.name); - $('.version').text(serverSettings.version); - $('.head').text(serverSettings.head); - if (serverSettings.apiEnabled) { - $('.serverSettings').show(); } - $('#treatmentDrawerToggle').toggle(serverSettings.careportalEnabled); - Nightscout.plugins.init(serverSettings); - browserSettings = getBrowserSettings(browserStorage); - sbx = Nightscout.sandbox.clientInit(serverSettings, browserSettings, Date.now()); - $('.container').toggleClass('has-minor-pills', Nightscout.plugins.hasShownType('pill-minor', browserSettings)); + init(); })(); From 63ebc0f9188f90d6ce7d01b5140b597160f7f05c Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 22 Jul 2015 01:13:34 -0700 Subject: [PATCH 460/937] move client.js to a module; needs massive clean up; but there is a very basic test --- bower.json | 1 - bundle/bundle.source.js | 6 +- lib/client/index.js | 1710 +++++++++++++++++++++++++++++++++++++++ package.json | 6 +- static/js/client.js | 1575 +----------------------------------- static/js/ui-utils.js | 139 ---- tests/client.test.js | 53 ++ 7 files changed, 1774 insertions(+), 1716 deletions(-) create mode 100644 lib/client/index.js create mode 100644 tests/client.test.js diff --git a/bower.json b/bower.json index da22cab72e3..1ca7b038629 100644 --- a/bower.json +++ b/bower.json @@ -4,7 +4,6 @@ "dependencies": { "angularjs": "1.3.0-beta.19", "bootstrap": "~3.2.0", - "d3": "~3.5.5", "jquery": "2.1.0", "jQuery-Storage-API": "~1.7.2", "jsSHA": "~1.5.0", diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index f0e43488b6f..22569054a2c 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -5,15 +5,19 @@ window.moment = require('moment-timezone'); window.Nightscout = window.Nightscout || {}; + var plugins = require('../lib/plugins/')().registerClientDefaults(); + window.Nightscout = { units: require('../lib/units')(), utils: require('../lib/utils')(), profile: require('../lib/profilefunctions')(), language: require('../lib/language')(), - plugins: require('../lib/plugins/')().registerClientDefaults(), + plugins: plugins, sandbox: require('../lib/sandbox')() }; + window.Nightscout.client = require('../lib/client'); + console.info('Nightscout bundle ready', window.Nightscout); })(); diff --git a/lib/client/index.js b/lib/client/index.js new file mode 100644 index 00000000000..9d69c25c606 --- /dev/null +++ b/lib/client/index.js @@ -0,0 +1,1710 @@ +'use strict'; + +var $ = (global && global.$) || require('jquery'); +var _ = require('lodash'); +var d3 = (global && global.d3) || require('d3'); + +var language = require('../language')(); +var profile = require('../profilefunctions')(); +var sandbox = require('../sandbox')(); +var utils = require('../utils')(); + +function init(plugins, serverSettings) { + + var BRUSH_TIMEOUT = 300000 // 5 minutes in ms + , DEBOUNCE_MS = 10 + , TOOLTIP_TRANS_MS = 200 // milliseconds + , UPDATE_TRANS_MS = 750 // milliseconds + , ONE_MIN_IN_MS = 60000 + , FIVE_MINS_IN_MS = 300000 + , THREE_HOURS_MS = 3 * 60 * 60 * 1000 + , TWENTY_FIVE_MINS_IN_MS = 1500000 + , THIRTY_MINS_IN_MS = 1800000 + , SIXTY_MINS_IN_MS = 3600000 + , FORMAT_TIME_12 = '%-I:%M %p' + , FORMAT_TIME_12_COMAPCT = '%-I:%M' + , FORMAT_TIME_24 = '%H:%M%' + , FORMAT_TIME_12_SCALE = '%-I %p' + , FORMAT_TIME_24_SCALE = '%H' + , WIDTH_SMALL_DOTS = 400 + , WIDTH_BIG_DOTS = 800; + + var socket + , isInitialData = false + , SGVdata = [] + , MBGdata = [] + , latestSGV + , latestUpdateTime + , prevSGV + , treatments + , cal + , devicestatusData + , padding = { top: 0, right: 10, bottom: 30, left: 10 } + , opacity = {current: 1, DAY: 1, NIGHT: 0.5} + , now = Date.now() + , data = [] + , foucusRangeMS = THREE_HOURS_MS + , clientAlarms = {} + , alarmInProgress = false + , alarmMessage + , currentAlarmType = null + , alarmSound = 'alarm.mp3' + , urgentAlarmSound = 'alarm2.mp3'; + + var sbx + , rawbg = plugins('rawbg') + , delta = plugins('delta') + , direction = plugins('direction') + , errorcodes = plugins('errorcodes') + , translate = language.translate + , timeAgo = utils.timeAgo; + + var tooltip + , tickValues + , charts + , futureOpacity + , focus + , context + , xScale, xScale2, yScale, yScale2 + , xAxis, yAxis, xAxis2, yAxis2 + , prevChartWidth = 0 + , prevChartHeight = 0 + , focusHeight + , contextHeight + , dateFn = function (d) { return new Date(d.mills) } + , documentHidden = false + , brush + , brushTimer + , brushInProgress = false + , clip; + + var container = $('.container') + , bgStatus = $('.bgStatus') + , currentBG = $('.bgStatus .currentBG') + , majorPills = $('.bgStatus .majorPills') + , minorPills = $('.bgStatus .minorPills') + , statusPills = $('.status .statusPills') + ; + + var browserSettings = getBrowserSettings(); + language.set(serverSettings.defaults.language).DOMtranslate(); + + function formatTime(time, compact) { + var timeFormat = getTimeFormat(false, compact); + time = d3.time.format(timeFormat)(time); + if (!isTimeFormat24()) { + time = time.toLowerCase(); + } + return time; + } + + function isTimeFormat24() { + return browserSettings && browserSettings.timeFormat && parseInt(browserSettings.timeFormat) === 24; + } + + function getTimeFormat(isForScale, compact) { + var timeFormat = FORMAT_TIME_12; + if (isTimeFormat24()) { + timeFormat = isForScale ? FORMAT_TIME_24_SCALE : FORMAT_TIME_24; + } else { + timeFormat = isForScale ? FORMAT_TIME_12_SCALE : (compact ? FORMAT_TIME_12_COMAPCT : FORMAT_TIME_12); + } + + return timeFormat; + } + +// lixgbg: Convert mg/dL BG value to metric mmol + function scaleBg(bg) { + if (browserSettings.units === 'mmol') { + return units.mgdlToMMOL(bg); + } else { + return bg; + } + } + + function generateTitle() { + + function s(value, sep) { return value ? value + ' ' : sep || ''; } + + var bg_title = ''; + + var time = latestSGV ? latestSGV.mills : (prevSGV ? prevSGV.mills : -1) + , ago = timeAgo(time, browserSettings); + + if (browserSettings.customTitle) { + $('.customTitle').text(browserSettings.customTitle); + } + + if (ago && ago.status !== 'current') { + bg_title = s(ago.value) + s(ago.label, ' - ') + bg_title; + } else if (latestSGV) { + var currentMgdl = latestSGV.mgdl; + + if (currentMgdl < 39) { + bg_title = s(errorcodes.toDisplay(currentMgdl), ' - ') + bg_title; + } else { + var deltaDisplay = delta.calc(prevSGV, latestSGV, sbx).display; + bg_title = s(scaleBg(currentMgdl)) + s(deltaDisplay) + s(direction.info(latestSGV).label) + bg_title; + } + } + return bg_title; + } + + function updateTitle(skipPageTitle) { + + var bg_title = browserSettings.customTitle || ''; + + if (alarmMessage && alarmInProgress) { + bg_title = alarmMessage + ': ' + generateTitle(); + $('.customTitle').text(alarmMessage); + } else { + bg_title = generateTitle(); + } + + if (!skipPageTitle) { + $(document).attr('title', bg_title); + } + } + +// initial setup of chart when data is first made available + function initializeCharts() { + + // define the parts of the axis that aren't dependent on width or height + xScale = d3.time.scale() + .domain(d3.extent(data, dateFn)); + + yScale = d3.scale.log() + .domain([scaleBg(30), scaleBg(510)]); + + xScale2 = d3.time.scale() + .domain(d3.extent(data, dateFn)); + + yScale2 = d3.scale.log() + .domain([scaleBg(36), scaleBg(420)]); + + var tickFormat = d3.time.format.multi( [ + ['.%L', function(d) { return d.getMilliseconds(); }], + [':%S', function(d) { return d.getSeconds(); }], + ['%I:%M', function(d) { return d.getMinutes(); }], + [isTimeFormat24() ? '%H:%M' : '%-I %p', function(d) { return d.getHours(); }], + ['%a %d', function(d) { return d.getDay() && d.getDate() !== 1; }], + ['%b %d', function(d) { return d.getDate() !== 1; }], + ['%B', function(d) { return d.getMonth(); }], + ['%Y', function() { return true; }] + ]); + + xAxis = d3.svg.axis() + .scale(xScale) + .tickFormat(tickFormat) + .ticks(4) + .orient('bottom'); + + yAxis = d3.svg.axis() + .scale(yScale) + .tickFormat(d3.format('d')) + .tickValues(tickValues) + .orient('left'); + + xAxis2 = d3.svg.axis() + .scale(xScale2) + .tickFormat(tickFormat) + .ticks(6) + .orient('bottom'); + + yAxis2 = d3.svg.axis() + .scale(yScale2) + .tickFormat(d3.format('d')) + .tickValues(tickValues) + .orient('right'); + + // setup a brush + brush = d3.svg.brush() + .x(xScale2) + .on('brushstart', brushStarted) + .on('brush', brushed) + .on('brushend', brushEnded); + + updateChart(true); + } + +// get the desired opacity for context chart based on the brush extent + function highlightBrushPoints(data) { + if (data.mills >= brush.extent()[0].getTime() && data.mills <= brush.extent()[1].getTime()) { + return futureOpacity(data.mills - latestSGV.mills); + } else { + return 0.5; + } + } + + function addPlaceholderPoints () { + // TODO: This is a kludge to advance the time as data becomes stale by making old predictor clear (using color = 'none') + // This shouldn't need to be generated and can be fixed by using xScale.domain([x0,x1]) function with + // 2 days before now as x0 and 30 minutes from now for x1 for context plot, but this will be + // required to happen when 'now' event is sent from websocket.js every minute. When fixed, + // remove this code and all references to `type: 'server-forecast'` + var last = _.last(data); + var lastTime = last && last.mills; + if (!lastTime) { + console.error('Bad Data, last point has no mills', last); + lastTime = Date.now(); + } + + var n = Math.ceil(12 * (1 / 2 + (now - lastTime) / SIXTY_MINS_IN_MS)) + 1; + for (var i = 1; i <= n; i++) { + data.push({ + mills: lastTime + (i * FIVE_MINS_IN_MS), mgdl: 100, color: 'none', type: 'server-forecast' + }); + } + } + +// clears the current user brush and resets to the current real time data + function updateBrushToNow(skipBrushing) { + + // get current time range + var dataRange = d3.extent(data, dateFn); + + // update brush and focus chart with recent data + d3.select('.brush') + .transition() + .duration(UPDATE_TRANS_MS) + .call(brush.extent([new Date(dataRange[1].getTime() - foucusRangeMS), dataRange[1]])); + + addPlaceholderPoints(); + + if (!skipBrushing) { + brushed(true); + + // clear user brush tracking + brushInProgress = false; + } + } + + function brushStarted() { + // update the opacity of the context data points to brush extent + context.selectAll('circle') + .data(data) + .style('opacity', 1); + } + + function brushEnded() { + // update the opacity of the context data points to brush extent + context.selectAll('circle') + .data(data) + .style('opacity', function (d) { return highlightBrushPoints(d) }); + } + + function alarmingNow() { + return container.hasClass('alarming'); + } + + function inRetroMode() { + if (!brush) { + return false; + } + + var time = brush.extent()[1].getTime(); + + return !alarmingNow() && time - TWENTY_FIVE_MINS_IN_MS < now; + } + + function brushed(skipTimer) { + + if (!skipTimer) { + // set a timer to reset focus chart to real-time data + clearTimeout(brushTimer); + brushTimer = setTimeout(updateBrushToNow, BRUSH_TIMEOUT); + brushInProgress = true; + } + + var brushExtent = brush.extent(); + + // ensure that brush extent is fixed at 3.5 hours + if (brushExtent[1].getTime() - brushExtent[0].getTime() !== foucusRangeMS) { + // ensure that brush updating is with the time range + if (brushExtent[0].getTime() + foucusRangeMS > d3.extent(data, dateFn)[1].getTime()) { + brushExtent[0] = new Date(brushExtent[1].getTime() - foucusRangeMS); + d3.select('.brush') + .call(brush.extent([brushExtent[0], brushExtent[1]])); + } else { + brushExtent[1] = new Date(brushExtent[0].getTime() + foucusRangeMS); + d3.select('.brush') + .call(brush.extent([brushExtent[0], brushExtent[1]])); + } + } + + var nowDate = new Date(brushExtent[1] - THIRTY_MINS_IN_MS); + + function updateCurrentSGV(entry) { + var value = entry.mgdl + , ago = timeAgo(entry.mills, browserSettings) + , isCurrent = ago.status === 'current'; + + if (value === 9) { + currentBG.text(''); + } else if (value < 39) { + currentBG.html(errorcodes.toDisplay(value)); + } else if (value < 40) { + currentBG.text('LOW'); + } else if (value > 400) { + currentBG.text('HIGH'); + } else { + currentBG.text(scaleBg(value)); + } + + bgStatus.toggleClass('current', alarmingNow() || (isCurrent && !inRetroMode())); + if (!alarmingNow()) { + container.removeClass('urgent warning inrange'); + if (isCurrent && !inRetroMode()) { + container.addClass(sgvToColoredRange(value)); + } + } + + currentBG.toggleClass('icon-hourglass', value === 9); + currentBG.toggleClass('error-code', value < 39); + currentBG.toggleClass('bg-limit', value === 39 || value > 400); + + $('.container').removeClass('loading'); + + } + + function updatePlugins (sgvs, time) { + + var pluginBase = plugins.base(majorPills, minorPills, statusPills, bgStatus, tooltip); + + sbx = sandbox.clientInit(serverSettings, browserSettings, time, pluginBase, { + sgvs: sgvs + , cals: [cal] + , treatments: treatments + , profile: Nightscout.profile + , uploaderBattery: devicestatusData && devicestatusData.uploaderBattery + }); + + //all enabled plugins get a chance to set properties, even if they aren't shown + plugins.setProperties(sbx); + + //only shown plugins get a chance to update visualisations + plugins.updateVisualisations(sbx); + } + + var nowData = data.filter(function(d) { + return d.type === 'sgv'; + }); + + if (inRetroMode()) { + var retroTime = brushExtent[1].getTime() - THIRTY_MINS_IN_MS; + + nowData = nowData.filter(function(d) { + return d.mills >= brushExtent[1].getTime() - (2 * THIRTY_MINS_IN_MS) && + d.mills <= brushExtent[1].getTime() - TWENTY_FIVE_MINS_IN_MS; + }); + + // sometimes nowData contains duplicates. uniq it. + var lastDate = 0; + nowData = nowData.filter(function(d) { + var ok = lastDate + ONE_MIN_IN_MS < d.mills; + lastDate = d.mills; + return ok; + }); + + var focusPoint = nowData.length > 0 ? nowData[nowData.length - 1] : null; + if (focusPoint) { + updateCurrentSGV(focusPoint); + } else { + currentBG.text('---'); + container.removeClass('urgent warning inrange'); + } + + updatePlugins(nowData, retroTime); + + $('#currentTime') + .text(formatTime(new Date(retroTime), true)) + .css('text-decoration','line-through'); + + updateTimeAgo(); + } else { + // if the brush comes back into the current time range then it should reset to the current time and sg + nowData = nowData.slice(nowData.length - 2, nowData.length); + nowDate = now; + + updateCurrentSGV(latestSGV); + updateClockDisplay(); + updateTimeAgo(); + updatePlugins(nowData, nowDate); + + } + + xScale.domain(brush.extent()); + + // get slice of data so that concatenation of predictions do not interfere with subsequent updates + var focusData = data.slice(); + + if (sbx.pluginBase.forecastPoints) { + focusData = focusData.concat(sbx.pluginBase.forecastPoints); + } + + // bind up the focus chart data to an array of circles + // selects all our data into data and uses date function to get current max date + var focusCircles = focus.selectAll('circle').data(focusData, dateFn); + + var focusRangeAdjustment = foucusRangeMS === THREE_HOURS_MS ? 1 : 1 + ((foucusRangeMS - THREE_HOURS_MS) / THREE_HOURS_MS / 8); + + var dotRadius = function(type) { + var radius = prevChartWidth > WIDTH_BIG_DOTS ? 4 : (prevChartWidth < WIDTH_SMALL_DOTS ? 2 : 3); + if (type === 'mbg') { + radius *= 2; + } else if (type === 'forecast') { + radius = Math.min(3, radius - 1); + } else if (type === 'rawbg') { + radius = Math.min(2, radius - 1); + } + + return radius / focusRangeAdjustment; + }; + + function isDexcom(device) { + return device && device.toLowerCase().indexOf('dexcom') === 0; + } + + function prepareFocusCircles(sel) { + var badData = []; + sel.attr('cx', function (d) { + if (!d) { + console.error('Bad data', d); + return xScale(new Date(0)); + } else if (!d.mills) { + console.error('Bad data, no mills', d); + return xScale(new Date(0)); + } else { + return xScale(new Date(d.mills)); + } + }) + .attr('cy', function (d) { + var scaled = sbx.scaleEntry(d); + if (isNaN(scaled)) { + badData.push(d); + return yScale(scaleBg(450)); + } else { + return yScale(scaled); + } + }) + .attr('fill', function (d) { return d.type === 'forecast' ? 'none' : d.color; }) + .attr('opacity', function (d) { return futureOpacity(d.mills - latestSGV.mills); }) + .attr('stroke-width', function (d) { return d.type === 'mbg' ? 2 : d.type === 'forecast' ? 1 : 0; }) + .attr('stroke', function (d) { + return (isDexcom(d.device) ? 'white' : d.type === 'forecast' ? d.color : '#0099ff'); + }) + .attr('r', function (d) { return dotRadius(d.type); }); + + if (badData.length > 0) { + console.warn('Bad Data: isNaN(sgv)', badData); + } + + return sel; + } + + // if already existing then transition each circle to its new position + prepareFocusCircles(focusCircles.transition().duration(UPDATE_TRANS_MS)); + + // if new circle then just display + prepareFocusCircles(focusCircles.enter().append('circle')) + .on('mouseover', function (d) { + if (d.type === 'sgv' || d.type === 'mbg') { + var bgType = (d.type === 'sgv' ? 'CGM' : (isDexcom(d.device) ? 'Calibration' : 'Meter')) + , rawbgValue = 0 + , noiseLabel = ''; + + if (d.type === 'sgv') { + if (rawbg.showRawBGs(d.mgdl, d.noise, cal, sbx)) { + rawbgValue = scaleBg(rawbg.calc(d, cal, sbx)); + } + noiseLabel = rawbg.noiseCodeToDisplay(d.mgdl, d.noise); + } + + tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); + tooltip.html('' + bgType + ' BG: ' + sbx.scaleEntry(d) + + (d.type === 'mbg' ? '
    Device: ' + d.device : '') + + (rawbgValue ? '
    Raw BG: ' + rawbgValue : '') + + (noiseLabel ? '
    Noise: ' + noiseLabel : '') + + '
    Time: ' + formatTime(new Date(d.mills))) + .style('left', (d3.event.pageX) + 'px') + .style('top', (d3.event.pageY + 15) + 'px'); + } + }) + .on('mouseout', function (d) { + if (d.type === 'sgv' || d.type === 'mbg') { + tooltip.transition() + .duration(TOOLTIP_TRANS_MS) + .style('opacity', 0); + } + }); + + focusCircles.exit() + .remove(); + + // remove all insulin/carb treatment bubbles so that they can be redrawn to correct location + d3.selectAll('.path').remove(); + + // add treatment bubbles + // a higher bubbleScale will produce smaller bubbles (it's not a radius like focusDotRadius) + var bubbleScale = (prevChartWidth < WIDTH_SMALL_DOTS ? 4 : (prevChartWidth < WIDTH_BIG_DOTS ? 3 : 2)) * focusRangeAdjustment; + + focus.selectAll('circle') + .data(treatments) + .each(function (d) { drawTreatment(d, bubbleScale, true) }); + + // transition open-top line to correct location + focus.select('.open-top') + .attr('x1', xScale2(brush.extent()[0])) + .attr('y1', yScale(scaleBg(30))) + .attr('x2', xScale2(brush.extent()[1])) + .attr('y2', yScale(scaleBg(30))); + + // transition open-left line to correct location + focus.select('.open-left') + .attr('x1', xScale2(brush.extent()[0])) + .attr('y1', focusHeight) + .attr('x2', xScale2(brush.extent()[0])) + .attr('y2', prevChartHeight); + + // transition open-right line to correct location + focus.select('.open-right') + .attr('x1', xScale2(brush.extent()[1])) + .attr('y1', focusHeight) + .attr('x2', xScale2(brush.extent()[1])) + .attr('y2', prevChartHeight); + + focus.select('.now-line') + .transition() + .duration(UPDATE_TRANS_MS) + .attr('x1', xScale(nowDate)) + .attr('y1', yScale(scaleBg(36))) + .attr('x2', xScale(nowDate)) + .attr('y2', yScale(scaleBg(420))); + + context.select('.now-line') + .transition() + .attr('x1', xScale2(brush.extent()[1]- THIRTY_MINS_IN_MS)) + .attr('y1', yScale2(scaleBg(36))) + .attr('x2', xScale2(brush.extent()[1]- THIRTY_MINS_IN_MS)) + .attr('y2', yScale2(scaleBg(420))); + + // update x axis + focus.select('.x.axis') + .call(xAxis); + + // add clipping path so that data stays within axis + focusCircles.attr('clip-path', 'url(#clip)'); + + function prepareTreatCircles(sel) { + sel.attr('cx', function (d) { return xScale(new Date(d.mills)); }) + .attr('cy', function (d) { return yScale(sbx.scaleEntry(d)); }) + .attr('r', function () { return dotRadius('mbg'); }) + .attr('stroke-width', 2) + .attr('stroke', function (d) { return d.glucose ? 'grey' : 'white'; }) + .attr('fill', function (d) { return d.glucose ? 'red' : 'grey'; }); + + return sel; + } + + //NOTE: treatments with insulin or carbs are drawn by drawTreatment() + // bind up the focus chart data to an array of circles + var treatCircles = focus.selectAll('rect').data(treatments.filter(function(treatment) { + return !treatment.carbs && !treatment.insulin; + })); + + // if already existing then transition each circle to its new position + prepareTreatCircles(treatCircles.transition().duration(UPDATE_TRANS_MS)); + + // if new circle then just display + prepareTreatCircles(treatCircles.enter().append('circle')) + .on('mouseover', function (d) { + tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); + tooltip.html(''+translate('Time')+': ' + formatTime(new Date(d.mills)) + '
    ' + + (d.eventType ? ''+translate('Treatment type')+': ' + d.eventType + '
    ' : '') + + (d.glucose ? ''+translate('BG')+': ' + d.glucose + (d.glucoseType ? ' (' + translate(d.glucoseType) + ')': '') + '
    ' : '') + + (d.enteredBy ? ''+translate('Entered by')+': ' + d.enteredBy + '
    ' : '') + + (d.notes ? ''+translate('Notes')+': ' + d.notes : '') + ) + .style('left', (d3.event.pageX) + 'px') + .style('top', (d3.event.pageY + 15) + 'px'); + }) + .on('mouseout', function () { + tooltip.transition() + .duration(TOOLTIP_TRANS_MS) + .style('opacity', 0); + }); + + treatCircles.attr('clip-path', 'url(#clip)'); + } + +// called for initial update and updates for resize + var updateChart = _.debounce(function debouncedUpdateChart(init) { + + if (documentHidden && !init) { + console.info('Document Hidden, not updating - ' + (new Date())); + return; + } + // get current data range + var dataRange = d3.extent(data, dateFn); + + // get the entire container height and width subtracting the padding + var chartWidth = (document.getElementById('chartContainer') + .getBoundingClientRect().width) - padding.left - padding.right; + + var chartHeight = (document.getElementById('chartContainer') + .getBoundingClientRect().height) - padding.top - padding.bottom; + + // get the height of each chart based on its container size ratio + focusHeight = chartHeight * .7; + contextHeight = chartHeight * .2; + + // get current brush extent + var currentBrushExtent = brush.extent(); + + // only redraw chart if chart size has changed + if ((prevChartWidth !== chartWidth) || (prevChartHeight !== chartHeight)) { + + prevChartWidth = chartWidth; + prevChartHeight = chartHeight; + + //set the width and height of the SVG element + charts.attr('width', chartWidth + padding.left + padding.right) + .attr('height', chartHeight + padding.top + padding.bottom); + + // ranges are based on the width and height available so reset + xScale.range([0, chartWidth]); + xScale2.range([0, chartWidth]); + yScale.range([focusHeight, 0]); + yScale2.range([chartHeight, chartHeight - contextHeight]); + + if (init) { + + // if first run then just display axis with no transition + focus.select('.x') + .attr('transform', 'translate(0,' + focusHeight + ')') + .call(xAxis); + + focus.select('.y') + .attr('transform', 'translate(' + chartWidth + ',0)') + .call(yAxis); + + // if first run then just display axis with no transition + context.select('.x') + .attr('transform', 'translate(0,' + chartHeight + ')') + .call(xAxis2); + + context.append('g') + .attr('class', 'x brush') + .call(d3.svg.brush().x(xScale2).on('brush', brushed)) + .selectAll('rect') + .attr('y', focusHeight) + .attr('height', chartHeight - focusHeight); + + // disable resizing of brush + d3.select('.x.brush').select('.background').style('cursor', 'move'); + d3.select('.x.brush').select('.resize.e').style('cursor', 'move'); + d3.select('.x.brush').select('.resize.w').style('cursor', 'move'); + + // create a clipPath for when brushing + clip = charts.append('defs') + .append('clipPath') + .attr('id', 'clip') + .append('rect') + .attr('height', chartHeight) + .attr('width', chartWidth); + + // add a line that marks the current time + focus.append('line') + .attr('class', 'now-line') + .attr('x1', xScale(new Date(now))) + .attr('y1', yScale(scaleBg(30))) + .attr('x2', xScale(new Date(now))) + .attr('y2', yScale(scaleBg(420))) + .style('stroke-dasharray', ('3, 3')) + .attr('stroke', 'grey'); + + // add a y-axis line that shows the high bg threshold + focus.append('line') + .attr('class', 'high-line') + .attr('x1', xScale(dataRange[0])) + .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_high))) + .attr('x2', xScale(dataRange[1])) + .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_high))) + .style('stroke-dasharray', ('1, 6')) + .attr('stroke', '#777'); + + // add a y-axis line that shows the high bg threshold + focus.append('line') + .attr('class', 'target-top-line') + .attr('x1', xScale(dataRange[0])) + .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_target_top))) + .attr('x2', xScale(dataRange[1])) + .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_target_top))) + .style('stroke-dasharray', ('3, 3')) + .attr('stroke', 'grey'); + + // add a y-axis line that shows the low bg threshold + focus.append('line') + .attr('class', 'target-bottom-line') + .attr('x1', xScale(dataRange[0])) + .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_target_bottom))) + .attr('x2', xScale(dataRange[1])) + .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_target_bottom))) + .style('stroke-dasharray', ('3, 3')) + .attr('stroke', 'grey'); + + // add a y-axis line that shows the low bg threshold + focus.append('line') + .attr('class', 'low-line') + .attr('x1', xScale(dataRange[0])) + .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_low))) + .attr('x2', xScale(dataRange[1])) + .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_low))) + .style('stroke-dasharray', ('1, 6')) + .attr('stroke', '#777'); + + // add a y-axis line that opens up the brush extent from the context to the focus + focus.append('line') + .attr('class', 'open-top') + .attr('stroke', 'black') + .attr('stroke-width', 2); + + // add a x-axis line that closes the the brush container on left side + focus.append('line') + .attr('class', 'open-left') + .attr('stroke', 'white'); + + // add a x-axis line that closes the the brush container on right side + focus.append('line') + .attr('class', 'open-right') + .attr('stroke', 'white'); + + // add a line that marks the current time + context.append('line') + .attr('class', 'now-line') + .attr('x1', xScale(new Date(now))) + .attr('y1', yScale2(scaleBg(36))) + .attr('x2', xScale(new Date(now))) + .attr('y2', yScale2(scaleBg(420))) + .style('stroke-dasharray', ('3, 3')) + .attr('stroke', 'grey'); + + // add a y-axis line that shows the high bg threshold + context.append('line') + .attr('class', 'high-line') + .attr('x1', xScale(dataRange[0])) + .attr('y1', yScale2(scaleBg(serverSettings.thresholds.bg_target_top))) + .attr('x2', xScale(dataRange[1])) + .attr('y2', yScale2(scaleBg(serverSettings.thresholds.bg_target_top))) + .style('stroke-dasharray', ('3, 3')) + .attr('stroke', 'grey'); + + // add a y-axis line that shows the low bg threshold + context.append('line') + .attr('class', 'low-line') + .attr('x1', xScale(dataRange[0])) + .attr('y1', yScale2(scaleBg(serverSettings.thresholds.bg_target_bottom))) + .attr('x2', xScale(dataRange[1])) + .attr('y2', yScale2(scaleBg(serverSettings.thresholds.bg_target_bottom))) + .style('stroke-dasharray', ('3, 3')) + .attr('stroke', 'grey'); + + } else { + + // for subsequent updates use a transition to animate the axis to the new position + var focusTransition = focus.transition().duration(UPDATE_TRANS_MS); + + focusTransition.select('.x') + .attr('transform', 'translate(0,' + focusHeight + ')') + .call(xAxis); + + focusTransition.select('.y') + .attr('transform', 'translate(' + chartWidth + ', 0)') + .call(yAxis); + + var contextTransition = context.transition().duration(UPDATE_TRANS_MS); + + contextTransition.select('.x') + .attr('transform', 'translate(0,' + chartHeight + ')') + .call(xAxis2); + + if (clip) { + // reset clip to new dimensions + clip.transition() + .attr('width', chartWidth) + .attr('height', chartHeight); + } + + // reset brush location + context.select('.x.brush') + .selectAll('rect') + .attr('y', focusHeight) + .attr('height', chartHeight - focusHeight); + + // clear current brush + d3.select('.brush').call(brush.clear()); + + // redraw old brush with new dimensions + d3.select('.brush').transition().duration(UPDATE_TRANS_MS).call(brush.extent(currentBrushExtent)); + + // transition lines to correct location + focus.select('.high-line') + .transition() + .duration(UPDATE_TRANS_MS) + .attr('x1', xScale(currentBrushExtent[0])) + .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_high))) + .attr('x2', xScale(currentBrushExtent[1])) + .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_high))); + + focus.select('.target-top-line') + .transition() + .duration(UPDATE_TRANS_MS) + .attr('x1', xScale(currentBrushExtent[0])) + .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_target_top))) + .attr('x2', xScale(currentBrushExtent[1])) + .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_target_top))); + + focus.select('.target-bottom-line') + .transition() + .duration(UPDATE_TRANS_MS) + .attr('x1', xScale(currentBrushExtent[0])) + .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_target_bottom))) + .attr('x2', xScale(currentBrushExtent[1])) + .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_target_bottom))); + + focus.select('.low-line') + .transition() + .duration(UPDATE_TRANS_MS) + .attr('x1', xScale(currentBrushExtent[0])) + .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_low))) + .attr('x2', xScale(currentBrushExtent[1])) + .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_low))); + + // transition open-top line to correct location + focus.select('.open-top') + .transition() + .duration(UPDATE_TRANS_MS) + .attr('x1', xScale2(currentBrushExtent[0])) + .attr('y1', yScale(scaleBg(30))) + .attr('x2', xScale2(currentBrushExtent[1])) + .attr('y2', yScale(scaleBg(30))); + + // transition open-left line to correct location + focus.select('.open-left') + .transition() + .duration(UPDATE_TRANS_MS) + .attr('x1', xScale2(currentBrushExtent[0])) + .attr('y1', focusHeight) + .attr('x2', xScale2(currentBrushExtent[0])) + .attr('y2', chartHeight); + + // transition open-right line to correct location + focus.select('.open-right') + .transition() + .duration(UPDATE_TRANS_MS) + .attr('x1', xScale2(currentBrushExtent[1])) + .attr('y1', focusHeight) + .attr('x2', xScale2(currentBrushExtent[1])) + .attr('y2', chartHeight); + + // transition high line to correct location + context.select('.high-line') + .transition() + .duration(UPDATE_TRANS_MS) + .attr('x1', xScale2(dataRange[0])) + .attr('y1', yScale2(scaleBg(serverSettings.thresholds.bg_target_top))) + .attr('x2', xScale2(dataRange[1])) + .attr('y2', yScale2(scaleBg(serverSettings.thresholds.bg_target_top))); + + // transition low line to correct location + context.select('.low-line') + .transition() + .duration(UPDATE_TRANS_MS) + .attr('x1', xScale2(dataRange[0])) + .attr('y1', yScale2(scaleBg(serverSettings.thresholds.bg_target_bottom))) + .attr('x2', xScale2(dataRange[1])) + .attr('y2', yScale2(scaleBg(serverSettings.thresholds.bg_target_bottom))); + } + } + + // update domain + xScale2.domain(dataRange); + + // only if a user brush is not active, update brush and focus chart with recent data + // else, just transition brush + var updateBrush = d3.select('.brush').transition().duration(UPDATE_TRANS_MS); + if (!brushInProgress) { + updateBrush + .call(brush.extent([new Date(dataRange[1].getTime() - foucusRangeMS), dataRange[1]])); + brushed(true); + } else { + updateBrush + .call(brush.extent([new Date(currentBrushExtent[1].getTime() - foucusRangeMS), currentBrushExtent[1]])); + brushed(true); + } + + // bind up the context chart data to an array of circles + var contextCircles = context.selectAll('circle') + .data(data); + + function prepareContextCircles(sel) { + var badData = []; + sel.attr('cx', function (d) { return xScale2(new Date(d.mills)); }) + .attr('cy', function (d) { + var scaled = sbx.scaleEntry(d); + if (isNaN(scaled)) { + badData.push(d); + return yScale2(scaleBg(450)); + } else { + return yScale2(scaled); + } + }) + .attr('fill', function (d) { return d.color; }) + .style('opacity', function (d) { return highlightBrushPoints(d) }) + .attr('stroke-width', function (d) { return d.type === 'mbg' ? 2 : 0; }) + .attr('stroke', function ( ) { return 'white'; }) + .attr('r', function (d) { return d.type === 'mbg' ? 4 : 2; }); + + if (badData.length > 0) { + console.warn('Bad Data: isNaN(sgv)', badData); + } + + return sel; + } + + // if already existing then transition each circle to its new position + prepareContextCircles(contextCircles.transition().duration(UPDATE_TRANS_MS)); + + // if new circle then just display + prepareContextCircles(contextCircles.enter().append('circle')); + + contextCircles.exit() + .remove(); + + // update x axis domain + context.select('.x') + .call(xAxis2); + + }, DEBOUNCE_MS); + + function sgvToColor(sgv) { + var color = 'grey'; + + if (browserSettings.theme === 'colors') { + if (sgv > serverSettings.thresholds.bg_high) { + color = 'red'; + } else if (sgv > serverSettings.thresholds.bg_target_top) { + color = 'yellow'; + } else if (sgv >= serverSettings.thresholds.bg_target_bottom && sgv <= serverSettings.thresholds.bg_target_top) { + color = '#4cff00'; + } else if (sgv < serverSettings.thresholds.bg_low) { + color = 'red'; + } else if (sgv < serverSettings.thresholds.bg_target_bottom) { + color = 'yellow'; + } + } + + return color; + } + + function sgvToColoredRange(sgv) { + var range = ''; + + if (browserSettings.theme === 'colors') { + if (sgv > serverSettings.thresholds.bg_high) { + range = 'urgent'; + } else if (sgv > serverSettings.thresholds.bg_target_top) { + range = 'warning'; + } else if (sgv >= serverSettings.thresholds.bg_target_bottom && sgv <= serverSettings.thresholds.bg_target_top) { + range = 'inrange'; + } else if (sgv < serverSettings.thresholds.bg_low) { + range = 'urgent'; + } else if (sgv < serverSettings.thresholds.bg_target_bottom) { + range = 'warning'; + } + } + + return range; + } + + + function generateAlarm(file, reason) { + alarmInProgress = true; + alarmMessage = reason && reason.title; + var selector = '.audio.alarms audio.' + file; + + if (!alarmingNow()) { + d3.select(selector).each(function () { + var audio = this; + playAlarm(audio); + $(this).addClass('playing'); + }); + } + + container.addClass('alarming').addClass(file === urgentAlarmSound ? 'urgent' : 'warning'); + + var skipPageTitle = isTimeAgoAlarmType(currentAlarmType); + updateTitle(skipPageTitle); + } + + function playAlarm(audio) { + // ?mute=true disables alarms to testers. + if (querystring.mute !== 'true') { + audio.play(); + } else { + showNotification('Alarm was muted (?mute=true)'); + } + } + + function stopAlarm(isClient, silenceTime) { + alarmInProgress = false; + alarmMessage = null; + container.removeClass('urgent warning'); + d3.selectAll('audio.playing').each(function () { + var audio = this; + audio.pause(); + $(this).removeClass('playing'); + }); + + closeNotification(); + container.removeClass('alarming'); + + updateTitle(); + + // only emit ack if client invoke by button press + if (isClient) { + if (isTimeAgoAlarmType(currentAlarmType)) { + container.removeClass('alarming-timeago'); + var alarm = getClientAlarm(currentAlarmType); + alarm.lastAckTime = Date.now(); + alarm.silenceTime = silenceTime; + } + socket.emit('ack', currentAlarmType || 'alarm', silenceTime); + } + + brushed(false); + } + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//draw a compact visualization of a treatment (carbs, insulin) +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + function drawTreatment(treatment, scale, showValues) { + + if (!treatment.carbs && !treatment.insulin) { return; } + + // don't render the treatment if it's not visible + if (Math.abs(xScale(new Date(treatment.mills))) > window.innerWidth) { return; } + + var CR = treatment.CR || 20; + var carbs = treatment.carbs || CR; + var insulin = treatment.insulin || 1; + + var R1 = Math.sqrt(Math.min(carbs, insulin * CR)) / scale, + R2 = Math.sqrt(Math.max(carbs, insulin * CR)) / scale, + R3 = R2 + 8 / scale; + + if (isNaN(R1) || isNaN(R3) || isNaN(R3)) { + console.warn('Bad Data: Found isNaN value in treatment', treatment); + return; + } + + var arc_data = [ + { 'element': '', 'color': 'white', 'start': -1.5708, 'end': 1.5708, 'inner': 0, 'outer': R1 }, + { 'element': '', 'color': 'transparent', 'start': -1.5708, 'end': 1.5708, 'inner': R2, 'outer': R3 }, + { 'element': '', 'color': '#0099ff', 'start': 1.5708, 'end': 4.7124, 'inner': 0, 'outer': R1 }, + { 'element': '', 'color': 'transparent', 'start': 1.5708, 'end': 4.7124, 'inner': R2, 'outer': R3 } + ]; + + 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'; + } + + if (treatment.insulin > 0) { + arc_data[3].element = Math.round(treatment.insulin * 100) / 100 + ' U'; + } + + var arc = d3.svg.arc() + .innerRadius(function (d) { return 5 * d.inner; }) + .outerRadius(function (d) { return 5 * d.outer; }) + .endAngle(function (d) { return d.start; }) + .startAngle(function (d) { return d.end; }); + + var treatmentDots = focus.selectAll('treatment-dot') + .data(arc_data) + .enter() + .append('g') + .attr('transform', 'translate(' + xScale(new Date(treatment.mills)) + ', ' + yScale(sbx.scaleEntry(treatment)) + ')') + .on('mouseover', function () { + tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); + tooltip.html(''+translate('Time')+': ' + formatTime(new Date(treatment.mills)) + '
    ' + ''+translate('Treatment type')+': ' + translate(treatment.eventType) + '
    ' + + (treatment.carbs ? ''+translate('Carbs')+': ' + treatment.carbs + '
    ' : '') + + (treatment.insulin ? ''+translate('Insulin')+': ' + treatment.insulin + '
    ' : '') + + (treatment.glucose ? ''+translate('BG')+': ' + treatment.glucose + (treatment.glucoseType ? ' (' + translate(treatment.glucoseType) + ')': '') + '
    ' : '') + + (treatment.enteredBy ? ''+translate('Entered by')+': ' + treatment.enteredBy + '
    ' : '') + + (treatment.notes ? ''+translate('Notes')+': ' + treatment.notes : '') + ) + .style('left', (d3.event.pageX) + 'px') + .style('top', (d3.event.pageY + 15) + 'px'); + }) + .on('mouseout', function () { + tooltip.transition() + .duration(TOOLTIP_TRANS_MS) + .style('opacity', 0); + }); + + treatmentDots.append('path') + .attr('class', 'path') + .attr('fill', function (d) { return d.outlineOnly ? 'transparent' : d.color; }) + .attr('stroke-width', function (d) { return d.outlineOnly ? 1 : 0; }) + .attr('stroke', function (d) { return d.color; }) + .attr('id', function (d, i) { return 's' + i; }) + .attr('d', arc); + + + // labels for carbs and insulin + if (showValues) { + var label = treatmentDots.append('g') + .attr('class', 'path') + .attr('id', 'label') + .style('fill', 'white'); + label.append('text') + .style('font-size', 40 / scale) + .style('text-shadow', '0px 0px 10px rgba(0, 0, 0, 1)') + .attr('text-anchor', 'middle') + .attr('dy', '.35em') + .attr('transform', function (d) { + d.outerRadius = d.outerRadius * 2.1; + d.innerRadius = d.outerRadius * 2.1; + return 'translate(' + arc.centroid(d) + ')'; + }) + .text(function (d) { return d.element; }); + } + } + + function updateClock() { + updateClockDisplay(); + var interval = (60 - (new Date()).getSeconds()) * 1000 + 5; + setTimeout(updateClock,interval); + + updateTimeAgo(); + + // Dim the screen by reducing the opacity when at nighttime + if (browserSettings.nightMode) { + var dateTime = new Date(); + if (opacity.current !== opacity.NIGHT && (dateTime.getHours() > 21 || dateTime.getHours() < 7)) { + $('body').css({ 'opacity': opacity.NIGHT }); + } else { + $('body').css({ 'opacity': opacity.DAY }); + } + } + } + + function updateClockDisplay() { + if (inRetroMode()) { + return; + } + now = Date.now(); + $('#currentTime').text(formatTime(new Date(now), true)).css('text-decoration', ''); + } + + function getClientAlarm(type) { + var alarm = clientAlarms[type]; + if (!alarm) { + alarm = { type: type }; + clientAlarms[type] = alarm; + } + return alarm; + } + + function isTimeAgoAlarmType(alarmType) { + return alarmType === 'warnTimeAgo' || alarmType === 'urgentTimeAgo'; + } + + function checkTimeAgoAlarm(ago) { + var level = ago.status + , alarm = getClientAlarm(level + 'TimeAgo'); + + if (Date.now() >= (alarm.lastAckTime || 0) + (alarm.silenceTime || 0)) { + currentAlarmType = alarm.type; + console.info('generating timeAgoAlarm', alarm.type); + container.addClass('alarming-timeago'); + var message = {'title': 'Last data received ' + [ago.value, ago.label].join(' ')}; + if (level === 'warn') { + generateAlarm(alarmSound, message); + } else { + generateAlarm(urgentAlarmSound, message); + } + } + } + + function updateTimeAgo() { + var lastEntry = $('#lastEntry') + , time = latestSGV ? latestSGV.mills : -1 + , ago = timeAgo(time, browserSettings) + , retroMode = inRetroMode(); + + lastEntry.removeClass('current warn urgent'); + lastEntry.addClass(ago.status); + + if (ago.status !== 'current') { + updateTitle(); + } + + if ( + (browserSettings.alarmTimeAgoWarn && ago.status === 'warn') + || (browserSettings.alarmTimeAgoUrgent && ago.status === 'urgent')) { + checkTimeAgoAlarm(ago); + } + + container.toggleClass('alarming-timeago', ago.status !== 'current'); + + if (alarmingNow() && ago.status === 'current' && isTimeAgoAlarmType(currentAlarmType)) { + stopAlarm(true, ONE_MIN_IN_MS); + } + + if (retroMode || !ago.value) { + lastEntry.find('em').hide(); + } else { + lastEntry.find('em').show().text(ago.value); + } + + if (retroMode || ago.label) { + lastEntry.find('label').show().text(retroMode ? 'RETRO' : ago.label); + } else { + lastEntry.find('label').hide(); + } + } + + tooltip = d3.select('body').append('div') + .attr('class', 'tooltip') + .style('opacity', 0); + + // Tick Values + if (browserSettings.units === 'mmol') { + tickValues = [ + 2.0 + , Math.round(scaleBg(serverSettings.thresholds.bg_low)) + , Math.round(scaleBg(serverSettings.thresholds.bg_target_bottom)) + , 6.0 + , Math.round(scaleBg(serverSettings.thresholds.bg_target_top)) + , Math.round(scaleBg(serverSettings.thresholds.bg_high)) + , 22.0 + ]; + } else { + tickValues = [ + 40 + , serverSettings.thresholds.bg_low + , serverSettings.thresholds.bg_target_bottom + , 120 + , serverSettings.thresholds.bg_target_top + , serverSettings.thresholds.bg_high + , 400 + ]; + } + + futureOpacity = d3.scale.linear( ) + .domain([TWENTY_FIVE_MINS_IN_MS, SIXTY_MINS_IN_MS]) + .range([0.8, 0.1]); + + // create svg and g to contain the chart contents + charts = d3.select('#chartContainer').append('svg') + .append('g') + .attr('class', 'chartContainer') + .attr('transform', 'translate(' + padding.left + ',' + padding.top + ')'); + + focus = charts.append('g'); + + // create the x axis container + focus.append('g') + .attr('class', 'x axis'); + + // create the y axis container + focus.append('g') + .attr('class', 'y axis'); + + context = charts.append('g'); + + // create the x axis container + context.append('g') + .attr('class', 'x axis'); + + // create the y axis container + context.append('g') + .attr('class', 'y axis'); + + //updateChart is _.debounce'd + function refreshChart(updateToNow) { + if (updateToNow) { + updateBrushToNow(); + } + updateChart(false); + } + + function visibilityChanged() { + var prevHidden = documentHidden; + documentHidden = (document.hidden || document.webkitHidden || document.mozHidden || document.msHidden); + + if (prevHidden && !documentHidden) { + console.info('Document now visible, updating - ' + (new Date())); + refreshChart(true); + } + } + + window.onresize = refreshChart; + + document.addEventListener('webkitvisibilitychange', visibilityChanged); + + + updateClock(); + + function Dropdown(el) { + this.ddmenuitem = 0; + + this.$el = $(el); + var that = this; + + $(document).click(function() { that.close(); }); + } + Dropdown.prototype.close = function () { + if (this.ddmenuitem) { + this.ddmenuitem.css('visibility', 'hidden'); + this.ddmenuitem = 0; + } + }; + Dropdown.prototype.open = function (e) { + this.close(); + this.ddmenuitem = $(this.$el).css('visibility', 'visible'); + e.stopPropagation(); + }; + + var silenceDropdown = new Dropdown('.dropdown-menu'); + + $('.bgButton').click(function (e) { + if (alarmingNow()) { + silenceDropdown.open(e); + } + }); + + $('#silenceBtn').find('a').click(function (e) { + stopAlarm(true, $(this).data('snooze-time')); + e.preventDefault(); + }); + + $('.focus-range li').click(function(e) { + var li = $(e.target); + $('.focus-range li').removeClass('selected'); + li.addClass('selected'); + var hours = Number(li.data('hours')); + foucusRangeMS = hours * 60 * 60 * 1000; + refreshChart(); + }); + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Client-side code to connect to server and handle incoming data + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + socket = io.connect(); + + function mergeDataUpdate(isDelta, cachedDataArray, receivedDataArray) { + + function nsArrayDiff(oldArray, newArray) { + var seen = {}; + var l = oldArray.length; + for (var i = 0; i < l; i++) { seen[oldArray[i].mills] = true } + var result = []; + l = newArray.length; + for (var j = 0; j < l; j++) { if (!seen.hasOwnProperty(newArray[j].mills)) { result.push(newArray[j]); console.log('delta data found'); } } + return result; + } + + // If there was no delta data, just return the original data + if (!receivedDataArray) { + return cachedDataArray; + } + + // If this is not a delta update, replace all data + if (!isDelta) { + return receivedDataArray; + } + + // If this is delta, calculate the difference, merge and sort + var diff = nsArrayDiff(cachedDataArray,receivedDataArray); + return cachedDataArray.concat(diff).sort(function(a, b) { + return a.mills - b.mills; + }); + } + + socket.on('dataUpdate', function receivedSGV(d) { + + if (!d) { + return; + } + + // Calculate the diff to existing data and replace as needed + + SGVdata = mergeDataUpdate(d.delta, SGVdata, d.sgvs); + MBGdata = mergeDataUpdate(d.delta,MBGdata, d.mbgs); + treatments = mergeDataUpdate(d.delta,treatments, d.treatments); + + if (d.profiles) { + Nightscout.profile.loadData(d.profiles); + } + + if (d.cals) { cal = d.cals[d.cals.length-1]; } + if (d.devicestatus) { devicestatusData = d.devicestatus; } + + // Do some reporting on the console + console.log('Total SGV data size', SGVdata.length); + console.log('Total treatment data size', treatments.length); + + // Post processing after data is in + + if (d.sgvs) { + // change the next line so that it uses the prediction if the signal gets lost (max 1/2 hr) + latestUpdateTime = Date.now(); + latestSGV = SGVdata[SGVdata.length - 1]; + prevSGV = SGVdata[SGVdata.length - 2]; + } + + var temp1 = [ ]; + if (cal && rawbg.isEnabled(sbx)) { + temp1 = SGVdata.map(function (entry) { + var rawbgValue = rawbg.showRawBGs(entry.mgdl, entry.noise, cal, sbx) ? rawbg.calc(entry, cal, sbx) : 0; + if (rawbgValue > 0) { + return { mills: entry.mills - 2000, mgdl: rawbgValue, color: 'white', type: 'rawbg' }; + } else { + return null; + } + }).filter(function(entry) { return entry !== null; }); + } + var temp2 = SGVdata.map(function (obj) { + return { mills: obj.mills, mgdl: obj.mgdl, direction: obj.direction, color: sgvToColor(obj.mgdl), type: 'sgv', noise: obj.noise, filtered: obj.filtered, unfiltered: obj.unfiltered}; + }); + data = []; + data = data.concat(temp1, temp2); + + addPlaceholderPoints(); + + data = data.concat(MBGdata.map(function (obj) { return { mills: obj.mills, mgdl: obj.mgdl, color: 'red', type: 'mbg', device: obj.device } })); + + data.forEach(function (d) { + if (d.mgdl < 39) { d.color = 'transparent'; } + }); + + updateTitle(); + if (!isInitialData) { + isInitialData = true; + initializeCharts(); + } + else { + updateChart(false); + } + + }); + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Alarms and Text handling + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + socket.on('connect', function () { + console.log('Client connected to server.'); + }); + + + //with predicted alarms, latestSGV may still be in target so to see if the alarm + // is for a HIGH we can only check if it's >= the bottom of the target + function isAlarmForHigh() { + return latestSGV.mgdl >= serverSettings.thresholds.bg_target_bottom; + } + + //with predicted alarms, latestSGV may still be in target so to see if the alarm + // is for a LOW we can only check if it's <= the top of the target + function isAlarmForLow() { + return latestSGV.mgdl <= serverSettings.thresholds.bg_target_top; + } + + socket.on('alarm', function (notify) { + console.info('alarm received from server'); + console.log('notify:',notify); + var enabled = (isAlarmForHigh() && browserSettings.alarmHigh) || (isAlarmForLow() && browserSettings.alarmLow); + if (enabled) { + console.log('Alarm raised!'); + currentAlarmType = 'alarm'; + generateAlarm(alarmSound,notify); + } else { + console.info('alarm was disabled locally', latestSGV.mgdl, browserSettings); + } + brushInProgress = false; + updateChart(false); + }); + socket.on('urgent_alarm', function (notify) { + console.info('urgent alarm received from server'); + console.log('notify:',notify); + + var enabled = (isAlarmForHigh() && browserSettings.alarmUrgentHigh) || (isAlarmForLow() && browserSettings.alarmUrgentLow); + if (enabled) { + console.log('Urgent alarm raised!'); + currentAlarmType = 'urgent_alarm'; + generateAlarm(urgentAlarmSound,notify); + } else { + console.info('urgent alarm was disabled locally', latestSGV.mgdl, browserSettings); + } + brushInProgress = false; + updateChart(false); + }); + socket.on('clear_alarm', function () { + if (alarmInProgress) { + console.log('clearing alarm'); + stopAlarm(); + } + }); + + + $('#testAlarms').click(function(event) { + d3.selectAll('.audio.alarms audio').each(function () { + var audio = this; + playAlarm(audio); + setTimeout(function() { + audio.pause(); + }, 4000); + }); + event.preventDefault(); + }); + + if (serverSettings.enabledOptions.indexOf('ar2') < 0) { + serverSettings.enabledOptions += ' ar2'; + } + + $('.appName').text(serverSettings.name); + $('.version').text(serverSettings.version); + $('.head').text(serverSettings.head); + if (serverSettings.apiEnabled) { + $('.serverSettings').show(); + } + $('#treatmentDrawerToggle').toggle(serverSettings.careportalEnabled); + plugins.init(serverSettings); + sbx = sandbox.clientInit(serverSettings, browserSettings, Date.now()); + $('.container').toggleClass('has-minor-pills', plugins.hasShownType('pill-minor', browserSettings)); + + function showLocalstorageError() { + var msg = 'Settings are disabled.

    Please enable cookies so you may customize your Nightscout site.'; + $('.browserSettings').html('Settings'+msg+''); + $('#save').hide(); + } + + function getBrowserSettings() { + var json = {}; + var storage = $.localStorage; + + function scaleBg(bg) { + if (json.units === 'mmol') { + return units.mgdlToMMOL(bg); + } else { + return bg; + } + } + + function appendThresholdValue(threshold) { + return serverSettings.alarm_types.indexOf('simple') === -1 ? '' : ' (' + scaleBg(threshold) + ')'; + } + + try { + json = { + 'units': storage.get('units'), + 'alarmUrgentHigh': storage.get('alarmUrgentHigh'), + 'alarmHigh': storage.get('alarmHigh'), + 'alarmLow': storage.get('alarmLow'), + 'alarmUrgentLow': storage.get('alarmUrgentLow'), + 'alarmTimeAgoWarn': storage.get('alarmTimeAgoWarn'), + 'alarmTimeAgoWarnMins': storage.get('alarmTimeAgoWarnMins'), + 'alarmTimeAgoUrgent': storage.get('alarmTimeAgoUrgent'), + 'alarmTimeAgoUrgentMins': storage.get('alarmTimeAgoUrgentMins'), + 'nightMode': storage.get('nightMode'), + 'showRawbg': storage.get('showRawbg'), + 'customTitle': storage.get('customTitle'), + 'theme': storage.get('theme'), + 'timeFormat': storage.get('timeFormat'), + 'showPlugins': storage.get('showPlugins') + }; + + // Default browser units to server units if undefined. + json.units = setDefault(json.units, serverSettings.units); + if (json.units === 'mmol') { + $('#mmol-browser').prop('checked', true); + } else { + $('#mgdl-browser').prop('checked', true); + } + + json.alarmUrgentHigh = setDefault(json.alarmUrgentHigh, serverSettings.defaults.alarmUrgentHigh); + json.alarmHigh = setDefault(json.alarmHigh, serverSettings.defaults.alarmHigh); + json.alarmLow = setDefault(json.alarmLow, serverSettings.defaults.alarmLow); + json.alarmUrgentLow = setDefault(json.alarmUrgentLow, serverSettings.defaults.alarmUrgentLow); + json.alarmTimeAgoWarn = setDefault(json.alarmTimeAgoWarn, serverSettings.defaults.alarmTimeAgoWarn); + json.alarmTimeAgoWarnMins = setDefault(json.alarmTimeAgoWarnMins, serverSettings.defaults.alarmTimeAgoWarnMins); + json.alarmTimeAgoUrgent = setDefault(json.alarmTimeAgoUrgent, serverSettings.defaults.alarmTimeAgoUrgent); + json.alarmTimeAgoUrgentMins = setDefault(json.alarmTimeAgoUrgentMins, serverSettings.defaults.alarmTimeAgoUrgentMins); + $('#alarm-urgenthigh-browser').prop('checked', json.alarmUrgentHigh).next().text('Urgent High Alarm' + appendThresholdValue(serverSettings.thresholds.bg_high)); + $('#alarm-high-browser').prop('checked', json.alarmHigh).next().text('High Alarm' + appendThresholdValue(serverSettings.thresholds.bg_target_top)); + $('#alarm-low-browser').prop('checked', json.alarmLow).next().text('Low Alarm' + appendThresholdValue(serverSettings.thresholds.bg_target_bottom)); + $('#alarm-urgentlow-browser').prop('checked', json.alarmUrgentLow).next().text('Urgent Low Alarm' + appendThresholdValue(serverSettings.thresholds.bg_low)); + $('#alarm-timeagowarn-browser').prop('checked', json.alarmTimeAgoWarn); + $('#alarm-timeagowarnmins-browser').val(json.alarmTimeAgoWarnMins); + $('#alarm-timeagourgent-browser').prop('checked', json.alarmTimeAgoUrgent); + $('#alarm-timeagourgentmins-browser').val(json.alarmTimeAgoUrgentMins); + + json.nightMode = setDefault(json.nightMode, serverSettings.defaults.nightMode); + $('#nightmode-browser').prop('checked', json.nightMode); + + if (rawBGsEnabled()) { + $('#show-rawbg-option').show(); + json.showRawbg = setDefault(json.showRawbg, serverSettings.defaults.showRawbg); + $('#show-rawbg-' + json.showRawbg).prop('checked', true); + } else { + json.showRawbg = 'never'; + $('#show-rawbg-option').hide(); + } + + json.customTitle = setDefault(json.customTitle, serverSettings.defaults.customTitle); + $('h1.customTitle').text(json.customTitle); + $('input#customTitle').prop('value', json.customTitle); + + json.theme = setDefault(json.theme, serverSettings.defaults.theme); + if (json.theme === 'colors') { + $('#theme-colors-browser').prop('checked', true); + } else { + $('#theme-default-browser').prop('checked', true); + } + + json.timeFormat = setDefault(json.timeFormat, serverSettings.defaults.timeFormat); + + if (json.timeFormat === '24') { + $('#24-browser').prop('checked', true); + } else { + $('#12-browser').prop('checked', true); + } + + json.showPlugins = setDefault(json.showPlugins, serverSettings.defaults.showPlugins || plugins.enabledPluginNames()); + var showPluginsSettings = $('#show-plugins'); + plugins.eachEnabledPlugin(function each(plugin) { + if (plugins.specialPlugins.indexOf(plugin.name) > -1) { + //ignore these, they are always on for now + } else { + var id = 'plugin-' + plugin.name; + var dd = $('
    '); + showPluginsSettings.append(dd); + dd.find('input').prop('checked', json.showPlugins.indexOf(plugin.name) > -1); + } + }); + + + } catch(err) { + console.error(err); + showLocalstorageError(); + } + + return json; + } +} + +module.exports = init; \ No newline at end of file diff --git a/package.json b/package.json index ef3cc1796e2..ab60c6a9494 100644 --- a/package.json +++ b/package.json @@ -69,13 +69,15 @@ "pushover-notifications": "0.2.0", "request": "^2.58.0", "sgvdata": "git://github.com/ktind/sgvdata.git#wip/protobuf", - "socket.io": "^1.3.5" + "socket.io": "^1.3.5", + "d3": "^3.5.6" }, "devDependencies": { "istanbul": "~0.3.5", "mocha": "~1.20.1", "should": "~4.0.4", "supertest": "~0.13.0", - "jsdom": "^3.1.2" + "jsdom": "^3.1.2", + "benv": "^1.1.0" } } diff --git a/static/js/client.js b/static/js/client.js index 76059967aa3..e749fbd132e 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1,1574 +1,3 @@ -//TODO: clean up -var browserSettings = {}, browserStorage = $.localStorage; +'use strict'; -(function () { - 'use strict'; - - var BRUSH_TIMEOUT = 300000 // 5 minutes in ms - , DEBOUNCE_MS = 10 - , TOOLTIP_TRANS_MS = 200 // milliseconds - , UPDATE_TRANS_MS = 750 // milliseconds - , ONE_MIN_IN_MS = 60000 - , FIVE_MINS_IN_MS = 300000 - , THREE_HOURS_MS = 3 * 60 * 60 * 1000 - , TWENTY_FIVE_MINS_IN_MS = 1500000 - , THIRTY_MINS_IN_MS = 1800000 - , SIXTY_MINS_IN_MS = 3600000 - , FORMAT_TIME_12 = '%-I:%M %p' - , FORMAT_TIME_12_COMAPCT = '%-I:%M' - , FORMAT_TIME_24 = '%H:%M%' - , FORMAT_TIME_12_SCALE = '%-I %p' - , FORMAT_TIME_24_SCALE = '%H' - , WIDTH_SMALL_DOTS = 400 - , WIDTH_BIG_DOTS = 800; - - var socket - , isInitialData = false - , SGVdata = [] - , MBGdata = [] - , latestSGV - , latestUpdateTime - , prevSGV - , treatments - , profile - , cal - , devicestatusData - , padding = { top: 0, right: 10, bottom: 30, left: 10 } - , opacity = {current: 1, DAY: 1, NIGHT: 0.5} - , now = Date.now() - , data = [] - , foucusRangeMS = THREE_HOURS_MS - , clientAlarms = {} - , alarmInProgress = false - , alarmMessage - , currentAlarmType = null - , alarmSound = 'alarm.mp3' - , urgentAlarmSound = 'alarm2.mp3'; - - var sbx - , rawbg = Nightscout.plugins('rawbg') - , delta = Nightscout.plugins('delta') - , direction = Nightscout.plugins('direction') - , errorcodes = Nightscout.plugins('errorcodes') - , translate = Nightscout.language.translate - , timeAgo = Nightscout.utils.timeAgo; - - var jqWindow - , tooltip - , tickValues - , charts - , futureOpacity - , focus - , context - , xScale, xScale2, yScale, yScale2 - , xAxis, yAxis, xAxis2, yAxis2 - , prevChartWidth = 0 - , prevChartHeight = 0 - , focusHeight - , contextHeight - , dateFn = function (d) { return new Date(d.mills) } - , documentHidden = false - , brush - , brushTimer - , brushInProgress = false - , clip; - - var container = $('.container') - , bgStatus = $('.bgStatus') - , currentBG = $('.bgStatus .currentBG') - , majorPills = $('.bgStatus .majorPills') - , minorPills = $('.bgStatus .minorPills') - , statusPills = $('.status .statusPills') - ; - - Nightscout.language.set(serverSettings.defaults.language).DOMtranslate(); - - function formatTime(time, compact) { - var timeFormat = getTimeFormat(false, compact); - time = d3.time.format(timeFormat)(time); - if (!isTimeFormat24()) { - time = time.toLowerCase(); - } - return time; - } - - function isTimeFormat24() { - return browserSettings && browserSettings.timeFormat && parseInt(browserSettings.timeFormat) === 24; - } - - function getTimeFormat(isForScale, compact) { - var timeFormat = FORMAT_TIME_12; - if (isTimeFormat24()) { - timeFormat = isForScale ? FORMAT_TIME_24_SCALE : FORMAT_TIME_24; - } else { - timeFormat = isForScale ? FORMAT_TIME_12_SCALE : (compact ? FORMAT_TIME_12_COMAPCT : FORMAT_TIME_12); - } - - return timeFormat; - } - - // lixgbg: Convert mg/dL BG value to metric mmol - function scaleBg(bg) { - if (browserSettings.units === 'mmol') { - return Nightscout.units.mgdlToMMOL(bg); - } else { - return bg; - } - } - - function generateTitle() { - - function s(value, sep) { return value ? value + ' ' : sep || ''; } - - var bg_title = ''; - - var time = latestSGV ? latestSGV.mills : (prevSGV ? prevSGV.mills : -1) - , ago = timeAgo(time, browserSettings); - - if (browserSettings.customTitle) { - $('.customTitle').text(browserSettings.customTitle); - } - - if (ago && ago.status !== 'current') { - bg_title = s(ago.value) + s(ago.label, ' - ') + bg_title; - } else if (latestSGV) { - var currentMgdl = latestSGV.mgdl; - - if (currentMgdl < 39) { - bg_title = s(errorcodes.toDisplay(currentMgdl), ' - ') + bg_title; - } else { - var deltaDisplay = delta.calc(prevSGV, latestSGV, sbx).display; - bg_title = s(scaleBg(currentMgdl)) + s(deltaDisplay) + s(direction.info(latestSGV).label) + bg_title; - } - } - return bg_title; - } - - function updateTitle(skipPageTitle) { - - var bg_title = browserSettings.customTitle || ''; - - if (alarmMessage && alarmInProgress) { - bg_title = alarmMessage + ': ' + generateTitle(); - $('.customTitle').text(alarmMessage); - } else { - bg_title = generateTitle(); - } - - if (!skipPageTitle) { - $(document).attr('title', bg_title); - } - } - - // initial setup of chart when data is first made available - function initializeCharts() { - - // define the parts of the axis that aren't dependent on width or height - xScale = d3.time.scale() - .domain(d3.extent(data, dateFn)); - - yScale = d3.scale.log() - .domain([scaleBg(30), scaleBg(510)]); - - xScale2 = d3.time.scale() - .domain(d3.extent(data, dateFn)); - - yScale2 = d3.scale.log() - .domain([scaleBg(36), scaleBg(420)]); - - var tickFormat = d3.time.format.multi( [ - ['.%L', function(d) { return d.getMilliseconds(); }], - [':%S', function(d) { return d.getSeconds(); }], - ['%I:%M', function(d) { return d.getMinutes(); }], - [isTimeFormat24() ? '%H:%M' : '%-I %p', function(d) { return d.getHours(); }], - ['%a %d', function(d) { return d.getDay() && d.getDate() !== 1; }], - ['%b %d', function(d) { return d.getDate() !== 1; }], - ['%B', function(d) { return d.getMonth(); }], - ['%Y', function() { return true; }] - ]); - - xAxis = d3.svg.axis() - .scale(xScale) - .tickFormat(tickFormat) - .ticks(4) - .orient('bottom'); - - yAxis = d3.svg.axis() - .scale(yScale) - .tickFormat(d3.format('d')) - .tickValues(tickValues) - .orient('left'); - - xAxis2 = d3.svg.axis() - .scale(xScale2) - .tickFormat(tickFormat) - .ticks(6) - .orient('bottom'); - - yAxis2 = d3.svg.axis() - .scale(yScale2) - .tickFormat(d3.format('d')) - .tickValues(tickValues) - .orient('right'); - - // setup a brush - brush = d3.svg.brush() - .x(xScale2) - .on('brushstart', brushStarted) - .on('brush', brushed) - .on('brushend', brushEnded); - - updateChart(true); - } - - // get the desired opacity for context chart based on the brush extent - function highlightBrushPoints(data) { - if (data.mills >= brush.extent()[0].getTime() && data.mills <= brush.extent()[1].getTime()) { - return futureOpacity(data.mills - latestSGV.mills); - } else { - return 0.5; - } - } - - function addPlaceholderPoints () { - // TODO: This is a kludge to advance the time as data becomes stale by making old predictor clear (using color = 'none') - // This shouldn't need to be generated and can be fixed by using xScale.domain([x0,x1]) function with - // 2 days before now as x0 and 30 minutes from now for x1 for context plot, but this will be - // required to happen when 'now' event is sent from websocket.js every minute. When fixed, - // remove this code and all references to `type: 'server-forecast'` - var last = _.last(data); - var lastTime = last && last.mills; - if (!lastTime) { - console.error('Bad Data, last point has no mills', last); - lastTime = Date.now(); - } - - var n = Math.ceil(12 * (1 / 2 + (now - lastTime) / SIXTY_MINS_IN_MS)) + 1; - for (var i = 1; i <= n; i++) { - data.push({ - mills: lastTime + (i * FIVE_MINS_IN_MS), mgdl: 100, color: 'none', type: 'server-forecast' - }); - } - } - - // clears the current user brush and resets to the current real time data - function updateBrushToNow(skipBrushing) { - - // get current time range - var dataRange = d3.extent(data, dateFn); - - // update brush and focus chart with recent data - d3.select('.brush') - .transition() - .duration(UPDATE_TRANS_MS) - .call(brush.extent([new Date(dataRange[1].getTime() - foucusRangeMS), dataRange[1]])); - - addPlaceholderPoints(); - - if (!skipBrushing) { - brushed(true); - - // clear user brush tracking - brushInProgress = false; - } - } - - function brushStarted() { - // update the opacity of the context data points to brush extent - context.selectAll('circle') - .data(data) - .style('opacity', 1); - } - - function brushEnded() { - // update the opacity of the context data points to brush extent - context.selectAll('circle') - .data(data) - .style('opacity', function (d) { return highlightBrushPoints(d) }); - } - - function alarmingNow() { - return container.hasClass('alarming'); - } - - function inRetroMode() { - if (!brush) { - return false; - } - - var time = brush.extent()[1].getTime(); - - return !alarmingNow() && time - TWENTY_FIVE_MINS_IN_MS < now; - } - - function brushed(skipTimer) { - - if (!skipTimer) { - // set a timer to reset focus chart to real-time data - clearTimeout(brushTimer); - brushTimer = setTimeout(updateBrushToNow, BRUSH_TIMEOUT); - brushInProgress = true; - } - - var brushExtent = brush.extent(); - - // ensure that brush extent is fixed at 3.5 hours - if (brushExtent[1].getTime() - brushExtent[0].getTime() !== foucusRangeMS) { - // ensure that brush updating is with the time range - if (brushExtent[0].getTime() + foucusRangeMS > d3.extent(data, dateFn)[1].getTime()) { - brushExtent[0] = new Date(brushExtent[1].getTime() - foucusRangeMS); - d3.select('.brush') - .call(brush.extent([brushExtent[0], brushExtent[1]])); - } else { - brushExtent[1] = new Date(brushExtent[0].getTime() + foucusRangeMS); - d3.select('.brush') - .call(brush.extent([brushExtent[0], brushExtent[1]])); - } - } - - var nowDate = new Date(brushExtent[1] - THIRTY_MINS_IN_MS); - - function updateCurrentSGV(entry) { - var value = entry.mgdl - , ago = timeAgo(entry.mills, browserSettings) - , isCurrent = ago.status === 'current'; - - if (value === 9) { - currentBG.text(''); - } else if (value < 39) { - currentBG.html(errorcodes.toDisplay(value)); - } else if (value < 40) { - currentBG.text('LOW'); - } else if (value > 400) { - currentBG.text('HIGH'); - } else { - currentBG.text(scaleBg(value)); - } - - bgStatus.toggleClass('current', alarmingNow() || (isCurrent && !inRetroMode())); - if (!alarmingNow()) { - container.removeClass('urgent warning inrange'); - if (isCurrent && !inRetroMode()) { - container.addClass(sgvToColoredRange(value)); - } - } - - currentBG.toggleClass('icon-hourglass', value === 9); - currentBG.toggleClass('error-code', value < 39); - currentBG.toggleClass('bg-limit', value === 39 || value > 400); - - $('.container').removeClass('loading'); - - } - - function updatePlugins (sgvs, time) { - - var pluginBase = Nightscout.plugins.base(majorPills, minorPills, statusPills, bgStatus, tooltip); - - sbx = Nightscout.sandbox.clientInit(serverSettings, browserSettings, time, pluginBase, { - sgvs: sgvs - , cals: [cal] - , treatments: treatments - , profile: Nightscout.profile - , uploaderBattery: devicestatusData && devicestatusData.uploaderBattery - }); - - //all enabled plugins get a chance to set properties, even if they aren't shown - Nightscout.plugins.setProperties(sbx); - - //only shown plugins get a chance to update visualisations - Nightscout.plugins.updateVisualisations(sbx); - } - - var nowData = data.filter(function(d) { - return d.type === 'sgv'; - }); - - if (inRetroMode()) { - var retroTime = brushExtent[1].getTime() - THIRTY_MINS_IN_MS; - - nowData = nowData.filter(function(d) { - return d.mills >= brushExtent[1].getTime() - (2 * THIRTY_MINS_IN_MS) && - d.mills <= brushExtent[1].getTime() - TWENTY_FIVE_MINS_IN_MS; - }); - - // sometimes nowData contains duplicates. uniq it. - var lastDate = 0; - nowData = nowData.filter(function(d) { - var ok = lastDate + ONE_MIN_IN_MS < d.mills; - lastDate = d.mills; - return ok; - }); - - var focusPoint = nowData.length > 0 ? nowData[nowData.length - 1] : null; - if (focusPoint) { - updateCurrentSGV(focusPoint); - } else { - currentBG.text('---'); - container.removeClass('urgent warning inrange'); - } - - updatePlugins(nowData, retroTime); - - $('#currentTime') - .text(formatTime(new Date(retroTime), true)) - .css('text-decoration','line-through'); - - updateTimeAgo(); - } else { - // if the brush comes back into the current time range then it should reset to the current time and sg - nowData = nowData.slice(nowData.length - 2, nowData.length); - nowDate = now; - - updateCurrentSGV(latestSGV); - updateClockDisplay(); - updateTimeAgo(); - updatePlugins(nowData, nowDate); - - } - - xScale.domain(brush.extent()); - - // get slice of data so that concatenation of predictions do not interfere with subsequent updates - var focusData = data.slice(); - - if (sbx.pluginBase.forecastPoints) { - focusData = focusData.concat(sbx.pluginBase.forecastPoints); - } - - // bind up the focus chart data to an array of circles - // selects all our data into data and uses date function to get current max date - var focusCircles = focus.selectAll('circle').data(focusData, dateFn); - - var focusRangeAdjustment = foucusRangeMS === THREE_HOURS_MS ? 1 : 1 + ((foucusRangeMS - THREE_HOURS_MS) / THREE_HOURS_MS / 8); - - var dotRadius = function(type) { - var radius = prevChartWidth > WIDTH_BIG_DOTS ? 4 : (prevChartWidth < WIDTH_SMALL_DOTS ? 2 : 3); - if (type === 'mbg') { - radius *= 2; - } else if (type === 'forecast') { - radius = Math.min(3, radius - 1); - } else if (type === 'rawbg') { - radius = Math.min(2, radius - 1); - } - - return radius / focusRangeAdjustment; - }; - - function isDexcom(device) { - return device && device.toLowerCase().indexOf('dexcom') === 0; - } - - function prepareFocusCircles(sel) { - var badData = []; - sel.attr('cx', function (d) { - if (!d) { - console.error('Bad data', d); - return xScale(new Date(0)); - } else if (!d.mills) { - console.error('Bad data, no mills', d); - return xScale(new Date(0)); - } else { - return xScale(new Date(d.mills)); - } - }) - .attr('cy', function (d) { - var scaled = sbx.scaleEntry(d); - if (isNaN(scaled)) { - badData.push(d); - return yScale(scaleBg(450)); - } else { - return yScale(scaled); - } - }) - .attr('fill', function (d) { return d.type === 'forecast' ? 'none' : d.color; }) - .attr('opacity', function (d) { return futureOpacity(d.mills - latestSGV.mills); }) - .attr('stroke-width', function (d) { return d.type === 'mbg' ? 2 : d.type === 'forecast' ? 1 : 0; }) - .attr('stroke', function (d) { - return (isDexcom(d.device) ? 'white' : d.type === 'forecast' ? d.color : '#0099ff'); - }) - .attr('r', function (d) { return dotRadius(d.type); }); - - if (badData.length > 0) { - console.warn('Bad Data: isNaN(sgv)', badData); - } - - return sel; - } - - // if already existing then transition each circle to its new position - prepareFocusCircles(focusCircles.transition().duration(UPDATE_TRANS_MS)); - - // if new circle then just display - prepareFocusCircles(focusCircles.enter().append('circle')) - .on('mouseover', function (d) { - if (d.type === 'sgv' || d.type === 'mbg') { - var bgType = (d.type === 'sgv' ? 'CGM' : (isDexcom(d.device) ? 'Calibration' : 'Meter')) - , rawbgValue = 0 - , noiseLabel = ''; - - if (d.type === 'sgv') { - if (rawbg.showRawBGs(d.mgdl, d.noise, cal, sbx)) { - rawbgValue = scaleBg(rawbg.calc(d, cal, sbx)); - } - noiseLabel = rawbg.noiseCodeToDisplay(d.mgdl, d.noise); - } - - tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); - tooltip.html('' + bgType + ' BG: ' + sbx.scaleEntry(d) + - (d.type === 'mbg' ? '
    Device: ' + d.device : '') + - (rawbgValue ? '
    Raw BG: ' + rawbgValue : '') + - (noiseLabel ? '
    Noise: ' + noiseLabel : '') + - '
    Time: ' + formatTime(new Date(d.mills))) - .style('left', (d3.event.pageX) + 'px') - .style('top', (d3.event.pageY + 15) + 'px'); - } - }) - .on('mouseout', function (d) { - if (d.type === 'sgv' || d.type === 'mbg') { - tooltip.transition() - .duration(TOOLTIP_TRANS_MS) - .style('opacity', 0); - } - }); - - focusCircles.exit() - .remove(); - - // remove all insulin/carb treatment bubbles so that they can be redrawn to correct location - d3.selectAll('.path').remove(); - - // add treatment bubbles - // a higher bubbleScale will produce smaller bubbles (it's not a radius like focusDotRadius) - var bubbleScale = (prevChartWidth < WIDTH_SMALL_DOTS ? 4 : (prevChartWidth < WIDTH_BIG_DOTS ? 3 : 2)) * focusRangeAdjustment; - - focus.selectAll('circle') - .data(treatments) - .each(function (d) { drawTreatment(d, bubbleScale, true) }); - - // transition open-top line to correct location - focus.select('.open-top') - .attr('x1', xScale2(brush.extent()[0])) - .attr('y1', yScale(scaleBg(30))) - .attr('x2', xScale2(brush.extent()[1])) - .attr('y2', yScale(scaleBg(30))); - - // transition open-left line to correct location - focus.select('.open-left') - .attr('x1', xScale2(brush.extent()[0])) - .attr('y1', focusHeight) - .attr('x2', xScale2(brush.extent()[0])) - .attr('y2', prevChartHeight); - - // transition open-right line to correct location - focus.select('.open-right') - .attr('x1', xScale2(brush.extent()[1])) - .attr('y1', focusHeight) - .attr('x2', xScale2(brush.extent()[1])) - .attr('y2', prevChartHeight); - - focus.select('.now-line') - .transition() - .duration(UPDATE_TRANS_MS) - .attr('x1', xScale(nowDate)) - .attr('y1', yScale(scaleBg(36))) - .attr('x2', xScale(nowDate)) - .attr('y2', yScale(scaleBg(420))); - - context.select('.now-line') - .transition() - .attr('x1', xScale2(brush.extent()[1]- THIRTY_MINS_IN_MS)) - .attr('y1', yScale2(scaleBg(36))) - .attr('x2', xScale2(brush.extent()[1]- THIRTY_MINS_IN_MS)) - .attr('y2', yScale2(scaleBg(420))); - - // update x axis - focus.select('.x.axis') - .call(xAxis); - - // add clipping path so that data stays within axis - focusCircles.attr('clip-path', 'url(#clip)'); - - function prepareTreatCircles(sel) { - sel.attr('cx', function (d) { return xScale(new Date(d.mills)); }) - .attr('cy', function (d) { return yScale(sbx.scaleEntry(d)); }) - .attr('r', function () { return dotRadius('mbg'); }) - .attr('stroke-width', 2) - .attr('stroke', function (d) { return d.glucose ? 'grey' : 'white'; }) - .attr('fill', function (d) { return d.glucose ? 'red' : 'grey'; }); - - return sel; - } - - //NOTE: treatments with insulin or carbs are drawn by drawTreatment() - // bind up the focus chart data to an array of circles - var treatCircles = focus.selectAll('rect').data(treatments.filter(function(treatment) { - return !treatment.carbs && !treatment.insulin; - })); - - // if already existing then transition each circle to its new position - prepareTreatCircles(treatCircles.transition().duration(UPDATE_TRANS_MS)); - - // if new circle then just display - prepareTreatCircles(treatCircles.enter().append('circle')) - .on('mouseover', function (d) { - tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); - tooltip.html(''+translate('Time')+': ' + formatTime(new Date(d.mills)) + '
    ' + - (d.eventType ? ''+translate('Treatment type')+': ' + d.eventType + '
    ' : '') + - (d.glucose ? ''+translate('BG')+': ' + d.glucose + (d.glucoseType ? ' (' + translate(d.glucoseType) + ')': '') + '
    ' : '') + - (d.enteredBy ? ''+translate('Entered by')+': ' + d.enteredBy + '
    ' : '') + - (d.notes ? ''+translate('Notes')+': ' + d.notes : '') - ) - .style('left', (d3.event.pageX) + 'px') - .style('top', (d3.event.pageY + 15) + 'px'); - }) - .on('mouseout', function () { - tooltip.transition() - .duration(TOOLTIP_TRANS_MS) - .style('opacity', 0); - }); - - treatCircles.attr('clip-path', 'url(#clip)'); - } - - // called for initial update and updates for resize - var updateChart = _.debounce(function debouncedUpdateChart(init) { - - if (documentHidden && !init) { - console.info('Document Hidden, not updating - ' + (new Date())); - return; - } - // get current data range - var dataRange = d3.extent(data, dateFn); - - // get the entire container height and width subtracting the padding - var chartWidth = (document.getElementById('chartContainer') - .getBoundingClientRect().width) - padding.left - padding.right; - - var chartHeight = (document.getElementById('chartContainer') - .getBoundingClientRect().height) - padding.top - padding.bottom; - - // get the height of each chart based on its container size ratio - focusHeight = chartHeight * .7; - contextHeight = chartHeight * .2; - - // get current brush extent - var currentBrushExtent = brush.extent(); - - // only redraw chart if chart size has changed - if ((prevChartWidth !== chartWidth) || (prevChartHeight !== chartHeight)) { - - prevChartWidth = chartWidth; - prevChartHeight = chartHeight; - - //set the width and height of the SVG element - charts.attr('width', chartWidth + padding.left + padding.right) - .attr('height', chartHeight + padding.top + padding.bottom); - - // ranges are based on the width and height available so reset - xScale.range([0, chartWidth]); - xScale2.range([0, chartWidth]); - yScale.range([focusHeight, 0]); - yScale2.range([chartHeight, chartHeight - contextHeight]); - - if (init) { - - // if first run then just display axis with no transition - focus.select('.x') - .attr('transform', 'translate(0,' + focusHeight + ')') - .call(xAxis); - - focus.select('.y') - .attr('transform', 'translate(' + chartWidth + ',0)') - .call(yAxis); - - // if first run then just display axis with no transition - context.select('.x') - .attr('transform', 'translate(0,' + chartHeight + ')') - .call(xAxis2); - - context.append('g') - .attr('class', 'x brush') - .call(d3.svg.brush().x(xScale2).on('brush', brushed)) - .selectAll('rect') - .attr('y', focusHeight) - .attr('height', chartHeight - focusHeight); - - // disable resizing of brush - d3.select('.x.brush').select('.background').style('cursor', 'move'); - d3.select('.x.brush').select('.resize.e').style('cursor', 'move'); - d3.select('.x.brush').select('.resize.w').style('cursor', 'move'); - - // create a clipPath for when brushing - clip = charts.append('defs') - .append('clipPath') - .attr('id', 'clip') - .append('rect') - .attr('height', chartHeight) - .attr('width', chartWidth); - - // add a line that marks the current time - focus.append('line') - .attr('class', 'now-line') - .attr('x1', xScale(new Date(now))) - .attr('y1', yScale(scaleBg(30))) - .attr('x2', xScale(new Date(now))) - .attr('y2', yScale(scaleBg(420))) - .style('stroke-dasharray', ('3, 3')) - .attr('stroke', 'grey'); - - // add a y-axis line that shows the high bg threshold - focus.append('line') - .attr('class', 'high-line') - .attr('x1', xScale(dataRange[0])) - .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_high))) - .attr('x2', xScale(dataRange[1])) - .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_high))) - .style('stroke-dasharray', ('1, 6')) - .attr('stroke', '#777'); - - // add a y-axis line that shows the high bg threshold - focus.append('line') - .attr('class', 'target-top-line') - .attr('x1', xScale(dataRange[0])) - .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_target_top))) - .attr('x2', xScale(dataRange[1])) - .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_target_top))) - .style('stroke-dasharray', ('3, 3')) - .attr('stroke', 'grey'); - - // add a y-axis line that shows the low bg threshold - focus.append('line') - .attr('class', 'target-bottom-line') - .attr('x1', xScale(dataRange[0])) - .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_target_bottom))) - .attr('x2', xScale(dataRange[1])) - .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_target_bottom))) - .style('stroke-dasharray', ('3, 3')) - .attr('stroke', 'grey'); - - // add a y-axis line that shows the low bg threshold - focus.append('line') - .attr('class', 'low-line') - .attr('x1', xScale(dataRange[0])) - .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_low))) - .attr('x2', xScale(dataRange[1])) - .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_low))) - .style('stroke-dasharray', ('1, 6')) - .attr('stroke', '#777'); - - // add a y-axis line that opens up the brush extent from the context to the focus - focus.append('line') - .attr('class', 'open-top') - .attr('stroke', 'black') - .attr('stroke-width', 2); - - // add a x-axis line that closes the the brush container on left side - focus.append('line') - .attr('class', 'open-left') - .attr('stroke', 'white'); - - // add a x-axis line that closes the the brush container on right side - focus.append('line') - .attr('class', 'open-right') - .attr('stroke', 'white'); - - // add a line that marks the current time - context.append('line') - .attr('class', 'now-line') - .attr('x1', xScale(new Date(now))) - .attr('y1', yScale2(scaleBg(36))) - .attr('x2', xScale(new Date(now))) - .attr('y2', yScale2(scaleBg(420))) - .style('stroke-dasharray', ('3, 3')) - .attr('stroke', 'grey'); - - // add a y-axis line that shows the high bg threshold - context.append('line') - .attr('class', 'high-line') - .attr('x1', xScale(dataRange[0])) - .attr('y1', yScale2(scaleBg(serverSettings.thresholds.bg_target_top))) - .attr('x2', xScale(dataRange[1])) - .attr('y2', yScale2(scaleBg(serverSettings.thresholds.bg_target_top))) - .style('stroke-dasharray', ('3, 3')) - .attr('stroke', 'grey'); - - // add a y-axis line that shows the low bg threshold - context.append('line') - .attr('class', 'low-line') - .attr('x1', xScale(dataRange[0])) - .attr('y1', yScale2(scaleBg(serverSettings.thresholds.bg_target_bottom))) - .attr('x2', xScale(dataRange[1])) - .attr('y2', yScale2(scaleBg(serverSettings.thresholds.bg_target_bottom))) - .style('stroke-dasharray', ('3, 3')) - .attr('stroke', 'grey'); - - } else { - - // for subsequent updates use a transition to animate the axis to the new position - var focusTransition = focus.transition().duration(UPDATE_TRANS_MS); - - focusTransition.select('.x') - .attr('transform', 'translate(0,' + focusHeight + ')') - .call(xAxis); - - focusTransition.select('.y') - .attr('transform', 'translate(' + chartWidth + ', 0)') - .call(yAxis); - - var contextTransition = context.transition().duration(UPDATE_TRANS_MS); - - contextTransition.select('.x') - .attr('transform', 'translate(0,' + chartHeight + ')') - .call(xAxis2); - - if (clip) { - // reset clip to new dimensions - clip.transition() - .attr('width', chartWidth) - .attr('height', chartHeight); - } - - // reset brush location - context.select('.x.brush') - .selectAll('rect') - .attr('y', focusHeight) - .attr('height', chartHeight - focusHeight); - - // clear current brush - d3.select('.brush').call(brush.clear()); - - // redraw old brush with new dimensions - d3.select('.brush').transition().duration(UPDATE_TRANS_MS).call(brush.extent(currentBrushExtent)); - - // transition lines to correct location - focus.select('.high-line') - .transition() - .duration(UPDATE_TRANS_MS) - .attr('x1', xScale(currentBrushExtent[0])) - .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_high))) - .attr('x2', xScale(currentBrushExtent[1])) - .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_high))); - - focus.select('.target-top-line') - .transition() - .duration(UPDATE_TRANS_MS) - .attr('x1', xScale(currentBrushExtent[0])) - .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_target_top))) - .attr('x2', xScale(currentBrushExtent[1])) - .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_target_top))); - - focus.select('.target-bottom-line') - .transition() - .duration(UPDATE_TRANS_MS) - .attr('x1', xScale(currentBrushExtent[0])) - .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_target_bottom))) - .attr('x2', xScale(currentBrushExtent[1])) - .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_target_bottom))); - - focus.select('.low-line') - .transition() - .duration(UPDATE_TRANS_MS) - .attr('x1', xScale(currentBrushExtent[0])) - .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_low))) - .attr('x2', xScale(currentBrushExtent[1])) - .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_low))); - - // transition open-top line to correct location - focus.select('.open-top') - .transition() - .duration(UPDATE_TRANS_MS) - .attr('x1', xScale2(currentBrushExtent[0])) - .attr('y1', yScale(scaleBg(30))) - .attr('x2', xScale2(currentBrushExtent[1])) - .attr('y2', yScale(scaleBg(30))); - - // transition open-left line to correct location - focus.select('.open-left') - .transition() - .duration(UPDATE_TRANS_MS) - .attr('x1', xScale2(currentBrushExtent[0])) - .attr('y1', focusHeight) - .attr('x2', xScale2(currentBrushExtent[0])) - .attr('y2', chartHeight); - - // transition open-right line to correct location - focus.select('.open-right') - .transition() - .duration(UPDATE_TRANS_MS) - .attr('x1', xScale2(currentBrushExtent[1])) - .attr('y1', focusHeight) - .attr('x2', xScale2(currentBrushExtent[1])) - .attr('y2', chartHeight); - - // transition high line to correct location - context.select('.high-line') - .transition() - .duration(UPDATE_TRANS_MS) - .attr('x1', xScale2(dataRange[0])) - .attr('y1', yScale2(scaleBg(serverSettings.thresholds.bg_target_top))) - .attr('x2', xScale2(dataRange[1])) - .attr('y2', yScale2(scaleBg(serverSettings.thresholds.bg_target_top))); - - // transition low line to correct location - context.select('.low-line') - .transition() - .duration(UPDATE_TRANS_MS) - .attr('x1', xScale2(dataRange[0])) - .attr('y1', yScale2(scaleBg(serverSettings.thresholds.bg_target_bottom))) - .attr('x2', xScale2(dataRange[1])) - .attr('y2', yScale2(scaleBg(serverSettings.thresholds.bg_target_bottom))); - } - } - - // update domain - xScale2.domain(dataRange); - - // only if a user brush is not active, update brush and focus chart with recent data - // else, just transition brush - var updateBrush = d3.select('.brush').transition().duration(UPDATE_TRANS_MS); - if (!brushInProgress) { - updateBrush - .call(brush.extent([new Date(dataRange[1].getTime() - foucusRangeMS), dataRange[1]])); - brushed(true); - } else { - updateBrush - .call(brush.extent([new Date(currentBrushExtent[1].getTime() - foucusRangeMS), currentBrushExtent[1]])); - brushed(true); - } - - // bind up the context chart data to an array of circles - var contextCircles = context.selectAll('circle') - .data(data); - - function prepareContextCircles(sel) { - var badData = []; - sel.attr('cx', function (d) { return xScale2(new Date(d.mills)); }) - .attr('cy', function (d) { - var scaled = sbx.scaleEntry(d); - if (isNaN(scaled)) { - badData.push(d); - return yScale2(scaleBg(450)); - } else { - return yScale2(scaled); - } - }) - .attr('fill', function (d) { return d.color; }) - .style('opacity', function (d) { return highlightBrushPoints(d) }) - .attr('stroke-width', function (d) { return d.type === 'mbg' ? 2 : 0; }) - .attr('stroke', function ( ) { return 'white'; }) - .attr('r', function (d) { return d.type === 'mbg' ? 4 : 2; }); - - if (badData.length > 0) { - console.warn('Bad Data: isNaN(sgv)', badData); - } - - return sel; - } - - // if already existing then transition each circle to its new position - prepareContextCircles(contextCircles.transition().duration(UPDATE_TRANS_MS)); - - // if new circle then just display - prepareContextCircles(contextCircles.enter().append('circle')); - - contextCircles.exit() - .remove(); - - // update x axis domain - context.select('.x') - .call(xAxis2); - - }, DEBOUNCE_MS); - - function sgvToColor(sgv) { - var color = 'grey'; - - if (browserSettings.theme === 'colors') { - if (sgv > serverSettings.thresholds.bg_high) { - color = 'red'; - } else if (sgv > serverSettings.thresholds.bg_target_top) { - color = 'yellow'; - } else if (sgv >= serverSettings.thresholds.bg_target_bottom && sgv <= serverSettings.thresholds.bg_target_top) { - color = '#4cff00'; - } else if (sgv < serverSettings.thresholds.bg_low) { - color = 'red'; - } else if (sgv < serverSettings.thresholds.bg_target_bottom) { - color = 'yellow'; - } - } - - return color; - } - - function sgvToColoredRange(sgv) { - var range = ''; - - if (browserSettings.theme === 'colors') { - if (sgv > serverSettings.thresholds.bg_high) { - range = 'urgent'; - } else if (sgv > serverSettings.thresholds.bg_target_top) { - range = 'warning'; - } else if (sgv >= serverSettings.thresholds.bg_target_bottom && sgv <= serverSettings.thresholds.bg_target_top) { - range = 'inrange'; - } else if (sgv < serverSettings.thresholds.bg_low) { - range = 'urgent'; - } else if (sgv < serverSettings.thresholds.bg_target_bottom) { - range = 'warning'; - } - } - - return range; - } - - - function generateAlarm(file, reason) { - alarmInProgress = true; - alarmMessage = reason && reason.title; - var selector = '.audio.alarms audio.' + file; - - if (!alarmingNow()) { - d3.select(selector).each(function () { - var audio = this; - playAlarm(audio); - $(this).addClass('playing'); - }); - } - - container.addClass('alarming').addClass(file === urgentAlarmSound ? 'urgent' : 'warning'); - - var skipPageTitle = isTimeAgoAlarmType(currentAlarmType); - updateTitle(skipPageTitle); - } - - function playAlarm(audio) { - // ?mute=true disables alarms to testers. - if (querystring.mute !== 'true') { - audio.play(); - } else { - showNotification('Alarm was muted (?mute=true)'); - } - } - - function stopAlarm(isClient, silenceTime) { - alarmInProgress = false; - alarmMessage = null; - container.removeClass('urgent warning'); - d3.selectAll('audio.playing').each(function () { - var audio = this; - audio.pause(); - $(this).removeClass('playing'); - }); - - closeNotification(); - container.removeClass('alarming'); - - updateTitle(); - - // only emit ack if client invoke by button press - if (isClient) { - if (isTimeAgoAlarmType(currentAlarmType)) { - container.removeClass('alarming-timeago'); - var alarm = getClientAlarm(currentAlarmType); - alarm.lastAckTime = Date.now(); - alarm.silenceTime = silenceTime; - } - socket.emit('ack', currentAlarmType || 'alarm', silenceTime); - } - - brushed(false); - } - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - //draw a compact visualization of a treatment (carbs, insulin) - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - function drawTreatment(treatment, scale, showValues) { - - if (!treatment.carbs && !treatment.insulin) { return; } - - // don't render the treatment if it's not visible - if (Math.abs(xScale(new Date(treatment.mills))) > window.innerWidth) { return; } - - var CR = treatment.CR || 20; - var carbs = treatment.carbs || CR; - var insulin = treatment.insulin || 1; - - var R1 = Math.sqrt(Math.min(carbs, insulin * CR)) / scale, - R2 = Math.sqrt(Math.max(carbs, insulin * CR)) / scale, - R3 = R2 + 8 / scale; - - if (isNaN(R1) || isNaN(R3) || isNaN(R3)) { - console.warn('Bad Data: Found isNaN value in treatment', treatment); - return; - } - - var arc_data = [ - { 'element': '', 'color': 'white', 'start': -1.5708, 'end': 1.5708, 'inner': 0, 'outer': R1 }, - { 'element': '', 'color': 'transparent', 'start': -1.5708, 'end': 1.5708, 'inner': R2, 'outer': R3 }, - { 'element': '', 'color': '#0099ff', 'start': 1.5708, 'end': 4.7124, 'inner': 0, 'outer': R1 }, - { 'element': '', 'color': 'transparent', 'start': 1.5708, 'end': 4.7124, 'inner': R2, 'outer': R3 } - ]; - - 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'; - } - - if (treatment.insulin > 0) { - arc_data[3].element = Math.round(treatment.insulin * 100) / 100 + ' U'; - } - - var arc = d3.svg.arc() - .innerRadius(function (d) { return 5 * d.inner; }) - .outerRadius(function (d) { return 5 * d.outer; }) - .endAngle(function (d) { return d.start; }) - .startAngle(function (d) { return d.end; }); - - var treatmentDots = focus.selectAll('treatment-dot') - .data(arc_data) - .enter() - .append('g') - .attr('transform', 'translate(' + xScale(new Date(treatment.mills)) + ', ' + yScale(sbx.scaleEntry(treatment)) + ')') - .on('mouseover', function () { - tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); - tooltip.html(''+translate('Time')+': ' + formatTime(new Date(treatment.mills)) + '
    ' + ''+translate('Treatment type')+': ' + translate(treatment.eventType) + '
    ' + - (treatment.carbs ? ''+translate('Carbs')+': ' + treatment.carbs + '
    ' : '') + - (treatment.insulin ? ''+translate('Insulin')+': ' + treatment.insulin + '
    ' : '') + - (treatment.glucose ? ''+translate('BG')+': ' + treatment.glucose + (treatment.glucoseType ? ' (' + translate(treatment.glucoseType) + ')': '') + '
    ' : '') + - (treatment.enteredBy ? ''+translate('Entered by')+': ' + treatment.enteredBy + '
    ' : '') + - (treatment.notes ? ''+translate('Notes')+': ' + treatment.notes : '') - ) - .style('left', (d3.event.pageX) + 'px') - .style('top', (d3.event.pageY + 15) + 'px'); - }) - .on('mouseout', function () { - tooltip.transition() - .duration(TOOLTIP_TRANS_MS) - .style('opacity', 0); - }); - - treatmentDots.append('path') - .attr('class', 'path') - .attr('fill', function (d) { return d.outlineOnly ? 'transparent' : d.color; }) - .attr('stroke-width', function (d) { return d.outlineOnly ? 1 : 0; }) - .attr('stroke', function (d) { return d.color; }) - .attr('id', function (d, i) { return 's' + i; }) - .attr('d', arc); - - - // labels for carbs and insulin - if (showValues) { - var label = treatmentDots.append('g') - .attr('class', 'path') - .attr('id', 'label') - .style('fill', 'white'); - label.append('text') - .style('font-size', 40 / scale) - .style('text-shadow', '0px 0px 10px rgba(0, 0, 0, 1)') - .attr('text-anchor', 'middle') - .attr('dy', '.35em') - .attr('transform', function (d) { - d.outerRadius = d.outerRadius * 2.1; - d.innerRadius = d.outerRadius * 2.1; - return 'translate(' + arc.centroid(d) + ')'; - }) - .text(function (d) { return d.element; }); - } - } - - function updateClock() { - updateClockDisplay(); - var interval = (60 - (new Date()).getSeconds()) * 1000 + 5; - setTimeout(updateClock,interval); - - updateTimeAgo(); - - // Dim the screen by reducing the opacity when at nighttime - if (browserSettings.nightMode) { - var dateTime = new Date(); - if (opacity.current !== opacity.NIGHT && (dateTime.getHours() > 21 || dateTime.getHours() < 7)) { - $('body').css({ 'opacity': opacity.NIGHT }); - } else { - $('body').css({ 'opacity': opacity.DAY }); - } - } - } - - function updateClockDisplay() { - if (inRetroMode()) { - return; - } - now = Date.now(); - $('#currentTime').text(formatTime(new Date(now), true)).css('text-decoration', ''); - } - - function getClientAlarm(type) { - var alarm = clientAlarms[type]; - if (!alarm) { - alarm = { type: type }; - clientAlarms[type] = alarm; - } - return alarm; - } - - function isTimeAgoAlarmType(alarmType) { - return alarmType === 'warnTimeAgo' || alarmType === 'urgentTimeAgo'; - } - - function checkTimeAgoAlarm(ago) { - var level = ago.status - , alarm = getClientAlarm(level + 'TimeAgo'); - - if (Date.now() >= (alarm.lastAckTime || 0) + (alarm.silenceTime || 0)) { - currentAlarmType = alarm.type; - console.info('generating timeAgoAlarm', alarm.type); - container.addClass('alarming-timeago'); - var message = {'title': 'Last data received ' + [ago.value, ago.label].join(' ')}; - if (level === 'warn') { - generateAlarm(alarmSound, message); - } else { - generateAlarm(urgentAlarmSound, message); - } - } - } - - function updateTimeAgo() { - var lastEntry = $('#lastEntry') - , time = latestSGV ? latestSGV.mills : -1 - , ago = timeAgo(time, browserSettings) - , retroMode = inRetroMode(); - - lastEntry.removeClass('current warn urgent'); - lastEntry.addClass(ago.status); - - if (ago.status !== 'current') { - updateTitle(); - } - - if ( - (browserSettings.alarmTimeAgoWarn && ago.status === 'warn') - || (browserSettings.alarmTimeAgoUrgent && ago.status === 'urgent')) { - checkTimeAgoAlarm(ago); - } - - container.toggleClass('alarming-timeago', ago.status !== 'current'); - - if (alarmingNow() && ago.status === 'current' && isTimeAgoAlarmType(currentAlarmType)) { - stopAlarm(true, ONE_MIN_IN_MS); - } - - if (retroMode || !ago.value) { - lastEntry.find('em').hide(); - } else { - lastEntry.find('em').show().text(ago.value); - } - - if (retroMode || ago.label) { - lastEntry.find('label').show().text(retroMode ? 'RETRO' : ago.label); - } else { - lastEntry.find('label').hide(); - } - } - - function init() { - - jqWindow = $(window); - - tooltip = d3.select('body').append('div') - .attr('class', 'tooltip') - .style('opacity', 0); - - // Tick Values - if (browserSettings.units === 'mmol') { - tickValues = [ - 2.0 - , Math.round(scaleBg(serverSettings.thresholds.bg_low)) - , Math.round(scaleBg(serverSettings.thresholds.bg_target_bottom)) - , 6.0 - , Math.round(scaleBg(serverSettings.thresholds.bg_target_top)) - , Math.round(scaleBg(serverSettings.thresholds.bg_high)) - , 22.0 - ]; - } else { - tickValues = [ - 40 - , serverSettings.thresholds.bg_low - , serverSettings.thresholds.bg_target_bottom - , 120 - , serverSettings.thresholds.bg_target_top - , serverSettings.thresholds.bg_high - , 400 - ]; - } - - futureOpacity = d3.scale.linear( ) - .domain([TWENTY_FIVE_MINS_IN_MS, SIXTY_MINS_IN_MS]) - .range([0.8, 0.1]); - - // create svg and g to contain the chart contents - charts = d3.select('#chartContainer').append('svg') - .append('g') - .attr('class', 'chartContainer') - .attr('transform', 'translate(' + padding.left + ',' + padding.top + ')'); - - focus = charts.append('g'); - - // create the x axis container - focus.append('g') - .attr('class', 'x axis'); - - // create the y axis container - focus.append('g') - .attr('class', 'y axis'); - - context = charts.append('g'); - - // create the x axis container - context.append('g') - .attr('class', 'x axis'); - - // create the y axis container - context.append('g') - .attr('class', 'y axis'); - - //updateChart is _.debounce'd - function refreshChart(updateToNow) { - if (updateToNow) { - updateBrushToNow(); - } - updateChart(false); - } - - function visibilityChanged() { - var prevHidden = documentHidden; - documentHidden = (document.hidden || document.webkitHidden || document.mozHidden || document.msHidden); - - if (prevHidden && !documentHidden) { - console.info('Document now visible, updating - ' + (new Date())); - refreshChart(true); - } - } - - window.onresize = refreshChart; - - document.addEventListener('webkitvisibilitychange', visibilityChanged); - - - updateClock(); - - var silenceDropdown = new Dropdown('.dropdown-menu'); - - $('.bgButton').click(function (e) { - if (alarmingNow()) { - silenceDropdown.open(e); - } - }); - - $('#silenceBtn').find('a').click(function (e) { - stopAlarm(true, $(this).data('snooze-time')); - e.preventDefault(); - }); - - $('.focus-range li').click(function(e) { - var li = $(e.target); - $('.focus-range li').removeClass('selected'); - li.addClass('selected'); - var hours = Number(li.data('hours')); - foucusRangeMS = hours * 60 * 60 * 1000; - refreshChart(); - }); - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // Client-side code to connect to server and handle incoming data - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - socket = io.connect(); - - function mergeDataUpdate(isDelta, cachedDataArray, receivedDataArray) { - - function nsArrayDiff(oldArray, newArray) { - var seen = {}; - var l = oldArray.length; - for (var i = 0; i < l; i++) { seen[oldArray[i].mills] = true } - var result = []; - l = newArray.length; - for (var j = 0; j < l; j++) { if (!seen.hasOwnProperty(newArray[j].mills)) { result.push(newArray[j]); console.log('delta data found'); } } - return result; - } - - // If there was no delta data, just return the original data - if (!receivedDataArray) { - return cachedDataArray; - } - - // If this is not a delta update, replace all data - if (!isDelta) { - return receivedDataArray; - } - - // If this is delta, calculate the difference, merge and sort - var diff = nsArrayDiff(cachedDataArray,receivedDataArray); - return cachedDataArray.concat(diff).sort(function(a, b) { - return a.mills - b.mills; - }); - } - - socket.on('dataUpdate', function receivedSGV(d) { - - if (!d) { - return; - } - - // Calculate the diff to existing data and replace as needed - - SGVdata = mergeDataUpdate(d.delta, SGVdata, d.sgvs); - MBGdata = mergeDataUpdate(d.delta,MBGdata, d.mbgs); - treatments = mergeDataUpdate(d.delta,treatments, d.treatments); - - if (d.profiles) { - profile = d.profiles[0]; - Nightscout.profile.loadData(d.profiles); - } - - if (d.cals) { cal = d.cals[d.cals.length-1]; } - if (d.devicestatus) { devicestatusData = d.devicestatus; } - - // Do some reporting on the console - console.log('Total SGV data size', SGVdata.length); - console.log('Total treatment data size', treatments.length); - - // Post processing after data is in - - if (d.sgvs) { - // change the next line so that it uses the prediction if the signal gets lost (max 1/2 hr) - latestUpdateTime = Date.now(); - latestSGV = SGVdata[SGVdata.length - 1]; - prevSGV = SGVdata[SGVdata.length - 2]; - } - - var temp1 = [ ]; - if (cal && rawbg.isEnabled(sbx)) { - temp1 = SGVdata.map(function (entry) { - var rawbgValue = rawbg.showRawBGs(entry.mgdl, entry.noise, cal, sbx) ? rawbg.calc(entry, cal, sbx) : 0; - if (rawbgValue > 0) { - return { mills: entry.mills - 2000, mgdl: rawbgValue, color: 'white', type: 'rawbg' }; - } else { - return null; - } - }).filter(function(entry) { return entry !== null; }); - } - var temp2 = SGVdata.map(function (obj) { - return { mills: obj.mills, mgdl: obj.mgdl, direction: obj.direction, color: sgvToColor(obj.mgdl), type: 'sgv', noise: obj.noise, filtered: obj.filtered, unfiltered: obj.unfiltered}; - }); - data = []; - data = data.concat(temp1, temp2); - - addPlaceholderPoints(); - - data = data.concat(MBGdata.map(function (obj) { return { mills: obj.mills, mgdl: obj.mgdl, color: 'red', type: 'mbg', device: obj.device } })); - - data.forEach(function (d) { - if (d.mgdl < 39) { d.color = 'transparent'; } - }); - - updateTitle(); - if (!isInitialData) { - isInitialData = true; - initializeCharts(); - } - else { - updateChart(false); - } - - }); - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // Alarms and Text handling - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - socket.on('connect', function () { - console.log('Client connected to server.'); - }); - - - //with predicted alarms, latestSGV may still be in target so to see if the alarm - // is for a HIGH we can only check if it's >= the bottom of the target - function isAlarmForHigh() { - return latestSGV.mgdl >= serverSettings.thresholds.bg_target_bottom; - } - - //with predicted alarms, latestSGV may still be in target so to see if the alarm - // is for a LOW we can only check if it's <= the top of the target - function isAlarmForLow() { - return latestSGV.mgdl <= serverSettings.thresholds.bg_target_top; - } - - socket.on('alarm', function (notify) { - console.info('alarm received from server'); - console.log('notify:',notify); - var enabled = (isAlarmForHigh() && browserSettings.alarmHigh) || (isAlarmForLow() && browserSettings.alarmLow); - if (enabled) { - console.log('Alarm raised!'); - currentAlarmType = 'alarm'; - generateAlarm(alarmSound,notify); - } else { - console.info('alarm was disabled locally', latestSGV.mgdl, browserSettings); - } - brushInProgress = false; - updateChart(false); - }); - socket.on('urgent_alarm', function (notify) { - console.info('urgent alarm received from server'); - console.log('notify:',notify); - - var enabled = (isAlarmForHigh() && browserSettings.alarmUrgentHigh) || (isAlarmForLow() && browserSettings.alarmUrgentLow); - if (enabled) { - console.log('Urgent alarm raised!'); - currentAlarmType = 'urgent_alarm'; - generateAlarm(urgentAlarmSound,notify); - } else { - console.info('urgent alarm was disabled locally', latestSGV.mgdl, browserSettings); - } - brushInProgress = false; - updateChart(false); - }); - socket.on('clear_alarm', function () { - if (alarmInProgress) { - console.log('clearing alarm'); - stopAlarm(); - } - }); - - - $('#testAlarms').click(function(event) { - d3.selectAll('.audio.alarms audio').each(function () { - var audio = this; - playAlarm(audio); - setTimeout(function() { - audio.pause(); - }, 4000); - }); - event.preventDefault(); - }); - - if (serverSettings.enabledOptions.indexOf('ar2') < 0) { - serverSettings.enabledOptions += ' ar2'; - } - - $('.appName').text(serverSettings.name); - $('.version').text(serverSettings.version); - $('.head').text(serverSettings.head); - if (serverSettings.apiEnabled) { - $('.serverSettings').show(); - } - $('#treatmentDrawerToggle').toggle(serverSettings.careportalEnabled); - Nightscout.plugins.init(serverSettings); - browserSettings = getBrowserSettings(browserStorage); - sbx = Nightscout.sandbox.clientInit(serverSettings, browserSettings, Date.now()); - $('.container').toggleClass('has-minor-pills', Nightscout.plugins.hasShownType('pill-minor', browserSettings)); - - } - - init(); - -})(); +window.Nightscout.client(Nightscout.plugins, serverSettings); diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js index 93651c0cc22..d72d9f33b2c 100644 --- a/static/js/ui-utils.js +++ b/static/js/ui-utils.js @@ -6,118 +6,6 @@ function rawBGsEnabled() { return serverSettings.enabledOptions && serverSettings.enabledOptions.indexOf('rawbg') > -1; } -function getBrowserSettings(storage) { - var json = {}; - - function scaleBg(bg) { - if (json.units === 'mmol') { - return Nightscout.units.mgdlToMMOL(bg); - } else { - return bg; - } - } - - function appendThresholdValue(threshold) { - return serverSettings.alarm_types.indexOf('simple') === -1 ? '' : ' (' + scaleBg(threshold) + ')'; - } - - try { - json = { - 'units': storage.get('units'), - 'alarmUrgentHigh': storage.get('alarmUrgentHigh'), - 'alarmHigh': storage.get('alarmHigh'), - 'alarmLow': storage.get('alarmLow'), - 'alarmUrgentLow': storage.get('alarmUrgentLow'), - 'alarmTimeAgoWarn': storage.get('alarmTimeAgoWarn'), - 'alarmTimeAgoWarnMins': storage.get('alarmTimeAgoWarnMins'), - 'alarmTimeAgoUrgent': storage.get('alarmTimeAgoUrgent'), - 'alarmTimeAgoUrgentMins': storage.get('alarmTimeAgoUrgentMins'), - 'nightMode': storage.get('nightMode'), - 'showRawbg': storage.get('showRawbg'), - 'customTitle': storage.get('customTitle'), - 'theme': storage.get('theme'), - 'timeFormat': storage.get('timeFormat'), - 'showPlugins': storage.get('showPlugins') - }; - - // Default browser units to server units if undefined. - json.units = setDefault(json.units, serverSettings.units); - if (json.units === 'mmol') { - $('#mmol-browser').prop('checked', true); - } else { - $('#mgdl-browser').prop('checked', true); - } - - json.alarmUrgentHigh = setDefault(json.alarmUrgentHigh, serverSettings.defaults.alarmUrgentHigh); - json.alarmHigh = setDefault(json.alarmHigh, serverSettings.defaults.alarmHigh); - json.alarmLow = setDefault(json.alarmLow, serverSettings.defaults.alarmLow); - json.alarmUrgentLow = setDefault(json.alarmUrgentLow, serverSettings.defaults.alarmUrgentLow); - json.alarmTimeAgoWarn = setDefault(json.alarmTimeAgoWarn, serverSettings.defaults.alarmTimeAgoWarn); - json.alarmTimeAgoWarnMins = setDefault(json.alarmTimeAgoWarnMins, serverSettings.defaults.alarmTimeAgoWarnMins); - json.alarmTimeAgoUrgent = setDefault(json.alarmTimeAgoUrgent, serverSettings.defaults.alarmTimeAgoUrgent); - json.alarmTimeAgoUrgentMins = setDefault(json.alarmTimeAgoUrgentMins, serverSettings.defaults.alarmTimeAgoUrgentMins); - $('#alarm-urgenthigh-browser').prop('checked', json.alarmUrgentHigh).next().text('Urgent High Alarm' + appendThresholdValue(serverSettings.thresholds.bg_high)); - $('#alarm-high-browser').prop('checked', json.alarmHigh).next().text('High Alarm' + appendThresholdValue(serverSettings.thresholds.bg_target_top)); - $('#alarm-low-browser').prop('checked', json.alarmLow).next().text('Low Alarm' + appendThresholdValue(serverSettings.thresholds.bg_target_bottom)); - $('#alarm-urgentlow-browser').prop('checked', json.alarmUrgentLow).next().text('Urgent Low Alarm' + appendThresholdValue(serverSettings.thresholds.bg_low)); - $('#alarm-timeagowarn-browser').prop('checked', json.alarmTimeAgoWarn); - $('#alarm-timeagowarnmins-browser').val(json.alarmTimeAgoWarnMins); - $('#alarm-timeagourgent-browser').prop('checked', json.alarmTimeAgoUrgent); - $('#alarm-timeagourgentmins-browser').val(json.alarmTimeAgoUrgentMins); - - json.nightMode = setDefault(json.nightMode, serverSettings.defaults.nightMode); - $('#nightmode-browser').prop('checked', json.nightMode); - - if (rawBGsEnabled()) { - $('#show-rawbg-option').show(); - json.showRawbg = setDefault(json.showRawbg, serverSettings.defaults.showRawbg); - $('#show-rawbg-' + json.showRawbg).prop('checked', true); - } else { - json.showRawbg = 'never'; - $('#show-rawbg-option').hide(); - } - - json.customTitle = setDefault(json.customTitle, serverSettings.defaults.customTitle); - $('h1.customTitle').text(json.customTitle); - $('input#customTitle').prop('value', json.customTitle); - - json.theme = setDefault(json.theme, serverSettings.defaults.theme); - if (json.theme === 'colors') { - $('#theme-colors-browser').prop('checked', true); - } else { - $('#theme-default-browser').prop('checked', true); - } - - json.timeFormat = setDefault(json.timeFormat, serverSettings.defaults.timeFormat); - - if (json.timeFormat === '24') { - $('#24-browser').prop('checked', true); - } else { - $('#12-browser').prop('checked', true); - } - - json.showPlugins = setDefault(json.showPlugins, serverSettings.defaults.showPlugins || Nightscout.plugins.enabledPluginNames()); - var showPluginsSettings = $('#show-plugins'); - Nightscout.plugins.eachEnabledPlugin(function each(plugin) { - if (Nightscout.plugins.specialPlugins.indexOf(plugin.name) > -1) { - //ignore these, they are always on for now - } else { - var id = 'plugin-' + plugin.name; - var dd = $('
    '); - showPluginsSettings.append(dd); - dd.find('input').prop('checked', json.showPlugins.indexOf(plugin.name) > -1); - } - }); - - - } catch(err) { - console.error(err); - showLocalstorageError(); - } - - return json; -} - function setDefault(variable, defaultValue) { if (typeof(variable) === 'object') { return defaultValue; @@ -207,36 +95,9 @@ function showNotification(note, type) { notify.show(); } -function showLocalstorageError() { - var msg = 'Settings are disabled.

    Please enable cookies so you may customize your Nightscout site.'; - $('.browserSettings').html('Settings'+msg+''); - $('#save').hide(); -} - var querystring = getQueryParms(); -function Dropdown(el) { - this.ddmenuitem = 0; - - this.$el = $(el); - var that = this; - - $(document).click(function() { that.close(); }); -} -Dropdown.prototype.close = function () { - if (this.ddmenuitem) { - this.ddmenuitem.css('visibility', 'hidden'); - this.ddmenuitem = 0; - } -}; -Dropdown.prototype.open = function (e) { - this.close(); - this.ddmenuitem = $(this.$el).css('visibility', 'visible'); - e.stopPropagation(); -}; - - $('#drawerToggle').click(function(event) { toggleDrawer('#drawer'); event.preventDefault(); diff --git a/tests/client.test.js b/tests/client.test.js new file mode 100644 index 00000000000..4002890efe0 --- /dev/null +++ b/tests/client.test.js @@ -0,0 +1,53 @@ +'use strict'; + +require('should'); + +var benv = require('benv'); + +describe('client', function ( ) { + + before(function (done) { + benv.setup(function() { + benv.expose({ + $: require('jquery') + , jQuery: require('jquery') + , d3: require('d3') + , io: { + connect: function mockConnect() { + return { + on: function mockOn(event, callback) {} + }; + } + } + }); +// benv.require('../static/js/ui-utils.js', 'window'); +// benv.require('../static/bower_components/tipsy-jmalonzo/src/javascripts/jquery.tipsy.js'); + done(); + }); + }); + + after(function (done) { + benv.teardown(); + done(); + }); + + it ('not blow up', function () { +// var jsdom = require('jsdom').jsdom; +// var doc = jsdom(''); +// var window = doc.parentWindow; +// +// global.$ = global.jQuery = require('jquery')(window); + + var serverSettings = { + apiEnabled: true, careportalEnabled: true, enabledOptions: "ar2 careportal delta direction upbat errorcodes", defaults: { + "units": "mg/dl", "timeFormat": "12", "nightMode": false, "showRawbg": "noise", "customTitle": "Nightscout", "theme": "colors", "alarmUrgentHigh": true, "alarmHigh": true, "alarmLow": true, "alarmUrgentLow": true, "alarmTimeAgoWarn": true, "alarmTimeAgoWarnMins": 15, "alarmTimeAgoUrgent": true, "alarmTimeAgoUrgentMins": 30, "language": "en", "showPlugins": "iob delta direction upbatrawbg" + }, "units": "mg/dl", "head": "ae71dca", "version": "0.7.0", "thresholds": { + "bg_high": 200, "bg_target_top": 170, "bg_target_bottom": 80, "bg_low": 55 + }, "alarm_types": "predict", "name": "Nightscout" + }; + + var plugins = require('../lib/plugins/')().registerClientDefaults(); + var client = require('../lib/client')(plugins, serverSettings); + }); + +}); From b4378f8782f748cfcaf9099a441e109991ae11b3 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 22 Jul 2015 01:39:01 -0700 Subject: [PATCH 461/937] cleanup tests and hoefully make them pass --- package.json | 1 - tests/client.test.js | 46 ++++++++++++++++++++----------- tests/pluginbase.test.js | 58 ++++++++++++++++++++++++++-------------- 3 files changed, 69 insertions(+), 36 deletions(-) diff --git a/package.json b/package.json index ab60c6a9494..89da4c327c0 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,6 @@ "mocha": "~1.20.1", "should": "~4.0.4", "supertest": "~0.13.0", - "jsdom": "^3.1.2", "benv": "^1.1.0" } } diff --git a/tests/client.test.js b/tests/client.test.js index 4002890efe0..cd03296e08f 100644 --- a/tests/client.test.js +++ b/tests/client.test.js @@ -13,15 +13,13 @@ describe('client', function ( ) { , jQuery: require('jquery') , d3: require('d3') , io: { - connect: function mockConnect() { + connect: function mockConnect ( ) { return { - on: function mockOn(event, callback) {} + on: function mockOn ( ) { } }; } } }); -// benv.require('../static/js/ui-utils.js', 'window'); -// benv.require('../static/bower_components/tipsy-jmalonzo/src/javascripts/jquery.tipsy.js'); done(); }); }); @@ -32,18 +30,36 @@ describe('client', function ( ) { }); it ('not blow up', function () { -// var jsdom = require('jsdom').jsdom; -// var doc = jsdom(''); -// var window = doc.parentWindow; -// -// global.$ = global.jQuery = require('jquery')(window); - var serverSettings = { - apiEnabled: true, careportalEnabled: true, enabledOptions: "ar2 careportal delta direction upbat errorcodes", defaults: { - "units": "mg/dl", "timeFormat": "12", "nightMode": false, "showRawbg": "noise", "customTitle": "Nightscout", "theme": "colors", "alarmUrgentHigh": true, "alarmHigh": true, "alarmLow": true, "alarmUrgentLow": true, "alarmTimeAgoWarn": true, "alarmTimeAgoWarnMins": 15, "alarmTimeAgoUrgent": true, "alarmTimeAgoUrgentMins": 30, "language": "en", "showPlugins": "iob delta direction upbatrawbg" - }, "units": "mg/dl", "head": "ae71dca", "version": "0.7.0", "thresholds": { - "bg_high": 200, "bg_target_top": 170, "bg_target_bottom": 80, "bg_low": 55 - }, "alarm_types": "predict", "name": "Nightscout" + apiEnabled: true, careportalEnabled: true, enabledOptions: 'ar2 careportal delta direction upbat errorcodes', defaults: { + units: 'mg/dl' + , timeFormat: '12' + , nightMode: false + , showRawbg: 'noise' + , customTitle: 'Nightscout' + , theme: 'colors' + , alarmUrgentHigh: true + , alarmHigh: true + , alarmLow: true + , alarmUrgentLow: true + , alarmTimeAgoWarn: true + , alarmTimeAgoWarnMins: 15 + , alarmTimeAgoUrgent: true + , alarmTimeAgoUrgentMins: 30 + , language: 'en' + , showPlugins: 'iob delta direction upbatrawbg' + } + , units: 'mg/dl' + , head: 'ae71dca' + , version: '0.7.0' + , thresholds: { + bg_high: 200 + , bg_target_top: 170 + , bg_target_bottom: 80 + , bg_low: 55 + } + , alarm_types: 'predict' + , name: 'Nightscout' }; var plugins = require('../lib/plugins/')().registerClientDefaults(); diff --git a/tests/pluginbase.test.js b/tests/pluginbase.test.js index 0751a53104c..2b4d9566df4 100644 --- a/tests/pluginbase.test.js +++ b/tests/pluginbase.test.js @@ -1,35 +1,53 @@ 'use strict'; require('should'); +var benv = require('benv'); describe('pluginbase', function ( ) { - var jsdom = require('jsdom').jsdom; - var doc = jsdom(''); - var window = doc.parentWindow; + before(function (done) { + benv.setup(function() { + benv.expose({ + $: require('jquery') + , jQuery: require('jquery') + , d3: require('d3') + , io: { + connect: function mockConnect() { + return { + on: function mockOn(event, callback) {} + }; + } + } + }); + done(); + }); + }); + after(function (done) { + benv.teardown(); + done(); + }); - global.$ = global.jQuery = require('jquery')(window); + it('does stuff', function() { - function div (clazz) { - return $('
    '); - } + function div (clazz) { + return $('
    '); + } - var container = div('container') - , bgStatus = div('bgStatus').appendTo(container) - , majorPills = div('majorPills').appendTo(bgStatus) - , minorPills = div('minorPills').appendTo(bgStatus) - , statusPills = div('statusPills').appendTo(bgStatus) - , tooltip = div('tooltip').appendTo(container) - ; + var container = div('container') + , bgStatus = div('bgStatus').appendTo(container) + , majorPills = div('majorPills').appendTo(bgStatus) + , minorPills = div('minorPills').appendTo(bgStatus) + , statusPills = div('statusPills').appendTo(bgStatus) + , tooltip = div('tooltip').appendTo(container) + ; - var fake = { - name: 'fake' - , label: 'Insulin-on-Board' - , pluginType: 'pill-major' - }; + var fake = { + name: 'fake' + , label: 'Insulin-on-Board' + , pluginType: 'pill-major' + }; - it('does stuff', function() { var pluginbase = require('../lib/plugins/pluginbase')(majorPills, minorPills, statusPills, bgStatus, tooltip); pluginbase.updatePillText(fake, { From 60526158f7473ed1b6dc03de12a2afa4d1b187c4 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 22 Jul 2015 01:50:17 -0700 Subject: [PATCH 462/937] clean up unused --- tests/pluginbase.test.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tests/pluginbase.test.js b/tests/pluginbase.test.js index 2b4d9566df4..2397720b09e 100644 --- a/tests/pluginbase.test.js +++ b/tests/pluginbase.test.js @@ -10,14 +10,6 @@ describe('pluginbase', function ( ) { benv.expose({ $: require('jquery') , jQuery: require('jquery') - , d3: require('d3') - , io: { - connect: function mockConnect() { - return { - on: function mockOn(event, callback) {} - }; - } - } }); done(); }); From 2c489dd8cdb67853507a199063ee0ec483e3b41e Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Wed, 22 Jul 2015 16:45:33 +0200 Subject: [PATCH 463/937] ns translation --- lib/language.js | 15 +++- lib/treatments.js | 4 +- static/index.html | 151 +++++++++++++++++++++-------------------- static/js/treatment.js | 47 ++++--------- static/js/ui-utils.js | 11 +-- 5 files changed, 113 insertions(+), 115 deletions(-) diff --git a/lib/language.js b/lib/language.js index 369a17c253d..4d39782b63f 100644 --- a/lib/language.js +++ b/lib/language.js @@ -831,8 +831,17 @@ function init() { ,'Calibration' : { cs: 'Kalibrace' } - ,'1' : { - cs: '1' + ,'Show Plugins' : { + cs: 'Zobrazuj pluginy' + } + ,'About' : { + cs: 'O aplikaci' + } + ,'Value in' : { + cs: 'Hodnota v' + } + ,'Prebolus' : { + cs: 'Posunuté jídlo' } }; @@ -849,7 +858,7 @@ function init() { $('.translate').each(function () { $(this).text(language.translate($(this).text())); }); - $('.titletranslate').each(function () { + $('.titletranslate, .tip').each(function () { $(this).attr('title',language.translate($(this).attr('title'))); $(this).attr('original-title',language.translate($(this).attr('original-title'))); $(this).attr('placeholder',language.translate($(this).attr('placeholder'))); diff --git a/lib/treatments.js b/lib/treatments.js index 8a2bdebf123..a9f0856da00 100644 --- a/lib/treatments.js +++ b/lib/treatments.js @@ -16,7 +16,7 @@ function storage (env, ctx) { obj.created_at = created_at.toISOString(); var preBolusCarbs = ''; - if (obj.preBolus > 0 && obj.carbs) { + if (obj.preBolus != 0 && obj.carbs) { preBolusCarbs = obj.carbs; delete obj.carbs; } @@ -36,7 +36,7 @@ function storage (env, ctx) { api( ).insert(obj, function (err, doc) { fn(null, doc); - if (obj.preBolus > 0) { + if (obj.preBolus) { //create a new object to insert copying only the needed fields var pbTreat = { created_at: (new Date(created_at.getTime() + (obj.preBolus * 60000))).toISOString(), diff --git a/static/index.html b/static/index.html index 9d2ab4b79ea..e6bf3fdabd3 100644 --- a/static/index.html +++ b/static/index.html @@ -52,10 +52,10 @@ -
    @@ -84,65 +84,65 @@
    - Settings + Settings
    -
    Units
    +
    Units
    -
    Date format
    -
    -
    +
    Date format
    +
    +
    -
    Enable Alarms
    -
    -
    -
    -
    +
    Enable Alarms
    +
    +
    +
    +
    - + - mins + mins
    - + - mins + mins
    -
    Night Mode
    -
    +
    Night Mode
    +
    -
    Show Raw BG Data
    -
    -
    -
    +
    Show Raw BG Data
    +
    +
    +
    -
    Custom Title
    +
    Custom Title
    -
    Theme
    -
    -
    +
    Theme
    +
    +
    -
    Show Plugins
    +
    Show Plugins
    - About + About
    version
    head
    @@ -164,83 +164,90 @@
    - Log a Treatment -
    - View all treatments + View all treatments
    diff --git a/static/js/treatment.js b/static/js/treatment.js index 770f90205ac..1c0a13611de 100644 --- a/static/js/treatment.js +++ b/static/js/treatment.js @@ -1,9 +1,11 @@ 'use strict'; (function () { + var translate = Nightscout.language.translate; + function initTreatmentDrawer() { $('#eventType').val('BG Check'); - $('#glucoseValue').val('').attr('placeholder', 'Value in ' + browserSettings.units); + $('#glucoseValue').val('').attr('placeholder', translate('Value in') + ' ' + browserSettings.units); $('#meter').prop('checked', true); $('#carbsGiven').val(''); $('#insulinGiven').val(''); @@ -15,22 +17,6 @@ $('#eventDateValue').val(moment().format('YYYY-MM-D')); } - function checkForErrors(data) { - var errors = []; - if (isNaN(data.glucose)) { - errors.push('Blood glucose must be a number'); - } - - if (isNaN(data.carbs)) { - errors.push('Carbs must be a number'); - } - - if (isNaN(data.insulin)) { - errors.push('Insulin must be a number'); - } - return errors; - } - function prepareData() { var data = { enteredBy: $('#enteredBy').val() @@ -54,13 +40,8 @@ function treatmentSubmit(event) { var data = prepareData(); - var errors = checkForErrors(data); - if (errors.length > 0) { - window.alert(errors.join('\n')); - } else { - confirmPost(data); - } + confirmPost(data); if (event) { event.preventDefault(); @@ -69,22 +50,22 @@ function buildConfirmText(data) { var text = [ - 'Please verify that the data entered is correct: ' - , 'Event type: ' + data.eventType + translate('Please verify that the data entered is correct') + ': ' + , translate('Event Type') + ': ' + translate(data.eventType) ]; if (data.glucose) { - text.push('Blood glucose: ' + data.glucose); - text.push('Method: ' + data.glucoseType); + text.push(translate('Blood Glucose') + ': ' + data.glucose); + text.push(translate('Measurement Method') + ': ' + translate(data.glucoseType)); } - if (data.carbs) { text.push('Carbs Given: ' + data.carbs); } - if (data.insulin) { text.push('Insulin Given: ' + data.insulin); } - if (data.preBolus) { text.push('Insulin Given: ' + data.insulin); } - if (data.notes) { text.push('Notes: ' + data.notes); } - if (data.enteredBy) { text.push('Entered By: ' + data.enteredBy); } + if (data.carbs) { text.push(translate('Carbs Given') + ': ' + data.carbs); } + if (data.insulin) { text.push(translate('Insulin Given') + ': ' + data.insulin); } + if (data.preBolus) { text.push(translate('Prebolus') + ': ' + data.preBolus + ' ' + translate('min')); } + if (data.notes) { text.push(translate('Notes') + ': ' + data.notes); } + if (data.enteredBy) { text.push(translate('Entered By') + ': ' + data.enteredBy); } - text.push('Event Time: ' + (data.eventTime ? data.eventTime.toDate().toLocaleString() : new Date().toLocaleString())); + text.push(translate('Event Time') + ': ' + (data.eventTime ? data.eventTime.toDate().toLocaleString() : new Date().toLocaleString())); return text.join('\n'); } diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js index 4fcc6b85a4a..6ca484822a3 100644 --- a/static/js/ui-utils.js +++ b/static/js/ui-utils.js @@ -7,6 +7,7 @@ function rawBGsEnabled() { } function getBrowserSettings(storage) { + var translate = Nightscout.language.translate; var json = {}; function scaleBg(bg) { @@ -56,10 +57,10 @@ function getBrowserSettings(storage) { json.alarmTimeAgoWarnMins = setDefault(json.alarmTimeAgoWarnMins, app.defaults.alarmTimeAgoWarnMins); json.alarmTimeAgoUrgent = setDefault(json.alarmTimeAgoUrgent, app.defaults.alarmTimeAgoUrgent); json.alarmTimeAgoUrgentMins = setDefault(json.alarmTimeAgoUrgentMins, app.defaults.alarmTimeAgoUrgentMins); - $('#alarm-urgenthigh-browser').prop('checked', json.alarmUrgentHigh).next().text('Urgent High Alarm' + appendThresholdValue(app.thresholds.bg_high)); - $('#alarm-high-browser').prop('checked', json.alarmHigh).next().text('High Alarm' + appendThresholdValue(app.thresholds.bg_target_top)); - $('#alarm-low-browser').prop('checked', json.alarmLow).next().text('Low Alarm' + appendThresholdValue(app.thresholds.bg_target_bottom)); - $('#alarm-urgentlow-browser').prop('checked', json.alarmUrgentLow).next().text('Urgent Low Alarm' + appendThresholdValue(app.thresholds.bg_low)); + $('#alarm-urgenthigh-browser').prop('checked', json.alarmUrgentHigh).next().text(translate('Urgent High Alarm') + appendThresholdValue(app.thresholds.bg_high)); + $('#alarm-high-browser').prop('checked', json.alarmHigh).next().text(translate('High Alarm') + appendThresholdValue(app.thresholds.bg_target_top)); + $('#alarm-low-browser').prop('checked', json.alarmLow).next().text(translate('Low Alarm') + appendThresholdValue(app.thresholds.bg_target_bottom)); + $('#alarm-urgentlow-browser').prop('checked', json.alarmUrgentLow).next().text(translate('Urgent Low Alarm') + appendThresholdValue(app.thresholds.bg_low)); $('#alarm-timeagowarn-browser').prop('checked', json.alarmTimeAgoWarn); $('#alarm-timeagowarnmins-browser').val(json.alarmTimeAgoWarnMins); $('#alarm-timeagourgent-browser').prop('checked', json.alarmTimeAgoUrgent); @@ -103,7 +104,7 @@ function getBrowserSettings(storage) { //ignore these, they are always on for now } else { var id = 'plugin-' + plugin.name; - var dd = $('
    '); + var dd = $('
    '); showPluginsSettings.append(dd); dd.find('input').prop('checked', json.showPlugins.indexOf(plugin.name) > -1); } From 423538f1a938e35bd8854c3693f21c90c588c3e9 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Wed, 22 Jul 2015 17:12:29 +0200 Subject: [PATCH 464/937] fr translation from pierre --- lib/language.js | 269 ++++++++++++++++++++++++++++++++++++++++++++-- static/index.html | 2 +- 2 files changed, 264 insertions(+), 7 deletions(-) diff --git a/lib/language.js b/lib/language.js index 4d39782b63f..eb78ac1f929 100644 --- a/lib/language.js +++ b/lib/language.js @@ -11,825 +11,1082 @@ function init() { // Server 'Listening on port' : { cs: 'Poslouchám na portu' + ,fr: 'Ecoute sur port' } // Client ,'Mo' : { cs: 'Po' ,de: 'Mo' + ,fr: 'Lu' } ,'Tu' : { cs: 'Út' ,de: 'Di' + ,fr: 'Ma' }, ',We' : { cs: 'St' ,de: 'Mi' + ,fr: 'Me' } ,'Th' : { cs: 'Čt' ,de: 'Do' + ,fr: 'Je' } ,'Fr' : { cs: 'Pá' ,de: 'Fr' + ,fr: 'Ve' } ,'Sa' : { cs: 'So' ,de: 'Sa' + ,fr: 'Sa' } ,'Su' : { cs: 'Ne' ,de: 'So' + ,fr: 'Di' } ,'Monday' : { cs: 'Pondělí' ,de: 'Montag' + ,fr: 'Lundi' } ,'Tuesday' : { cs: 'Úterý' ,de: 'Dienstag' + ,fr: 'Mardi' } ,'Wednesday' : { cs: 'Středa' ,de: 'Mittwoch' + ,fr: 'Mercredi' } ,'Thursday' : { cs: 'Čtvrtek' ,de: 'Donnerstag' + ,fr: 'Jeudi' } ,'Friday' : { cs: 'Pátek' ,de: 'Freitag' + ,fr: 'Vendredi' } ,'Saturday' : { cs: 'Sobota' ,de: 'Samstag' + ,fr: 'Samedi' } ,'Sunday' : { cs: 'Neděle' ,de: 'Sonntag' + ,fr: 'Dimanche' } ,'Category' : { cs: 'Kategorie' ,de: 'Kategorie' + ,fr: 'Catégorie' } ,'Subcategory' : { cs: 'Podkategorie' ,de: 'Unterkategorie' + ,fr: 'Sous-catégorie' } ,'Name' : { cs: 'Jméno' ,de: 'Name' + ,fr: 'Nom' } ,'Today' : { cs: 'Dnes' ,de: 'Heute' + ,fr: 'Aujourd\'hui' } ,'Last 2 days' : { cs: 'Poslední 2 dny' ,de: 'letzte 2 Tage' + ,fr: '2 derniers jours' } ,'Last 3 days' : { cs: 'Poslední 3 dny' ,de: 'letzte 3 Tage' + ,fr: '3 derniers jours' } ,'Last week' : { cs: 'Poslední týden' ,de: 'letzte Woche' + ,fr: 'Semaine Dernière' } ,'Last 2 weeks' : { cs: 'Poslední 2 týdny' ,de: 'letzte 2 Wochen' + ,fr: '2 dernières semaines' } ,'Last month' : { cs: 'Poslední měsíc' ,de: 'letzter Monat' + ,fr: 'Mois dernier' } ,'Last 3 months' : { cs: 'Poslední 3 měsíce' ,de: 'letzte 3 Monate' + ,fr: '3 derniers mois' } ,'From' : { cs: 'Od' ,de: 'Von' + ,fr: 'De' } ,'To' : { cs: 'Do' ,de: 'Bis' + ,fr: 'à' } ,'Notes' : { cs: 'Poznámky' ,de: 'Notiz' + ,fr: 'Notes' } ,'Food' : { cs: 'Jídlo' ,de: 'Essen' + ,fr: 'Nourriture' } ,'Insulin' : { cs: 'Inzulín' ,de: 'Insulin' + ,fr: 'Insuline' } ,'Carbs' : { cs: 'Sacharidy' ,de: 'Kohlenhydrate' + ,fr: 'Glucides' } ,'Notes contain' : { cs: 'Poznámky obsahují' ,de: 'Notizen beinhalten' + ,fr: 'Notes contiennent' } ,'Event type contains' : { cs: 'Typ události obsahuje' ,de: 'Ereignis-Typ beinhaltet' + ,fr: 'Type d\'événement contient' } ,'Target bg range bottom' : { cs: 'Cílová glykémie spodní' ,de: 'Untergrenze des Blutzuckerzielbereichs' + ,fr: 'Limite inférieure glycémie' } ,'top' : { cs: 'horní' ,de: 'oben' + ,fr: 'Supérieur' } ,'Show' : { cs: 'Zobraz' ,de: 'Zeige' + ,fr: 'Montrer' } ,'Display' : { cs: 'Zobraz' ,de: 'Zeige' + ,fr: 'Afficher' } ,'Loading' : { cs: 'Nahrávám' ,de: 'Laden' + ,fr: 'Chargement' } ,'Loading profile' : { cs: 'Nahrávám profil' ,de: 'Lade Profil' + ,fr: 'Chargement du profil' } ,'Loading status' : { cs: 'Nahrávám status' ,de: 'Lade Status' + ,fr: 'Statut du chargement' } ,'Loading food database' : { cs: 'Nahrávám databázi jídel' ,de: 'Lade Essensdatenbank' + ,fr: 'Chargement de la base de données alimentaire' } ,'not displayed' : { cs: 'není zobrazeno' ,de: 'nicht angezeigt' + ,fr: 'non affiché' } ,'Loading CGM data of' : { cs: 'Nahrávám CGM data' ,de: 'Lade CGM-Daten von' + ,fr: 'Chargement données CGM de' } ,'Loading treatments data of' : { cs: 'Nahrávám data ošetření' ,de: 'Lade Behandlungsdaten von' + ,fr: 'Chargement données traitement de' } ,'Processing data of' : { cs: 'Zpracovávám data' ,de: 'Verarbeite Daten von' + ,fr: 'Traitement des données de' } ,'Portion' : { cs: 'Porce' ,de: 'Portion' + ,fr: 'Portion' } ,'Size' : { cs: 'Rozměr' ,de: 'Größe' + ,fr: 'Taille' } ,'(none)' : { cs: '(Prázdný)' ,de: '(nichts)' + ,fr: '(aucun)' } ,'Result is empty' : { cs: 'Prázdný výsledek' ,de: 'Leeres Ergebnis' + ,fr: 'Pas de résultat' } // ported reporting ,'Day to day' : { cs: 'Den po dni' + ,fr: 'jour par jour' } ,'Daily Stats' : { cs: 'Denní statistiky' + ,fr: 'Stats quotidiennes' } ,'Percentile Chart' : { cs: 'Percentil' + ,fr: 'Percentiles' } ,'Distribution' : { cs: 'Rozložení' - } + ,fr: 'Distribution' + } ,'Hourly stats' : { cs: 'Statistika po hodinách' - } + ,fr: 'Statistiques horaires' + } ,'Weekly success' : { cs: 'Statistika po týdnech' + ,fr: 'Résultat hebdomadaire' } ,'No data available' : { cs: 'Žádná dostupná data' + ,fr: 'Pas de données disponibles' } ,'Low' : { cs: 'Nízká' + ,fr: 'Bas' } ,'In Range' : { cs: 'V rozsahu' + ,fr: 'dans la norme' } ,'Period' : { cs: 'Období' + ,fr: 'Période' } ,'High' : { cs: 'Vysoká' + ,fr: 'Haut' } ,'Average' : { cs: 'Průměrná' + ,fr: 'Moyenne' } ,'Low Quartile' : { cs: 'Nízký kvartil' + ,fr: 'Quartile inférieur' } ,'Upper Quartile' : { cs: 'Vysoký kvartil' + ,fr: 'Quartile supérieur' } ,'Quartile' : { cs: 'Kvartil' + ,fr: 'Quartile' } ,'Date' : { cs: 'Datum' + ,fr: 'Date' } ,'Normal' : { cs: 'Normální' + ,fr: 'Normale' } ,'Median' : { cs: 'Medián' + ,fr: 'Médiane' } ,'Readings' : { cs: 'Záznamů' + ,fr: 'Valeurs' } ,'StDev' : { cs: 'St. odchylka' + ,fr: 'Déviation St.' } ,'Daily stats report' : { cs: 'Denní statistiky' + ,fr: 'Rapport quotidien' } ,'Glucose Percentile report' : { cs: 'Tabulka percentil glykémií' + ,fr: 'Rapport precentiles Glycémie' } ,'Glucose distribution' : { cs: 'Rozložení glykémií' + ,fr: 'Distribution glycémies' } ,'days total' : { cs: 'dní celkem' + ,fr: 'jours totaux' } ,'Overall' : { cs: 'Celkem' + ,fr: 'Dans l\'ensemble' } ,'Range' : { cs: 'Rozsah' + ,fr: 'Intervalle' } ,'% of Readings' : { cs: '% záznamů' } ,'# of Readings' : { cs: 'počet záznamů' + ,fr: 'nbr de valeurs' } ,'Mean' : { cs: 'Střední hodnota' + ,fr: 'Moyenne' } ,'Standard Deviation' : { cs: 'Standardní odchylka' + ,fr: 'Déviation Standard' } ,'Max' : { cs: 'Max' + ,fr: 'Max' } ,'Min' : { cs: 'Min' + ,fr: 'Min' } ,'A1c estimation*' : { cs: 'Předpokládané HBA1c*' + ,fr: 'Estimation HbA1c*' } ,'Weekly Success' : { cs: 'Týdenní úspěšnost' + ,fr: 'Réussite hebdomadaire' } ,'There is not sufficient data to run this report. Select more days.' : { cs: 'Není dostatek dat. Vyberte delší časové období.' - } + ,fr: 'Pas assez de données pour un rapport. Sélectionnez plus de jours.' + } // food editor ,'Using stored API secret hash' : { cs: 'Používám uložený hash API hesla' + ,fr: 'Utilisation du hash API existant' } ,'No API secret hash stored yet. You need to enter API secret.' : { cs: 'Není uložený žádný hash API hesla. Musíte zadat API heslo.' + ,fr: 'Pas de secret API existant. Vous devez en entrer un.' } ,'Database loaded' : { cs: 'Databáze načtena' + ,fr: 'Base de données chargée' } ,'Error: Database failed to load' : { cs: 'Chyba při načítání databáze' + ,fr: 'Erreur, le chargement de la base de données a échoué' } ,'Create new record' : { cs: 'Vytvořit nový záznam' + ,fr: 'Créer nouvel enregistrement' } ,'Save record' : { cs: 'Uložit záznam' + ,fr: 'Sauver enregistrement' } ,'Portions' : { cs: 'Porcí' + ,fr: 'Portions' } ,'Unit' : { cs: 'Jedn' + ,fr: 'Unités' } ,'GI' : { cs: 'GI' + ,fr: 'IG' } ,'Edit record' : { cs: 'Upravit záznam' + ,fr: 'Modifier enregistrement' } ,'Delete record' : { cs: 'Smazat záznam' + ,fr: 'Effacer enregistrement' } ,'Move to the top' : { cs: 'Přesuň na začátek' + ,fr: 'Déplacer au sommet' } ,'Hidden' : { cs: 'Skrytý' + ,fr: 'Caché' } ,'Hide after use' : { cs: 'Skryj po použití' + ,fr: 'Cacher après utilisation' } ,'Your API secret must be at least 12 characters long' : { cs: 'Vaše API heslo musí mít alespoň 12 znaků' + ,fr: 'Votre secret API doit contenir au moins 12 caractères' } ,'Bad API secret' : { cs: 'Chybné API heslo' + ,fr: 'Secret API erroné' } ,'API secret hash stored' : { cs: 'Hash API hesla uložen' + ,fr: 'Hash API secret sauvegardé' } ,'Status' : { cs: 'Status' + ,fr: 'Statut' } ,'Not loaded' : { cs: 'Nenačtený' + ,fr: 'Non chargé' } ,'Food editor' : { cs: 'Editor jídel' + ,fr: 'Editeur aliments' } ,'Your database' : { cs: 'Vaše databáze' + ,fr: 'Votre base de données' } ,'Filter' : { cs: 'Filtr' + ,fr: 'Filtre' } ,'Save' : { cs: 'Ulož' + ,fr: 'Sauver' } ,'Clear' : { cs: 'Vymaž' + ,fr: 'Effacer' } ,'Record' : { cs: 'Záznam' + ,fr: 'Enregistrement' } ,'Quick picks' : { cs: 'Rychlý výběr' + ,fr: 'Sélection rapide' } ,'Show hidden' : { cs: 'Zobraz skryté' + ,fr: 'Montrer cachés' } ,'Your API secret' : { cs: 'Vaše API heslo' + ,fr: 'Votre secret API' } ,'Store hash on this computer (Use only on private computers)' : { cs: 'Ulož hash na tomto počítači (používejte pouze na soukromých počítačích)' + ,fr: 'Sauver le hash sur cet ordinateur (privé uniquement)' } ,'Treatments' : { cs: 'Ošetření' + ,fr: 'Traitements' } ,'Time' : { cs: 'Čas' + ,fr: 'Heure' } ,'Event Type' : { cs: 'Typ události' + ,fr: 'Type d\'événement' } ,'Blood Glucose' : { cs: 'Glykémie' + ,fr: 'Glycémie' } ,'Entered By' : { cs: 'Zadal' + ,fr: 'Entré par' } ,'Delete this treatment?' : { cs: 'Vymazat toto ošetření?' + ,fr: 'Effacer ce traitement?' } ,'Carbs Given' : { cs: 'Sacharidů' + ,fr: 'Glucides donnés' } ,'Inzulin Given' : { cs: 'Inzulínu' + ,fr: 'Insuline donnée' } ,'Event Time' : { cs: 'Čas události' + ,fr: 'Heure de l\'événement' } ,'Please verify that the data entered is correct' : { cs: 'Prosím zkontrolujte, zda jsou údaje zadány správně' + ,fr: 'Merci de vérifier la correction des données entrées' } ,'BG' : { cs: 'Glykémie' + ,fr: 'Glycémie' } ,'Use BG correction in calculation' : { cs: 'Použij korekci na glykémii' + ,fr: 'Utiliser la correction de glycémie dans les calculs' } ,'BG from CGM (autoupdated)' : { cs: 'Glykémie z CGM (automaticky aktualizovaná)' + ,fr: 'Glycémie CGM (automatique)' } ,'BG from meter' : { cs: 'Glykémie z glukoměru' + ,fr: 'Glycémie glucomètre' } ,'Manual BG' : { cs: 'Ručně zadaná glykémie' + ,fr: 'Glycémie manuelle' } ,'Quickpick' : { cs: 'Rychlý výběr' + ,fr: 'Sélection rapide' } ,'or' : { cs: 'nebo' + ,fr: 'ou' } ,'Add from database' : { cs: 'Přidat z databáze' + ,fr: 'Ajouter à partir de la base de données' } ,'Use carbs correction in calculation' : { cs: 'Použij korekci na sacharidy' + ,fr: 'Utiliser la correction en glucides dans les calculs' } ,'Use COB correction in calculation' : { cs: 'Použij korekci na COB' + ,fr: 'Utiliser les COB dans les calculs' } ,'Use IOB in calculation' : { cs: 'Použij IOB ve výpočtu' + ,fr: 'Utiliser l\'IOB dans les calculs' } ,'Other correction' : { cs: 'Jiná korekce' + ,fr: 'Autre correction' } ,'Rounding' : { cs: 'Zaokrouhlení' - } + ,fr: 'Arrondi' + } ,'Enter insulin correction in treatment' : { cs: 'Zahrň inzulín do záznamu ošetření' + ,fr: 'Entrer correction insuline dans le traitement' } ,'Insulin needed' : { cs: 'Potřebný inzulín' + ,fr: 'Insuline nécessaire' } ,'Carbs needed' : { cs: 'Potřebné sach' + ,fr: 'Glucides nécessaires' } ,'Carbs needed if Insulin total is negative value' : { cs: 'Chybějící sacharidy v případě, že výsledek je záporný' + ,fr: 'Glucides nécessaires si insuline totale négative' } ,'Basal rate' : { cs: 'Bazál' + ,fr: 'Taux basal' } ,'Eating' : { cs: 'Jídlo' + ,fr: 'Repas' } ,'60 minutes before' : { cs: '60 min předem' + ,fr: '60 min avant' } ,'45 minutes before' : { cs: '45 min předem' + ,fr: '45 min avant' } ,'30 minutes before' : { cs: '30 min předem' + ,fr: '30 min avant' } ,'20 minutes before' : { cs: '20 min předem' + ,fr: '20 min avant' } ,'15 minutes before' : { cs: '15 min předem' + ,fr: '15 min avant' } ,'Time in minutes' : { cs: 'Čas v minutách' + ,fr: 'Durée en minutes' } ,'15 minutes after' : { cs: '15 min po' + ,fr: '15 min après' } ,'20 minutes after' : { cs: '20 min po' + ,fr: '20 min après' } ,'30 minutes after' : { cs: '30 min po' + ,fr: '30 min après' } ,'45 minutes after' : { cs: '45 min po' + ,fr: '45 min après' } ,'60 minutes after' : { cs: '60 min po' + ,fr: '60 min après' } - ,'Additional Notes, Comments:' : { - cs: 'Dalši poznámky, komentáře:' + ,'Additional Notes, Comments' : { + cs: 'Dalši poznámky, komentáře' + ,fr: 'Notes additionnelles, commentaires' } ,'RETRO MODE' : { cs: 'V MINULOSTI' + ,fr: 'MODE RETROSPECTIF' } ,'Now' : { cs: 'Nyní' + ,fr: 'Maintenant' } ,'Other' : { cs: 'Jiný' + ,fr: 'Autre' } ,'Submit Form' : { cs: 'Odeslat formulář' + ,fr: 'Formulaire de soumission' } ,'Profile editor' : { cs: 'Editor profilu' + ,fr: 'Editeur de profil' } ,'Reporting tool' : { cs: 'Výkazy' + ,fr: 'Outil de rapport' } ,'Add food from your database' : { cs: 'Přidat jidlo z Vaší databáze' + ,fr: 'Ajouter aliment de votre base de données' } ,'Reload database' : { cs: 'Znovu nahraj databázi' + ,fr: 'Recharger la base de données' } ,'Add' : { cs: 'Přidej' + ,fr: 'Ajouter' } ,'Unauthorized' : { cs: 'Neautorizováno' + ,fr: 'Non autorisé' } ,'Entering record failed' : { cs: 'Vložení záznamu selhalo' + ,fr: 'Entrée enregistrement a échoué' } ,'Device authenticated' : { cs: 'Zařízení ověřeno' + ,fr: 'Appareil authentifié' } ,'Device not authenticated' : { cs: 'Zařízení není ověřeno' + ,fr: 'Appareil non authentifié' } ,'Authentication status' : { cs: 'Stav ověření' + ,fr: 'Status de l\'authentification' } ,'Authenticate' : { cs: 'Ověřit' + ,fr: 'Authentifier' } ,'Remove' : { cs: 'Vymazat' + ,fr: 'Retirer' } ,'Your device is not authenticated yet' : { cs: 'Toto zařízení nebylo dosud ověřeno' + ,fr: 'Votre appareil n\'est pas encore authentifié' } ,'Sensor' : { cs: 'Senzor' + ,fr: 'Senseur' } ,'Finger' : { cs: 'Glukoměr' + ,fr: 'Doigt' } ,'Manual' : { cs: 'Ručně' + ,fr: 'Manuel' } ,'Scale' : { cs: 'Měřítko' + ,fr: 'Echelle' } ,'Linear' : { cs: 'lineární' + ,fr: 'Linéaire' } ,'Logarithmic' : { cs: 'logaritmické' + ,fr: 'Logarithmique' } ,'Silence for 30 minutes' : { cs: 'Ztlumit na 30 minut' + ,fr: 'Silence pendant 30 minutes' } ,'Silence for 60 minutes' : { cs: 'Ztlumit na 60 minut' + ,fr: 'Silence pendant 60 minutes' } ,'Silence for 90 minutes' : { cs: 'Ztlumit na 90 minut' + ,fr: 'Silence pendant 90 minutes' } ,'Silence for 120 minutes' : { cs: 'Ztlumit na 120 minut' + ,fr: 'Silence pendant 120 minutes' } ,'3HR' : { cs: '3hod' + ,fr: '3hr' } ,'6HR' : { cs: '6hod' + ,fr: '6hr' } ,'12HR' : { cs: '12hod' + ,fr: '12hr' } ,'24HR' : { cs: '24hod' + ,fr: '24hr' } ,'Sttings' : { cs: 'Nastavení' + ,fr: 'Paramètres' } ,'Units' : { cs: 'Jednotky' + ,fr: 'Unités' } ,'Date format' : { cs: 'Formát datumu' + ,fr: 'Format Date' } ,'12 hours' : { cs: '12 hodin' + ,fr: '12hr' } ,'24 hours' : { cs: '24 hodin' + ,fr: '24hr' } ,'Log a Treatment' : { cs: 'Záznam ošetření' + ,fr: 'Entrer un traitement' } ,'BG Check' : { cs: 'Kontrola glykémie' + ,fr: 'Contrôle glycémie' } ,'Meal Bolus' : { cs: 'Bolus na jídlo' + ,fr: 'Bolus repas' } ,'Snack Bolus' : { cs: 'Bolus na svačinu' + ,fr: 'Bolus friandise' } ,'Correction Bolus' : { cs: 'Bolus na glykémii' + ,fr: 'Bolus de correction' } ,'Carb Correction' : { cs: 'Přídavek sacharidů' + ,fr: 'Correction glucide' } ,'Note' : { cs: 'Poznámka' + ,fr: 'Note' } ,'Question' : { cs: 'Otázka' + ,fr: 'Question' } ,'Exercise' : { cs: 'Cvičení' + ,fr: 'Exercice' } ,'Pump Site Change' : { cs: 'Přepíchnutí kanyly' + ,fr: 'Changement de site pompe' } ,'Sensor Start' : { cs: 'Spuštění sensoru' + ,fr: 'Démarrage senseur' } ,'Sensor Change' : { cs: 'Výměna sensoru' + ,fr: 'Changement senseur' } ,'Dexcom Sensor Start' : { cs: 'Spuštění sensoru' + ,fr: 'Démarrage senseur Dexcom' } ,'Dexcom Sensor Change' : { cs: 'Výměna sensoru' + ,fr: 'Changement senseur Dexcom' } ,'Insulin Cartridge Change' : { cs: 'Výměna inzulínu' + ,fr: 'Changement cartouche d\'insuline' } ,'D.A.D. Alert' : { cs: 'D.A.D. Alert' + ,fr: 'Wouf! Wouf! Chien d\'alerte diabète' } ,'Glucose Reading' : { cs: 'Hodnota glykémie' + ,fr: 'Valeur de glycémie' } ,'Measurement Method' : { cs: 'Metoda měření' + ,fr: 'Méthode de mesure' } ,'Meter' : { cs: 'Glukoměr' + ,fr: 'Glucomètre' } ,'Insulin Given' : { cs: 'Inzulín' + ,fr: 'Insuline donnée' } ,'Amount in grams' : { cs: 'Množství v gramech' + ,fr: 'Quantité en grammes' } ,'Amount in units' : { cs: 'Množství v jednotkách' + ,fr: 'Quantité en unités' } ,'View all treatments' : { cs: 'Zobraz všechny ošetření' + ,fr: 'Voir tous les traitements' } ,'Enable Alarms' : { cs: 'Povolit alarmy' + ,fr: 'Activer les alarmes' } ,'When enabled an alarm may sound.' : { cs: 'Při povoleném alarmu zní zvuk' + ,fr: 'Si activée, un alarme peut sonner.' } ,'Urgent High Alarm' : { cs: 'Urgentní vysoká glykémie' + ,fr: 'Alarme haute urgente' } ,'High Alarm' : { cs: 'Vysoká glykémie' + ,fr: 'Alarme haute' } ,'Low Alarm' : { cs: 'Nízká glykémie' + ,fr: 'Alarme basse' } ,'Urgent Low Alarm' : { cs: 'Urgentní nízká glykémie' + ,fr: 'Alarme basse urgente' } ,'Stale Data: Warn' : { cs: 'Zastaralá data' + ,fr: 'Données dépassées' } ,'Stale Data: Urgent' : { cs: 'Zastaralá data urgentní' + ,fr: 'Données dépassées urgentes' } ,'mins' : { cs: 'min' + ,fr: 'mins' } ,'Night Mode' : { cs: 'Noční mód' + ,fr: 'Mode nocturne' } ,'When enabled the page will be dimmed from 10pm - 6am.' : { cs: 'Když je povoleno, obrazovka je ztlumena 22:00 - 6:00' + ,fr: 'Si activé, la page sera assombire de 22:00 à 6:00' } ,'Enable' : { cs: 'Povoleno' + ,fr: 'activer' } ,'Settings' : { cs: 'Nastavení' + ,fr: 'Paramètres' } ,'Show Raw BG Data' : { cs: 'Zobraz RAW data' + ,fr: 'Montrer les données BG brutes' } ,'Never' : { cs: 'Nikdy' + ,fr: 'Jamais' } ,'Always' : { cs: 'Vždy' + ,fr: 'Toujours' } ,'When there is noise' : { cs: 'Při šumu' + ,fr: 'Quand il y a du bruit' } ,'When enabled small white dots will be disaplyed for raw BG data' : { cs: 'Když je povoleno, malé tečky budou zobrazeny pro RAW data' + ,fr: 'Si activé, des points blancs représenteront les données brutes' } ,'Custom Title' : { cs: 'Vlastní název stránky' + ,fr: 'Titre sur mesure' } ,'Theme' : { cs: 'Téma' + ,fr: 'Thème' } ,'Default' : { cs: 'Výchozí' + ,fr: 'Par défaut' } ,'Colors' : { cs: 'Barevné' + ,fr: 'Couleurs' } ,'Reset, and use defaults' : { cs: 'Vymaž a nastav výchozí hodnoty' + ,fr: 'Remise à zéro et utilisation des valeurs par défaut' } ,'Calibrations' : { cs: 'Kalibrace' + ,fr: 'Calibration' } ,'Alarm Test / Smartphone Enable' : { cs: 'Test alarmu' + ,fr: 'Test alarme' } ,'Bolus Wizard' : { cs: 'Bolusový kalkulátor' + ,fr: 'Calculateur de bolus' } ,'in the future' : { cs: 'v budoucnosti' + ,fr: 'dans le futur' } ,'time ago' : { cs: 'min zpět' + ,fr: 'temps avant' } ,'hr ago' : { cs: 'hod zpět' + ,fr: 'hr avant' + } ,'hrs ago' : { cs: 'hod zpět' + ,fr: 'hrs avant' } ,'min ago' : { cs: 'min zpět' + ,fr: 'min avant' } ,'mins ago' : { cs: 'min zpět' + ,fr: 'mins avant' } ,'day ago' : { cs: 'den zpět' + ,fr: 'jour avant' } ,'days ago' : { cs: 'dnů zpět' + ,fr: 'jours avant' } ,'long ago' : { cs: 'dlouho zpět' + ,fr: 'il y a très longtemps...' } ,'Clean' : { cs: 'Čistý' + ,fr: 'Propre' } ,'Light' : { cs: 'Lehký' + ,fr: 'Léger' } ,'Medium' : { cs: 'Střední' + ,fr: 'Moyen' } ,'Heavy' : { cs: 'Velký' + ,fr: 'Important' } ,'Treatment type' : { cs: 'Typ ošetření' + ,fr: 'Type de traitement' } ,'Raw BG' : { cs: 'Glykémie z RAW dat' + ,fr: 'BG brut' } ,'Device' : { cs: 'Zařízení' + ,fr: 'Appareil' } ,'Noise' : { cs: 'Šum' + ,fr: 'Bruit' } ,'Calibration' : { cs: 'Kalibrace' + ,fr: 'Calibration' } ,'Show Plugins' : { cs: 'Zobrazuj pluginy' diff --git a/static/index.html b/static/index.html index e6bf3fdabd3..f01f6f4feb7 100644 --- a/static/index.html +++ b/static/index.html @@ -223,7 +223,7 @@ - + :
    From 1aa8a67eca9f30bcbed3bcc59fabe40284ef1a2a Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 15 Aug 2015 14:13:29 -0700 Subject: [PATCH 605/937] ; --- lib/settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/settings.js b/lib/settings.js index b6b4165f5d9..08e1dedf8b2 100644 --- a/lib/settings.js +++ b/lib/settings.js @@ -123,7 +123,7 @@ function init ( ) { if (settings.enable && typeof feature === 'object' && feature.length !== undefined) { enabled = _.find(feature, function eachFeature (f) { - return settings.enable.indexOf(f) > -1 + return settings.enable.indexOf(f) > -1; }) !== undefined; } else { enabled = settings.enable && settings.enable.indexOf(feature) > -1; From 3b5cb23c80db41603ffaed92463f28cdd340d9da Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 15 Aug 2015 18:32:02 -0700 Subject: [PATCH 606/937] added language selection to the settings panel --- lib/client/browser-settings.js | 2 ++ lib/language.js | 30 ++++++++++++++++++++++++++++++ static/index.html | 16 ++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/lib/client/browser-settings.js b/lib/client/browser-settings.js index bb43ec2555a..f69cf7a160b 100644 --- a/lib/client/browser-settings.js +++ b/lib/client/browser-settings.js @@ -48,6 +48,7 @@ function init (client, plugins, serverSettings, $) { $('#theme-default-browser').prop('checked', true); } + $('#language').val(settings.language); if (settings.timeFormat === 24) { $('#24-browser').prop('checked', true); @@ -110,6 +111,7 @@ function init (client, plugins, serverSettings, $) { customTitle: $('input#customTitle').prop('value'), theme: $('input:radio[name=theme-browser]:checked').val(), timeFormat: $('input:radio[name=timeformat-browser]:checked').val(), + language: $('#language').val(), showPlugins: checkedPluginNames() }); diff --git a/lib/language.js b/lib/language.js index 526385652f7..0c9659a64ea 100644 --- a/lib/language.js +++ b/lib/language.js @@ -19,6 +19,36 @@ function init() { ,hr: 'Slušanje na portu' } // Client + ,'Language' : { + + } + , 'Bulgarian': { + + } + , 'Croatian': { + + } + , 'Czech': { + + } + , 'English': { + + } + , 'French': { + + } + , 'German': { + + } + , 'Portuguese (Brazil)': { + + } + , 'Romanian': { + + } + , 'Spanish': { + + } ,'Mo' : { cs: 'Po' ,de: 'Mo' diff --git a/static/index.html b/static/index.html index 83f11eaecbe..8f6bec8d1de 100644 --- a/static/index.html +++ b/static/index.html @@ -96,6 +96,22 @@
    +
    +
    Language
    +
    + +
    +
    Enable Alarms
    From deab2b927d36daca6ecd6c74845cfde3f682a29f Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 15 Aug 2015 18:37:33 -0700 Subject: [PATCH 607/937] make codacy happy --- lib/plugins/treatmentnotify.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/plugins/treatmentnotify.js b/lib/plugins/treatmentnotify.js index 32143e5ec93..167c895b83d 100644 --- a/lib/plugins/treatmentnotify.js +++ b/lib/plugins/treatmentnotify.js @@ -101,7 +101,9 @@ function init() { } function isCurrent(last) { - if (!last) return false; + if (!last) { + return false; + } var now = Date.now(); var lastTime = last.mills; From 6a60df5de437207019ae4d941e7d060673eaf49f Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 16 Aug 2015 00:48:41 -0700 Subject: [PATCH 608/937] include stale data in the adjustment used to transition the threshold lines --- lib/client/chart.js | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/lib/client/chart.js b/lib/client/chart.js index b5966e9e417..c1dce960a3c 100644 --- a/lib/client/chart.js +++ b/lib/client/chart.js @@ -138,6 +138,19 @@ function init (client, d3, $) { chart.context.append('g') .attr('class', 'y axis'); + function createAdjustedRange() { + var range = chart.brush.extent().slice(); + + var end = range[1].getTime() + client.forecastTime; + if (!chart.inRetroMode()) { + var lastSGVMills = client.latestSGV ? client.latestSGV.mills : client.now; + end += (client.now - lastSGVMills); + } + range[1] = new Date(end); + + return range; + } + chart.inRetroMode = function inRetroMode() { if (!chart.brush || !chart.xScale2) { return false; @@ -169,8 +182,7 @@ function init (client, d3, $) { var contextHeight = chart.contextHeight = chartHeight * .2; // get current brush extent - var currentBrushExtent = chart.brush.extent().slice(); - currentBrushExtent[1] = new Date(currentBrushExtent[1].getTime() + client.forecastTime); + var currentBrushExtent = createAdjustedRange(); // only redraw chart if chart size has changed if ((client.prevChartWidth !== chartWidth) || (client.prevChartHeight !== chartHeight)) { @@ -453,20 +465,9 @@ function init (client, d3, $) { }, DEBOUNCE_MS); - function updateDomain() { - var range = chart.brush.extent().slice(); - - var end = range[1].getTime() + client.forecastTime; - if (!chart.inRetroMode()) { - var lastSGVMills = client.latestSGV ? client.latestSGV.mills : client.now; - end += (client.now - lastSGVMills); - } - range[1] = new Date(end); - chart.xScale.domain(range); - } - chart.scroll = function scroll (nowDate) { - updateDomain(); + chart.xScale.domain(createAdjustedRange()); + // remove all insulin/carb treatment bubbles so that they can be redrawn to correct location d3.selectAll('.path').remove(); From 8524f8c5724e94c4b8346d417a5d60996c5e429f Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 16 Aug 2015 01:24:04 -0700 Subject: [PATCH 609/937] hide the upbat pill if there is no uploader batter value --- lib/plugins/upbat.js | 1 + tests/upbat.test.js | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/lib/plugins/upbat.js b/lib/plugins/upbat.js index 0276f297e0c..8bf0127528c 100644 --- a/lib/plugins/upbat.js +++ b/lib/plugins/upbat.js @@ -50,6 +50,7 @@ function init() { value: prop && prop.display , labelClass: prop && prop.level && 'icon-battery-' + prop.level , pillClass: prop && prop.status + , hide: !(prop && prop.value && prop.value >= 0) }); }; diff --git a/tests/upbat.test.js b/tests/upbat.test.js index ac7f095b9e1..27f227f7f19 100644 --- a/tests/upbat.test.js +++ b/tests/upbat.test.js @@ -43,6 +43,36 @@ describe('Uploader Battery', function ( ) { }); + it('hide the pill if there is no uploader battery status', function (done) { + var pluginBase = { + updatePillText: function mockedUpdatePillText (plugin, options) { + options.hide.should.equal(true); + done(); + } + }; + + var sandbox = require('../lib/sandbox')(); + var sbx = sandbox.clientInit(clientSettings, Date.now(), pluginBase, {}); + var upbat = require('../lib/plugins/upbat')(); + upbat.setProperties(sbx); + upbat.updateVisualisation(sbx); + }); + + it('hide the pill if there is uploader battery status is -1', function (done) { + var pluginBase = { + updatePillText: function mockedUpdatePillText (plugin, options) { + options.hide.should.equal(true); + done(); + } + }; + + var sandbox = require('../lib/sandbox')(); + var sbx = sandbox.clientInit(clientSettings, Date.now(), pluginBase, {uploaderBattery: -1}); + var upbat = require('../lib/plugins/upbat')(); + upbat.setProperties(sbx); + upbat.updateVisualisation(sbx); + }); + }); From afd0202b29c82d7186237a7db11d2ba501d8e071 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 16 Aug 2015 11:09:14 -0700 Subject: [PATCH 610/937] 0.8 beta1 bump --- bower.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index 0d720dfa593..64b94a2a75f 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "nightscout", - "version": "0.8.0-dev", + "version": "0.8.0-beta1", "dependencies": { "angularjs": "1.3.0-beta.19", "bootstrap": "~3.2.0", diff --git a/package.json b/package.json index 59403f1f22c..f21f1952251 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Nightscout", - "version": "0.8.0-dev", + "version": "0.8.0-beta1", "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": "AGPL3", "author": "Nightscout Team", From 13a5a90e19ec28197cc5cc172cf1c31895efbf47 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 16 Aug 2015 23:10:37 +0300 Subject: [PATCH 611/937] Send BWP to Pebble --- lib/pebble.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/pebble.js b/lib/pebble.js index e96fe8af32f..943ca0dcb1a 100644 --- a/lib/pebble.js +++ b/lib/pebble.js @@ -6,6 +6,7 @@ var sandbox = require('./sandbox')(); var units = require('./units')(); var iob = require('./plugins/iob')(); var delta = require('./plugins/delta')(); +var bwp = require('./plugins/boluswizardpreview')(); var DIRECTIONS = { NONE: 0 @@ -84,6 +85,14 @@ function prepareSGVs (req, sbx) { if (iobResult) { bgs[0].iob = iobResult.display; } + + sbx.properties.iob = iobResult; + var bwpResult = bwp.calc(sbx); + console.log(bwpResult); + if (bwpResult) { + bgs[0].bwp = bwpResult.bolusEstimateDisplay; + bgs[0].bwpo = bwpResult.outcomeDisplay; + } } } From 390af0e2106ecb17e5481f326e6f5646ae6e3489 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 16 Aug 2015 23:14:15 +0300 Subject: [PATCH 612/937] Remove logging --- lib/pebble.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/pebble.js b/lib/pebble.js index 943ca0dcb1a..a49710b19da 100644 --- a/lib/pebble.js +++ b/lib/pebble.js @@ -88,7 +88,6 @@ function prepareSGVs (req, sbx) { sbx.properties.iob = iobResult; var bwpResult = bwp.calc(sbx); - console.log(bwpResult); if (bwpResult) { bgs[0].bwp = bwpResult.bolusEstimateDisplay; bgs[0].bwpo = bwpResult.outcomeDisplay; From 76c40c556f7d1421bad6a1858f839046dc43147d Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 16 Aug 2015 22:59:49 -0700 Subject: [PATCH 613/937] dynamical resize chart top and height based on bottom of the pills --- lib/client/index.js | 29 ++++++++++++++++++----- static/css/main.css | 58 --------------------------------------------- 2 files changed, 23 insertions(+), 64 deletions(-) diff --git a/lib/client/index.js b/lib/client/index.js index 68466dca7f7..47614614d38 100644 --- a/lib/client/index.js +++ b/lib/client/index.js @@ -73,12 +73,6 @@ client.init = function init(serverSettings, plugins) { client.renderer = require('./renderer')(client, d3, $); client.careportal = require('./careportal')(client, $); - client.dataExtent = function dataExtent ( ) { - return client.data.length > 0 ? - d3.extent(client.data, client.entryToDate) - : d3.extent([new Date(client.now - times.hours(48).msecs), new Date(client.now)]); - }; - var timeAgo = client.utils.timeAgo; var container = $('.container') @@ -89,6 +83,16 @@ client.init = function init(serverSettings, plugins) { , statusPills = $('.status .statusPills') ; + client.dataExtent = function dataExtent ( ) { + return client.data.length > 0 ? + d3.extent(client.data, client.entryToDate) + : d3.extent([new Date(client.now - times.hours(48).msecs), new Date(client.now)]); + }; + + client.bottomOfPills = function bottomOfPills ( ) { + return minorPills.offset().top + minorPills.height(); + }; + function formatTime(time, compact) { var timeFormat = getTimeFormat(false, compact); time = d3.time.format(timeFormat)(time); @@ -266,6 +270,8 @@ client.init = function init(serverSettings, plugins) { function updatePlugins (sgvs, time) { var pluginBase = plugins.base(majorPills, minorPills, statusPills, bgStatus, client.tooltip); + var preBottomOfPills = client.bottomOfPills(); + client.sbx = sandbox.clientInit( client.settings , new Date(time).getTime() //make sure we send a timestamp @@ -283,6 +289,17 @@ client.init = function init(serverSettings, plugins) { //only shown plugins get a chance to update visualisations plugins.updateVisualisations(client.sbx); + + if (client.bottomOfPills() != preBottomOfPills) { + chart.update(false); + } + + var chartContainer = $('#chartContainer'); + + console.info('>>>>updatePlugins client.bottomOfPills()', client.bottomOfPills()); + chartContainer.css('top', (client.bottomOfPills() + 10) + 'px'); + chartContainer.find('svg').css('height', 'calc(100vh - ' + (client.bottomOfPills() + 10)+ 'px)'); + } function clearCurrentSGV ( ) { diff --git a/static/css/main.css b/static/css/main.css index f2c469594d6..1c87a86584f 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -195,7 +195,6 @@ body { } #chartContainer { - top: 225px; /*(toolbar height + status height)*/ left:0; right:0; bottom:0; @@ -206,14 +205,8 @@ body { position:absolute; } -.has-minor-pills #chartContainer { - top: 245px; -} - #chartContainer svg { - height: calc(100vh - (180px + 45px)); width: 100%; - top: calc(45px + 180px); } #silenceBtn { @@ -392,22 +385,8 @@ body { } #chartContainer { - top: 165px; font-size: 14px; } - - #chartContainer svg { - height: calc(100vh - 165px); - } - - .has-minor-pills #chartContainer { - top: 175px; - } - - #chartContainer svg { - height: calc(100vh - 175px); - } - } @media (max-width: 750px) { @@ -451,14 +430,6 @@ body { line-height: 35px; width: 250px; } - - #chartContainer { - top: 175px; - } - #chartContainer svg { - height: calc(100vh - 165px); - } - } @media (max-width: 400px) { @@ -544,22 +515,6 @@ body { .focus-range li { display: block; } - - #chartContainer { - top: 190px; - } - - #chartContainer svg { - height: calc(100vh - (190px)); - } - - .has-minor-pills #chartContainer { - top: 210px; - } - - .has-minor-pills #chartContainer svg { - height: calc(100vh - (210px)); - } } @media (max-height: 700px) { @@ -590,21 +545,8 @@ body { } #chartContainer { - top: 130px; font-size: 10px; } - - #chartContainer svg { - height: calc(100vh - 130px); - } - - .has-minor-pills #chartContainer { - top: 140px; - } - - .has-minor-pills #chartContainer svg { - height: calc(100vh - 140px); - } } @media (max-height: 480px) and (min-width: 400px) { From f930610719e7bf97add8e2ba01d682529cc11494 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 16 Aug 2015 23:05:56 -0700 Subject: [PATCH 614/937] delete pushover receipts after they are used so alarms can't be acked multiple times --- lib/pushnotify.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/pushnotify.js b/lib/pushnotify.js index 5286b9ca0bb..3117f936fa1 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -58,6 +58,7 @@ function init(env, ctx) { console.info('push ack, response: ', response, ', notify: ', notify); if (notify) { ctx.notifications.ack(notify.level, times.mins(30).msecs, true); + receipts.del(response.receipt); } return !!notify; }; From 791e4f22fad61134f0fd0dd91f1d8339d601e9c2 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 17 Aug 2015 00:02:09 -0700 Subject: [PATCH 615/937] Italian translations from @francescaneri --- lib/language.js | 806 ++++++++++++++++++++++++++++++---------------- static/index.html | 1 + 2 files changed, 530 insertions(+), 277 deletions(-) diff --git a/lib/language.js b/lib/language.js index 3ac555045b0..cafe54b7f4f 100644 --- a/lib/language.js +++ b/lib/language.js @@ -2,7 +2,7 @@ function init() { var lang; - + function language() { return language; } @@ -18,41 +18,33 @@ function init() { ,ro: 'Activ pe portul' ,bg: 'Активиране на порта' ,hr: 'Slušanje na portu' + ,it: 'Porta in ascolto' } // Client ,'Language' : { - - } + } , 'Bulgarian': { - - } + } , 'Croatian': { - - } + } , 'Czech': { - - } + } , 'English': { - - } + } , 'French': { - - } + } , 'German': { - - } + } + , 'Italian': { + } , 'Portuguese (Brazil)': { - - } + } , 'Romanian': { - - } + } , 'Spanish': { - - } + } , 'Swedish': { - - } + } ,'Mo' : { cs: 'Po' ,de: 'Mo' @@ -63,6 +55,7 @@ function init() { ,ro: 'Lu' ,bg: 'Пон' ,hr: 'Pon' + ,it: 'Lun' } ,'Tu' : { cs: 'Út' @@ -74,6 +67,7 @@ function init() { ,ro: 'Ma' ,bg: 'Вт' ,hr: 'Ut' + ,it: 'Mar' }, ',We' : { cs: 'St' @@ -85,6 +79,7 @@ function init() { ,ro: 'Mie' ,bg: 'Ср' ,hr: 'Sri' + ,it: 'Mer' } ,'Th' : { cs: 'Čt' @@ -96,6 +91,7 @@ function init() { ,ro: 'Jo' ,bg: 'Четв' ,hr: 'Čet' + ,it: 'Gio' } ,'Fr' : { cs: 'Pá' @@ -107,6 +103,7 @@ function init() { ,ro: 'Vi' ,bg: 'Пет' ,hr: 'Pet' + ,it: 'Ven' } ,'Sa' : { cs: 'So' @@ -118,6 +115,7 @@ function init() { ,ro: 'Sa' ,bg: 'Съб' ,hr: 'Sub' + ,it: 'Sab' } ,'Su' : { cs: 'Ne' @@ -129,6 +127,7 @@ function init() { ,ro: 'Du' ,bg: 'Нед' ,hr: 'Ned' + ,it: 'Dom' } ,'Monday' : { cs: 'Pondělí' @@ -140,6 +139,7 @@ function init() { ,ro: 'Luni' ,bg: 'Понеделник' ,hr: 'Ponedjeljak' + ,it: 'Lunedì' } ,'Tuesday' : { cs: 'Úterý' @@ -151,6 +151,7 @@ function init() { ,bg: 'Вторник' ,hr: 'Utorak' ,sv: 'Tisdag' + ,it: 'Martedì' } ,'Wednesday' : { cs: 'Středa' @@ -162,6 +163,7 @@ function init() { ,ro: 'Miercuri' ,bg: 'Сряда' ,hr: 'Srijeda' + ,it: 'Mercoledì' } ,'Thursday' : { cs: 'Čtvrtek' @@ -173,6 +175,7 @@ function init() { ,ro: 'Joi' ,bg: 'Четвъртък' ,hr: 'Četvrtak' + ,it: 'Giovedì' } ,'Friday' : { cs: 'Pátek' @@ -184,6 +187,7 @@ function init() { ,es: 'Viernes' ,bg: 'Петък' ,hr: 'Petak' + ,it: 'Venerdì' } ,'Saturday' : { cs: 'Sobota' @@ -195,6 +199,7 @@ function init() { ,bg: 'Събота' ,hr: 'Subota' ,sv: 'Lördag' + ,it: 'Sabato' } ,'Sunday' : { cs: 'Neděle' @@ -206,6 +211,7 @@ function init() { ,bg: 'Неделя' ,hr: 'Nedjelja' ,sv: 'Söndag' + ,it: 'Domenica' } ,'Category' : { cs: 'Kategorie' @@ -217,8 +223,9 @@ function init() { ,ro: 'Categorie' ,bg: 'Категория' ,hr: 'Kategorija' + ,it:'Categoria' } - ,'Subcategory' : { + ,'Subcategory' : { cs: 'Podkategorie' ,de: 'Unterkategorie' ,es: 'Subcategoría' @@ -228,8 +235,9 @@ function init() { ,ro: 'Subcategorie' ,bg: 'Подкатегория' ,hr: 'Podkategorija' + ,it: 'Sottocategoria' } - ,'Name' : { + ,'Name' : { cs: 'Jméno' ,de: 'Name' ,es: 'Nombre' @@ -239,8 +247,9 @@ function init() { ,ro: 'Nume' ,bg: 'Име' ,hr: 'Ime' + ,it: 'Nome' } - ,'Today' : { + ,'Today' : { cs: 'Dnes' ,de: 'Heute' ,es: 'Hoy' @@ -250,8 +259,9 @@ function init() { ,bg: 'Днес' ,hr: 'Danas' ,sv: 'Idag' + ,it: 'Oggi' } - ,'Last 2 days' : { + ,'Last 2 days' : { cs: 'Poslední 2 dny' ,de: 'letzten 2 Tage' ,es: 'Últimos 2 días' @@ -261,8 +271,9 @@ function init() { ,bg: 'Последните 2 дни' ,hr: 'Posljednja 2 dana' ,sv: 'Senaste 2 dagarna' + ,it: 'Ultimi 2 giorni' } - ,'Last 3 days' : { + ,'Last 3 days' : { cs: 'Poslední 3 dny' ,de: 'letzten 3 Tage' ,es: 'Últimos 3 días' @@ -272,8 +283,9 @@ function init() { ,ro: 'Ultimele 3 zile' ,bg: 'Последните 3 дни' ,hr: 'Posljednja 3 dana' + ,it: 'Ultimi 3 giorni' } - ,'Last week' : { + ,'Last week' : { cs: 'Poslední týden' ,de: 'letzte Woche' ,es: 'Semana pasada' @@ -283,8 +295,9 @@ function init() { ,bg: 'Последната седмица' ,hr: 'Protekli tjedan' ,sv: 'Senaste veckan' + ,it: 'Settimana scorsa' } - ,'Last 2 weeks' : { + ,'Last 2 weeks' : { cs: 'Poslední 2 týdny' ,de: 'letzten 2 Wochen' ,es: 'Últimas 2 semanas' @@ -294,8 +307,9 @@ function init() { ,bg: 'Последните 2 седмици' ,hr: 'Protekla 2 tjedna' ,sv: 'Senaste 2 veckorna' + ,it: 'Ultime 2 settimane' } - ,'Last month' : { + ,'Last month' : { cs: 'Poslední měsíc' ,de: 'letzter Monat' ,es: 'Mes pasado' @@ -305,8 +319,9 @@ function init() { ,bg: 'Последният месец' ,hr: 'Protekli mjesec' ,sv: 'Senaste månaden' + ,it: 'Mese scorso' } - ,'Last 3 months' : { + ,'Last 3 months' : { cs: 'Poslední 3 měsíce' ,de: 'letzten 3 Monate' ,es: 'Últimos 3 meses' @@ -316,8 +331,9 @@ function init() { ,bg: 'Последните 3 месеца' ,hr: 'Protekla 3 mjeseca' ,sv: 'Senaste 3 månaderna' + ,it: 'Ultimi 3 mesi' } - ,'From' : { + ,'From' : { cs: 'Od' ,de: 'Von' ,es: 'Desde' @@ -327,8 +343,9 @@ function init() { ,ro: 'De la' ,bg: 'От' ,hr: 'Od' + ,it: 'Da' } - ,'To' : { + ,'To' : { cs: 'Do' ,de: 'Bis' ,es: 'Hasta' @@ -338,8 +355,9 @@ function init() { ,bg: 'До' ,hr: 'Do' ,sv: 'Till' + ,it: 'A' } - ,'Notes' : { + ,'Notes' : { cs: 'Poznámky' ,de: 'Notiz' ,es: 'Notas' @@ -349,8 +367,9 @@ function init() { ,ro: 'Note' ,bg: 'Бележки' ,hr: 'Bilješke' + ,it: 'Note' } - ,'Food' : { + ,'Food' : { cs: 'Jídlo' ,de: 'Essen' ,es: 'Comida' @@ -360,8 +379,9 @@ function init() { ,ro: 'Mâncare' ,bg: 'Храна' ,hr: 'Hrana' + ,it: 'Cibo' } - ,'Insulin' : { + ,'Insulin' : { cs: 'Inzulín' ,de: 'Insulin' ,es: 'Insulina' @@ -371,8 +391,9 @@ function init() { ,bg: 'Инсулин' ,hr: 'Inzulin' ,sv: 'Insulin' + ,it: 'Insulina' } - ,'Carbs' : { + ,'Carbs' : { cs: 'Sacharidy' ,de: 'Kohlenhydrate' ,es: 'Hidratos de carbono' @@ -382,8 +403,9 @@ function init() { ,bg: 'Въглехидрати' ,hr: 'Ugljikohidrati' ,sv: 'Kolhydrater' + ,it: 'Carboidrati' } - ,'Notes contain' : { + ,'Notes contain' : { cs: 'Poznámky obsahují' ,de: 'Erläuterungen' ,es: 'Contenido de las notas' @@ -393,8 +415,9 @@ function init() { ,bg: 'бележките съдържат' ,hr: 'Sadržaj bilješki' ,sv: 'Notering innehåller' + ,it: 'Contiene note' } - ,'Event type contains' : { + ,'Event type contains' : { cs: 'Typ události obsahuje' ,de: 'Ereignis-Typ beinhaltet' ,es: 'Contenido del tipo de evento' @@ -404,8 +427,9 @@ function init() { ,bg: 'Типа събитие включва' ,hr: 'Sadržaj vrste događaja' ,sv: 'Händelsen innehåller' + ,it: 'Contiene evento' } - ,'Target bg range bottom' : { + ,'Target bg range bottom' : { cs: 'Cílová glykémie spodní' ,de: 'Untergrenze des Blutzuckerzielbereiches' ,es: 'Objetivo inferior de glucemia' @@ -415,8 +439,9 @@ function init() { ,bg: 'Долна граница на КЗ' ,hr: 'Ciljna donja granica GUK-a' ,sv: 'Gräns för nedre blodsockervärde' + ,it: 'Limite inferiore della glicemia' } - ,'top' : { + ,'top' : { cs: 'horní' ,de: 'oben' ,es: 'Superior' @@ -426,8 +451,9 @@ function init() { ,bg: 'горе' ,hr: 'Gornja' ,sv: 'Toppen' + ,it: 'Superiore' } - ,'Show' : { + ,'Show' : { cs: 'Zobraz' ,de: 'Zeigen' ,es: 'Mostrar' @@ -436,8 +462,9 @@ function init() { ,ro: 'Arată' ,bg: 'Покажи' ,hr: 'Prikaži' + ,it: 'Mostra' } - ,'Display' : { + ,'Display' : { cs: 'Zobraz' ,de: 'Darstellen' ,es: 'Visualizar' @@ -447,8 +474,9 @@ function init() { ,bg: 'Покажи' ,hr: 'Prikaži' ,sv: 'Visa' + ,it: 'Schermo' } - ,'Loading' : { + ,'Loading' : { cs: 'Nahrávám' ,de: 'Laden' ,es: 'Cargando' @@ -458,8 +486,9 @@ function init() { ,bg: 'Зареждане' ,hr: 'Učitavanje' ,sv: 'Laddar' + ,it: 'Sto Caricando' } - ,'Loading profile' : { + ,'Loading profile' : { cs: 'Nahrávám profil' ,de: 'Lade Profil' ,es: 'Cargando perfil' @@ -469,8 +498,9 @@ function init() { ,ro: 'Încarc profilul' ,bg: 'Зареждане на профил' ,hr: 'Učitavanje profila' + ,it: 'Sto Caricando il profilo' } - ,'Loading status' : { + ,'Loading status' : { cs: 'Nahrávám status' ,de: 'Lade Status' ,es: 'Cargando estado' @@ -480,8 +510,9 @@ function init() { ,ro: 'Încarc statusul' ,bg: 'Зареждане на статус' ,hr: 'Učitavanje statusa' + ,it: 'Stato di caricamento' } - ,'Loading food database' : { + ,'Loading food database' : { cs: 'Nahrávám databázi jídel' ,de: 'Lade Nahrungsmittel-Datenbank' ,es: 'Cargando base de datos de alimentos' @@ -491,8 +522,9 @@ function init() { ,ro: 'Încarc baza de date de alimente' ,bg: 'Зареждане на данни за храни' ,hr: 'Učitavanje baze podataka o hrani' + ,it: 'Carico dati alimenti' } - ,'not displayed' : { + ,'not displayed' : { cs: 'není zobrazeno' ,de: 'nicht angezeigt' ,es: 'No mostrado' @@ -502,8 +534,9 @@ function init() { ,bg: 'Не се показва' ,hr: 'Ne prikazuje se' ,sv: 'Visas ej' + ,it: 'Non visualizzato' } - ,'Loading CGM data of' : { + ,'Loading CGM data of' : { cs: 'Nahrávám CGM data' ,de: 'Lade CGM-Daten von' ,es: 'Cargando datos de CGM de' @@ -513,8 +546,9 @@ function init() { ,ro: 'Încarc datele CGM ale lui' ,bg: 'Зареждане на CGM данни от' ,hr: 'Učitavanja podataka CGM-a' + ,it: 'Carico dati CGM' } - ,'Loading treatments data of' : { + ,'Loading treatments data of' : { cs: 'Nahrávám data ošetření' ,de: 'Lade Behandlungsdaten von' ,es: 'Cargando datos de tratamientos de' @@ -524,8 +558,9 @@ function init() { ,ro: 'Încarc datele despre tratament pentru' ,bg: 'Зареждане на въведените лечения от' ,hr: 'Učitavanje podataka o tretmanu' + ,it: 'Carico trattamenti dei dati di' } - ,'Processing data of' : { + ,'Processing data of' : { cs: 'Zpracovávám data' ,de: 'Verarbeite Daten von' ,es: 'Procesando datos de' @@ -535,8 +570,9 @@ function init() { ,ro: 'Procesez datele lui' ,bg: 'Зареждане на данни от' ,hr: 'Obrada podataka' + ,it: 'Elaborazione dei dati di' } - ,'Portion' : { + ,'Portion' : { cs: 'Porce' ,de: 'Portion' ,es: 'Porción' @@ -546,8 +582,9 @@ function init() { ,bg: 'Порция' ,hr: 'Dio' ,sv: 'Portion' + ,it: 'Porzione' } - ,'Size' : { + ,'Size' : { cs: 'Rozměr' ,de: 'Größe' ,es: 'Tamaño' @@ -556,18 +593,20 @@ function init() { ,ro: 'Mărime' ,bg: 'Големина' ,hr: 'Veličina' + ,it: 'Formato' } - ,'(none)' : { + ,'(none)' : { cs: '(Prázdný)' ,de: '(nichts)' ,es: '(ninguno)' ,fr: '(aucun)' ,pt: '(nenhum)' ,ro: '(fără)' - ,bg: 'няма' + ,bg: '(няма)' ,hr: '(Prazno)' + ,it: '(Nessuno)' } - ,'Result is empty' : { + ,'Result is empty' : { cs: 'Prázdný výsledek' ,de: 'Leeres Ergebnis' ,es: 'Resultado vacío' @@ -576,9 +615,10 @@ function init() { ,ro: 'Fără rezultat' ,bg: 'Няма резултат' ,hr: 'Prazan rezultat' + ,it: 'Risultato vuoto' } // ported reporting - ,'Day to day' : { + ,'Day to day' : { cs: 'Den po dni' ,de: 'Von Tag zu Tag' ,es: 'Día a día' @@ -588,8 +628,9 @@ function init() { ,ro: 'Zi cu zi' ,bg: 'Ден за ден' ,hr: 'Svakodnevno' + ,it: 'Giorno per giorno' } - ,'Daily Stats' : { + ,'Daily Stats' : { cs: 'Denní statistiky' ,de: 'Tägliche Statistik' ,es: 'Estadísticas diarias' @@ -599,8 +640,9 @@ function init() { ,ro: 'Statistici zilnice' ,bg: 'Дневна статистика' ,hr: 'Dnevna statistika' + ,it: 'Statistiche giornaliere' } - ,'Percentile Chart' : { + ,'Percentile Chart' : { cs: 'Percentil' ,de: 'Durchschnittswert' ,es: 'Percentiles' @@ -610,8 +652,9 @@ function init() { ,bg: 'Процентна графика' ,hr: 'Tablica u postotcima' ,sv: 'Procentgraf' + ,it: 'Grafico percentile' } - ,'Distribution' : { + ,'Distribution' : { cs: 'Rozložení' ,de: 'Streuung' ,es: 'Distribución' @@ -621,8 +664,9 @@ function init() { ,bg: 'Разпределение' ,hr: 'Distribucija' ,sv: 'Distribution' - } - ,'Hourly stats' : { + ,it: 'Distribuzione' + } + ,'Hourly stats' : { cs: 'Statistika po hodinách' ,de: 'Stündliche Statistik' ,es: 'Estadísticas por hora' @@ -632,8 +676,9 @@ function init() { ,ro: 'Statistici orare' ,bg: 'Статистика по часове' ,hr: 'Statistika po satu' - } - ,'Weekly success' : { + ,it: 'Statistiche per ore' + } + ,'Weekly success' : { cs: 'Statistika po týdnech' ,de: 'Wöchentlicher Erfolg' ,es: 'Resultados semanales' @@ -643,8 +688,9 @@ function init() { ,bg: 'Седмичен успех' ,hr: 'Tjedni uspjeh' ,sv: 'Veckoresultat' + ,it: 'Statistiche settimanali' } - ,'No data available' : { + ,'No data available' : { cs: 'Žádná dostupná data' ,de: 'Keine Daten verfügbar' ,es: 'No hay datos disponibles' @@ -654,8 +700,9 @@ function init() { ,bg: 'Няма данни за показване' ,hr: 'Nema raspoloživih podataka' ,sv: 'Data saknas' + ,it: 'Dati non disponibili' } - ,'Low' : { + ,'Low' : { cs: 'Nízká' ,de: 'Tief' ,es: 'Bajo' @@ -665,8 +712,9 @@ function init() { ,ro: 'Prea jos' ,bg: 'Ниска' ,hr: 'Nizak' + ,it: 'Basso' } - ,'In Range' : { + ,'In Range' : { cs: 'V rozsahu' ,de: 'Im Zielbereich' ,es: 'En rango' @@ -676,8 +724,9 @@ function init() { ,ro: 'În interval' ,bg: 'В граници' ,hr: 'U rasponu' + ,it: 'In intervallo' } - ,'Period' : { + ,'Period' : { cs: 'Období' ,de: 'Zeitraum' ,es: 'Periodo' @@ -687,8 +736,9 @@ function init() { ,ro: 'Perioada' ,bg: 'Период' ,hr: 'Period' + ,it: 'Periodo' } - ,'High' : { + ,'High' : { cs: 'Vysoká' ,de: 'Hoch' ,es: 'Alto' @@ -698,8 +748,9 @@ function init() { ,ro: 'Prea sus' ,bg: 'Висока' ,hr: 'Visok' + ,it: 'Alto' } - ,'Average' : { + ,'Average' : { cs: 'Průměrná' ,de: 'Mittelwert' ,es: 'Media' @@ -709,8 +760,9 @@ function init() { ,ro: 'Media' ,bg: 'Средна' ,hr: 'Prosjek' + ,it: 'Media' } - ,'Low Quartile' : { + ,'Low Quartile' : { cs: 'Nízký kvartil' ,de: 'Unteres Quartil' ,es: 'Cuartil inferior' @@ -720,8 +772,9 @@ function init() { ,bg: 'Ниска четвъртинка' ,hr: 'Donji kvartil' ,sv: 'Nedre kvadranten' + ,it: 'Quartile basso' } - ,'Upper Quartile' : { + ,'Upper Quartile' : { cs: 'Vysoký kvartil' ,de: 'Oberes Quartil' ,es: 'Cuartil superior' @@ -731,8 +784,9 @@ function init() { ,bg: 'Висока четвъртинка' ,hr: 'Gornji kvartil' ,sv: 'Övre kvadranten' + ,it: 'Quartile alto' } - ,'Quartile' : { + ,'Quartile' : { cs: 'Kvartil' ,de: 'Quartil' ,es: 'Cuartil' @@ -741,8 +795,9 @@ function init() { ,ro: 'Pătrime' ,bg: 'Четвъртинка' ,hr: 'Kvartil' + ,it: 'Quartile' } - ,'Date' : { + ,'Date' : { cs: 'Datum' ,de: 'Datum' ,es: 'Fecha' @@ -752,8 +807,9 @@ function init() { ,ro: 'Data' ,bg: 'Дата' ,hr: 'Datum' + ,it: 'Data' } - ,'Normal' : { + ,'Normal' : { cs: 'Normální' ,de: 'Normal' ,es: 'Normal' @@ -763,8 +819,9 @@ function init() { ,ro: 'Normal' ,bg: 'Нормално' ,hr: 'Normalno' + ,it: 'Normale' } - ,'Median' : { + ,'Median' : { cs: 'Medián' ,de: 'Median' ,es: 'Mediana' @@ -774,8 +831,9 @@ function init() { ,bg: 'Средно' ,hr: 'Srednje' ,sv: 'Median' + ,it: 'Mediana' } - ,'Readings' : { + ,'Readings' : { cs: 'Záznamů' ,de: 'Messwerte' ,es: 'Valores' @@ -785,8 +843,9 @@ function init() { ,ro: 'Valori' ,bg: 'Измервания' ,hr: 'Vrijednosti' + ,it: 'Valori' } - ,'StDev' : { + ,'StDev' : { cs: 'St. odchylka' ,de: 'Standardabweichung' ,es: 'Desviación estándar' @@ -796,8 +855,9 @@ function init() { ,ro: 'Dev Std' ,bg: 'Стандартно отклонение' ,hr: 'Standardna devijacija' + ,it: 'Deviazione standard' } - ,'Daily stats report' : { + ,'Daily stats report' : { cs: 'Denní statistiky' ,de: 'Tagesstatistik Bericht' ,es: 'Informe de estadísticas diarias' @@ -807,8 +867,9 @@ function init() { ,bg: 'Дневна статистика' ,hr: 'Izvješće o dnevnim statistikama' ,sv: 'Dygnsstatistik' + ,it: 'Statistiche giornaliere' } - ,'Glucose Percentile report' : { + ,'Glucose Percentile report' : { cs: 'Tabulka percentil glykémií' ,de: 'Glukose-Prozent Bericht' ,es: 'Informe de percetiles de glucemia' @@ -818,8 +879,9 @@ function init() { ,ro: 'Raport percentile glicemii' ,bg: 'Графика на КЗ' ,hr: 'Izvješće o postotku GUK-a' + ,it: 'Percentuale Glicemie' } - ,'Glucose distribution' : { + ,'Glucose distribution' : { cs: 'Rozložení glykémií' ,de: 'Glukuse Verteilung' ,es: 'Distribución de glucemias' @@ -829,8 +891,9 @@ function init() { ,bg: 'Разпределение на КЗ' ,hr: 'Distribucija GUK-a' ,sv: 'Glukosdistribution' + ,it: 'Distribuzione glicemie' } - ,'days total' : { + ,'days total' : { cs: 'dní celkem' ,de: 'Gesamttage' ,es: 'Total de días' @@ -840,8 +903,9 @@ function init() { ,ro: 'total zile' ,bg: 'общо за деня' ,hr: 'ukupno dana' + ,it: 'Giorni totali' } - ,'Overall' : { + ,'Overall' : { cs: 'Celkem' ,de: 'Insgesamt' ,es: 'General' @@ -851,8 +915,9 @@ function init() { ,ro: 'General' ,bg: 'Общо' ,hr: 'Ukupno' + ,it: 'Generale' } - ,'Range' : { + ,'Range' : { cs: 'Rozsah' ,de: 'Bereich' ,es: 'Intervalo' @@ -862,8 +927,9 @@ function init() { ,ro: 'Interval' ,bg: 'Диапазон' ,hr: 'Raspon' + ,it: 'Intervallo' } - ,'% of Readings' : { + ,'% of Readings' : { cs: '% záznamů' ,de: '% der Messwerte' ,es: '% de valores' @@ -873,8 +939,9 @@ function init() { ,ro: '% de valori' ,bg: '% от измервания' ,hr: '% očitanja' + ,it: '% dei valori' } - ,'# of Readings' : { + ,'# of Readings' : { cs: 'počet záznamů' ,de: '# der Messwerte' ,es: 'N° de valores' @@ -884,8 +951,9 @@ function init() { ,ro: 'nr. de valori' ,bg: '№ от измервания' ,hr: 'broj očitanja' + ,it: '# di valori' } - ,'Mean' : { + ,'Mean' : { cs: 'Střední hodnota' ,de: 'Durchschnittlich' ,es: 'Media' @@ -895,8 +963,9 @@ function init() { ,ro: 'Medie' ,bg: 'Средна стойност' ,hr: 'Prosjek' + ,it: 'Medio' } - ,'Standard Deviation' : { + ,'Standard Deviation' : { cs: 'Standardní odchylka' ,de: 'Standardabweichung' ,es: 'Desviación estándar' @@ -906,8 +975,9 @@ function init() { ,bg: 'Стандартно отклонение' ,hr: 'Standardna devijacija' ,sv: 'Standardavvikelse' + ,it: 'Deviazione Standard' } - ,'Max' : { + ,'Max' : { cs: 'Max' ,de: 'Max' ,es: 'Max' @@ -917,8 +987,9 @@ function init() { ,ro: 'Max' ,bg: 'Максимално' ,hr: 'Max' + ,it: 'Max' } - ,'Min' : { + ,'Min' : { cs: 'Min' ,de: 'Min' ,es: 'Min' @@ -928,8 +999,9 @@ function init() { ,ro: 'Min' ,bg: 'Минимално' ,hr: 'Min' + ,it: 'Min' } - ,'A1c estimation*' : { + ,'A1c estimation*' : { cs: 'Předpokládané HBA1c*' ,de: 'Prognose HbA1c*' ,es: 'Estimación de HbA1c*' @@ -939,8 +1011,9 @@ function init() { ,bg: 'Очакван HbA1c' ,hr: 'Procjena HbA1c-a' ,sv: 'Beräknat A1c-värde ' + ,it: 'stima A1c' } - ,'Weekly Success' : { + ,'Weekly Success' : { cs: 'Týdenní úspěšnost' ,de: 'Wöchtlicher Erfolg' ,es: 'Resultados semanales' @@ -950,8 +1023,9 @@ function init() { ,bg: 'Седмичен успех' ,hr: 'Tjedni uspjeh' ,sv: 'Veckoresultat' + ,it: 'Risultati settimanali' } - ,'There is not sufficient data to run this report. Select more days.' : { + ,'There is not sufficient data to run this report. Select more days.' : { cs: 'Není dostatek dat. Vyberte delší časové období.' ,de: 'Für diesen Bericht sind nicht genug Daten verfügbar. Bitte weitere Tage auswählen' ,es: 'No hay datos suficientes para generar este informe. Seleccione más días.' @@ -961,9 +1035,10 @@ function init() { ,bg: 'Няма достатъчно данни за показване. Изберете повече дни.' ,hr: 'Nema dovoljno podataka za izvođenje izvještaja. Odaberite još dana.' ,sv: 'Data saknas för att köra rapport. Välj fler dagar.' - } + ,it: 'Non ci sono dati sufficienti per eseguire questo rapporto. Selezionare più giorni.' + } // food editor - ,'Using stored API secret hash' : { + ,'Using stored API secret hash' : { cs: 'Používám uložený hash API hesla' ,de: 'Gespeicherte API-Prüfsumme verwenden' ,es: 'Usando el hash del API pre-almacenado' @@ -973,8 +1048,9 @@ function init() { ,bg: 'Използване на запаметена API парола' ,hr: 'Koristi se pohranjeni API tajni hash' ,sv: 'Använd hemlig API-nyckel' + ,it: 'Stai utilizzando API hash segreta' } - ,'No API secret hash stored yet. You need to enter API secret.' : { + ,'No API secret hash stored yet. You need to enter API secret.' : { cs: 'Není uložený žádný hash API hesla. Musíte zadat API heslo.' ,de: 'Keine API-Prüfsumme gespeichert. Bitte API-Prüfsumme eingeben.' ,es: 'No se ha almacenado ningún hash todavía. Debe introducir su secreto API.' @@ -984,8 +1060,9 @@ function init() { ,bg: 'Няма запаметена API парола. Tрябва да въведете API парола' ,hr: 'Nema pohranjenog API tajnog hasha. Unesite tajni API' ,sv: 'Hemlig api-nyckel saknas. Du måste ange API hemlighet' - } - ,'Database loaded' : { + ,it: 'No API hash segreto ancora memorizzato. È necessario inserire API segreto.' + } + ,'Database loaded' : { cs: 'Databáze načtena' ,de: 'Datenbank geladen' ,es: 'Base de datos cargada' @@ -995,8 +1072,9 @@ function init() { ,bg: 'База с данни заредена' ,hr: 'Baza podataka je učitana' ,sv: 'Databas laddad' + ,it: 'Database caricato' } - ,'Error: Database failed to load' : { + ,'Error: Database failed to load' : { cs: 'Chyba při načítání databáze' ,de: 'Fehler: Datenbank konnte nicht geladen werden' ,es: 'Error: Carga de base de datos fallida' @@ -1006,8 +1084,9 @@ function init() { ,bg: 'ГРЕШКА. Базата с данни не успя да се зареди' ,hr: 'Greška: Baza podataka nije učitana' ,sv: 'Error: Databas kan ej laddas' + ,it: 'Errore: database non è stato caricato' } - ,'Create new record' : { + ,'Create new record' : { cs: 'Vytvořit nový záznam' ,de: 'Erstelle neuen Datensatz' ,es: 'Crear nuevo registro' @@ -1017,8 +1096,9 @@ function init() { ,bg: 'Създаване на нов запис' ,hr: 'Kreiraj novi zapis' ,sv: 'Skapa ny post' + ,it: 'Crea nuovo registro' } - ,'Save record' : { + ,'Save record' : { cs: 'Uložit záznam' ,de: 'Speichere Datensatz' ,es: 'Guardar registro' @@ -1028,8 +1108,9 @@ function init() { ,bg: 'Запази запис' ,hr: 'Spremi zapis' ,sv: 'Spara post' + ,it: 'Salva Registro' } - ,'Portions' : { + ,'Portions' : { cs: 'Porcí' ,de: 'Portionen' ,es: 'Porciones' @@ -1039,8 +1120,9 @@ function init() { ,bg: 'Порции' ,hr: 'Dijelovi' ,sv: 'Portion' + ,it: 'Porzioni' } - ,'Unit' : { + ,'Unit' : { cs: 'Jedn' ,de: 'Einheit' ,es: 'Unidades' @@ -1050,8 +1132,9 @@ function init() { ,bg: 'Единици' ,hr: 'Jedinica' ,sv: 'Enhet' + ,it: 'Unità' } - ,'GI' : { + ,'GI' : { cs: 'GI' ,de: 'GI' ,es: 'IG' @@ -1061,8 +1144,9 @@ function init() { ,ro: 'CI' ,bg: 'ГИ' ,hr: 'GI' + ,it: 'GI' } - ,'Edit record' : { + ,'Edit record' : { cs: 'Upravit záznam' ,de: 'Bearbeite Datensatz' ,es: 'Editar registro' @@ -1072,8 +1156,9 @@ function init() { ,bg: 'Редактирай запис' ,hr: 'Uredi zapis' ,sv: 'Editera post' + ,it: 'Modifica registro' } - ,'Delete record' : { + ,'Delete record' : { cs: 'Smazat záznam' ,de: 'Lösche Datensatz' ,es: 'Borrar registro' @@ -1083,8 +1168,9 @@ function init() { ,bg: 'Изтрий запис' ,hr: 'Izbriši zapis' ,sv: 'Ta bort post' + ,it: 'Cancella registro' } - ,'Move to the top' : { + ,'Move to the top' : { cs: 'Přesuň na začátek' ,de: 'Gehe zum Anfang' ,es: 'Mover arriba' @@ -1094,8 +1180,9 @@ function init() { ,ro: 'Mergi la început' ,bg: 'Преместване в началото' ,hr: 'Premjesti na vrh' + ,it: 'Spostare verso l\'alto' } - ,'Hidden' : { + ,'Hidden' : { cs: 'Skrytý' ,de: 'Versteckt' ,es: 'Oculto' @@ -1105,8 +1192,9 @@ function init() { ,ro: 'Ascuns' ,bg: 'Скрити' ,hr: 'Skriveno' + ,it: 'Nascosto' } - ,'Hide after use' : { + ,'Hide after use' : { cs: 'Skryj po použití' ,de: 'Verberge nach Gebrauch' ,es: 'Ocultar después de utilizar' @@ -1116,8 +1204,9 @@ function init() { ,bg: 'Скрий след употреба' ,hr: 'Sakrij nakon korištenja' ,sv: 'Dölj efter användning' + ,it: 'Nascondi dopo l\'uso' } - ,'Your API secret must be at least 12 characters long' : { + ,'Your API secret must be at least 12 characters long' : { cs: 'Vaše API heslo musí mít alespoň 12 znaků' ,de: 'Deine API-Prüfsumme muss mindestens 12 Zeichen lang sein' ,es: 'Su secreo API debe contener al menos 12 caracteres' @@ -1127,8 +1216,9 @@ function init() { ,bg: 'Вашата АPI парола трябва да е дълга поне 12 символа' ,hr: 'Vaš tajni API mora sadržavati barem 12 znakova' ,sv: 'Hemlig API-nyckel måsta innehålla 12 tecken' + ,it: 'il vostro API secreto deve essere lungo almeno 12 caratteri' } - ,'Bad API secret' : { + ,'Bad API secret' : { cs: 'Chybné API heslo' ,de: 'Fehlerhafte API-Prüfsumme' ,es: 'Secreto API incorrecto' @@ -1138,8 +1228,9 @@ function init() { ,bg: 'Некоректна API парола' ,hr: 'Neispravan tajni API' ,sv: 'Felaktig API-nyckel' + ,it: 'API secreto incorretto' } - ,'API secret hash stored' : { + ,'API secret hash stored' : { cs: 'Hash API hesla uložen' ,de: 'API-Prüfsumme gespeichert' ,es: 'Hash de secreto API guardado' @@ -1149,8 +1240,9 @@ function init() { ,bg: 'УРА! API парола запаметена' ,hr: 'API tajni hash je pohranjen' ,sv: 'Hemlig API-hash lagrad' + ,it: 'Hash API secreto memorizzato' } - ,'Status' : { + ,'Status' : { cs: 'Status' ,de: 'Status' ,es: 'Estado' @@ -1160,8 +1252,9 @@ function init() { ,ro: 'Status' ,bg: 'Статус' ,hr: 'Status' + ,it: 'Stato' } - ,'Not loaded' : { + ,'Not loaded' : { cs: 'Nenačtený' ,de: 'Nicht geladen' ,es: 'No cargado' @@ -1171,8 +1264,9 @@ function init() { ,bg: 'Не е заредено' ,hr: 'Nije učitano' ,sv: 'Ej laddad' + ,it: 'Non caricato' } - ,'Food editor' : { + ,'Food editor' : { cs: 'Editor jídel' ,de: 'Nahrungsmittel Editor' ,es: 'Editor de alimentos' @@ -1182,8 +1276,9 @@ function init() { ,bg: 'Редактор за храна' ,hr: 'Editor hrane' ,sv: 'Födoämneseditor' + ,it: 'Modifica alimenti' } - ,'Your database' : { + ,'Your database' : { cs: 'Vaše databáze' ,de: 'Deine Datenbank' ,es: 'Su base de datos' @@ -1193,8 +1288,9 @@ function init() { ,ro: 'Baza de date' ,bg: 'Твоята база с данни' ,hr: 'Vaša baza podataka' + ,it: 'Vostro database' } - ,'Filter' : { + ,'Filter' : { cs: 'Filtr' ,de: 'Filter' ,es: 'Filtro' @@ -1204,8 +1300,9 @@ function init() { ,ro: 'Filtru' ,bg: 'Филтър' ,hr: 'Filter' + ,it: 'Filtro' } - ,'Save' : { + ,'Save' : { cs: 'Ulož' ,de: 'Speichern' ,es: 'Salvar' @@ -1215,8 +1312,9 @@ function init() { ,bg: 'Запази' ,hr: 'Spremi' ,sv: 'Spara' + ,it: 'Salva' } - ,'Clear' : { + ,'Clear' : { cs: 'Vymaž' ,de: 'Löschen' ,es: 'Limpiar' @@ -1226,8 +1324,9 @@ function init() { ,bg: 'Изчисти' ,hr: 'Očisti' ,sv: 'Rensa' + ,it: 'Pulisci' } - ,'Record' : { + ,'Record' : { cs: 'Záznam' ,de: 'Datensatz' ,es: 'Guardar' @@ -1237,8 +1336,9 @@ function init() { ,ro: 'Înregistrare' ,bg: 'Запиши' ,hr: 'Zapis' + ,it: 'Registro' } - ,'Quick picks' : { + ,'Quick picks' : { cs: 'Rychlý výběr' ,de: 'Schnellauswahl' ,es: 'Selección rápida' @@ -1248,8 +1348,9 @@ function init() { ,bg: 'Бърз избор' ,hr: 'Brzi izbor' ,sv: 'Snabbval' + ,it: 'Scelta rapida' } - ,'Show hidden' : { + ,'Show hidden' : { cs: 'Zobraz skryté' ,de: 'Zeige verborgen' ,es: 'Mostrar ocultos' @@ -1259,8 +1360,9 @@ function init() { ,bg: 'Покажи скритото' ,hr: 'Prikaži skriveno' ,sv: 'Visa dolda' + ,it: 'Mostra nascosto' } - ,'Your API secret' : { + ,'Your API secret' : { cs: 'Vaše API heslo' ,de: 'Deine API Prüfsumme' ,es: 'Su secreto API' @@ -1270,8 +1372,9 @@ function init() { ,ro: 'Cheia API' ,bg: 'Твоята API парола' ,hr: 'Vaš tajni API' + ,it: 'Il tuo API secreto' } - ,'Store hash on this computer (Use only on private computers)' : { + ,'Store hash on this computer (Use only on private computers)' : { cs: 'Ulož hash na tomto počítači (používejte pouze na soukromých počítačích)' ,de: 'Speichere Prüfsumme auf diesem Computer (nur auf privaten Computern anwenden)' ,es: 'Guardar hash en este ordenador (Usar solo en ordenadores privados)' @@ -1281,8 +1384,9 @@ function init() { ,bg: 'Запамети данните на този компютър. ( Използвай само на собствен компютър)' ,hr: 'Pohrani hash na ovom računalu (Koristiti samo na osobnom računalu)' ,sv: 'Lagra hashvärde på denna dator (använd endast på privat dator)' + ,it: 'Conservare hash su questo computer (utilizzare solo su computer privati)' } - ,'Treatments' : { + ,'Treatments' : { cs: 'Ošetření' ,de: 'Bearbeitung' ,es: 'Tratamientos' @@ -1292,8 +1396,9 @@ function init() { ,ro: 'Tratamente' ,bg: 'Събития' ,hr: 'Tretmani' + ,it: 'Somministrazione' } - ,'Time' : { + ,'Time' : { cs: 'Čas' ,de: 'Zeit' ,es: 'Hora' @@ -1303,8 +1408,9 @@ function init() { ,ro: 'Ora' ,bg: 'Време' ,hr: 'Vrijeme' + ,it: 'Tempo' } - ,'Event Type' : { + ,'Event Type' : { cs: 'Typ události' ,de: 'Ereignis-Typ' ,es: 'Tipo de evento' @@ -1314,8 +1420,9 @@ function init() { ,ro: 'Tip eveniment' ,bg: 'Вид събитие' ,hr: 'Vrsta događaja' + ,it: 'Tipo di evento' } - ,'Blood Glucose' : { + ,'Blood Glucose' : { cs: 'Glykémie' ,de: 'Blutglukose' ,es: 'Glucemia' @@ -1325,8 +1432,9 @@ function init() { ,ro: 'Glicemie' ,bg: 'Кръвна захар' ,hr: 'GUK' + ,it: 'Glicemia' } - ,'Entered By' : { + ,'Entered By' : { cs: 'Zadal' ,de: 'Eingabe durch' ,es: 'Introducido por' @@ -1336,8 +1444,9 @@ function init() { ,ro: 'Introdus de' ,bg: 'Въведено от' ,hr: 'Unos izvršio' + ,it: 'inserito da' } - ,'Delete this treatment?' : { + ,'Delete this treatment?' : { cs: 'Vymazat toto ošetření?' ,de: 'Bearbeitung löschen' ,es: '¿Borrar este tratamiento?' @@ -1347,8 +1456,9 @@ function init() { ,bg: 'Изтрий това събитие' ,hr: 'Izbriši ovaj tretman?' ,sv: 'Ta bort händelse?' + ,it: 'Eliminare questa somministrazione?' } - ,'Carbs Given' : { + ,'Carbs Given' : { cs: 'Sacharidů' ,de: 'Kohlenhydratgabe' ,es: 'Hidratos de carbono dados' @@ -1358,8 +1468,9 @@ function init() { ,bg: 'ВХ' ,hr: 'Količina UH' ,sv: 'Antal kolhydrater' + ,it: 'Carboidrati' } - ,'Inzulin Given' : { + ,'Inzulin Given' : { cs: 'Inzulínu' ,de: 'Insulingabe' ,es: 'Insulina dada' @@ -1369,8 +1480,9 @@ function init() { ,bg: 'Инсулин' ,hr: 'Količina inzulina' ,sv: 'Insulin' + ,it: 'Insulina' } - ,'Event Time' : { + ,'Event Time' : { cs: 'Čas události' ,de: 'Ereignis Zeit' ,es: 'Hora del evento' @@ -1380,8 +1492,9 @@ function init() { ,ro: 'Ora evenimentului' ,bg: 'Въвеждане' ,hr: 'Vrijeme događaja' + ,it: 'Ora Evento' } - ,'Please verify that the data entered is correct' : { + ,'Please verify that the data entered is correct' : { cs: 'Prosím zkontrolujte, zda jsou údaje zadány správně' ,de: 'Bitte Daten auf Plausibilität prüfen' ,es: 'Por favor, verifique que los datos introducidos son correctos' @@ -1391,8 +1504,9 @@ function init() { ,bg: 'Моля проверете, че датата е въведена правилно' ,hr: 'Molim Vas provjerite jesu li uneseni podaci ispravni' ,sv: 'Vänligen verifiera att inlagd data stämmer' + ,it: 'Si prega di verificare che i dati inseriti sono corretti' } - ,'BG' : { + ,'BG' : { cs: 'Glykémie' ,de: 'Blutglukose' ,es: 'Glucemia en sangre' @@ -1402,8 +1516,9 @@ function init() { ,ro: 'Glicemie' ,bg: 'КЗ' ,hr: 'GUK' + ,it: 'Glicemia' } - ,'Use BG correction in calculation' : { + ,'Use BG correction in calculation' : { cs: 'Použij korekci na glykémii' ,de: 'Verwende Blutglukose-Korrektur zur Kalkulation' ,es: 'Usar la corrección de glucemia en los cálculos' @@ -1413,8 +1528,9 @@ function init() { ,bg: 'Въведи корекция за КЗ ' ,hr: 'Koristi korekciju GUK-a u izračunu' ,sv: 'Använd BS-korrektion för uträkning' + ,it: 'Utilizzare la correzione Glicemia nei calcoli' } - ,'BG from CGM (autoupdated)' : { + ,'BG from CGM (autoupdated)' : { cs: 'Glykémie z CGM (automaticky aktualizovaná)' ,de: 'Blutglukose vom CGM (Autoupdate)' ,es: 'Glucemia del sensor (Actualizado automáticamente)' @@ -1424,8 +1540,9 @@ function init() { ,ro: 'Glicemie în senzor (automat)' ,bg: 'КЗ от сензора (автоматично)' ,hr: 'GUK sa CGM-a (ažuriran automatski)' + ,it: 'Glicemie da CGM (aggiornamento automatico)' } - ,'BG from meter' : { + ,'BG from meter' : { cs: 'Glykémie z glukoměru' ,de: 'Blutzucker vom Messgerät' ,es: 'Glucemia del glucómetro' @@ -1435,8 +1552,9 @@ function init() { ,ro: 'Glicemie în glucometru' ,bg: 'КЗ от глюкомер' ,hr: 'GUK s glukometra' + ,it: 'Glicemie da glucometro' } - ,'Manual BG' : { + ,'Manual BG' : { cs: 'Ručně zadaná glykémie' ,de: 'Blutzucker händisch' ,es: 'Glucemia manual' @@ -1446,6 +1564,7 @@ function init() { ,bg: 'Ръчно въведена КЗ' ,hr: 'Ručno unesen GUK' ,sv: 'Manuellt BS' + ,it: 'Inserisci Glicemia' } ,'Quickpick' : { cs: 'Rychlý výběr' @@ -1457,6 +1576,7 @@ function init() { ,bg: 'Бърз избор' ,hr: 'Brzi izbor' ,sv: 'Snabbval' + ,it: 'Scelta rapida' } ,'or' : { cs: 'nebo' @@ -1468,8 +1588,9 @@ function init() { ,ro: 'sau' ,bg: 'или' ,hr: 'ili' + ,it: 'o' } - ,'Add from database' : { + ,'Add from database' : { cs: 'Přidat z databáze' ,de: 'Ergänzt aus Datenbank' ,es: 'Añadir desde la base de datos' @@ -1479,8 +1600,9 @@ function init() { ,bg: 'Добави към базата с данни' ,hr: 'Dodaj iz baze podataka' ,sv: 'Lägg till från databas' + ,it: 'Aggiungi dal database' } - ,'Use carbs correction in calculation' : { + ,'Use carbs correction in calculation' : { cs: 'Použij korekci na sacharidy' ,de: 'Verwende Kohlenhydrate-Korrektur zur Kalkulation' ,es: 'Usar la corrección de hidratos de carbono en los cálculos' @@ -1490,8 +1612,9 @@ function init() { ,bg: 'Въведи корекция за въглехидратите' ,hr: 'Koristi korekciju za UH u izračunu' ,sv: 'Använd kolhydratkorrektion i utäkning' + ,it: 'Utilizzare la correzione con carboidrati nel calcolo' } - ,'Use COB correction in calculation' : { + ,'Use COB correction in calculation' : { cs: 'Použij korekci na COB' ,de: 'Verwende verzehrte Kohlenhydrate zur Kalkulation' ,es: 'Usar la corrección de COB en los cálculos' @@ -1501,8 +1624,9 @@ function init() { ,bg: 'Въведи корекция за останалите въглехидрати' ,hr: 'Koristi aktivne UH u izračunu' ,sv: 'Använd aktiva kolhydrater för beräkning' + ,it: 'Utilizzare la correzione COB nel calcolo' } - ,'Use IOB in calculation' : { + ,'Use IOB in calculation' : { cs: 'Použij IOB ve výpočtu' ,de: 'Verwende gespritzes Insulin zur Kalkulation' ,es: 'Usar la IOB en los cálculos' @@ -1512,8 +1636,9 @@ function init() { ,bg: 'Използвай активния инсулин' ,hr: 'Koristi aktivni inzulin u izračunu"' ,sv: 'Använd aktivt insulin för uträkning' + ,it: 'Utilizzare la correzione IOB nel calcolo' } - ,'Other correction' : { + ,'Other correction' : { cs: 'Jiná korekce' ,de: 'Weitere Korrektur' ,es: 'Otra correción' @@ -1523,8 +1648,9 @@ function init() { ,bg: 'Друга корекция' ,hr: 'Druga korekcija' ,sv: 'Övrig korrektion' + ,it: 'Altre correzioni' } - ,'Rounding' : { + ,'Rounding' : { cs: 'Zaokrouhlení' ,de: 'Gerundet' ,es: 'Redondeo' @@ -1534,8 +1660,9 @@ function init() { ,ro: 'Rotunjire' ,bg: 'Закръгляне' ,hr: 'Zaokruživanje' - } - ,'Enter insulin correction in treatment' : { + ,it: 'Arrotondamento' + } + ,'Enter insulin correction in treatment' : { cs: 'Zahrň inzulín do záznamu ošetření' ,de: 'Insulin Korrektur zur Behandlung eingeben' ,es: 'Introducir correción de insulina en tratamiento' @@ -1545,8 +1672,9 @@ function init() { ,bg: 'Въведи корекция с инсулин като лечение' ,hr: 'Unesi korekciju inzulinom u tretman' ,sv: 'Ange insulinkorrektion för händelse' + ,it: 'Inserisci correzione insulina nella somministrazione' } - ,'Insulin needed' : { + ,'Insulin needed' : { cs: 'Potřebný inzulín' ,de: 'Benötigtes Insulin' ,es: 'Insulina necesaria' @@ -1556,8 +1684,9 @@ function init() { ,bg: 'Необходим инсулин' ,hr: 'Potrebno inzulina' ,sv: 'Beräknad insulinmängd' + ,it: 'Insulina necessaria' } - ,'Carbs needed' : { + ,'Carbs needed' : { cs: 'Potřebné sach' ,de: 'Benötigte Kohlenhydrate' ,es: 'Hidratos de carbono necesarios' @@ -1567,8 +1696,9 @@ function init() { ,bg: 'Необходими въглехидрати' ,hr: 'Potrebno UH' ,sv: 'Beräknad kolhydratmängd' + ,it: 'Carboidrati necessari' } - ,'Carbs needed if Insulin total is negative value' : { + ,'Carbs needed if Insulin total is negative value' : { cs: 'Chybějící sacharidy v případě, že výsledek je záporný' ,de: 'Benötigte Kohlenhydrate sofern Gesamtinsulin einen negativen Wert aufweist' ,es: 'Hidratos de carbono necesarios si el total de insulina es un valor negativo' @@ -1578,8 +1708,9 @@ function init() { ,bg: 'Необходими въглехидрати, ако няма инсулин' ,hr: 'Potrebno UH ako je ukupna vrijednost inzulina negativna' ,sv: 'Nödvändig kolhydratmängd för angiven insulinmängd' + ,it: 'Carboidrati necessari se l\'insulina totale è un valore negativo' } - ,'Basal rate' : { + ,'Basal rate' : { cs: 'Bazál' ,de: 'Basalrate' ,es: 'Tasa basal' @@ -1589,8 +1720,9 @@ function init() { ,bg: 'Базален инсулин' ,hr: 'Bazal' ,sv: 'Basaldos' + ,it: 'Velocità basale' } - ,'60 minutes earlier' : { + ,'60 minutes earlier' : { cs: '60 min předem' ,de: '60 Min. früher' ,es: '60 min antes' @@ -1600,8 +1732,9 @@ function init() { ,ro: 'acum 60 min' ,bg: 'Преди 60 минути' ,hr: 'Prije 60 minuta' + ,it: '60 minuti prima' } - ,'45 minutes earlier' : { + ,'45 minutes earlier' : { cs: '45 min předem' ,de: '45 Min. früher' ,es: '45 min antes' @@ -1611,8 +1744,9 @@ function init() { ,ro: 'acum 45 min' ,bg: 'Преди 45 минути' ,hr: 'Prije 45 minuta' + ,it: '45 minuti prima' } - ,'30 minutes earlier' : { + ,'30 minutes earlier' : { cs: '30 min předem' ,de: '30 Min früher' ,es: '30 min antes' @@ -1622,8 +1756,9 @@ function init() { ,ro: 'acum 30 min' ,bg: 'Преди 30 минути' ,hr: 'Prije 30 minuta' + ,it: '30 minuti prima' } - ,'20 minutes earlier' : { + ,'20 minutes earlier' : { cs: '20 min předem' ,de: '20 Min. früher' ,es: '20 min antes' @@ -1633,8 +1768,9 @@ function init() { ,ro: 'acum 20 min' ,bg: 'Преди 20 минути' ,hr: 'Prije 20 minuta' + ,it: '20 minuti prima' } - ,'15 minutes earlier' : { + ,'15 minutes earlier' : { cs: '15 min předem' ,de: '15 Min. früher' ,es: '15 min antes' @@ -1644,8 +1780,9 @@ function init() { ,ro: 'acu 15 min' ,bg: 'Преди 15 минути' ,hr: 'Prije 15 minuta' + ,it: '15 minuti prima' } - ,'Time in minutes' : { + ,'Time in minutes' : { cs: 'Čas v minutách' ,de: 'Zeit in Minuten' ,es: 'Tiempo en minutos' @@ -1655,8 +1792,9 @@ function init() { ,ro: 'Timp în minute' ,bg: 'Времето в минути' ,hr: 'Vrijeme u minutama' + ,it: 'Tempo in minuti' } - ,'15 minutes later' : { + ,'15 minutes later' : { cs: '15 min po' ,de: '15 Min. später' ,es: '15 min más tarde' @@ -1665,8 +1803,9 @@ function init() { ,bg: 'След 15 минути' ,hr: '15 minuta kasnije' ,sv: '15 min senare' + ,it: '15 minuti più tardi' } - ,'20 minutes later' : { + ,'20 minutes later' : { cs: '20 min po' ,de: '20 Min. später' ,es: '20 min más tarde' @@ -1676,8 +1815,9 @@ function init() { ,bg: 'След 20 минути' ,hr: '20 minuta kasnije' ,sv: '20 min senare' + ,it: '20 minuti più tardi' } - ,'30 minutes later' : { + ,'30 minutes later' : { cs: '30 min po' ,de: '30 Min. später' ,es: '30 min más tarde' @@ -1687,8 +1827,9 @@ function init() { ,bg: 'След 30 минути' ,hr: '30 minuta kasnije' ,sv: '30 min senare' + ,it: '30 minuti più tardi' } - ,'45 minutes later' : { + ,'45 minutes later' : { cs: '45 min po' ,de: '45 Min. später' ,es: '45 min más tarde' @@ -1698,8 +1839,9 @@ function init() { ,bg: 'След 45 минути' ,hr: '45 minuta kasnije' ,sv: '45 min senare' + ,it: '45 minuti più tardi' } - ,'60 minutes later' : { + ,'60 minutes later' : { cs: '60 min po' ,de: '60 Min. später' ,es: '60 min más tarde' @@ -1709,8 +1851,9 @@ function init() { ,bg: 'След 60 минути' ,hr: '60 minuta kasnije' ,sv: '60 min senare' + ,it: '60 minuti più tardi' } - ,'Additional Notes, Comments' : { + ,'Additional Notes, Comments' : { cs: 'Dalši poznámky, komentáře' ,de: 'Ergänzende Hinweise / Kommentare' ,es: 'Notas adicionales, Comentarios' @@ -1720,8 +1863,9 @@ function init() { ,bg: 'Допълнителни бележки, коментари' ,hr: 'Dodatne bilješke, komentari' ,sv: 'Notering, övrigt' + ,it: 'Note aggiuntive, commenti' } - ,'RETRO MODE' : { + ,'RETRO MODE' : { cs: 'V MINULOSTI' ,de: 'RETRO MODUS' ,es: 'Modo Retrospectivo' @@ -1731,8 +1875,9 @@ function init() { ,ro: 'MOD RETROSPECTIV' ,bg: 'МИНАЛО ВРЕМЕ' ,hr: 'Retrospektivni način' + ,it: 'Modalità retrospettiva' } - ,'Now' : { + ,'Now' : { cs: 'Nyní' ,de: 'Jetzt' ,es: 'Ahora' @@ -1742,8 +1887,9 @@ function init() { ,ro: 'Acum' ,bg: 'Сега' ,hr: 'Sad' + ,it: 'Adesso' } - ,'Other' : { + ,'Other' : { cs: 'Jiný' ,de: 'Weitere' ,es: 'Otro' @@ -1753,8 +1899,9 @@ function init() { ,ro: 'Altul' ,bg: 'Друго' ,hr: 'Drugo' + ,it: 'Altro' } - ,'Submit Form' : { + ,'Submit Form' : { cs: 'Odeslat formulář' ,de: 'Eingabe senden' ,es: 'Enviar formulario' @@ -1764,8 +1911,9 @@ function init() { ,ro: 'Trimite formularul' ,bg: 'Въвеждане на данните' ,hr: 'Predaj obrazac' + ,it: 'Inviare il modulo' } - ,'Profile editor' : { + ,'Profile editor' : { cs: 'Editor profilu' ,de: 'Profil-Einstellungen' ,es: 'Editor de perfil' @@ -1775,8 +1923,9 @@ function init() { ,ro: 'Editare profil' ,bg: 'Редактор на профила' ,hr: 'Editor profila' + ,it: 'Editor dei profili' } - ,'Reporting tool' : { + ,'Reporting tool' : { cs: 'Výkazy' ,de: 'Berichte' ,es: 'Herramienta de informes' @@ -1786,8 +1935,9 @@ function init() { ,ro: 'Instrument de rapoarte' ,bg: 'Статистика' ,hr: 'Alat za prijavu' + ,it: 'Strumento di reporting' } - ,'Add food from your database' : { + ,'Add food from your database' : { cs: 'Přidat jidlo z Vaší databáze' ,de: 'Ergänzt durch deine Datenbank' ,es: 'Añadir alimento a su base de datos' @@ -1797,8 +1947,9 @@ function init() { ,bg: 'Добави храна от твоята база с данни' ,hr: 'Dodajte hranu iz svoje baze podataka' ,sv: 'Lägg till livsmedel från databas' + ,it: 'Aggiungere cibo al database' } - ,'Reload database' : { + ,'Reload database' : { cs: 'Znovu nahraj databázi' ,de: 'Datenbank nachladen' ,es: 'Recargar base de datos' @@ -1808,8 +1959,9 @@ function init() { ,bg: 'Презареди базата с данни' ,hr: 'Ponovo učitajte bazu podataka' ,sv: 'Ladda om databas' + ,it: 'Ricarica database' } - ,'Add' : { + ,'Add' : { cs: 'Přidej' ,de: 'Hinzufügen' ,es: 'Añadir' @@ -1819,8 +1971,9 @@ function init() { ,bg: 'Добави' ,hr: 'Dodaj' ,sv: 'Lägg till' + ,it: 'Aggiungere' } - ,'Unauthorized' : { + ,'Unauthorized' : { cs: 'Neautorizováno' ,de: 'Unbestätigt' ,es: 'No autorizado' @@ -1830,8 +1983,9 @@ function init() { ,ro: 'Neautorizat' ,bg: 'Нямаш достъп' ,hr: 'Neautorizirano' + ,it: 'non autorizzato' } - ,'Entering record failed' : { + ,'Entering record failed' : { cs: 'Vložení záznamu selhalo' ,de: 'Eingabe Datensatz fehlerhaft' ,es: 'Entrada de registro fallida' @@ -1841,8 +1995,9 @@ function init() { ,bg: 'Въвеждане на записа не се осъществи' ,hr: 'Neuspjeli unos podataka' ,sv: 'Lägga till post nekas' + ,it: 'Voce del Registro fallita' } - ,'Device authenticated' : { + ,'Device authenticated' : { cs: 'Zařízení ověřeno' ,de: 'Gerät authentifiziert' ,es: 'Dispositivo autenticado' @@ -1852,8 +2007,9 @@ function init() { ,ro: 'Dispozitiv autentificat' ,bg: 'Устройстово е разпознато' ,hr: 'Uređaj autenticiran' + ,it: 'Dispositivo autenticato' } - ,'Device not authenticated' : { + ,'Device not authenticated' : { cs: 'Zařízení není ověřeno' ,de: 'Gerät nicht authentifiziert' ,es: 'Dispositivo no autenticado' @@ -1863,8 +2019,9 @@ function init() { ,ro: 'Dispozitiv neautentificat' ,bg: 'Устройсройството не е разпознато' ,hr: 'Uređaj nije autenticiran' + ,it: 'Dispositivo non autenticato' } - ,'Authentication status' : { + ,'Authentication status' : { cs: 'Stav ověření' ,de: 'Authentifications Status' ,es: 'Estado de autenticación' @@ -1874,8 +2031,9 @@ function init() { ,bg: 'Статус на удостоверяване' ,hr: 'Status autentikacije' ,sv: 'Autentiseringsstatus' + ,it: 'Stato di autenticazione' } - ,'Authenticate' : { + ,'Authenticate' : { cs: 'Ověřit' ,de: 'Authentifizieren' ,es: 'Autenticar' @@ -1885,8 +2043,9 @@ function init() { ,ro: 'Autentificare' ,bg: 'Удостоверяване' ,hr: 'Autenticirati' + ,it: 'Autenticare' } - ,'Remove' : { + ,'Remove' : { cs: 'Vymazat' ,de: 'Entfernen' ,es: 'Eliminar' @@ -1896,6 +2055,7 @@ function init() { ,bg: 'Премахни' ,hr: 'Ukloniti' ,sv: 'Ta bort' + ,it: 'Rimuovere' } ,'Your device is not authenticated yet' : { cs: 'Toto zařízení nebylo dosud ověřeno' @@ -1907,8 +2067,9 @@ function init() { ,bg: 'Вашето устройство все още не е удостоверено' ,hr: 'Vaš uređaj još nije autenticiran' ,sv: 'Din enhet är ej autentiserad' + ,it: 'Il tuo dispositivo non è ancora stato autenticato' } - ,'Sensor' : { + ,'Sensor' : { cs: 'Senzor' ,de: 'Sensor' ,es: 'Sensor' @@ -1918,8 +2079,9 @@ function init() { ,ro: 'Senzor' ,bg: 'Сензор' ,hr: 'Senzor' + ,it: 'Sensore' } - ,'Finger' : { + ,'Finger' : { cs: 'Glukoměr' ,de: 'Finger' ,es: 'Dedo' @@ -1929,8 +2091,9 @@ function init() { ,ro: 'Deget' ,bg: 'От пръстта' ,hr: 'Prst' + ,it: 'Dito' } - ,'Manual' : { + ,'Manual' : { cs: 'Ručně' ,de: 'Händisch' ,es: 'Manual' @@ -1940,8 +2103,9 @@ function init() { ,ro: 'Manual' ,bg: 'Ръчно' ,hr: 'Ručno' + ,it: 'Manuale' } - ,'Scale' : { + ,'Scale' : { cs: 'Měřítko' ,de: 'Messen' ,es: 'Escala' @@ -1951,8 +2115,9 @@ function init() { ,bg: 'Скала' ,hr: 'Skala' ,sv: 'Skala' + ,it: 'Scala' } - ,'Linear' : { + ,'Linear' : { cs: 'lineární' ,de: 'Linear' ,es: 'Lineal' @@ -1962,8 +2127,9 @@ function init() { ,ro: 'Liniar' ,bg: 'Линеен' ,hr: 'Linearno' + ,it: 'Lineare' } - ,'Logarithmic' : { + ,'Logarithmic' : { cs: 'logaritmické' ,de: 'Logarithmisch' ,es: 'Logarítmica' @@ -1973,8 +2139,9 @@ function init() { ,ro: 'Logaritmic' ,bg: 'Логоритмичен' ,hr: 'Logaritamski' + ,it: 'Logaritmica' } - ,'Silence for 30 minutes' : { + ,'Silence for 30 minutes' : { cs: 'Ztlumit na 30 minut' ,de: 'Ausschalten für 30 Minuten' ,es: 'Silenciar durante 30 minutos' @@ -1984,8 +2151,9 @@ function init() { ,bg: 'Заглуши за 30 минути' ,hr: 'Tišina 30 minuta' ,sv: 'Tyst i 30 min' + ,it: 'Silenzio per 30 minuti' } - ,'Silence for 60 minutes' : { + ,'Silence for 60 minutes' : { cs: 'Ztlumit na 60 minut' ,de: 'Ausschalten für 60 Minuten' ,es: 'Silenciar durante 60 minutos' @@ -1995,8 +2163,9 @@ function init() { ,bg: 'Заглуши за 60 минути' ,hr: 'Tišina 60 minuta' ,sv: 'Tyst i 60 min' + ,it: 'Silenzio per 60 minuti' } - ,'Silence for 90 minutes' : { + ,'Silence for 90 minutes' : { cs: 'Ztlumit na 90 minut' ,de: 'Ausschalten für 90 Minuten' ,es: 'Silenciar durante 90 minutos' @@ -2006,8 +2175,9 @@ function init() { ,bg: 'Заглуши за 90 минути' ,hr: 'Tišina 90 minuta' ,sv: 'Tyst i 90 min' + ,it: 'Silenzio per 90 minuti' } - ,'Silence for 120 minutes' : { + ,'Silence for 120 minutes' : { cs: 'Ztlumit na 120 minut' ,de: 'Ausschalten für 120 Minuten' ,es: 'Silenciar durante 120 minutos' @@ -2017,8 +2187,9 @@ function init() { ,bg: 'Заглуши за 120 минути' ,hr: 'Tišina 120 minuta' ,sv: 'Tyst i 120 min' + ,it: 'Silenzio per 120 minuti' } - ,'3HR' : { + ,'3HR' : { cs: '3hod' ,de: '3h' ,es: '3h' @@ -2028,8 +2199,9 @@ function init() { ,ro: '3h' ,bg: '3часа' ,hr: '3h' + ,it: '3h' } - ,'6HR' : { + ,'6HR' : { cs: '6hod' ,de: '6h' ,es: '6h' @@ -2039,8 +2211,9 @@ function init() { ,ro: '6h' ,bg: '6часа' ,hr: '6h' + ,it: '6h' } - ,'12HR' : { + ,'12HR' : { cs: '12hod' ,de: '12h' ,es: '12h' @@ -2050,8 +2223,9 @@ function init() { ,ro: '12h' ,bg: '12часа' ,hr: '12h' + ,it: '12h' } - ,'24HR' : { + ,'24HR' : { cs: '24hod' ,de: '24h' ,es: '24h' @@ -2061,6 +2235,7 @@ function init() { ,ro: '24h' ,bg: '24часа' ,hr: '24h' + ,it: '24h' } ,'Settings' : { cs: 'Nastavení' @@ -2071,6 +2246,7 @@ function init() { ,ro: 'Setări' ,bg: 'Настройки' ,hr: 'Postavke' + ,it: 'Impostazioni' } ,'Units' : { cs: 'Jednotky' @@ -2082,8 +2258,9 @@ function init() { ,bg: 'Единици' ,hr: 'Jedinice' ,sv: 'Enheter' + ,it: 'Unità' } - ,'Date format' : { + ,'Date format' : { cs: 'Formát datumu' ,de: 'Datum Format' ,es: 'Formato de fecha' @@ -2093,8 +2270,9 @@ function init() { ,ro: 'Formatul datei' ,bg: 'Формат на датата' ,hr: 'Format datuma' + ,it: 'Formato data' } - ,'12 hours' : { + ,'12 hours' : { cs: '12 hodin' ,de: '12 Stunden' ,es: '12 horas' @@ -2104,8 +2282,9 @@ function init() { ,ro: '12 ore' ,bg: '12 часа' ,hr: '12 sati' + ,it: '12 ore' } - ,'24 hours' : { + ,'24 hours' : { cs: '24 hodin' ,de: '24 Stunden' ,es: '24 horas' @@ -2115,8 +2294,9 @@ function init() { ,ro: '24 ore' ,bg: '24 часа' ,hr: '24 sata' + ,it: '24 ore' } - ,'Log a Treatment' : { + ,'Log a Treatment' : { cs: 'Záznam ošetření' ,de: 'Dateneingabe' ,es: 'Apuntar un tratamiento' @@ -2126,8 +2306,9 @@ function init() { ,bg: 'Въвеждане на събитие' ,hr: 'Evidencija tretmana' ,sv: 'Ange händelse' + ,it: 'Somministrazione' } - ,'BG Check' : { + ,'BG Check' : { cs: 'Kontrola glykémie' ,de: 'Blutglukose-Prüfung' ,es: 'Control de glucemia' @@ -2137,8 +2318,9 @@ function init() { ,ro: 'Verificare glicemie' ,bg: 'Проверка на КЗ' ,hr: 'Kontrola GUK-a' + ,it: 'Controllo Glicemia' } - ,'Meal Bolus' : { + ,'Meal Bolus' : { cs: 'Bolus na jídlo' ,de: 'Mahlzeiten Bolus' ,es: 'Bolo de comida' @@ -2148,8 +2330,9 @@ function init() { ,bg: 'Болус-основно хранене' ,hr: 'Bolus za obrok' ,sv: 'Måltidsbolus' + ,it: 'bolo pasto' } - ,'Snack Bolus' : { + ,'Snack Bolus' : { cs: 'Bolus na svačinu' ,de: 'Snack Bolus' ,es: 'Bolo de aperitivo' @@ -2159,8 +2342,9 @@ function init() { ,ro: 'Bolus gustare' ,bg: 'Болус-лека закуска' ,hr: 'Bolus za užinu' + ,it: 'Bolo merenda' } - ,'Correction Bolus' : { + ,'Correction Bolus' : { cs: 'Bolus na glykémii' ,de: 'Korrektur Bolus' ,es: 'Bolo corrector' @@ -2170,8 +2354,9 @@ function init() { ,bg: 'Болус корекция' ,hr: 'Korekcija' ,sv: 'Korrektionsbolus' + ,it: 'Correzione bolo' } - ,'Carb Correction' : { + ,'Carb Correction' : { cs: 'Přídavek sacharidů' ,de: 'Kohlenhydrate Korrektur' ,es: 'Hidratos de carbono de corrección' @@ -2181,8 +2366,9 @@ function init() { ,bg: 'Корекция за въглехидратите' ,hr: 'Bolus za hranu' ,sv: 'Kolhydratskorrektion' + ,it: 'Correzione carboidrati' } - ,'Note' : { + ,'Note' : { cs: 'Poznámka' ,de: 'Bemerkungen' ,es: 'Nota' @@ -2192,8 +2378,9 @@ function init() { ,bg: 'Бележка' ,hr: 'Bilješka' ,sv: 'Notering' + ,it: 'Nota' } - ,'Question' : { + ,'Question' : { cs: 'Otázka' ,de: 'Frage' ,es: 'Pregunta' @@ -2203,8 +2390,9 @@ function init() { ,ro: 'Întrebare' ,bg: 'Въпрос' ,hr: 'Pitanje' + ,it: 'Domanda' } - ,'Exercise' : { + ,'Exercise' : { cs: 'Cvičení' ,de: 'Bewegung' ,es: 'Ejercicio' @@ -2214,8 +2402,9 @@ function init() { ,bg: 'Спорт' ,hr: 'Aktivnost' ,sv: 'Aktivitet' + ,it: 'Esercizio' } - ,'Pump Site Change' : { + ,'Pump Site Change' : { cs: 'Přepíchnutí kanyly' ,de: 'Pumpen-Katheter Wechsel' ,es: 'Cambio de catéter' @@ -2225,8 +2414,9 @@ function init() { ,bg: 'Смяна на сет' ,hr: 'Promjena seta' ,sv: 'Pump/nålbyte' + ,it: 'Cambio Ago' } - ,'Sensor Start' : { + ,'Sensor Start' : { cs: 'Spuštění sensoru' ,de: 'Sensor Start' ,es: 'Inicio de sensor' @@ -2236,8 +2426,9 @@ function init() { ,ro: 'Start senzor' ,bg: 'Стартиране на сензор' ,hr: 'Start senzora' + ,it: 'Avvio sensore' } - ,'Sensor Change' : { + ,'Sensor Change' : { cs: 'Výměna sensoru' ,de: 'Sensor Wechsel' ,es: 'Cambio de sensor' @@ -2247,8 +2438,9 @@ function init() { ,ro: 'Schimbare senzor' ,bg: 'Смяна на сензор' ,hr: 'Promjena senzora' + ,it: 'Cambio sensore' } - ,'Dexcom Sensor Start' : { + ,'Dexcom Sensor Start' : { cs: 'Spuštění sensoru' ,de: 'Dexcom Sensor Start' ,es: 'Inicio de sensor Dexcom' @@ -2258,8 +2450,9 @@ function init() { ,ro: 'Pornire senzor Dexcom' ,bg: 'Поставяне на Декском сензор' ,hr: 'Start Dexcom senzora' + ,it: 'Avvio sensore Dexcom' } - ,'Dexcom Sensor Change' : { + ,'Dexcom Sensor Change' : { cs: 'Výměna sensoru' ,de: 'Dexcom Sensor Wechsel' ,es: 'Cambio de sensor Dexcom' @@ -2269,8 +2462,9 @@ function init() { ,ro: 'Schimbare senzor Dexcom' ,bg: 'Смяна на Декском сензор' ,hr: 'Promjena Dexcom senzora' + ,it: 'Cambio sensore Dexcom' } - ,'Insulin Cartridge Change' : { + ,'Insulin Cartridge Change' : { cs: 'Výměna inzulínu' ,de: 'Insulin Ampullen Wechsel' ,es: 'Cambio de reservorio de insulina' @@ -2280,8 +2474,9 @@ function init() { ,bg: 'Смяна на резервоар' ,hr: 'Promjena spremnika inzulina' ,sv: 'Insulinreservoarbyte' + ,it: 'Cambio cartuccia insulina' } - ,'D.A.D. Alert' : { + ,'D.A.D. Alert' : { cs: 'D.A.D. Alert' ,de: 'Diabetes-Hund Alarm' ,es: 'Alerta de perro de alerta diabética' @@ -2291,8 +2486,9 @@ function init() { ,bg: 'Сигнал от обучено куче' ,hr: 'Obavijest dijabetičkog psa' ,sv: 'Voff voff! (Diabeteshundalarm!)' + ,it: 'Allarme D.A.D.' } - ,'Glucose Reading' : { + ,'Glucose Reading' : { cs: 'Hodnota glykémie' ,de: 'Glukose Messwert' ,es: 'Valor de glucemia' @@ -2302,8 +2498,9 @@ function init() { ,ro: 'Valoare glicemie' ,bg: 'Кръвна захар' ,hr: 'Vrijednost GUK-a' + ,it: 'Lettura glicemie' } - ,'Measurement Method' : { + ,'Measurement Method' : { cs: 'Metoda měření' ,de: 'Messmethode' ,es: 'Método de medida' @@ -2313,8 +2510,9 @@ function init() { ,bg: 'Метод на измерване' ,hr: 'Metoda mjerenja' ,sv: 'Mätmetod' + ,it: 'Metodo di misurazione' } - ,'Meter' : { + ,'Meter' : { cs: 'Glukoměr' ,de: 'BZ-Messgerät' ,fr: 'Glucomètre' @@ -2324,8 +2522,9 @@ function init() { ,es: 'Glucómetro' ,bg: 'Глюкомер' ,hr: 'Glukometar' + ,it: 'Glucometro' } - ,'Insulin Given' : { + ,'Insulin Given' : { cs: 'Inzulín' ,de: 'Insulingabe' ,es: 'Insulina' @@ -2335,8 +2534,9 @@ function init() { ,bg: 'Инсулин' ,hr: 'Količina iznulina' ,sv: 'Insulindos' + ,it: 'Insulina' } - ,'Amount in grams' : { + ,'Amount in grams' : { cs: 'Množství v gramech' ,de: 'Menge in Gramm' ,es: 'Cantidad en gramos' @@ -2346,8 +2546,9 @@ function init() { ,ro: 'Cantitate în grame' ,bg: ' К-во в грамове' ,hr: 'Količina u gramima' + ,it: 'Quantità in grammi' } - ,'Amount in units' : { + ,'Amount in units' : { cs: 'Množství v jednotkách' ,de: 'Anzahl in Einheiten' ,es: 'Cantidad en unidades' @@ -2357,8 +2558,9 @@ function init() { ,bg: 'К-во в единици' ,hr: 'Količina u jedinicama' ,sv: 'Antal enheter' + ,it: 'Quantità in unità' } - ,'View all treatments' : { + ,'View all treatments' : { cs: 'Zobraz všechny ošetření' ,de: 'Zeige alle Eingaben' ,es: 'Visualizar todos los tratamientos' @@ -2368,8 +2570,9 @@ function init() { ,ro: 'Vezi toate evenimentele' ,bg: 'Преглед на всички събития' ,hr: 'Prikaži sve tretmane' + ,it: 'Visualizza tutti le somministrazioni' } - ,'Enable Alarms' : { + ,'Enable Alarms' : { cs: 'Povolit alarmy' ,de: 'Alarm einschalten' ,es: 'Activar las alarmas' @@ -2378,8 +2581,9 @@ function init() { ,ro: 'Activează alarmele' ,bg: 'Активни аларми' ,hr: 'Aktiviraj alarme' + ,it: 'Attiva Allarme' } - ,'When enabled an alarm may sound.' : { + ,'When enabled an alarm may sound.' : { cs: 'Při povoleném alarmu zní zvuk' ,de: 'Sofern eingeschaltet ertönt ein Alarm' ,es: 'Cuando estén activas, una alarma podrá sonar' @@ -2388,8 +2592,9 @@ function init() { ,ro: 'Când este activ, poate suna o alarmă.' ,bg: 'Когато е активирано, алармата ще има звук' ,hr: 'Kad je aktiviran, alarm se može oglasiti' + ,it: 'Quando si attiva un allarme acustico.' } - ,'Urgent High Alarm' : { + ,'Urgent High Alarm' : { cs: 'Urgentní vysoká glykémie' ,de: 'Achtung Hoch Alarm' ,es: 'Alarma de glucemia alta urgente' @@ -2398,8 +2603,9 @@ function init() { ,ro: 'Alarmă urgentă hiper' ,bg: 'Много висока КЗ' ,hr: 'Hitni alarm za hiper' + ,it: 'Allarme Urgente: Glicemia Molto Alta' } - ,'High Alarm' : { + ,'High Alarm' : { cs: 'Vysoká glykémie' ,de: 'Hoch Alarm' ,es: 'Alarma de glucemia alta' @@ -2408,8 +2614,9 @@ function init() { ,ro: 'Alarmă hiper' ,bg: 'Висока КЗ' ,hr: 'Alarm za hiper' + ,it: 'Allarme: Glicemia Alta' } - ,'Low Alarm' : { + ,'Low Alarm' : { cs: 'Nízká glykémie' ,de: 'Tief Alarm' ,es: 'Alarma de glucemia baja' @@ -2418,8 +2625,9 @@ function init() { ,ro: 'Alarmă hipo' ,bg: 'Ниска КЗ' ,hr: 'Alarm za hipo' + ,it: 'Allarme Glicemia bassa' } - ,'Urgent Low Alarm' : { + ,'Urgent Low Alarm' : { cs: 'Urgentní nízká glykémie' ,de: 'Achtung Tief Alarm' ,es: 'Alarma de glucemia baja urgente' @@ -2428,8 +2636,9 @@ function init() { ,ro: 'Alarmă urgentă hipo' ,bg: 'Много ниска КЗ' ,hr: 'Hitni alarm za hipo' + ,it: 'Allarme Urgente. Glicemia Molto Bassa' } - ,'Stale Data: Warn' : { + ,'Stale Data: Warn' : { cs: 'Zastaralá data' ,de: 'Warnung: Daten nicht mehr gültig' ,es: 'Datos obsoletos: aviso' @@ -2438,8 +2647,9 @@ function init() { ,ro: 'Date învechite: alertă' ,bg: 'Стари данни' ,hr: 'Pažnja: Stari podaci' + ,it: 'Dati obsoleti: Notifica' } - ,'Stale Data: Urgent' : { + ,'Stale Data: Urgent' : { cs: 'Zastaralá data urgentní' ,de: 'Achtung: Daten nicht mehr gültig' ,es: 'Datos obsoletos: Urgente' @@ -2449,8 +2659,9 @@ function init() { ,ro: 'Date învechite: urgent' ,bg: 'Много стари данни' ,hr: 'Hitno: Stari podaci' + ,it: 'Dati obsoleti: Urgente' } - ,'mins' : { + ,'mins' : { cs: 'min' ,de: 'min' ,es: 'min' @@ -2460,8 +2671,10 @@ function init() { ,ro: 'min' ,bg: 'мин' ,hr: 'min' + ,it: 'min' + } - ,'Night Mode' : { + ,'Night Mode' : { cs: 'Noční mód' ,de: 'Nacht Modus' ,es: 'Modo nocturno' @@ -2471,8 +2684,9 @@ function init() { ,ro: 'Mod nocturn' ,bg: 'Нощен режим' ,hr: 'Noćni način' + ,it: 'Modalità notte' } - ,'When enabled the page will be dimmed from 10pm - 6am.' : { + ,'When enabled the page will be dimmed from 10pm - 6am.' : { cs: 'Když je povoleno, obrazovka je ztlumena 22:00 - 6:00' ,de: 'Wenn aktiviert wird die Anzeige von 22 Uhr - 6 Uhr gedimmt' ,es: 'Cuando esté activo, el brillo de la página bajará de 10pm a 6am.' @@ -2481,8 +2695,9 @@ function init() { ,ro: 'La activare va scădea iluminarea între 22 și 6' ,bg: 'Когато е активирано, страницата ще е затъмнена от 22-06ч' ,hr: 'Kad je uključen, stranica će biti zatamnjena od 22-06' + ,it: 'Attivandola, la pagina sarà oscurato da 10:00-06:00.' } - ,'Enable' : { + ,'Enable' : { cs: 'Povoleno' ,de: 'Eingeschaltet' ,es: 'Activar' @@ -2492,6 +2707,7 @@ function init() { ,ro: 'Activează' ,bg: 'Активно' ,hr: 'Aktiviraj' + ,it: 'Permettere' } ,'Show Raw BG Data' : { cs: 'Zobraz RAW data' @@ -2503,8 +2719,9 @@ function init() { ,bg: 'Показвай RAW данни' ,hr: 'Prikazuj sirove podatke o GUK-u' ,sv: 'Visa RAW-data' + ,it: 'Mostra dati Raw BG' } - ,'Never' : { + ,'Never' : { cs: 'Nikdy' ,de: 'Nie' ,es: 'Nunca' @@ -2514,8 +2731,9 @@ function init() { ,ro: 'Niciodată' ,bg: 'Никога' ,hr: 'Nikad' + ,it: 'Mai' } - ,'Always' : { + ,'Always' : { cs: 'Vždy' ,de: 'Immer' ,es: 'Siempre' @@ -2525,8 +2743,9 @@ function init() { ,ro: 'Întotdeauna' ,bg: 'Винаги' ,hr: 'Uvijek' + ,it: 'Sempre' } - ,'When there is noise' : { + ,'When there is noise' : { cs: 'Při šumu' ,de: 'Sofern Störgeräusch vorhanden' ,es: 'Cuando hay ruido' @@ -2536,8 +2755,9 @@ function init() { ,ro: 'Atunci când este diferență' ,bg: 'Когато има шум' ,hr: 'Kad postoji šum' + ,it: 'Quando vi è rumore' } - ,'When enabled small white dots will be disaplyed for raw BG data' : { + ,'When enabled small white dots will be displayed for raw BG data' : { cs: 'Když je povoleno, malé tečky budou zobrazeny pro RAW data' ,de: 'Bei Aktivierung erscheinen kleine weiße Punkte für Roh-Blutglukose Daten' ,es: 'Cuando esté activo, pequeños puntos blancos mostrarán los datos en crudo' @@ -2546,8 +2766,9 @@ function init() { ,ro: 'La activare vor apărea puncte albe reprezentând citirea brută a glicemiei' ,bg: 'Когато е активирано, малки бели точки ще показват RAW данните' ,hr: 'Kad je omogućeno, male bijele točkice će prikazivati sirove podatke o GUK-u.' + ,it: 'Quando lo abiliti, visualizzerai piccoli puntini bianchi (raw BG data)' } - ,'Custom Title' : { + ,'Custom Title' : { cs: 'Vlastní název stránky' ,de: 'Benutzerdefiniert' ,es: 'Título personalizado' @@ -2557,8 +2778,9 @@ function init() { ,ro: 'Titlu particularizat' ,bg: 'Име на страницата' ,hr: 'Vlastiti naziv' + ,it: 'Titolo personalizzato' } - ,'Theme' : { + ,'Theme' : { cs: 'Téma' ,de: 'Thema' ,es: 'Tema' @@ -2568,8 +2790,9 @@ function init() { ,bg: 'Тема' ,hr: 'Tema' ,sv: 'Tema' + ,it: 'Tema' } - ,'Default' : { + ,'Default' : { cs: 'Výchozí' ,de: 'Voreingestellt' ,es: 'Por defecto' @@ -2579,8 +2802,9 @@ function init() { ,bg: 'Черно-бяла' ,hr: 'Default' ,sv: 'Standard' + ,it: 'Predefinito' } - ,'Colors' : { + ,'Colors' : { cs: 'Barevné' ,de: 'Farben' ,es: 'Colores' @@ -2589,8 +2813,9 @@ function init() { ,ro: 'Colorată' ,bg: 'Цветна' ,hr: 'Boje' + ,it: 'Colori' } - ,'Reset, and use defaults' : { + ,'Reset, and use defaults' : { cs: 'Vymaž a nastav výchozí hodnoty' ,de: 'Zurücksetzen und Voreinstellungen verwenden' ,es: 'Inicializar y utilizar los valores por defecto' @@ -2600,8 +2825,9 @@ function init() { ,ro: 'Resetează și folosește setările implicite' ,bg: 'Нулирай и използвай стандартните настройки' ,hr: 'Resetiraj i koristi defaultne vrijednosti' + ,it: 'Resetta e utilizza le impostazioni predefinite' } - ,'Calibrations' : { + ,'Calibrations' : { cs: 'Kalibrace' ,de: 'Kalibrierung' ,es: 'Calibraciones' @@ -2610,8 +2836,9 @@ function init() { ,ro: 'Calibrări' ,bg: 'Калибрации' ,hr: 'Kalibriranje' + ,it: 'Calibrazioni' } - ,'Alarm Test / Smartphone Enable' : { + ,'Alarm Test / Smartphone Enable' : { cs: 'Test alarmu' ,de: 'Alarm Test / Smartphone aktivieren' ,es: 'Test de Alarma / Activar teléfono' @@ -2620,8 +2847,9 @@ function init() { ,ro: 'Teste alarme / Activează pe smartphone' ,bg: 'Тестване на алармата / Активно за мобилни телефони' ,hr: 'Alarm test / Aktiviraj smartphone' + ,it: 'Test Allarme / Abilita Smartphone' } - ,'Bolus Wizard' : { + ,'Bolus Wizard' : { cs: 'Bolusový kalkulátor' ,de: 'Bolus Kalkulator' ,es: 'Bolus Wizard' @@ -2631,8 +2859,9 @@ function init() { ,ro: 'Calculator sugestie bolus' ,bg: 'Съветник при изчисление на болуса' ,hr: 'Bolus wizard' + ,it: 'Bolo guidato' } - ,'in the future' : { + ,'in the future' : { cs: 'v budoucnosti' ,de: 'in der Zuknft' ,es: 'en el futuro' @@ -2642,8 +2871,9 @@ function init() { ,ro: 'în viitor' ,bg: 'в бъдещето' ,hr: 'U budućnosti' + ,it: 'nel futuro' } - ,'time ago' : { + ,'time ago' : { cs: 'min zpět' ,de: 'Aktualisiert' ,es: 'tiempo atrás' @@ -2653,8 +2883,9 @@ function init() { ,ro: 'în trecut' ,bg: 'преди време' ,hr: 'prije' + ,it: 'tempo fa' } - ,'hr ago' : { + ,'hr ago' : { cs: 'hod zpět' ,de: 'Std. vorher' ,es: 'hr atrás' @@ -2663,8 +2894,9 @@ function init() { ,ro: 'oră în trecut' ,bg: 'час по-рано' ,hr: 'sat unazad' + ,it: 'ora fa' } - ,'hrs ago' : { + ,'hrs ago' : { cs: 'hod zpět' ,de: 'Std. vorher' ,es: 'hr atrás' @@ -2674,8 +2906,9 @@ function init() { ,ro: 'h în trecut' ,bg: 'часа по-рано' ,hr: 'sati unazad' + ,it: 'ore fa' } - ,'min ago' : { + ,'min ago' : { cs: 'min zpět' ,de: 'Min. vorher' ,es: 'min atrás' @@ -2685,8 +2918,10 @@ function init() { ,ro: 'minut în trecut' ,bg: 'минута по-рано' ,hr: 'minuta unazad' + ,it: 'minuto fa' + } - ,'mins ago' : { + ,'mins ago' : { cs: 'min zpět' ,de: 'Min. vorher' ,es: 'min atrás' @@ -2696,8 +2931,9 @@ function init() { ,ro: 'minute în trecut' ,bg: 'минути по-рано' ,hr: 'minuta unazad' + ,it: 'minuti fa' } - ,'day ago' : { + ,'day ago' : { cs: 'den zpět' ,de: 'Tag vorher' ,es: 'día atrás' @@ -2707,8 +2943,9 @@ function init() { ,ro: 'zi în trecut' ,bg: 'ден по-рано' ,hr: 'dan unazad' + ,it: 'Giorno fa' } - ,'days ago' : { + ,'days ago' : { cs: 'dnů zpět' ,de: 'Tage vorher' ,es: 'días atrás' @@ -2718,8 +2955,9 @@ function init() { ,ro: 'zile în trecut' ,bg: 'дни по-рано' ,hr: 'dana unazad' + ,it: 'giorni fa' } - ,'long ago' : { + ,'long ago' : { cs: 'dlouho zpět' ,de: 'Lange her' ,es: 'Hace mucho tiempo' @@ -2729,8 +2967,9 @@ function init() { ,ro: 'timp în trecut' ,bg: 'преди много време' ,hr: 'prije dosta vremena' + ,it: 'Molto tempo fa' } - ,'Clean' : { + ,'Clean' : { cs: 'Čistý' ,de: 'Komplett' ,es: 'Limpio' @@ -2740,8 +2979,9 @@ function init() { ,ro: 'Curat' ,bg: 'Чист' ,hr: 'Čisto' + ,it: 'Pulito' } - ,'Light' : { + ,'Light' : { cs: 'Lehký' ,de: 'Leicht' ,es: 'Ligero' @@ -2751,8 +2991,9 @@ function init() { ,ro: 'Ușor' ,bg: 'Лек' ,hr: 'Lagano' + ,it: 'Leggero' } - ,'Medium' : { + ,'Medium' : { cs: 'Střední' ,de: 'Mittel' ,es: 'Medio' @@ -2762,8 +3003,9 @@ function init() { ,ro: 'Mediu' ,bg: 'Среден' ,hr: 'Srednje' + ,it: 'Medio' } - ,'Heavy' : { + ,'Heavy' : { cs: 'Velký' ,de: 'Schwer' ,es: 'Fuerte' @@ -2773,8 +3015,9 @@ function init() { ,ro: 'Puternic' ,bg: 'Висок' ,hr: 'Teško' + ,it: 'Pesante' } - ,'Treatment type' : { + ,'Treatment type' : { cs: 'Typ ošetření' ,de: 'Eingabe Typ' ,es: 'Tipo de tratamiento' @@ -2784,8 +3027,9 @@ function init() { ,ro: 'Tip tratament' ,bg: 'Вид събитие' ,hr: 'Vrsta tretmana' + ,it: 'Somministrazione' } - ,'Raw BG' : { + ,'Raw BG' : { cs: 'Glykémie z RAW dat' ,de: 'Roh Blutglukose' ,es: 'Glucemia en crudo' @@ -2795,8 +3039,9 @@ function init() { ,ro: 'Citire brută a glicemiei' ,bg: 'Непреработена КЗ' ,hr: 'Sirovi podaci o GUK-u' + ,it: 'Raw BG' } - ,'Device' : { + ,'Device' : { cs: 'Zařízení' ,de: 'Gerät' ,es: 'Dispositivo' @@ -2806,8 +3051,9 @@ function init() { ,ro: 'Dispozitiv' ,bg: 'Устройство' ,hr: 'Uređaj' + ,it: 'Dispositivo' } - ,'Noise' : { + ,'Noise' : { cs: 'Šum' ,de: 'Störgeräusch' ,es: 'Ruido' @@ -2817,8 +3063,9 @@ function init() { ,ro: 'Zgomot' ,bg: 'Шум' ,hr: 'Šum' + ,it: 'Rumore' } - ,'Calibration' : { + ,'Calibration' : { cs: 'Kalibrace' ,de: 'Kalibrierung' ,es: 'Calibración' @@ -2828,8 +3075,9 @@ function init() { ,ro: 'Calibrare' ,bg: 'Калибрация' ,hr: 'Kalibriranje' + ,it: 'Calibratura' } - ,'Show Plugins' : { + ,'Show Plugins' : { cs: 'Zobrazuj pluginy' ,de: 'Zeige Plugins' ,es: 'Mostrar Plugins' @@ -2838,8 +3086,9 @@ function init() { ,ro: 'Arată plugin-urile' ,bg: 'Покажи добавките' ,hr: 'Prikaži plugine' + ,it: 'Mostra Plugin' } - ,'About' : { + ,'About' : { cs: 'O aplikaci' ,de: 'Über' ,es: 'Sobre' @@ -2849,8 +3098,9 @@ function init() { ,bg: 'Относно' ,hr: 'O aplikaciji' ,sv: 'Om' + ,it: 'Informazioni' } - ,'Value in' : { + ,'Value in' : { cs: 'Hodnota v' ,de: 'Wert in' ,es: 'Valor en' @@ -2860,8 +3110,9 @@ function init() { ,bg: 'Стойност в' ,hr: 'Vrijednost u' ,sv: 'Värde om' + ,it: 'Valore in' } - ,'Carb Time' : { + ,'Carb Time' : { cs: 'Čas jídla' ,de: 'Kohlenhydrate Zeit' ,es: 'Momento de la ingesta' @@ -2869,35 +3120,36 @@ function init() { ,bg: 'ВХ действа след' ,hr: 'Vrijeme unosa UH' ,sv: 'Kolhydratstid' + ,it: 'Tempo' } - - }; - - language.translate = function translate(text) { + + }; + + language.translate = function translate(text) { if (translations[text] && translations[text][lang]) { return translations[text][lang]; - } + } return text; }; - + language.DOMtranslate = function DOMtranslate($) { // do translation of static text on load $('.translate').each(function () { $(this).text(language.translate($(this).text())); - }); + }); $('.titletranslate, .tip').each(function () { $(this).attr('title',language.translate($(this).attr('title'))); $(this).attr('original-title',language.translate($(this).attr('original-title'))); $(this).attr('placeholder',language.translate($(this).attr('placeholder'))); - }); + }); }; - + language.set = function set(newlang) { lang = newlang; return language(); }; - + return language(); } -module.exports = init; +module.exports = init; \ No newline at end of file diff --git a/static/index.html b/static/index.html index 98c1f1bbdca..9956e8a4eb8 100644 --- a/static/index.html +++ b/static/index.html @@ -106,6 +106,7 @@ + From bb850aea385a55d80cd5531d12dea90f60461bbb Mon Sep 17 00:00:00 2001 From: Matteo Neri Date: Mon, 17 Aug 2015 13:47:31 +0200 Subject: [PATCH 616/937] update2 it-laguage.js Hi Jason, adapted words. Missing translation of announcement. (menu food). no charge translation of amount in gramns and the explanation of shows raw data BG. Thanks Matteo --- lib/language.js | 60 ++++++++++++++++++++++++------------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/lib/language.js b/lib/language.js index cafe54b7f4f..1ce1888b379 100644 --- a/lib/language.js +++ b/lib/language.js @@ -223,7 +223,7 @@ function init() { ,ro: 'Categorie' ,bg: 'Категория' ,hr: 'Kategorija' - ,it:'Categoria' + ,it: 'Categoria' } ,'Subcategory' : { cs: 'Podkategorie' @@ -1887,7 +1887,7 @@ function init() { ,ro: 'Acum' ,bg: 'Сега' ,hr: 'Sad' - ,it: 'Adesso' + ,it: 'Ora' } ,'Other' : { cs: 'Jiný' @@ -1983,7 +1983,7 @@ function init() { ,ro: 'Neautorizat' ,bg: 'Нямаш достъп' ,hr: 'Neautorizirano' - ,it: 'non autorizzato' + ,it: 'Non autorizzato' } ,'Entering record failed' : { cs: 'Vložení záznamu selhalo' @@ -2199,7 +2199,7 @@ function init() { ,ro: '3h' ,bg: '3часа' ,hr: '3h' - ,it: '3h' + ,it: '3ORE } ,'6HR' : { cs: '6hod' @@ -2211,7 +2211,7 @@ function init() { ,ro: '6h' ,bg: '6часа' ,hr: '6h' - ,it: '6h' + ,it: '6ORE' } ,'12HR' : { cs: '12hod' @@ -2223,7 +2223,7 @@ function init() { ,ro: '12h' ,bg: '12часа' ,hr: '12h' - ,it: '12h' + ,it: '12ORE' } ,'24HR' : { cs: '24hod' @@ -2235,7 +2235,7 @@ function init() { ,ro: '24h' ,bg: '24часа' ,hr: '24h' - ,it: '24h' + ,it: '24ORE' } ,'Settings' : { cs: 'Nastavení' @@ -2330,7 +2330,7 @@ function init() { ,bg: 'Болус-основно хранене' ,hr: 'Bolus za obrok' ,sv: 'Måltidsbolus' - ,it: 'bolo pasto' + ,it: 'Bolo Pasto' } ,'Snack Bolus' : { cs: 'Bolus na svačinu' @@ -2342,7 +2342,7 @@ function init() { ,ro: 'Bolus gustare' ,bg: 'Болус-лека закуска' ,hr: 'Bolus za užinu' - ,it: 'Bolo merenda' + ,it: 'Bolo Merenda' } ,'Correction Bolus' : { cs: 'Bolus na glykémii' @@ -2354,7 +2354,7 @@ function init() { ,bg: 'Болус корекция' ,hr: 'Korekcija' ,sv: 'Korrektionsbolus' - ,it: 'Correzione bolo' + ,it: 'Bolo Correttivo' } ,'Carb Correction' : { cs: 'Přídavek sacharidů' @@ -2366,7 +2366,7 @@ function init() { ,bg: 'Корекция за въглехидратите' ,hr: 'Bolus za hranu' ,sv: 'Kolhydratskorrektion' - ,it: 'Correzione carboidrati' + ,it: 'Carboidrati Correttivi' } ,'Note' : { cs: 'Poznámka' @@ -2426,7 +2426,7 @@ function init() { ,ro: 'Start senzor' ,bg: 'Стартиране на сензор' ,hr: 'Start senzora' - ,it: 'Avvio sensore' + ,it: 'Avvio Sensore' } ,'Sensor Change' : { cs: 'Výměna sensoru' @@ -2438,7 +2438,7 @@ function init() { ,ro: 'Schimbare senzor' ,bg: 'Смяна на сензор' ,hr: 'Promjena senzora' - ,it: 'Cambio sensore' + ,it: 'Cambio Sensore' } ,'Dexcom Sensor Start' : { cs: 'Spuštění sensoru' @@ -2450,7 +2450,7 @@ function init() { ,ro: 'Pornire senzor Dexcom' ,bg: 'Поставяне на Декском сензор' ,hr: 'Start Dexcom senzora' - ,it: 'Avvio sensore Dexcom' + ,it: 'Avvio Sensore Dexcom' } ,'Dexcom Sensor Change' : { cs: 'Výměna sensoru' @@ -2462,7 +2462,7 @@ function init() { ,ro: 'Schimbare senzor Dexcom' ,bg: 'Смяна на Декском сензор' ,hr: 'Promjena Dexcom senzora' - ,it: 'Cambio sensore Dexcom' + ,it: 'Cambio Sensore Dexcom' } ,'Insulin Cartridge Change' : { cs: 'Výměna inzulínu' @@ -2474,7 +2474,7 @@ function init() { ,bg: 'Смяна на резервоар' ,hr: 'Promjena spremnika inzulina' ,sv: 'Insulinreservoarbyte' - ,it: 'Cambio cartuccia insulina' + ,it: 'Cambio Cartuccia Insulina' } ,'D.A.D. Alert' : { cs: 'D.A.D. Alert' @@ -2498,7 +2498,7 @@ function init() { ,ro: 'Valoare glicemie' ,bg: 'Кръвна захар' ,hr: 'Vrijednost GUK-a' - ,it: 'Lettura glicemie' + ,it: 'Lettura Glicemie' } ,'Measurement Method' : { cs: 'Metoda měření' @@ -2581,7 +2581,7 @@ function init() { ,ro: 'Activează alarmele' ,bg: 'Активни аларми' ,hr: 'Aktiviraj alarme' - ,it: 'Attiva Allarme' + ,it: 'Attiva Allarmi' } ,'When enabled an alarm may sound.' : { cs: 'Při povoleném alarmu zní zvuk' @@ -2592,7 +2592,7 @@ function init() { ,ro: 'Când este activ, poate suna o alarmă.' ,bg: 'Когато е активирано, алармата ще има звук' ,hr: 'Kad je aktiviran, alarm se može oglasiti' - ,it: 'Quando si attiva un allarme acustico.' + ,it: 'Attiverai gli allarmi acustici.' } ,'Urgent High Alarm' : { cs: 'Urgentní vysoká glykémie' @@ -2603,7 +2603,7 @@ function init() { ,ro: 'Alarmă urgentă hiper' ,bg: 'Много висока КЗ' ,hr: 'Hitni alarm za hiper' - ,it: 'Allarme Urgente: Glicemia Molto Alta' + ,it: 'Urgente:Glicemia Molto Alta' } ,'High Alarm' : { cs: 'Vysoká glykémie' @@ -2614,7 +2614,7 @@ function init() { ,ro: 'Alarmă hiper' ,bg: 'Висока КЗ' ,hr: 'Alarm za hiper' - ,it: 'Allarme: Glicemia Alta' + ,it: 'Glicemia Alta' } ,'Low Alarm' : { cs: 'Nízká glykémie' @@ -2625,7 +2625,7 @@ function init() { ,ro: 'Alarmă hipo' ,bg: 'Ниска КЗ' ,hr: 'Alarm za hipo' - ,it: 'Allarme Glicemia bassa' + ,it: 'Glicemia Bassa' } ,'Urgent Low Alarm' : { cs: 'Urgentní nízká glykémie' @@ -2636,7 +2636,7 @@ function init() { ,ro: 'Alarmă urgentă hipo' ,bg: 'Много ниска КЗ' ,hr: 'Hitni alarm za hipo' - ,it: 'Allarme Urgente. Glicemia Molto Bassa' + ,it: 'Urgente:Glicemia Bassa' } ,'Stale Data: Warn' : { cs: 'Zastaralá data' @@ -2647,7 +2647,7 @@ function init() { ,ro: 'Date învechite: alertă' ,bg: 'Стари данни' ,hr: 'Pažnja: Stari podaci' - ,it: 'Dati obsoleti: Notifica' + ,it: 'Dati non aggiornati' } ,'Stale Data: Urgent' : { cs: 'Zastaralá data urgentní' @@ -2659,7 +2659,7 @@ function init() { ,ro: 'Date învechite: urgent' ,bg: 'Много стари данни' ,hr: 'Hitno: Stari podaci' - ,it: 'Dati obsoleti: Urgente' + ,it: 'Dati non aggiornati' } ,'mins' : { cs: 'min' @@ -2695,7 +2695,7 @@ function init() { ,ro: 'La activare va scădea iluminarea între 22 și 6' ,bg: 'Когато е активирано, страницата ще е затъмнена от 22-06ч' ,hr: 'Kad je uključen, stranica će biti zatamnjena od 22-06' - ,it: 'Attivandola, la pagina sarà oscurato da 10:00-06:00.' + ,it: 'Attivandola, la pagina sarà oscurata dalle 22:00 alle 06:00.' } ,'Enable' : { cs: 'Povoleno' @@ -2766,7 +2766,7 @@ function init() { ,ro: 'La activare vor apărea puncte albe reprezentând citirea brută a glicemiei' ,bg: 'Когато е активирано, малки бели точки ще показват RAW данните' ,hr: 'Kad je omogućeno, male bijele točkice će prikazivati sirove podatke o GUK-u.' - ,it: 'Quando lo abiliti, visualizzerai piccoli puntini bianchi (raw BG data)' + ,it: 'Quando lo abiliti, visualizzerai piccoli puntini bianchi cioè i dati grezzi' } ,'Custom Title' : { cs: 'Vlastní název stránky' @@ -2825,7 +2825,7 @@ function init() { ,ro: 'Resetează și folosește setările implicite' ,bg: 'Нулирай и използвай стандартните настройки' ,hr: 'Resetiraj i koristi defaultne vrijednosti' - ,it: 'Resetta e utilizza le impostazioni predefinite' + ,it: 'Resetta le impostazioni' } ,'Calibrations' : { cs: 'Kalibrace' @@ -3086,7 +3086,7 @@ function init() { ,ro: 'Arată plugin-urile' ,bg: 'Покажи добавките' ,hr: 'Prikaži plugine' - ,it: 'Mostra Plugin' + ,it: 'Mostra Plugins' } ,'About' : { cs: 'O aplikaci' @@ -3152,4 +3152,4 @@ function init() { return language(); } -module.exports = init; \ No newline at end of file +module.exports = init; From 0d87ca3f13a0a5b06af0b69486c61fc2f045908b Mon Sep 17 00:00:00 2001 From: Matteo Neri Date: Mon, 17 Aug 2015 20:35:07 +0200 Subject: [PATCH 617/937] update2 it-laguage.js Jason , correct . --- lib/language.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/language.js b/lib/language.js index 1ce1888b379..b28f38a40d8 100644 --- a/lib/language.js +++ b/lib/language.js @@ -2199,7 +2199,7 @@ function init() { ,ro: '3h' ,bg: '3часа' ,hr: '3h' - ,it: '3ORE + ,it: '3ORE' } ,'6HR' : { cs: '6hod' From e269db8a68ed58f72923d4e1ed4ec120ed1587b0 Mon Sep 17 00:00:00 2001 From: sebastianlorant Date: Mon, 17 Aug 2015 20:47:54 +0200 Subject: [PATCH 618/937] Edited by Johan Lorant (swedish) --- lib/language.js | 125 +++++++++++++++++++++++++++++------------------- 1 file changed, 75 insertions(+), 50 deletions(-) diff --git a/lib/language.js b/lib/language.js index b28f38a40d8..e11dbcb504c 100644 --- a/lib/language.js +++ b/lib/language.js @@ -223,7 +223,7 @@ function init() { ,ro: 'Categorie' ,bg: 'Категория' ,hr: 'Kategorija' - ,it: 'Categoria' + ,it:'Categoria' } ,'Subcategory' : { cs: 'Podkategorie' @@ -460,6 +460,7 @@ function init() { ,fr: 'Montrer' ,pt: 'Mostrar' ,ro: 'Arată' + ,sv: 'Visa' ,bg: 'Покажи' ,hr: 'Prikaži' ,it: 'Mostra' @@ -591,6 +592,7 @@ function init() { ,fr: 'Taille' ,pt: 'Tamanho' ,ro: 'Mărime' + ,sv: 'Storlek' ,bg: 'Големина' ,hr: 'Veličina' ,it: 'Formato' @@ -601,6 +603,7 @@ function init() { ,es: '(ninguno)' ,fr: '(aucun)' ,pt: '(nenhum)' + ,sv: '(tom)' ,ro: '(fără)' ,bg: '(няма)' ,hr: '(Prazno)' @@ -612,6 +615,7 @@ function init() { ,es: 'Resultado vacío' ,fr: 'Pas de résultat' ,pt: 'Resultado vazio' + ,sv: 'Resultat saknas' ,ro: 'Fără rezultat' ,bg: 'Няма резултат' ,hr: 'Prazan rezultat' @@ -795,6 +799,7 @@ function init() { ,ro: 'Pătrime' ,bg: 'Четвъртинка' ,hr: 'Kvartil' + ,sv: 'Kvadrant' ,it: 'Quartile' } ,'Date' : { @@ -839,7 +844,7 @@ function init() { ,es: 'Valores' ,fr: 'Valeurs' ,pt: 'Valores' - ,sv: 'Avläsning' + ,sv: 'Avläsningar' ,ro: 'Valori' ,bg: 'Измервания' ,hr: 'Vrijednosti' @@ -852,7 +857,7 @@ function init() { ,fr: 'Déviation St.' ,pt: 'DesvPadr' ,sv: 'StdDev' - ,ro: 'Dev Std' + ,ro: 'Standarddeviation' ,bg: 'Стандартно отклонение' ,hr: 'Standardna devijacija' ,it: 'Deviazione standard' @@ -947,7 +952,7 @@ function init() { ,es: 'N° de valores' ,fr: 'nbr de valeurs' ,pt: 'N° de valores' - ,sv: 'av avläsningar' + ,sv: '# av avläsningar' ,ro: 'nr. de valori' ,bg: '№ от измервания' ,hr: 'broj očitanja' @@ -1167,7 +1172,7 @@ function init() { ,ro: 'Șterge înregistrarea' ,bg: 'Изтрий запис' ,hr: 'Izbriši zapis' - ,sv: 'Ta bort post' + ,sv: 'Radera post' ,it: 'Cancella registro' } ,'Move to the top' : { @@ -1239,7 +1244,7 @@ function init() { ,ro: 'Cheie API înregistrată' ,bg: 'УРА! API парола запаметена' ,hr: 'API tajni hash je pohranjen' - ,sv: 'Hemlig API-hash lagrad' + ,sv: 'Lagrad hemlig API-hash' ,it: 'Hash API secreto memorizzato' } ,'Status' : { @@ -1503,7 +1508,7 @@ function init() { ,ro: 'Verificați conexiunea datelor introduse' ,bg: 'Моля проверете, че датата е въведена правилно' ,hr: 'Molim Vas provjerite jesu li uneseni podaci ispravni' - ,sv: 'Vänligen verifiera att inlagd data stämmer' + ,sv: 'Vänligen verifiera att inlagd data är korrekt' ,it: 'Si prega di verificare che i dati inseriti sono corretti' } ,'BG' : { @@ -1527,7 +1532,7 @@ function init() { ,ro: 'Folosește corecția de glicemie în calcule' ,bg: 'Въведи корекция за КЗ ' ,hr: 'Koristi korekciju GUK-a u izračunu' - ,sv: 'Använd BS-korrektion för uträkning' + ,sv: 'Använd BS-korrektion för beräkning' ,it: 'Utilizzare la correzione Glicemia nei calcoli' } ,'BG from CGM (autoupdated)' : { @@ -1611,7 +1616,7 @@ function init() { ,ro: 'Folosește corecția de carbohidrați în calcule' ,bg: 'Въведи корекция за въглехидратите' ,hr: 'Koristi korekciju za UH u izračunu' - ,sv: 'Använd kolhydratkorrektion i utäkning' + ,sv: 'Använd kolhydratkorrektion för beräkning' ,it: 'Utilizzare la correzione con carboidrati nel calcolo' } ,'Use COB correction in calculation' : { @@ -1635,7 +1640,7 @@ function init() { ,ro: 'Folosește IOB în calcule' ,bg: 'Използвай активния инсулин' ,hr: 'Koristi aktivni inzulin u izračunu"' - ,sv: 'Använd aktivt insulin för uträkning' + ,sv: 'Använd aktivt insulin för beräkning' ,it: 'Utilizzare la correzione IOB nel calcolo' } ,'Other correction' : { @@ -1887,7 +1892,7 @@ function init() { ,ro: 'Acum' ,bg: 'Сега' ,hr: 'Sad' - ,it: 'Ora' + ,it: 'Adesso' } ,'Other' : { cs: 'Jiný' @@ -1983,7 +1988,7 @@ function init() { ,ro: 'Neautorizat' ,bg: 'Нямаш достъп' ,hr: 'Neautorizirano' - ,it: 'Non autorizzato' + ,it: 'non autorizzato' } ,'Entering record failed' : { cs: 'Vložení záznamu selhalo' @@ -2199,7 +2204,7 @@ function init() { ,ro: '3h' ,bg: '3часа' ,hr: '3h' - ,it: '3ORE' + ,it: '3h' } ,'6HR' : { cs: '6hod' @@ -2211,7 +2216,7 @@ function init() { ,ro: '6h' ,bg: '6часа' ,hr: '6h' - ,it: '6ORE' + ,it: '6h' } ,'12HR' : { cs: '12hod' @@ -2223,7 +2228,7 @@ function init() { ,ro: '12h' ,bg: '12часа' ,hr: '12h' - ,it: '12ORE' + ,it: '12h' } ,'24HR' : { cs: '24hod' @@ -2235,7 +2240,7 @@ function init() { ,ro: '24h' ,bg: '24часа' ,hr: '24h' - ,it: '24ORE' + ,it: '24h' } ,'Settings' : { cs: 'Nastavení' @@ -2243,6 +2248,7 @@ function init() { ,es: 'Ajustes' ,fr: 'Paramètres' ,pt: 'Definições' + ,sv: 'Inställningar' ,ro: 'Setări' ,bg: 'Настройки' ,hr: 'Postavke' @@ -2314,7 +2320,7 @@ function init() { ,es: 'Control de glucemia' ,fr: 'Contrôle glycémie' ,pt: 'Medida de glicemia' - ,sv: 'BS-kontroll' + ,sv: 'Blodsockerkontroll' ,ro: 'Verificare glicemie' ,bg: 'Проверка на КЗ' ,hr: 'Kontrola GUK-a' @@ -2330,7 +2336,7 @@ function init() { ,bg: 'Болус-основно хранене' ,hr: 'Bolus za obrok' ,sv: 'Måltidsbolus' - ,it: 'Bolo Pasto' + ,it: 'bolo pasto' } ,'Snack Bolus' : { cs: 'Bolus na svačinu' @@ -2342,7 +2348,7 @@ function init() { ,ro: 'Bolus gustare' ,bg: 'Болус-лека закуска' ,hr: 'Bolus za užinu' - ,it: 'Bolo Merenda' + ,it: 'Bolo merenda' } ,'Correction Bolus' : { cs: 'Bolus na glykémii' @@ -2354,7 +2360,7 @@ function init() { ,bg: 'Болус корекция' ,hr: 'Korekcija' ,sv: 'Korrektionsbolus' - ,it: 'Bolo Correttivo' + ,it: 'Correzione bolo' } ,'Carb Correction' : { cs: 'Přídavek sacharidů' @@ -2366,7 +2372,7 @@ function init() { ,bg: 'Корекция за въглехидратите' ,hr: 'Bolus za hranu' ,sv: 'Kolhydratskorrektion' - ,it: 'Carboidrati Correttivi' + ,it: 'Correzione carboidrati' } ,'Note' : { cs: 'Poznámka' @@ -2375,9 +2381,9 @@ function init() { ,fr: 'Note' ,pt: 'Nota' ,ro: 'Notă' + ,sv: 'Notering' ,bg: 'Бележка' ,hr: 'Bilješka' - ,sv: 'Notering' ,it: 'Nota' } ,'Question' : { @@ -2386,8 +2392,8 @@ function init() { ,es: 'Pregunta' ,fr: 'Question' ,pt: 'Pergunta' - ,sv: 'Fråga' ,ro: 'Întrebare' + ,sv: 'Fråga' ,bg: 'Въпрос' ,hr: 'Pitanje' ,it: 'Domanda' @@ -2398,10 +2404,10 @@ function init() { ,es: 'Ejercicio' ,fr: 'Exercice' ,pt: 'Exercício' + ,sv: 'Aktivitet' ,ro: 'Activitate fizică' ,bg: 'Спорт' ,hr: 'Aktivnost' - ,sv: 'Aktivitet' ,it: 'Esercizio' } ,'Pump Site Change' : { @@ -2410,10 +2416,10 @@ function init() { ,es: 'Cambio de catéter' ,fr: 'Changement de site pompe' ,pt: 'Troca de catéter' + ,sv: 'Pump/nålbyte' ,ro: 'Schimbare loc pompă' ,bg: 'Смяна на сет' ,hr: 'Promjena seta' - ,sv: 'Pump/nålbyte' ,it: 'Cambio Ago' } ,'Sensor Start' : { @@ -2426,7 +2432,7 @@ function init() { ,ro: 'Start senzor' ,bg: 'Стартиране на сензор' ,hr: 'Start senzora' - ,it: 'Avvio Sensore' + ,it: 'Avvio sensore' } ,'Sensor Change' : { cs: 'Výměna sensoru' @@ -2438,7 +2444,7 @@ function init() { ,ro: 'Schimbare senzor' ,bg: 'Смяна на сензор' ,hr: 'Promjena senzora' - ,it: 'Cambio Sensore' + ,it: 'Cambio sensore' } ,'Dexcom Sensor Start' : { cs: 'Spuštění sensoru' @@ -2450,7 +2456,7 @@ function init() { ,ro: 'Pornire senzor Dexcom' ,bg: 'Поставяне на Декском сензор' ,hr: 'Start Dexcom senzora' - ,it: 'Avvio Sensore Dexcom' + ,it: 'Avvio sensore Dexcom' } ,'Dexcom Sensor Change' : { cs: 'Výměna sensoru' @@ -2462,7 +2468,7 @@ function init() { ,ro: 'Schimbare senzor Dexcom' ,bg: 'Смяна на Декском сензор' ,hr: 'Promjena Dexcom senzora' - ,it: 'Cambio Sensore Dexcom' + ,it: 'Cambio sensore Dexcom' } ,'Insulin Cartridge Change' : { cs: 'Výměna inzulínu' @@ -2470,11 +2476,11 @@ function init() { ,es: 'Cambio de reservorio de insulina' ,fr: 'Changement cartouche d\'insuline' ,pt: 'Troca de reservatório de insulina' + ,sv: 'Insulinreservoarbyte' ,ro: 'Schimbare cartuș insulină' ,bg: 'Смяна на резервоар' ,hr: 'Promjena spremnika inzulina' - ,sv: 'Insulinreservoarbyte' - ,it: 'Cambio Cartuccia Insulina' + ,it: 'Cambio cartuccia insulina' } ,'D.A.D. Alert' : { cs: 'D.A.D. Alert' @@ -2482,10 +2488,10 @@ function init() { ,es: 'Alerta de perro de alerta diabética' ,fr: 'Wouf! Wouf! Chien d\'alerte diabète' ,pt: 'Alerta de cão sentinela de diabetes' + ,sv: 'Diabeteshundlarm (Duktig vovve!)' ,ro: 'Alertă câine de serviciu' ,bg: 'Сигнал от обучено куче' ,hr: 'Obavijest dijabetičkog psa' - ,sv: 'Voff voff! (Diabeteshundalarm!)' ,it: 'Allarme D.A.D.' } ,'Glucose Reading' : { @@ -2494,11 +2500,11 @@ function init() { ,es: 'Valor de glucemia' ,fr: 'Valeur de glycémie' ,pt: 'Valor de glicemia' - ,sv: 'Blodglukosavläsning' + ,sv: 'Glukosvärde' ,ro: 'Valoare glicemie' ,bg: 'Кръвна захар' ,hr: 'Vrijednost GUK-a' - ,it: 'Lettura Glicemie' + ,it: 'Lettura glicemie' } ,'Measurement Method' : { cs: 'Metoda měření' @@ -2506,10 +2512,10 @@ function init() { ,es: 'Método de medida' ,fr: 'Méthode de mesure' ,pt: 'Método de medida' + ,sv: 'Mätmetod' ,ro: 'Metodă măsurare' ,bg: 'Метод на измерване' ,hr: 'Metoda mjerenja' - ,sv: 'Mätmetod' ,it: 'Metodo di misurazione' } ,'Meter' : { @@ -2530,10 +2536,10 @@ function init() { ,es: 'Insulina' ,fr: 'Insuline donnée' ,pt: 'Insulina' + ,sv: 'Insulindos' ,ro: 'Insulină administrată' ,bg: 'Инсулин' ,hr: 'Količina iznulina' - ,sv: 'Insulindos' ,it: 'Insulina' } ,'Amount in grams' : { @@ -2578,10 +2584,11 @@ function init() { ,es: 'Activar las alarmas' ,fr: 'Activer les alarmes' ,pt: 'Ativar alarmes' + ,sv: 'Aktivera larm' ,ro: 'Activează alarmele' ,bg: 'Активни аларми' ,hr: 'Aktiviraj alarme' - ,it: 'Attiva Allarmi' + ,it: 'Attiva Allarme' } ,'When enabled an alarm may sound.' : { cs: 'Při povoleném alarmu zní zvuk' @@ -2590,9 +2597,10 @@ function init() { ,fr: 'Si activée, un alarme peut sonner.' ,pt: 'Quando ativado, um alarme poderá soar' ,ro: 'Când este activ, poate suna o alarmă.' + ,sv: 'När markerad är ljudlarm aktivt' ,bg: 'Когато е активирано, алармата ще има звук' ,hr: 'Kad je aktiviran, alarm se može oglasiti' - ,it: 'Attiverai gli allarmi acustici.' + ,it: 'Quando si attiva un allarme acustico.' } ,'Urgent High Alarm' : { cs: 'Urgentní vysoká glykémie' @@ -2600,10 +2608,11 @@ function init() { ,es: 'Alarma de glucemia alta urgente' ,fr: 'Alarme haute urgente' ,pt: 'Alarme de alto urgente' + ,sv: 'Brådskande högt larmvärde' ,ro: 'Alarmă urgentă hiper' ,bg: 'Много висока КЗ' ,hr: 'Hitni alarm za hiper' - ,it: 'Urgente:Glicemia Molto Alta' + ,it: 'Allarme Urgente: Glicemia Molto Alta' } ,'High Alarm' : { cs: 'Vysoká glykémie' @@ -2611,10 +2620,11 @@ function init() { ,es: 'Alarma de glucemia alta' ,fr: 'Alarme haute' ,pt: 'Alarme de alto' + ,sv: 'Högt larmvärde' ,ro: 'Alarmă hiper' ,bg: 'Висока КЗ' ,hr: 'Alarm za hiper' - ,it: 'Glicemia Alta' + ,it: 'Allarme: Glicemia Alta' } ,'Low Alarm' : { cs: 'Nízká glykémie' @@ -2622,10 +2632,11 @@ function init() { ,es: 'Alarma de glucemia baja' ,fr: 'Alarme basse' ,pt: 'Alarme de baixo' + ,sv: 'Lågt larmvärde' ,ro: 'Alarmă hipo' ,bg: 'Ниска КЗ' ,hr: 'Alarm za hipo' - ,it: 'Glicemia Bassa' + ,it: 'Allarme Glicemia bassa' } ,'Urgent Low Alarm' : { cs: 'Urgentní nízká glykémie' @@ -2633,10 +2644,11 @@ function init() { ,es: 'Alarma de glucemia baja urgente' ,fr: 'Alarme basse urgente' ,pt: 'Alarme de baixo urgente' + ,sv: 'Brådskande lågt larmvärde' ,ro: 'Alarmă urgentă hipo' ,bg: 'Много ниска КЗ' ,hr: 'Hitni alarm za hipo' - ,it: 'Urgente:Glicemia Bassa' + ,it: 'Allarme Urgente. Glicemia Molto Bassa' } ,'Stale Data: Warn' : { cs: 'Zastaralá data' @@ -2644,10 +2656,11 @@ function init() { ,es: 'Datos obsoletos: aviso' ,fr: 'Données dépassées: avis' ,pt: 'Dados antigos: aviso' + ,sv: 'Förfluten data: Varning!' ,ro: 'Date învechite: alertă' ,bg: 'Стари данни' ,hr: 'Pažnja: Stari podaci' - ,it: 'Dati non aggiornati' + ,it: 'Dati obsoleti: Notifica' } ,'Stale Data: Urgent' : { cs: 'Zastaralá data urgentní' @@ -2655,11 +2668,11 @@ function init() { ,es: 'Datos obsoletos: Urgente' ,fr: 'Données dépassées urgentes' ,pt: 'Dados antigos: Urgente' - ,sv: 'Brådskande varning, Inaktuell data' + ,sv: 'Brådskande varning, inaktuell data' ,ro: 'Date învechite: urgent' ,bg: 'Много стари данни' ,hr: 'Hitno: Stari podaci' - ,it: 'Dati non aggiornati' + ,it: 'Dati obsoleti: Urgente' } ,'mins' : { cs: 'min' @@ -2692,10 +2705,11 @@ function init() { ,es: 'Cuando esté activo, el brillo de la página bajará de 10pm a 6am.' ,fr: 'Si activé, la page sera assombire de 22:00 à 6:00' ,pt: 'Se ativado, a página será escurecida de 22h a 6h' + ,sv: 'När aktiverad dimmas sidan mellan 22:00 - 06:00' ,ro: 'La activare va scădea iluminarea între 22 și 6' ,bg: 'Когато е активирано, страницата ще е затъмнена от 22-06ч' ,hr: 'Kad je uključen, stranica će biti zatamnjena od 22-06' - ,it: 'Attivandola, la pagina sarà oscurata dalle 22:00 alle 06:00.' + ,it: 'Attivandola, la pagina sarà oscurato da 10:00-06:00.' } ,'Enable' : { cs: 'Povoleno' @@ -2715,10 +2729,10 @@ function init() { ,es: 'Mostrat datos en glucemia en crudo' ,fr: 'Montrer les données BG brutes' ,pt: 'Mostrar dados de glicemia não processados' + ,sv: 'Visa RAW-data' ,ro: 'Afișează date primare glicemie' ,bg: 'Показвай RAW данни' ,hr: 'Prikazuj sirove podatke o GUK-u' - ,sv: 'Visa RAW-data' ,it: 'Mostra dati Raw BG' } ,'Never' : { @@ -2763,10 +2777,11 @@ function init() { ,es: 'Cuando esté activo, pequeños puntos blancos mostrarán los datos en crudo' ,fr: 'Si activé, des points blancs représenteront les données brutes' ,pt: 'Se ativado, pontinhos brancos representarão os dados de glicemia não processados' + ,sv: 'När aktiverad visar de vita punkterna RAW-blodglukosevärden' ,ro: 'La activare vor apărea puncte albe reprezentând citirea brută a glicemiei' ,bg: 'Когато е активирано, малки бели точки ще показват RAW данните' ,hr: 'Kad je omogućeno, male bijele točkice će prikazivati sirove podatke o GUK-u.' - ,it: 'Quando lo abiliti, visualizzerai piccoli puntini bianchi cioè i dati grezzi' + ,it: 'Quando lo abiliti, visualizzerai piccoli puntini bianchi (raw BG data)' } ,'Custom Title' : { cs: 'Vlastní název stránky' @@ -2798,6 +2813,7 @@ function init() { ,es: 'Por defecto' ,fr: 'Par défaut' ,pt: 'Padrão' + ,sv: 'Standard' ,ro: 'Implicită' ,bg: 'Черно-бяла' ,hr: 'Default' @@ -2810,6 +2826,7 @@ function init() { ,es: 'Colores' ,fr: 'Couleurs' ,pt: 'Cores' + ,sv: 'Färg' ,ro: 'Colorată' ,bg: 'Цветна' ,hr: 'Boje' @@ -2825,7 +2842,7 @@ function init() { ,ro: 'Resetează și folosește setările implicite' ,bg: 'Нулирай и използвай стандартните настройки' ,hr: 'Resetiraj i koristi defaultne vrijednosti' - ,it: 'Resetta le impostazioni' + ,it: 'Resetta e utilizza le impostazioni predefinite' } ,'Calibrations' : { cs: 'Kalibrace' @@ -2833,6 +2850,7 @@ function init() { ,es: 'Calibraciones' ,fr: 'Calibration' ,pt: 'Calibraçôes' + ,sv: 'Kalibreringar' ,ro: 'Calibrări' ,bg: 'Калибрации' ,hr: 'Kalibriranje' @@ -2844,6 +2862,7 @@ function init() { ,es: 'Test de Alarma / Activar teléfono' ,fr: 'Test alarme / Activer Smartphone' ,pt: 'Testar Alarme / Ativar Smartphone' + ,sv: 'Testa alarm / Aktivera Smatphone' ,ro: 'Teste alarme / Activează pe smartphone' ,bg: 'Тестване на алармата / Активно за мобилни телефони' ,hr: 'Alarm test / Aktiviraj smartphone' @@ -2879,6 +2898,7 @@ function init() { ,es: 'tiempo atrás' ,fr: 'temps avant' ,pt: 'tempo atrás' + ,sv: 'förfluten tid' ,sv: 'tid sedan' ,ro: 'în trecut' ,bg: 'преди време' @@ -2891,6 +2911,7 @@ function init() { ,es: 'hr atrás' ,fr: 'hr avant' ,pt: 'h atrás' + ,sv: 'timmar sedan' ,ro: 'oră în trecut' ,bg: 'час по-рано' ,hr: 'sat unazad' @@ -3051,6 +3072,7 @@ function init() { ,ro: 'Dispozitiv' ,bg: 'Устройство' ,hr: 'Uređaj' + ,sv: 'Enhet' ,it: 'Dispositivo' } ,'Noise' : { @@ -3063,6 +3085,7 @@ function init() { ,ro: 'Zgomot' ,bg: 'Шум' ,hr: 'Šum' + ,sv: 'Brus' ,it: 'Rumore' } ,'Calibration' : { @@ -3075,6 +3098,7 @@ function init() { ,ro: 'Calibrare' ,bg: 'Калибрация' ,hr: 'Kalibriranje' + ,sv: 'Kalibrering' ,it: 'Calibratura' } ,'Show Plugins' : { @@ -3086,7 +3110,8 @@ function init() { ,ro: 'Arată plugin-urile' ,bg: 'Покажи добавките' ,hr: 'Prikaži plugine' - ,it: 'Mostra Plugins' + ,sv: 'Visa tillägg' + ,it: 'Mostra Plugin' } ,'About' : { cs: 'O aplikaci' From 0deac611f39ee13040498cf9abebf7202e21fba5 Mon Sep 17 00:00:00 2001 From: Matteo Neri Date: Mon, 17 Aug 2015 21:51:17 +0200 Subject: [PATCH 619/937] update3 it-laguage.js Jason: last adjustment : 1 line. thanks --- lib/language.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/language.js b/lib/language.js index b28f38a40d8..64628cc922e 100644 --- a/lib/language.js +++ b/lib/language.js @@ -2603,7 +2603,7 @@ function init() { ,ro: 'Alarmă urgentă hiper' ,bg: 'Много висока КЗ' ,hr: 'Hitni alarm za hiper' - ,it: 'Urgente:Glicemia Molto Alta' + ,it: 'Urgente:Glicemia Alta' } ,'High Alarm' : { cs: 'Vysoká glykémie' From 59f7c258bdede7bd84d70d24c258b5e7d5d0729b Mon Sep 17 00:00:00 2001 From: xpucuto Date: Tue, 18 Aug 2015 15:10:36 +0300 Subject: [PATCH 620/937] Bulgarian language edit - 18.08.2015 I think i made it right this time :) --- lib/language.js | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/lib/language.js b/lib/language.js index 00506aa4556..b2de6c8ad62 100644 --- a/lib/language.js +++ b/lib/language.js @@ -521,7 +521,7 @@ function init() { ,pt: 'Carregando dados de alimentos' ,sv: 'Laddar födoämnesdatabas' ,ro: 'Încarc baza de date de alimente' - ,bg: 'Зареждане на данни за храни' + ,bg: 'Зареждане на данни с храни' ,hr: 'Učitavanje baze podataka o hrani' ,it: 'Carico dati alimenti' } @@ -990,7 +990,7 @@ function init() { ,pt: 'Max' ,sv: 'Max' ,ro: 'Max' - ,bg: 'Максимално' + ,bg: 'Макс.' ,hr: 'Max' ,it: 'Max' } @@ -1002,7 +1002,7 @@ function init() { ,pt: 'Min' ,sv: 'Min' ,ro: 'Min' - ,bg: 'Минимално' + ,bg: 'Мин.' ,hr: 'Min' ,it: 'Min' } @@ -1530,7 +1530,7 @@ function init() { ,fr: 'Utiliser la correction de glycémie dans les calculs' ,pt: 'Usar correção de glicemia nos cálculos' ,ro: 'Folosește corecția de glicemie în calcule' - ,bg: 'Въведи корекция за КЗ ' + ,bg: 'Използвай корекцията за КЗ в изчислението' ,hr: 'Koristi korekciju GUK-a u izračunu' ,sv: 'Använd BS-korrektion för beräkning' ,it: 'Utilizzare la correzione Glicemia nei calcoli' @@ -1614,7 +1614,7 @@ function init() { ,fr: 'Utiliser la correction en glucides dans les calculs' ,pt: 'Usar correção com carboidratos no cálculo' ,ro: 'Folosește corecția de carbohidrați în calcule' - ,bg: 'Въведи корекция за въглехидратите' + ,bg: 'Включи корекцията чрез ВХ в изчислението' ,hr: 'Koristi korekciju za UH u izračunu' ,sv: 'Använd kolhydratkorrektion för beräkning' ,it: 'Utilizzare la correzione con carboidrati nel calcolo' @@ -1626,7 +1626,7 @@ function init() { ,fr: 'Utiliser les COB dans les calculs' ,pt: 'Usar COB no cálculo' ,ro: 'Folosește COB în calcule' - ,bg: 'Въведи корекция за останалите въглехидрати' + ,bg: 'Включи активните ВХ в изчислението' ,hr: 'Koristi aktivne UH u izračunu' ,sv: 'Använd aktiva kolhydrater för beräkning' ,it: 'Utilizzare la correzione COB nel calcolo' @@ -1638,7 +1638,7 @@ function init() { ,fr: 'Utiliser l\'IOB dans les calculs' ,pt: 'Usar IOB no cálculo' ,ro: 'Folosește IOB în calcule' - ,bg: 'Използвай активния инсулин' + ,bg: 'Включи активния инсулин в изчислението' ,hr: 'Koristi aktivni inzulin u izračunu"' ,sv: 'Använd aktivt insulin för beräkning' ,it: 'Utilizzare la correzione IOB nel calcolo' @@ -1986,7 +1986,7 @@ function init() { ,pt: 'Não autorizado' ,sv: 'Ej behörig' ,ro: 'Neautorizat' - ,bg: 'Нямаш достъп' + ,bg: 'Неразрешен достъп' ,hr: 'Neautorizirano' ,it: 'non autorizzato' } @@ -2369,7 +2369,7 @@ function init() { ,fr: 'Correction glucide' ,pt: 'Carboidrato de correção' ,ro: 'Corecție de carbohidrați' - ,bg: 'Корекция за въглехидратите' + ,bg: 'Корекция чрез въглехидрати' ,hr: 'Bolus za hranu' ,sv: 'Kolhydratskorrektion' ,it: 'Correzione carboidrati' @@ -2397,6 +2397,9 @@ function init() { ,bg: 'Въпрос' ,hr: 'Pitanje' ,it: 'Domanda' + } + ,'Announcement' : { + bg: 'Известяване' } ,'Exercise' : { cs: 'Cvičení' @@ -2550,7 +2553,7 @@ function init() { ,pt: 'Quantidade em gramas' ,sv: 'Antal gram' ,ro: 'Cantitate în grame' - ,bg: ' К-во в грамове' + ,bg: 'К-во в грамове' ,hr: 'Količina u gramima' ,it: 'Quantità in grammi' } @@ -2719,7 +2722,7 @@ function init() { ,pt: 'Ativar' ,sv: 'Aktivera' ,ro: 'Activează' - ,bg: 'Активно' + ,bg: 'Активен' ,hr: 'Aktiviraj' ,it: 'Permettere' } @@ -2779,7 +2782,7 @@ function init() { ,pt: 'Se ativado, pontinhos brancos representarão os dados de glicemia não processados' ,sv: 'När aktiverad visar de vita punkterna RAW-blodglukosevärden' ,ro: 'La activare vor apărea puncte albe reprezentând citirea brută a glicemiei' - ,bg: 'Когато е активирано, малки бели точки ще показват RAW данните' + ,bg: 'Когато е активно, малки бели точки ще показват RAW данните' ,hr: 'Kad je omogućeno, male bijele točkice će prikazivati sirove podatke o GUK-u.' ,it: 'Quando lo abiliti, visualizzerai piccoli puntini bianchi (raw BG data)' } @@ -2935,7 +2938,7 @@ function init() { ,pt: 'min atrás' ,sv: 'minut sedan' ,ro: 'minut în trecut' - ,bg: 'минута по-рано' + ,bg: 'мин. по-рано' ,hr: 'minuta unazad' ,it: 'minuto fa' @@ -2948,7 +2951,7 @@ function init() { ,pt: 'min atrás' ,sv: 'minuter sedan' ,ro: 'minute în trecut' - ,bg: 'минути по-рано' + ,bg: 'мин. по-рано' ,hr: 'minuta unazad' ,it: 'minuti fa' } @@ -3032,7 +3035,7 @@ function init() { ,pt: 'Pesado' ,sv: 'Rikligt' ,ro: 'Puternic' - ,bg: 'Висок' + ,bg: 'Силен' ,hr: 'Teško' ,it: 'Pesante' } @@ -3137,7 +3140,7 @@ function init() { ,de: 'Kohlenhydrate Zeit' ,es: 'Momento de la ingesta' ,fr: 'Moment de Glucide' - ,bg: 'ВХ действа след' + ,bg: 'Ядене след' ,hr: 'Vrijeme unosa UH' ,sv: 'Kolhydratstid' ,it: 'Tempo' From 902f41eb364e8fd155e24a0d35b8025bcc7a5938 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 18 Aug 2015 22:15:05 -0700 Subject: [PATCH 621/937] fix css indents --- static/css/main.css | 756 ++++++++++++++++++++++---------------------- 1 file changed, 378 insertions(+), 378 deletions(-) diff --git a/static/css/main.css b/static/css/main.css index 1c87a86584f..40aa2177fcf 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -4,26 +4,26 @@ html, body { - height: 100%; - margin: 0; - padding: 0; + height: 100%; + margin: 0; + padding: 0; } body { - font-family: 'Open Sans', Helvetica, Arial, sans-serif; - background: #000; - color: #bdbdbd; + font-family: 'Open Sans', Helvetica, Arial, sans-serif; + background: #000; + color: #bdbdbd; } .container { - bottom: 0; - display: block; - height: 100%; - left: 0; - margin: 0; - padding: 0; - top: 45px; - width: 100%; - z-index: 2; + bottom: 0; + display: block; + height: 100%; + left: 0; + margin: 0; + padding: 0; + top: 45px; + width: 100%; + z-index: 2; } #container.announcing .customTitle { @@ -39,10 +39,10 @@ body { } .primary { - font-family: 'Ubuntu', Helvetica, Arial, sans-serif; - height: 180px; - vertical-align: middle; - clear: both; + font-family: 'Ubuntu', Helvetica, Arial, sans-serif; + height: 180px; + vertical-align: middle; + clear: both; } .has-minor-pills .primary { @@ -50,10 +50,10 @@ body { } .bgStatus { - float: right; - text-align: center; - white-space: nowrap; - padding-right: 20px; + float: right; + text-align: center; + white-space: nowrap; + padding-right: 20px; } .urgent .bgButton { @@ -69,10 +69,10 @@ body { } .bgStatus .currentBG { - font-size: 120px; - line-height: 100px; - text-decoration: line-through; - vertical-align: middle; + font-size: 120px; + line-height: 100px; + text-decoration: line-through; + vertical-align: middle; } .bgStatus .currentBG.bg-limit, .bgStatus .currentBG.icon-hourglass { @@ -89,50 +89,50 @@ body { } .bgStatus .majorPills { - font-size: 30px; + font-size: 30px; } .minorPills { - font-size: 22px; - margin-top: 10px; - z-index: 500; + font-size: 22px; + margin-top: 10px; + z-index: 500; } .majorPills > span:not(:first-child) { - margin-left: 5px; + margin-left: 5px; } .minorPills > span:not(:first-child) { - margin-left: 5px; + margin-left: 5px; } .pill { - white-space: nowrap; - border-radius: 5px; - border: 2px solid #808080; + white-space: nowrap; + border-radius: 5px; + border: 2px solid #808080; } .pill em, .pill label { - padding-left: 2px; - padding-right: 2px; + padding-left: 2px; + padding-right: 2px; } .pill em { - font-style: normal; - font-weight: bold; + font-style: normal; + font-weight: bold; } .pill label { - color: #000; - background: #808080; + color: #000; + background: #808080; } .pill.warn em { - color: yellow !important; + color: yellow !important; } .pill.urgent em { - color: red !important; + color: red !important; } .alarming-timeago #lastEntry.pill.warn { @@ -150,7 +150,7 @@ body { } .bgStatus.current .currentBG { - text-decoration: none; + text-decoration: none; } .alarming-timeago .bgStatus.current .currentBG { @@ -158,65 +158,65 @@ body { } .status { - color: #808080; - font-size: 100px; - line-height: 100px; + color: #808080; + font-size: 100px; + line-height: 100px; } .statusBox { - text-align: center; - width: 250px; + text-align: center; + width: 250px; } .statusPills { - font-size: 20px; - line-height: 30px; + font-size: 20px; + line-height: 30px; } .loading .statusPills { - display: none; + display: none; } .statusPills .pill { - background: #808080; - border-color: #808080; + background: #808080; + border-color: #808080; } .statusPills .pill em { - color: #bdbdbd; - background: #000; - border-radius: 4px 0 0 4px; + color: #bdbdbd; + background: #000; + border-radius: 4px 0 0 4px; } .statusPills .pill label { - background: #808080; + background: #808080; } .pill.upbat label { - padding: 0 !important; + padding: 0 !important; } #chartContainer { - left:0; - right:0; - bottom:0; - height:auto; - font-size: 20px; - background: #111; - display: block; - position:absolute; + left:0; + right:0; + bottom:0; + height:auto; + font-size: 20px; + background: #111; + display: block; + position:absolute; } #chartContainer svg { - width: 100%; + width: 100%; } #silenceBtn { - z-index: 99; - border-radius: 5px; - border: 2px solid #bdbdbd; + z-index: 99; + border-radius: 5px; + border: 2px solid #bdbdbd; } #silenceBtn a { - font-size: 40px + font-size: 40px } .bgButton { @@ -227,99 +227,99 @@ body { } .loading .pill.rawbg { - display: none; + display: none; } .pill.rawbg { - display: inline-block; - border-radius: 2px; - border: 1px solid #808080; + display: inline-block; + border-radius: 2px; + border: 1px solid #808080; } .pill.rawbg em { - color: white; - background-color: black; - display: block; - font-size: 20px; + color: white; + background-color: black; + display: block; + font-size: 20px; } .pill.rawbg label { - display: block; - font-size: 14px; + display: block; + font-size: 14px; } .alarming .bgButton { - border-color: #bdbdbd; - color: #000; - box-shadow: 2px 2px 0 #ddd; + border-color: #bdbdbd; + color: #000; + box-shadow: 2px 2px 0 #ddd; } .alarming.urgent .bgButton { - background-color: red; + background-color: red; } .alarming.warning .bgButton { - background-color: yellow; + background-color: yellow; } .alarming .bgButton:active { - border: 2px solid #bdbdbd; - box-shadow: none; - -moz-box-shadow: none; - -webkit-box-shadow: none; + border: 2px solid #bdbdbd; + box-shadow: none; + -moz-box-shadow: none; + -webkit-box-shadow: none; } .button { - text-align: center; - background: #ababab; - margin: 10px auto; + text-align: center; + background: #ababab; + margin: 10px auto; } .tooltip { - top: 0; - left: 0; - color: #444; - position: absolute; - text-align: left; - padding: 4px; - font-size: 14px; - line-height: 15px; - background: white; - border: 0; - border-radius: 8px; + top: 0; + left: 0; + color: #444; + position: absolute; + text-align: left; + padding: 4px; + font-size: 14px; + line-height: 15px; + background: white; + border: 0; + border-radius: 8px; } .alarms { - display: none; + display: none; } .loading .focus-range { - display: none; + display: none; } .focus-range { - list-style: none; - margin: 4px; - padding: 0; - color: #808080; - width: 250px; - text-align: center; + list-style: none; + margin: 4px; + padding: 0; + color: #808080; + width: 250px; + text-align: center; } .focus-range li { - display: inline-block; - font-size: 18px; - white-space: nowrap; - border-radius: 5px; - border: 2px solid #000; - cursor: pointer; + display: inline-block; + font-size: 18px; + white-space: nowrap; + border-radius: 5px; + border: 2px solid #000; + cursor: pointer; } .focus-range .selected { - border-color: #808080; - color: #000; - background: #808080; + border-color: #808080; + color: #000; + background: #808080; } .pill.hidden { @@ -327,322 +327,322 @@ body { } @media (max-width: 800px) { - .bgStatus { - width: 300px; - } + .bgStatus { + width: 300px; + } - .bgButton { - width: auto; - } + .bgButton { + width: auto; + } - .bgStatus .currentBG { - font-size: 80px; - line-height: 80px; - } + .bgStatus .currentBG { + font-size: 80px; + line-height: 80px; + } - .bgStatus .currentBG.bg-limit, .bgStatus .currentBG.icon-hourglass { - font-size: 70px; - } + .bgStatus .currentBG.bg-limit, .bgStatus .currentBG.icon-hourglass { + font-size: 70px; + } - .bgStatus .pill.direction { - font-size: 70px; - line-height: 70px; - } + .bgStatus .pill.direction { + font-size: 70px; + line-height: 70px; + } - .bgStatus .majorPills { - font-size: 20px; - } + .bgStatus .majorPills { + font-size: 20px; + } - .bgStatus .minorPills { - font-size: 16px; - } + .bgStatus .minorPills { + font-size: 16px; + } - .status { - font-size: 70px; - line-height: 60px; - padding-top: 8px; - } + .status { + font-size: 70px; + line-height: 60px; + padding-top: 8px; + } - .statusPills { - font-size: 15px; - line-height: 40px; - } + .statusPills { + font-size: 15px; + line-height: 40px; + } - .pill.upbat label { - font-size: 15px !important; - } + .pill.upbat label { + font-size: 15px !important; + } - .focus-range { - margin: 0; - } + .focus-range { + margin: 0; + } - .focus-range li { - font-size: 14px; - } + .focus-range li { + font-size: 14px; + } - #silenceBtn a { - font-size: 30px; - } + #silenceBtn a { + font-size: 30px; + } - #chartContainer { - font-size: 14px; - } + #chartContainer { + font-size: 14px; + } } @media (max-width: 750px) { - .bgStatus { - width: 240px; - padding: 0; - } + .bgStatus { + width: 240px; + padding: 0; + } - .pill.rawbg em { - font-size: 14px; - } + .pill.rawbg em { + font-size: 14px; + } - .pill.rawbg label { - font-size: 12px; - } + .pill.rawbg label { + font-size: 12px; + } - .bgStatus .currentBG { - font-size: 70px; - line-height: 60px; - } + .bgStatus .currentBG { + font-size: 70px; + line-height: 60px; + } - .bgStatus .currentBG.bg-limit, .bgStatus .currentBG.icon-hourglass { - font-size: 50px; - } + .bgStatus .currentBG.bg-limit, .bgStatus .currentBG.icon-hourglass { + font-size: 50px; + } - .bgStatus .pill.direction { - font-size: 50px; - line-height: 50px; - } + .bgStatus .pill.direction { + font-size: 50px; + line-height: 50px; + } - .bgStatus .majorPills { - font-size: 15px; - } + .bgStatus .majorPills { + font-size: 15px; + } - .bgStatus .minorPills { - font-size: 12px; - } + .bgStatus .minorPills { + font-size: 12px; + } - .status { - font-size: 50px; - line-height: 35px; - width: 250px; - } + .status { + font-size: 50px; + line-height: 35px; + width: 250px; + } } @media (max-width: 400px) { - .primary { - text-align: center; - margin-bottom: 0; - height: 152px; - } - - .bgStatus { - float: none; - padding: 0; - text-align: center; - width: 250px; - } - - - .bgButton { - margin: 5px; - width: auto; - } - - .bgStatus .currentBG { - font-size: 70px; - line-height: 70px; - } - - .bgStatus .currentBG.bg-limit, .bgStatus .currentBG.icon-hourglass { - font-size: 60px; - padding-left: 20px; - } - - .bgStatus .pill.direction { - font-size: 60px; - line-height: 60px; - } - - .bgStatus .majorPills { - font-size: 15px; - } - - .bgStatus .minorPills { - font-size: 12px; - } - - #silenceBtn a { - font-size: 20px; - } - - .status { - padding-top: 0; - font-size: 20px !important; - } - - .statusBox { - width: auto; - } - - #currentTime { - display: inline; - vertical-align: middle; - } - - .statusPills { - display: inline; - margin-left: auto; - font-size: 15px; - } - - .pill.upbat label { - font-size: 15px !important; - } - - .focus-range { - position: absolute; - top: 80px; - left: auto; - right: 10px; - margin: 0; - width: auto; - } - - .focus-range li { - display: block; - } + .primary { + text-align: center; + margin-bottom: 0; + height: 152px; + } + + .bgStatus { + float: none; + padding: 0; + text-align: center; + width: 250px; + } + + + .bgButton { + margin: 5px; + width: auto; + } + + .bgStatus .currentBG { + font-size: 70px; + line-height: 70px; + } + + .bgStatus .currentBG.bg-limit, .bgStatus .currentBG.icon-hourglass { + font-size: 60px; + padding-left: 20px; + } + + .bgStatus .pill.direction { + font-size: 60px; + line-height: 60px; + } + + .bgStatus .majorPills { + font-size: 15px; + } + + .bgStatus .minorPills { + font-size: 12px; + } + + #silenceBtn a { + font-size: 20px; + } + + .status { + padding-top: 0; + font-size: 20px !important; + } + + .statusBox { + width: auto; + } + + #currentTime { + display: inline; + vertical-align: middle; + } + + .statusPills { + display: inline; + margin-left: auto; + font-size: 15px; + } + + .pill.upbat label { + font-size: 15px !important; + } + + .focus-range { + position: absolute; + top: 80px; + left: auto; + right: 10px; + margin: 0; + width: auto; + } + + .focus-range li { + display: block; + } } @media (max-height: 700px) { - #chartContainer { - font-size: 14px; - } + #chartContainer { + font-size: 14px; + } } @media (max-height: 600px) { - #chartContainer { - font-size: 12px; - } + #chartContainer { + font-size: 12px; + } } @media (max-height: 480px) { - #toolbar { - float: right; - height: auto !important; - } + #toolbar { + float: right; + height: auto !important; + } - #toolbar .customTitle { - display: none; - } + #toolbar .customTitle { + display: none; + } - .container { - top: 15px; - padding-bottom: 20px; - } + .container { + top: 15px; + padding-bottom: 20px; + } - #chartContainer { - font-size: 10px; - } + #chartContainer { + font-size: 10px; + } } @media (max-height: 480px) and (min-width: 400px) { - .bgStatus .currentBG { - font-size: 70px; - line-height: 60px; - } + .bgStatus .currentBG { + font-size: 70px; + line-height: 60px; + } - .bgStatus .currentBG.bg-limit, .bgStatus .currentBG.icon-hourglass { - font-size: 70px; - } + .bgStatus .currentBG.bg-limit, .bgStatus .currentBG.icon-hourglass { + font-size: 70px; + } - .bgStatus .pill.direction { - font-size: 50px; - line-height: 50px; - } + .bgStatus .pill.direction { + font-size: 50px; + line-height: 50px; + } - .bgStatus .majorPills { - font-size: 15px; - } + .bgStatus .majorPills { + font-size: 15px; + } - .bgStatus .minorPills { - font-size: 12px; - } + .bgStatus .minorPills { + font-size: 12px; + } - .status { - font-size: 50px; - line-height: 40px; - padding-top: 5px; - } + .status { + font-size: 50px; + line-height: 40px; + padding-top: 5px; + } - .statusPills { - font-size: 15px; - } + .statusPills { + font-size: 15px; + } - .pill.upbat label { - font-size: 15px !important; - } + .pill.upbat label { + font-size: 15px !important; + } } @media (max-height: 480px) and (max-width: 400px) { - .bgStatus { - text-align: center; - width: 220px; - left: 0; - position: absolute; - } + .bgStatus { + text-align: center; + width: 220px; + left: 0; + position: absolute; + } - .bgStatus .currentBG { - font-size: 60px; - line-height: 60px; - } + .bgStatus .currentBG { + font-size: 60px; + line-height: 60px; + } - .bgStatus .currentBG.bg-limit, .bgStatus .currentBG.icon-hourglass { - font-size: 60px; - } + .bgStatus .currentBG.bg-limit, .bgStatus .currentBG.icon-hourglass { + font-size: 60px; + } - .bgStatus .pill.direction { - font-size: 50px; - line-height: 50px; - } + .bgStatus .pill.direction { + font-size: 50px; + line-height: 50px; + } - .bgStatus .majorPills { - font-size: 15px; - } + .bgStatus .majorPills { + font-size: 15px; + } - .bgStatus .majorPills { - font-size: 12px; - } + .bgStatus .majorPills { + font-size: 12px; + } - #silenceBtn { - right: -25px; - } + #silenceBtn { + right: -25px; + } .focus-range { - position: absolute; - top: 40px; - left: auto; - right: 35px; - margin: 0; - width: auto; - } - - .focus-range li { - display: block; - } - - .status { - position: absolute; - top: 90px; - } - - .statusBox { - width: 220px; - } + position: absolute; + top: 40px; + left: auto; + right: 35px; + margin: 0; + width: auto; + } + + .focus-range li { + display: block; + } + + .status { + position: absolute; + top: 90px; + } + + .statusBox { + width: 220px; + } } From b007b6779fb10017de1cfed50136449e8749f185 Mon Sep 17 00:00:00 2001 From: jweismann Date: Wed, 19 Aug 2015 09:47:23 +0200 Subject: [PATCH 622/937] danish translation --- lib/language.js | 262 ++++++++++++++++++++++++++++++++++++++++++++++ static/index.html | 1 + 2 files changed, 263 insertions(+) diff --git a/lib/language.js b/lib/language.js index 3ac555045b0..f9b98c84f99 100644 --- a/lib/language.js +++ b/lib/language.js @@ -18,6 +18,7 @@ function init() { ,ro: 'Activ pe portul' ,bg: 'Активиране на порта' ,hr: 'Slušanje na portu' + ,dk: 'Lytter på port' } // Client ,'Language' : { @@ -52,6 +53,9 @@ function init() { } , 'Swedish': { + } + , 'Danish': { + } ,'Mo' : { cs: 'Po' @@ -63,6 +67,7 @@ function init() { ,ro: 'Lu' ,bg: 'Пон' ,hr: 'Pon' + ,dk: 'Man' } ,'Tu' : { cs: 'Út' @@ -74,6 +79,7 @@ function init() { ,ro: 'Ma' ,bg: 'Вт' ,hr: 'Ut' + ,dk: 'Tir' }, ',We' : { cs: 'St' @@ -85,6 +91,7 @@ function init() { ,ro: 'Mie' ,bg: 'Ср' ,hr: 'Sri' + ,dk: 'Ons' } ,'Th' : { cs: 'Čt' @@ -96,6 +103,7 @@ function init() { ,ro: 'Jo' ,bg: 'Четв' ,hr: 'Čet' + ,dk: 'Tor' } ,'Fr' : { cs: 'Pá' @@ -107,6 +115,7 @@ function init() { ,ro: 'Vi' ,bg: 'Пет' ,hr: 'Pet' + ,dk: 'Fre' } ,'Sa' : { cs: 'So' @@ -118,6 +127,7 @@ function init() { ,ro: 'Sa' ,bg: 'Съб' ,hr: 'Sub' + ,dk: 'Lør' } ,'Su' : { cs: 'Ne' @@ -129,6 +139,7 @@ function init() { ,ro: 'Du' ,bg: 'Нед' ,hr: 'Ned' + ,dk: 'Søn' } ,'Monday' : { cs: 'Pondělí' @@ -140,6 +151,7 @@ function init() { ,ro: 'Luni' ,bg: 'Понеделник' ,hr: 'Ponedjeljak' + ,dk: 'Mandag' } ,'Tuesday' : { cs: 'Úterý' @@ -151,6 +163,7 @@ function init() { ,bg: 'Вторник' ,hr: 'Utorak' ,sv: 'Tisdag' + ,dk: 'Tirsdag' } ,'Wednesday' : { cs: 'Středa' @@ -162,6 +175,7 @@ function init() { ,ro: 'Miercuri' ,bg: 'Сряда' ,hr: 'Srijeda' + ,dk: 'Onsdag' } ,'Thursday' : { cs: 'Čtvrtek' @@ -173,6 +187,7 @@ function init() { ,ro: 'Joi' ,bg: 'Четвъртък' ,hr: 'Četvrtak' + ,dk: 'Torsdag' } ,'Friday' : { cs: 'Pátek' @@ -184,6 +199,7 @@ function init() { ,es: 'Viernes' ,bg: 'Петък' ,hr: 'Petak' + ,dk: 'Fredag' } ,'Saturday' : { cs: 'Sobota' @@ -195,6 +211,7 @@ function init() { ,bg: 'Събота' ,hr: 'Subota' ,sv: 'Lördag' + ,dk: 'Lørdag' } ,'Sunday' : { cs: 'Neděle' @@ -206,6 +223,7 @@ function init() { ,bg: 'Неделя' ,hr: 'Nedjelja' ,sv: 'Söndag' + ,dk: 'Søndag' } ,'Category' : { cs: 'Kategorie' @@ -217,6 +235,7 @@ function init() { ,ro: 'Categorie' ,bg: 'Категория' ,hr: 'Kategorija' + ,dk: 'Kategori' } ,'Subcategory' : { cs: 'Podkategorie' @@ -228,6 +247,7 @@ function init() { ,ro: 'Subcategorie' ,bg: 'Подкатегория' ,hr: 'Podkategorija' + ,dk: 'Underkategori' } ,'Name' : { cs: 'Jméno' @@ -239,6 +259,7 @@ function init() { ,ro: 'Nume' ,bg: 'Име' ,hr: 'Ime' + ,dk: 'Navn' } ,'Today' : { cs: 'Dnes' @@ -250,6 +271,7 @@ function init() { ,bg: 'Днес' ,hr: 'Danas' ,sv: 'Idag' + ,dk: 'Idag' } ,'Last 2 days' : { cs: 'Poslední 2 dny' @@ -261,6 +283,7 @@ function init() { ,bg: 'Последните 2 дни' ,hr: 'Posljednja 2 dana' ,sv: 'Senaste 2 dagarna' + ,dk: 'Sidste 2 dage' } ,'Last 3 days' : { cs: 'Poslední 3 dny' @@ -272,6 +295,7 @@ function init() { ,ro: 'Ultimele 3 zile' ,bg: 'Последните 3 дни' ,hr: 'Posljednja 3 dana' + ,dk: 'Sidste 3 dage' } ,'Last week' : { cs: 'Poslední týden' @@ -283,6 +307,7 @@ function init() { ,bg: 'Последната седмица' ,hr: 'Protekli tjedan' ,sv: 'Senaste veckan' + ,dk: 'Sidste uge' } ,'Last 2 weeks' : { cs: 'Poslední 2 týdny' @@ -294,6 +319,7 @@ function init() { ,bg: 'Последните 2 седмици' ,hr: 'Protekla 2 tjedna' ,sv: 'Senaste 2 veckorna' + ,dk: 'Sidste 2 uger' } ,'Last month' : { cs: 'Poslední měsíc' @@ -305,6 +331,7 @@ function init() { ,bg: 'Последният месец' ,hr: 'Protekli mjesec' ,sv: 'Senaste månaden' + ,dk: 'Sidste måned' } ,'Last 3 months' : { cs: 'Poslední 3 měsíce' @@ -316,6 +343,7 @@ function init() { ,bg: 'Последните 3 месеца' ,hr: 'Protekla 3 mjeseca' ,sv: 'Senaste 3 månaderna' + ,dk: 'Sidste 3 måneder' } ,'From' : { cs: 'Od' @@ -327,6 +355,7 @@ function init() { ,ro: 'De la' ,bg: 'От' ,hr: 'Od' + ,dk: 'Fra' } ,'To' : { cs: 'Do' @@ -338,6 +367,7 @@ function init() { ,bg: 'До' ,hr: 'Do' ,sv: 'Till' + ,dk: 'Til' } ,'Notes' : { cs: 'Poznámky' @@ -349,6 +379,7 @@ function init() { ,ro: 'Note' ,bg: 'Бележки' ,hr: 'Bilješke' + ,dk: 'Noter' } ,'Food' : { cs: 'Jídlo' @@ -360,6 +391,7 @@ function init() { ,ro: 'Mâncare' ,bg: 'Храна' ,hr: 'Hrana' + ,dk: 'Mad' } ,'Insulin' : { cs: 'Inzulín' @@ -371,6 +403,7 @@ function init() { ,bg: 'Инсулин' ,hr: 'Inzulin' ,sv: 'Insulin' + ,dk: 'Insulin' } ,'Carbs' : { cs: 'Sacharidy' @@ -382,6 +415,7 @@ function init() { ,bg: 'Въглехидрати' ,hr: 'Ugljikohidrati' ,sv: 'Kolhydrater' + ,dk: 'Kulhydrater' } ,'Notes contain' : { cs: 'Poznámky obsahují' @@ -393,6 +427,7 @@ function init() { ,bg: 'бележките съдържат' ,hr: 'Sadržaj bilješki' ,sv: 'Notering innehåller' + ,dk: 'Noter indeholder' } ,'Event type contains' : { cs: 'Typ události obsahuje' @@ -404,6 +439,7 @@ function init() { ,bg: 'Типа събитие включва' ,hr: 'Sadržaj vrste događaja' ,sv: 'Händelsen innehåller' + ,dk: 'Hændelsen indeholder' } ,'Target bg range bottom' : { cs: 'Cílová glykémie spodní' @@ -415,6 +451,7 @@ function init() { ,bg: 'Долна граница на КЗ' ,hr: 'Ciljna donja granica GUK-a' ,sv: 'Gräns för nedre blodsockervärde' + ,dk: 'Nedre grænse for blodsukkerværdier' } ,'top' : { cs: 'horní' @@ -426,6 +463,7 @@ function init() { ,bg: 'горе' ,hr: 'Gornja' ,sv: 'Toppen' + ,dk: 'Top' } ,'Show' : { cs: 'Zobraz' @@ -436,6 +474,7 @@ function init() { ,ro: 'Arată' ,bg: 'Покажи' ,hr: 'Prikaži' + ,dk: 'Vis' } ,'Display' : { cs: 'Zobraz' @@ -447,6 +486,7 @@ function init() { ,bg: 'Покажи' ,hr: 'Prikaži' ,sv: 'Visa' + ,dk: 'Vis' } ,'Loading' : { cs: 'Nahrávám' @@ -458,6 +498,7 @@ function init() { ,bg: 'Зареждане' ,hr: 'Učitavanje' ,sv: 'Laddar' + ,dk: 'Indlæser' } ,'Loading profile' : { cs: 'Nahrávám profil' @@ -469,6 +510,7 @@ function init() { ,ro: 'Încarc profilul' ,bg: 'Зареждане на профил' ,hr: 'Učitavanje profila' + ,dk: 'Indlæser profil' } ,'Loading status' : { cs: 'Nahrávám status' @@ -480,6 +522,7 @@ function init() { ,ro: 'Încarc statusul' ,bg: 'Зареждане на статус' ,hr: 'Učitavanje statusa' + ,dk: 'Indlæsnings status' } ,'Loading food database' : { cs: 'Nahrávám databázi jídel' @@ -491,6 +534,7 @@ function init() { ,ro: 'Încarc baza de date de alimente' ,bg: 'Зареждане на данни за храни' ,hr: 'Učitavanje baze podataka o hrani' + ,dk: 'Indlæser mad database' } ,'not displayed' : { cs: 'není zobrazeno' @@ -502,6 +546,7 @@ function init() { ,bg: 'Не се показва' ,hr: 'Ne prikazuje se' ,sv: 'Visas ej' + ,dk: 'Vises ikke' } ,'Loading CGM data of' : { cs: 'Nahrávám CGM data' @@ -513,6 +558,7 @@ function init() { ,ro: 'Încarc datele CGM ale lui' ,bg: 'Зареждане на CGM данни от' ,hr: 'Učitavanja podataka CGM-a' + ,dk: 'Indlæser CGM-data for' } ,'Loading treatments data of' : { cs: 'Nahrávám data ošetření' @@ -524,6 +570,7 @@ function init() { ,ro: 'Încarc datele despre tratament pentru' ,bg: 'Зареждане на въведените лечения от' ,hr: 'Učitavanje podataka o tretmanu' + ,dk: 'Indlæser data for' } ,'Processing data of' : { cs: 'Zpracovávám data' @@ -535,6 +582,7 @@ function init() { ,ro: 'Procesez datele lui' ,bg: 'Зареждане на данни от' ,hr: 'Obrada podataka' + ,dk: 'Behandler data for' } ,'Portion' : { cs: 'Porce' @@ -546,6 +594,7 @@ function init() { ,bg: 'Порция' ,hr: 'Dio' ,sv: 'Portion' + ,dk: 'Portion' } ,'Size' : { cs: 'Rozměr' @@ -556,6 +605,7 @@ function init() { ,ro: 'Mărime' ,bg: 'Големина' ,hr: 'Veličina' + ,dk: 'Størrelse' } ,'(none)' : { cs: '(Prázdný)' @@ -566,6 +616,7 @@ function init() { ,ro: '(fără)' ,bg: 'няма' ,hr: '(Prazno)' + ,dk: '(ingen)' } ,'Result is empty' : { cs: 'Prázdný výsledek' @@ -576,6 +627,7 @@ function init() { ,ro: 'Fără rezultat' ,bg: 'Няма резултат' ,hr: 'Prazan rezultat' + ,dk: 'Tomt resultat' } // ported reporting ,'Day to day' : { @@ -588,6 +640,7 @@ function init() { ,ro: 'Zi cu zi' ,bg: 'Ден за ден' ,hr: 'Svakodnevno' + ,dk: 'Dag til dag' } ,'Daily Stats' : { cs: 'Denní statistiky' @@ -599,6 +652,7 @@ function init() { ,ro: 'Statistici zilnice' ,bg: 'Дневна статистика' ,hr: 'Dnevna statistika' + ,dk: 'Daglig statistik' } ,'Percentile Chart' : { cs: 'Percentil' @@ -610,6 +664,7 @@ function init() { ,bg: 'Процентна графика' ,hr: 'Tablica u postotcima' ,sv: 'Procentgraf' + ,dk: 'Procentgraf' } ,'Distribution' : { cs: 'Rozložení' @@ -621,6 +676,7 @@ function init() { ,bg: 'Разпределение' ,hr: 'Distribucija' ,sv: 'Distribution' + ,dk: 'Distribution' } ,'Hourly stats' : { cs: 'Statistika po hodinách' @@ -632,6 +688,7 @@ function init() { ,ro: 'Statistici orare' ,bg: 'Статистика по часове' ,hr: 'Statistika po satu' + ,dk: 'Timestatistik' } ,'Weekly success' : { cs: 'Statistika po týdnech' @@ -643,6 +700,7 @@ function init() { ,bg: 'Седмичен успех' ,hr: 'Tjedni uspjeh' ,sv: 'Veckoresultat' + ,dk: 'Uge resultat' } ,'No data available' : { cs: 'Žádná dostupná data' @@ -654,6 +712,7 @@ function init() { ,bg: 'Няма данни за показване' ,hr: 'Nema raspoloživih podataka' ,sv: 'Data saknas' + ,dk: 'Mangler data' } ,'Low' : { cs: 'Nízká' @@ -665,6 +724,7 @@ function init() { ,ro: 'Prea jos' ,bg: 'Ниска' ,hr: 'Nizak' + ,dk: 'Lav' } ,'In Range' : { cs: 'V rozsahu' @@ -676,6 +736,7 @@ function init() { ,ro: 'În interval' ,bg: 'В граници' ,hr: 'U rasponu' + ,dk: 'Indenfor intervallet' } ,'Period' : { cs: 'Období' @@ -687,6 +748,7 @@ function init() { ,ro: 'Perioada' ,bg: 'Период' ,hr: 'Period' + ,dk: 'Period' } ,'High' : { cs: 'Vysoká' @@ -698,6 +760,7 @@ function init() { ,ro: 'Prea sus' ,bg: 'Висока' ,hr: 'Visok' + ,dk: 'Høj' } ,'Average' : { cs: 'Průměrná' @@ -709,6 +772,7 @@ function init() { ,ro: 'Media' ,bg: 'Средна' ,hr: 'Prosjek' + ,dk: 'Gennemsnit' } ,'Low Quartile' : { cs: 'Nízký kvartil' @@ -720,6 +784,7 @@ function init() { ,bg: 'Ниска четвъртинка' ,hr: 'Donji kvartil' ,sv: 'Nedre kvadranten' + ,dk: 'Nedre kvartil' } ,'Upper Quartile' : { cs: 'Vysoký kvartil' @@ -731,6 +796,7 @@ function init() { ,bg: 'Висока четвъртинка' ,hr: 'Gornji kvartil' ,sv: 'Övre kvadranten' + ,dk: 'Øvre kvartil' } ,'Quartile' : { cs: 'Kvartil' @@ -741,6 +807,7 @@ function init() { ,ro: 'Pătrime' ,bg: 'Четвъртинка' ,hr: 'Kvartil' + ,dk: 'Kvartil' } ,'Date' : { cs: 'Datum' @@ -752,6 +819,7 @@ function init() { ,ro: 'Data' ,bg: 'Дата' ,hr: 'Datum' + ,dk: 'Dato' } ,'Normal' : { cs: 'Normální' @@ -763,6 +831,7 @@ function init() { ,ro: 'Normal' ,bg: 'Нормално' ,hr: 'Normalno' + ,dk: 'Normal' } ,'Median' : { cs: 'Medián' @@ -774,6 +843,7 @@ function init() { ,bg: 'Средно' ,hr: 'Srednje' ,sv: 'Median' + ,dk: 'Median' } ,'Readings' : { cs: 'Záznamů' @@ -785,6 +855,7 @@ function init() { ,ro: 'Valori' ,bg: 'Измервания' ,hr: 'Vrijednosti' + ,dk: 'Aflæsning' } ,'StDev' : { cs: 'St. odchylka' @@ -796,6 +867,7 @@ function init() { ,ro: 'Dev Std' ,bg: 'Стандартно отклонение' ,hr: 'Standardna devijacija' + ,dk: 'Standard afvigelse' } ,'Daily stats report' : { cs: 'Denní statistiky' @@ -807,6 +879,7 @@ function init() { ,bg: 'Дневна статистика' ,hr: 'Izvješće o dnevnim statistikama' ,sv: 'Dygnsstatistik' + ,dk: 'Daglig statistik rapport' } ,'Glucose Percentile report' : { cs: 'Tabulka percentil glykémií' @@ -818,6 +891,7 @@ function init() { ,ro: 'Raport percentile glicemii' ,bg: 'Графика на КЗ' ,hr: 'Izvješće o postotku GUK-a' + ,dk: 'Glukoserapport i procent' } ,'Glucose distribution' : { cs: 'Rozložení glykémií' @@ -829,6 +903,7 @@ function init() { ,bg: 'Разпределение на КЗ' ,hr: 'Distribucija GUK-a' ,sv: 'Glukosdistribution' + ,dk: 'Glukosefordeling' } ,'days total' : { cs: 'dní celkem' @@ -840,6 +915,7 @@ function init() { ,ro: 'total zile' ,bg: 'общо за деня' ,hr: 'ukupno dana' + ,dk: 'antal dage' } ,'Overall' : { cs: 'Celkem' @@ -851,6 +927,7 @@ function init() { ,ro: 'General' ,bg: 'Общо' ,hr: 'Ukupno' + ,dk: 'Overall' } ,'Range' : { cs: 'Rozsah' @@ -862,6 +939,7 @@ function init() { ,ro: 'Interval' ,bg: 'Диапазон' ,hr: 'Raspon' + ,dk: 'Interval' } ,'% of Readings' : { cs: '% záznamů' @@ -873,6 +951,7 @@ function init() { ,ro: '% de valori' ,bg: '% от измервания' ,hr: '% očitanja' + ,dk: '% af aflæsningerne' } ,'# of Readings' : { cs: 'počet záznamů' @@ -884,6 +963,7 @@ function init() { ,ro: 'nr. de valori' ,bg: '№ от измервания' ,hr: 'broj očitanja' + ,dk: 'Antal af aflæsninger' } ,'Mean' : { cs: 'Střední hodnota' @@ -895,6 +975,7 @@ function init() { ,ro: 'Medie' ,bg: 'Средна стойност' ,hr: 'Prosjek' + ,dk: 'Gennemsnit' } ,'Standard Deviation' : { cs: 'Standardní odchylka' @@ -906,6 +987,7 @@ function init() { ,bg: 'Стандартно отклонение' ,hr: 'Standardna devijacija' ,sv: 'Standardavvikelse' + ,dk: 'Standardafgivelse' } ,'Max' : { cs: 'Max' @@ -917,6 +999,7 @@ function init() { ,ro: 'Max' ,bg: 'Максимално' ,hr: 'Max' + ,dk: 'Max' } ,'Min' : { cs: 'Min' @@ -928,6 +1011,7 @@ function init() { ,ro: 'Min' ,bg: 'Минимално' ,hr: 'Min' + ,dk: 'Min' } ,'A1c estimation*' : { cs: 'Předpokládané HBA1c*' @@ -939,6 +1023,7 @@ function init() { ,bg: 'Очакван HbA1c' ,hr: 'Procjena HbA1c-a' ,sv: 'Beräknat A1c-värde ' + ,dk: 'Beregnet A1c-værdi ' } ,'Weekly Success' : { cs: 'Týdenní úspěšnost' @@ -950,6 +1035,7 @@ function init() { ,bg: 'Седмичен успех' ,hr: 'Tjedni uspjeh' ,sv: 'Veckoresultat' + ,dk: 'Uge resultat' } ,'There is not sufficient data to run this report. Select more days.' : { cs: 'Není dostatek dat. Vyberte delší časové období.' @@ -961,6 +1047,7 @@ function init() { ,bg: 'Няма достатъчно данни за показване. Изберете повече дни.' ,hr: 'Nema dovoljno podataka za izvođenje izvještaja. Odaberite još dana.' ,sv: 'Data saknas för att köra rapport. Välj fler dagar.' + ,dk: 'Der er utilstrækkeligt data til at generere rapporten. Vælg flere dage.' } // food editor ,'Using stored API secret hash' : { @@ -973,6 +1060,7 @@ function init() { ,bg: 'Използване на запаметена API парола' ,hr: 'Koristi se pohranjeni API tajni hash' ,sv: 'Använd hemlig API-nyckel' + ,dk: 'Anvender gemt API-nøgle' } ,'No API secret hash stored yet. You need to enter API secret.' : { cs: 'Není uložený žádný hash API hesla. Musíte zadat API heslo.' @@ -984,6 +1072,7 @@ function init() { ,bg: 'Няма запаметена API парола. Tрябва да въведете API парола' ,hr: 'Nema pohranjenog API tajnog hasha. Unesite tajni API' ,sv: 'Hemlig api-nyckel saknas. Du måste ange API hemlighet' + ,dk: 'Mangler API-nøgle. Du skal indtaste API hemmelighed' } ,'Database loaded' : { cs: 'Databáze načtena' @@ -995,6 +1084,7 @@ function init() { ,bg: 'База с данни заредена' ,hr: 'Baza podataka je učitana' ,sv: 'Databas laddad' + ,dk: 'Database indlæst' } ,'Error: Database failed to load' : { cs: 'Chyba při načítání databáze' @@ -1006,6 +1096,7 @@ function init() { ,bg: 'ГРЕШКА. Базата с данни не успя да се зареди' ,hr: 'Greška: Baza podataka nije učitana' ,sv: 'Error: Databas kan ej laddas' + ,dk: 'Fejl: Database kan ikke indlæses' } ,'Create new record' : { cs: 'Vytvořit nový záznam' @@ -1017,6 +1108,7 @@ function init() { ,bg: 'Създаване на нов запис' ,hr: 'Kreiraj novi zapis' ,sv: 'Skapa ny post' + ,dk: 'Danner ny post' } ,'Save record' : { cs: 'Uložit záznam' @@ -1028,6 +1120,7 @@ function init() { ,bg: 'Запази запис' ,hr: 'Spremi zapis' ,sv: 'Spara post' + ,dk: 'Gemmer post' } ,'Portions' : { cs: 'Porcí' @@ -1039,6 +1132,7 @@ function init() { ,bg: 'Порции' ,hr: 'Dijelovi' ,sv: 'Portion' + ,dk: 'Portioner' } ,'Unit' : { cs: 'Jedn' @@ -1050,6 +1144,7 @@ function init() { ,bg: 'Единици' ,hr: 'Jedinica' ,sv: 'Enhet' + ,dk: 'Enheder' } ,'GI' : { cs: 'GI' @@ -1061,6 +1156,7 @@ function init() { ,ro: 'CI' ,bg: 'ГИ' ,hr: 'GI' + ,dk: 'GI' } ,'Edit record' : { cs: 'Upravit záznam' @@ -1072,6 +1168,7 @@ function init() { ,bg: 'Редактирай запис' ,hr: 'Uredi zapis' ,sv: 'Editera post' + ,dk: 'Editere post' } ,'Delete record' : { cs: 'Smazat záznam' @@ -1083,6 +1180,7 @@ function init() { ,bg: 'Изтрий запис' ,hr: 'Izbriši zapis' ,sv: 'Ta bort post' + ,dk: 'Slet post' } ,'Move to the top' : { cs: 'Přesuň na začátek' @@ -1094,6 +1192,7 @@ function init() { ,ro: 'Mergi la început' ,bg: 'Преместване в началото' ,hr: 'Premjesti na vrh' + ,dk: 'Gå til toppen' } ,'Hidden' : { cs: 'Skrytý' @@ -1105,6 +1204,7 @@ function init() { ,ro: 'Ascuns' ,bg: 'Скрити' ,hr: 'Skriveno' + ,dk: 'Skjult' } ,'Hide after use' : { cs: 'Skryj po použití' @@ -1116,6 +1216,7 @@ function init() { ,bg: 'Скрий след употреба' ,hr: 'Sakrij nakon korištenja' ,sv: 'Dölj efter användning' + ,dk: 'Skjul efter brug' } ,'Your API secret must be at least 12 characters long' : { cs: 'Vaše API heslo musí mít alespoň 12 znaků' @@ -1127,6 +1228,7 @@ function init() { ,bg: 'Вашата АPI парола трябва да е дълга поне 12 символа' ,hr: 'Vaš tajni API mora sadržavati barem 12 znakova' ,sv: 'Hemlig API-nyckel måsta innehålla 12 tecken' + ,dk: 'Din API nøgle skal være mindst 12 tegn lang' } ,'Bad API secret' : { cs: 'Chybné API heslo' @@ -1138,6 +1240,7 @@ function init() { ,bg: 'Некоректна API парола' ,hr: 'Neispravan tajni API' ,sv: 'Felaktig API-nyckel' + ,dk: 'Forkert API-nøgle' } ,'API secret hash stored' : { cs: 'Hash API hesla uložen' @@ -1149,6 +1252,7 @@ function init() { ,bg: 'УРА! API парола запаметена' ,hr: 'API tajni hash je pohranjen' ,sv: 'Hemlig API-hash lagrad' + ,dk: 'Hemmelig API-hash gemt' } ,'Status' : { cs: 'Status' @@ -1160,6 +1264,7 @@ function init() { ,ro: 'Status' ,bg: 'Статус' ,hr: 'Status' + ,dk: 'Status' } ,'Not loaded' : { cs: 'Nenačtený' @@ -1171,6 +1276,7 @@ function init() { ,bg: 'Не е заредено' ,hr: 'Nije učitano' ,sv: 'Ej laddad' + ,dk: 'Ikke indlæst' } ,'Food editor' : { cs: 'Editor jídel' @@ -1182,6 +1288,7 @@ function init() { ,bg: 'Редактор за храна' ,hr: 'Editor hrane' ,sv: 'Födoämneseditor' + ,dk: 'Mad editor' } ,'Your database' : { cs: 'Vaše databáze' @@ -1193,6 +1300,7 @@ function init() { ,ro: 'Baza de date' ,bg: 'Твоята база с данни' ,hr: 'Vaša baza podataka' + ,dk: 'Din database' } ,'Filter' : { cs: 'Filtr' @@ -1204,6 +1312,7 @@ function init() { ,ro: 'Filtru' ,bg: 'Филтър' ,hr: 'Filter' + ,dk: 'Filter' } ,'Save' : { cs: 'Ulož' @@ -1215,6 +1324,7 @@ function init() { ,bg: 'Запази' ,hr: 'Spremi' ,sv: 'Spara' + ,dk: 'Gem' } ,'Clear' : { cs: 'Vymaž' @@ -1226,6 +1336,7 @@ function init() { ,bg: 'Изчисти' ,hr: 'Očisti' ,sv: 'Rensa' + ,dk: 'Rense' } ,'Record' : { cs: 'Záznam' @@ -1237,6 +1348,7 @@ function init() { ,ro: 'Înregistrare' ,bg: 'Запиши' ,hr: 'Zapis' + ,dk: 'Post' } ,'Quick picks' : { cs: 'Rychlý výběr' @@ -1248,6 +1360,7 @@ function init() { ,bg: 'Бърз избор' ,hr: 'Brzi izbor' ,sv: 'Snabbval' + ,dk: 'Hurtig valg' } ,'Show hidden' : { cs: 'Zobraz skryté' @@ -1259,6 +1372,7 @@ function init() { ,bg: 'Покажи скритото' ,hr: 'Prikaži skriveno' ,sv: 'Visa dolda' + ,dk: 'Vis skjulte' } ,'Your API secret' : { cs: 'Vaše API heslo' @@ -1270,6 +1384,7 @@ function init() { ,ro: 'Cheia API' ,bg: 'Твоята API парола' ,hr: 'Vaš tajni API' + ,dk: 'Din API-nøgle' } ,'Store hash on this computer (Use only on private computers)' : { cs: 'Ulož hash na tomto počítači (používejte pouze na soukromých počítačích)' @@ -1281,6 +1396,7 @@ function init() { ,bg: 'Запамети данните на този компютър. ( Използвай само на собствен компютър)' ,hr: 'Pohrani hash na ovom računalu (Koristiti samo na osobnom računalu)' ,sv: 'Lagra hashvärde på denna dator (använd endast på privat dator)' + ,dk: 'Gemme hash på denne computer (brug kun på privat computer)' } ,'Treatments' : { cs: 'Ošetření' @@ -1292,6 +1408,7 @@ function init() { ,ro: 'Tratamente' ,bg: 'Събития' ,hr: 'Tretmani' + ,dk: 'Behandling' } ,'Time' : { cs: 'Čas' @@ -1303,6 +1420,7 @@ function init() { ,ro: 'Ora' ,bg: 'Време' ,hr: 'Vrijeme' + ,dk: 'Tid' } ,'Event Type' : { cs: 'Typ události' @@ -1314,6 +1432,7 @@ function init() { ,ro: 'Tip eveniment' ,bg: 'Вид събитие' ,hr: 'Vrsta događaja' + ,dk: 'Hændelsestype' } ,'Blood Glucose' : { cs: 'Glykémie' @@ -1325,6 +1444,7 @@ function init() { ,ro: 'Glicemie' ,bg: 'Кръвна захар' ,hr: 'GUK' + ,dk: 'Glukoseværdi' } ,'Entered By' : { cs: 'Zadal' @@ -1336,6 +1456,7 @@ function init() { ,ro: 'Introdus de' ,bg: 'Въведено от' ,hr: 'Unos izvršio' + ,dk: 'Indtastet af' } ,'Delete this treatment?' : { cs: 'Vymazat toto ošetření?' @@ -1347,6 +1468,7 @@ function init() { ,bg: 'Изтрий това събитие' ,hr: 'Izbriši ovaj tretman?' ,sv: 'Ta bort händelse?' + ,dk: 'Slet denne hændelse?' } ,'Carbs Given' : { cs: 'Sacharidů' @@ -1358,6 +1480,7 @@ function init() { ,bg: 'ВХ' ,hr: 'Količina UH' ,sv: 'Antal kolhydrater' + ,dk: 'Antal kulhydrater' } ,'Inzulin Given' : { cs: 'Inzulínu' @@ -1369,6 +1492,7 @@ function init() { ,bg: 'Инсулин' ,hr: 'Količina inzulina' ,sv: 'Insulin' + ,dk: 'Insulin' } ,'Event Time' : { cs: 'Čas události' @@ -1380,6 +1504,7 @@ function init() { ,ro: 'Ora evenimentului' ,bg: 'Въвеждане' ,hr: 'Vrijeme događaja' + ,dk: 'Tidspunkt for hændelsen' } ,'Please verify that the data entered is correct' : { cs: 'Prosím zkontrolujte, zda jsou údaje zadány správně' @@ -1391,6 +1516,7 @@ function init() { ,bg: 'Моля проверете, че датата е въведена правилно' ,hr: 'Molim Vas provjerite jesu li uneseni podaci ispravni' ,sv: 'Vänligen verifiera att inlagd data stämmer' + ,dk: 'Venligst verificer at indtastet data er korrekt' } ,'BG' : { cs: 'Glykémie' @@ -1402,6 +1528,7 @@ function init() { ,ro: 'Glicemie' ,bg: 'КЗ' ,hr: 'GUK' + ,dk: 'BS' } ,'Use BG correction in calculation' : { cs: 'Použij korekci na glykémii' @@ -1413,6 +1540,7 @@ function init() { ,bg: 'Въведи корекция за КЗ ' ,hr: 'Koristi korekciju GUK-a u izračunu' ,sv: 'Använd BS-korrektion för uträkning' + ,dk: 'Anvend BS-korrektion før beregning' } ,'BG from CGM (autoupdated)' : { cs: 'Glykémie z CGM (automaticky aktualizovaná)' @@ -1424,6 +1552,7 @@ function init() { ,ro: 'Glicemie în senzor (automat)' ,bg: 'КЗ от сензора (автоматично)' ,hr: 'GUK sa CGM-a (ažuriran automatski)' + ,dk: 'BS fra CGM (automatisk)' } ,'BG from meter' : { cs: 'Glykémie z glukoměru' @@ -1435,6 +1564,7 @@ function init() { ,ro: 'Glicemie în glucometru' ,bg: 'КЗ от глюкомер' ,hr: 'GUK s glukometra' + ,dk: 'BS fra blodsukkerapperat' } ,'Manual BG' : { cs: 'Ručně zadaná glykémie' @@ -1446,6 +1576,7 @@ function init() { ,bg: 'Ръчно въведена КЗ' ,hr: 'Ručno unesen GUK' ,sv: 'Manuellt BS' + ,dk: 'Manuelt BS' } ,'Quickpick' : { cs: 'Rychlý výběr' @@ -1457,6 +1588,7 @@ function init() { ,bg: 'Бърз избор' ,hr: 'Brzi izbor' ,sv: 'Snabbval' + ,dk: 'Hurtig snack' } ,'or' : { cs: 'nebo' @@ -1468,6 +1600,7 @@ function init() { ,ro: 'sau' ,bg: 'или' ,hr: 'ili' + ,dk: 'eller' } ,'Add from database' : { cs: 'Přidat z databáze' @@ -1479,6 +1612,7 @@ function init() { ,bg: 'Добави към базата с данни' ,hr: 'Dodaj iz baze podataka' ,sv: 'Lägg till från databas' + ,dk: 'Tilføj fra database' } ,'Use carbs correction in calculation' : { cs: 'Použij korekci na sacharidy' @@ -1490,6 +1624,7 @@ function init() { ,bg: 'Въведи корекция за въглехидратите' ,hr: 'Koristi korekciju za UH u izračunu' ,sv: 'Använd kolhydratkorrektion i utäkning' + ,dk: 'Benyt kulhydratkorrektion i beregning' } ,'Use COB correction in calculation' : { cs: 'Použij korekci na COB' @@ -1501,6 +1636,7 @@ function init() { ,bg: 'Въведи корекция за останалите въглехидрати' ,hr: 'Koristi aktivne UH u izračunu' ,sv: 'Använd aktiva kolhydrater för beräkning' + ,dk: 'Benyt aktive kulhydrater i beregning' } ,'Use IOB in calculation' : { cs: 'Použij IOB ve výpočtu' @@ -1512,6 +1648,7 @@ function init() { ,bg: 'Използвай активния инсулин' ,hr: 'Koristi aktivni inzulin u izračunu"' ,sv: 'Använd aktivt insulin för uträkning' + ,dk: 'Benyt aktivt insulin i beregningen' } ,'Other correction' : { cs: 'Jiná korekce' @@ -1523,6 +1660,7 @@ function init() { ,bg: 'Друга корекция' ,hr: 'Druga korekcija' ,sv: 'Övrig korrektion' + ,dk: 'Øvrig korrektion' } ,'Rounding' : { cs: 'Zaokrouhlení' @@ -1534,6 +1672,7 @@ function init() { ,ro: 'Rotunjire' ,bg: 'Закръгляне' ,hr: 'Zaokruživanje' + ,dk: 'Afrunding' } ,'Enter insulin correction in treatment' : { cs: 'Zahrň inzulín do záznamu ošetření' @@ -1545,6 +1684,7 @@ function init() { ,bg: 'Въведи корекция с инсулин като лечение' ,hr: 'Unesi korekciju inzulinom u tretman' ,sv: 'Ange insulinkorrektion för händelse' + ,dk: 'Indtast insulionkorrektion' } ,'Insulin needed' : { cs: 'Potřebný inzulín' @@ -1556,6 +1696,7 @@ function init() { ,bg: 'Необходим инсулин' ,hr: 'Potrebno inzulina' ,sv: 'Beräknad insulinmängd' + ,dk: 'Insulin påkrævet' } ,'Carbs needed' : { cs: 'Potřebné sach' @@ -1567,6 +1708,7 @@ function init() { ,bg: 'Необходими въглехидрати' ,hr: 'Potrebno UH' ,sv: 'Beräknad kolhydratmängd' + ,dk: 'Kulhydrater påkrævet' } ,'Carbs needed if Insulin total is negative value' : { cs: 'Chybějící sacharidy v případě, že výsledek je záporný' @@ -1578,6 +1720,7 @@ function init() { ,bg: 'Необходими въглехидрати, ако няма инсулин' ,hr: 'Potrebno UH ako je ukupna vrijednost inzulina negativna' ,sv: 'Nödvändig kolhydratmängd för angiven insulinmängd' + ,dk: 'Kulhydrater er nødvendige når total insulin mængde er negativ' } ,'Basal rate' : { cs: 'Bazál' @@ -1589,6 +1732,7 @@ function init() { ,bg: 'Базален инсулин' ,hr: 'Bazal' ,sv: 'Basaldos' + ,dk: 'Basal rate' } ,'60 minutes earlier' : { cs: '60 min předem' @@ -1600,6 +1744,7 @@ function init() { ,ro: 'acum 60 min' ,bg: 'Преди 60 минути' ,hr: 'Prije 60 minuta' + ,dk: '60 min tidligere' } ,'45 minutes earlier' : { cs: '45 min předem' @@ -1611,6 +1756,7 @@ function init() { ,ro: 'acum 45 min' ,bg: 'Преди 45 минути' ,hr: 'Prije 45 minuta' + ,dk: '45 min tidligere' } ,'30 minutes earlier' : { cs: '30 min předem' @@ -1622,6 +1768,7 @@ function init() { ,ro: 'acum 30 min' ,bg: 'Преди 30 минути' ,hr: 'Prije 30 minuta' + ,dk: '30 min tidigere' } ,'20 minutes earlier' : { cs: '20 min předem' @@ -1633,6 +1780,7 @@ function init() { ,ro: 'acum 20 min' ,bg: 'Преди 20 минути' ,hr: 'Prije 20 minuta' + ,dk: '20 min tidligere' } ,'15 minutes earlier' : { cs: '15 min předem' @@ -1644,6 +1792,7 @@ function init() { ,ro: 'acu 15 min' ,bg: 'Преди 15 минути' ,hr: 'Prije 15 minuta' + ,dk: '15 min tidligere' } ,'Time in minutes' : { cs: 'Čas v minutách' @@ -1655,6 +1804,7 @@ function init() { ,ro: 'Timp în minute' ,bg: 'Времето в минути' ,hr: 'Vrijeme u minutama' + ,dk: 'Tid i minutter' } ,'15 minutes later' : { cs: '15 min po' @@ -1665,6 +1815,7 @@ function init() { ,bg: 'След 15 минути' ,hr: '15 minuta kasnije' ,sv: '15 min senare' + ,dk: '15 min senere' } ,'20 minutes later' : { cs: '20 min po' @@ -1676,6 +1827,7 @@ function init() { ,bg: 'След 20 минути' ,hr: '20 minuta kasnije' ,sv: '20 min senare' + ,dk: '20 min senere' } ,'30 minutes later' : { cs: '30 min po' @@ -1687,6 +1839,7 @@ function init() { ,bg: 'След 30 минути' ,hr: '30 minuta kasnije' ,sv: '30 min senare' + ,dk: '30 min senere' } ,'45 minutes later' : { cs: '45 min po' @@ -1698,6 +1851,7 @@ function init() { ,bg: 'След 45 минути' ,hr: '45 minuta kasnije' ,sv: '45 min senare' + ,dk: '45 min senere' } ,'60 minutes later' : { cs: '60 min po' @@ -1709,6 +1863,7 @@ function init() { ,bg: 'След 60 минути' ,hr: '60 minuta kasnije' ,sv: '60 min senare' + ,dk: '60 min senere' } ,'Additional Notes, Comments' : { cs: 'Dalši poznámky, komentáře' @@ -1720,6 +1875,7 @@ function init() { ,bg: 'Допълнителни бележки, коментари' ,hr: 'Dodatne bilješke, komentari' ,sv: 'Notering, övrigt' + ,dk: 'Ekstra noter, kommentarer' } ,'RETRO MODE' : { cs: 'V MINULOSTI' @@ -1731,6 +1887,7 @@ function init() { ,ro: 'MOD RETROSPECTIV' ,bg: 'МИНАЛО ВРЕМЕ' ,hr: 'Retrospektivni način' + ,dk: 'Retro mode' } ,'Now' : { cs: 'Nyní' @@ -1742,6 +1899,7 @@ function init() { ,ro: 'Acum' ,bg: 'Сега' ,hr: 'Sad' + ,dk: 'Nu' } ,'Other' : { cs: 'Jiný' @@ -1753,6 +1911,7 @@ function init() { ,ro: 'Altul' ,bg: 'Друго' ,hr: 'Drugo' + ,dk: 'Øvrige' } ,'Submit Form' : { cs: 'Odeslat formulář' @@ -1764,6 +1923,7 @@ function init() { ,ro: 'Trimite formularul' ,bg: 'Въвеждане на данните' ,hr: 'Predaj obrazac' + ,dk: 'Gem hændelsen' } ,'Profile editor' : { cs: 'Editor profilu' @@ -1775,6 +1935,7 @@ function init() { ,ro: 'Editare profil' ,bg: 'Редактор на профила' ,hr: 'Editor profila' + ,dk: 'Profil editor' } ,'Reporting tool' : { cs: 'Výkazy' @@ -1786,6 +1947,7 @@ function init() { ,ro: 'Instrument de rapoarte' ,bg: 'Статистика' ,hr: 'Alat za prijavu' + ,dk: 'Rapporteringsværktøj' } ,'Add food from your database' : { cs: 'Přidat jidlo z Vaší databáze' @@ -1797,6 +1959,7 @@ function init() { ,bg: 'Добави храна от твоята база с данни' ,hr: 'Dodajte hranu iz svoje baze podataka' ,sv: 'Lägg till livsmedel från databas' + ,dk: 'Tilføj mad fra din database' } ,'Reload database' : { cs: 'Znovu nahraj databázi' @@ -1808,6 +1971,7 @@ function init() { ,bg: 'Презареди базата с данни' ,hr: 'Ponovo učitajte bazu podataka' ,sv: 'Ladda om databas' + ,dk: 'Genindlæs databasen' } ,'Add' : { cs: 'Přidej' @@ -1819,6 +1983,7 @@ function init() { ,bg: 'Добави' ,hr: 'Dodaj' ,sv: 'Lägg till' + ,dk: 'Tilføj' } ,'Unauthorized' : { cs: 'Neautorizováno' @@ -1830,6 +1995,7 @@ function init() { ,ro: 'Neautorizat' ,bg: 'Нямаш достъп' ,hr: 'Neautorizirano' + ,dk: 'Uautoriseret' } ,'Entering record failed' : { cs: 'Vložení záznamu selhalo' @@ -1841,6 +2007,7 @@ function init() { ,bg: 'Въвеждане на записа не се осъществи' ,hr: 'Neuspjeli unos podataka' ,sv: 'Lägga till post nekas' + ,dk: 'Tilføjelse af post fejlede' } ,'Device authenticated' : { cs: 'Zařízení ověřeno' @@ -1852,6 +2019,7 @@ function init() { ,ro: 'Dispozitiv autentificat' ,bg: 'Устройстово е разпознато' ,hr: 'Uređaj autenticiran' + ,dk: 'Enhed godkendt' } ,'Device not authenticated' : { cs: 'Zařízení není ověřeno' @@ -1863,6 +2031,7 @@ function init() { ,ro: 'Dispozitiv neautentificat' ,bg: 'Устройсройството не е разпознато' ,hr: 'Uređaj nije autenticiran' + ,dk: 'Enhed ikke godkendt' } ,'Authentication status' : { cs: 'Stav ověření' @@ -1874,6 +2043,7 @@ function init() { ,bg: 'Статус на удостоверяване' ,hr: 'Status autentikacije' ,sv: 'Autentiseringsstatus' + ,dk: 'Autentifikationsstatus' } ,'Authenticate' : { cs: 'Ověřit' @@ -1885,6 +2055,7 @@ function init() { ,ro: 'Autentificare' ,bg: 'Удостоверяване' ,hr: 'Autenticirati' + ,dk: 'Godkende' } ,'Remove' : { cs: 'Vymazat' @@ -1896,6 +2067,7 @@ function init() { ,bg: 'Премахни' ,hr: 'Ukloniti' ,sv: 'Ta bort' + ,dk: 'Fjern' } ,'Your device is not authenticated yet' : { cs: 'Toto zařízení nebylo dosud ověřeno' @@ -1907,6 +2079,7 @@ function init() { ,bg: 'Вашето устройство все още не е удостоверено' ,hr: 'Vaš uređaj još nije autenticiran' ,sv: 'Din enhet är ej autentiserad' + ,dk: 'Din enhed er ikke godkendt endnu' } ,'Sensor' : { cs: 'Senzor' @@ -1918,6 +2091,7 @@ function init() { ,ro: 'Senzor' ,bg: 'Сензор' ,hr: 'Senzor' + ,dk: 'Sensor' } ,'Finger' : { cs: 'Glukoměr' @@ -1929,6 +2103,7 @@ function init() { ,ro: 'Deget' ,bg: 'От пръстта' ,hr: 'Prst' + ,dk: 'Finger' } ,'Manual' : { cs: 'Ručně' @@ -1940,6 +2115,7 @@ function init() { ,ro: 'Manual' ,bg: 'Ръчно' ,hr: 'Ručno' + ,dk: 'Manuel' } ,'Scale' : { cs: 'Měřítko' @@ -1951,6 +2127,7 @@ function init() { ,bg: 'Скала' ,hr: 'Skala' ,sv: 'Skala' + ,dk: 'Skala' } ,'Linear' : { cs: 'lineární' @@ -1962,6 +2139,7 @@ function init() { ,ro: 'Liniar' ,bg: 'Линеен' ,hr: 'Linearno' + ,dk: 'Lineær' } ,'Logarithmic' : { cs: 'logaritmické' @@ -1973,6 +2151,7 @@ function init() { ,ro: 'Logaritmic' ,bg: 'Логоритмичен' ,hr: 'Logaritamski' + ,dk: 'Logaritmisk' } ,'Silence for 30 minutes' : { cs: 'Ztlumit na 30 minut' @@ -1984,6 +2163,7 @@ function init() { ,bg: 'Заглуши за 30 минути' ,hr: 'Tišina 30 minuta' ,sv: 'Tyst i 30 min' + ,dk: 'Stilhed i 30 min' } ,'Silence for 60 minutes' : { cs: 'Ztlumit na 60 minut' @@ -1995,6 +2175,7 @@ function init() { ,bg: 'Заглуши за 60 минути' ,hr: 'Tišina 60 minuta' ,sv: 'Tyst i 60 min' + ,dk: 'Stilhed i 60 min' } ,'Silence for 90 minutes' : { cs: 'Ztlumit na 90 minut' @@ -2006,6 +2187,7 @@ function init() { ,bg: 'Заглуши за 90 минути' ,hr: 'Tišina 90 minuta' ,sv: 'Tyst i 90 min' + ,dk: 'Stilhed i 90 min' } ,'Silence for 120 minutes' : { cs: 'Ztlumit na 120 minut' @@ -2017,6 +2199,7 @@ function init() { ,bg: 'Заглуши за 120 минути' ,hr: 'Tišina 120 minuta' ,sv: 'Tyst i 120 min' + ,dk: 'Stilhed i 120 min' } ,'3HR' : { cs: '3hod' @@ -2028,6 +2211,7 @@ function init() { ,ro: '3h' ,bg: '3часа' ,hr: '3h' + ,dk: '3tim' } ,'6HR' : { cs: '6hod' @@ -2039,6 +2223,7 @@ function init() { ,ro: '6h' ,bg: '6часа' ,hr: '6h' + ,dk: '6tim' } ,'12HR' : { cs: '12hod' @@ -2050,6 +2235,7 @@ function init() { ,ro: '12h' ,bg: '12часа' ,hr: '12h' + ,dk: '12tim' } ,'24HR' : { cs: '24hod' @@ -2061,6 +2247,7 @@ function init() { ,ro: '24h' ,bg: '24часа' ,hr: '24h' + ,dk: '24tim' } ,'Settings' : { cs: 'Nastavení' @@ -2071,6 +2258,7 @@ function init() { ,ro: 'Setări' ,bg: 'Настройки' ,hr: 'Postavke' + ,dk: 'Opsætning' } ,'Units' : { cs: 'Jednotky' @@ -2082,6 +2270,7 @@ function init() { ,bg: 'Единици' ,hr: 'Jedinice' ,sv: 'Enheter' + ,dk: 'Enheder' } ,'Date format' : { cs: 'Formát datumu' @@ -2093,6 +2282,7 @@ function init() { ,ro: 'Formatul datei' ,bg: 'Формат на датата' ,hr: 'Format datuma' + ,dk: 'dato format' } ,'12 hours' : { cs: '12 hodin' @@ -2104,6 +2294,7 @@ function init() { ,ro: '12 ore' ,bg: '12 часа' ,hr: '12 sati' + ,dk: '12 timer' } ,'24 hours' : { cs: '24 hodin' @@ -2115,6 +2306,7 @@ function init() { ,ro: '24 ore' ,bg: '24 часа' ,hr: '24 sata' + ,dk: '24 timer' } ,'Log a Treatment' : { cs: 'Záznam ošetření' @@ -2126,6 +2318,7 @@ function init() { ,bg: 'Въвеждане на събитие' ,hr: 'Evidencija tretmana' ,sv: 'Ange händelse' + ,dk: 'Log en hændelse' } ,'BG Check' : { cs: 'Kontrola glykémie' @@ -2137,6 +2330,7 @@ function init() { ,ro: 'Verificare glicemie' ,bg: 'Проверка на КЗ' ,hr: 'Kontrola GUK-a' + ,dk: 'BS kontrol' } ,'Meal Bolus' : { cs: 'Bolus na jídlo' @@ -2148,6 +2342,7 @@ function init() { ,bg: 'Болус-основно хранене' ,hr: 'Bolus za obrok' ,sv: 'Måltidsbolus' + ,dk: 'Måltidsbolus' } ,'Snack Bolus' : { cs: 'Bolus na svačinu' @@ -2159,6 +2354,7 @@ function init() { ,ro: 'Bolus gustare' ,bg: 'Болус-лека закуска' ,hr: 'Bolus za užinu' + ,dk: 'Mellemmåltidsbolus' } ,'Correction Bolus' : { cs: 'Bolus na glykémii' @@ -2170,6 +2366,7 @@ function init() { ,bg: 'Болус корекция' ,hr: 'Korekcija' ,sv: 'Korrektionsbolus' + ,dk: 'Korrektionsbolus' } ,'Carb Correction' : { cs: 'Přídavek sacharidů' @@ -2181,6 +2378,7 @@ function init() { ,bg: 'Корекция за въглехидратите' ,hr: 'Bolus za hranu' ,sv: 'Kolhydratskorrektion' + ,dk: 'Kulhydratskorrektion' } ,'Note' : { cs: 'Poznámka' @@ -2192,6 +2390,7 @@ function init() { ,bg: 'Бележка' ,hr: 'Bilješka' ,sv: 'Notering' + ,dk: 'Note' } ,'Question' : { cs: 'Otázka' @@ -2203,6 +2402,7 @@ function init() { ,ro: 'Întrebare' ,bg: 'Въпрос' ,hr: 'Pitanje' + ,dk: 'Spørgsmål' } ,'Exercise' : { cs: 'Cvičení' @@ -2214,6 +2414,7 @@ function init() { ,bg: 'Спорт' ,hr: 'Aktivnost' ,sv: 'Aktivitet' + ,dk: 'Træning' } ,'Pump Site Change' : { cs: 'Přepíchnutí kanyly' @@ -2225,6 +2426,7 @@ function init() { ,bg: 'Смяна на сет' ,hr: 'Promjena seta' ,sv: 'Pump/nålbyte' + ,dk: 'Skift insulin infusionssted' } ,'Sensor Start' : { cs: 'Spuštění sensoru' @@ -2236,6 +2438,7 @@ function init() { ,ro: 'Start senzor' ,bg: 'Стартиране на сензор' ,hr: 'Start senzora' + ,dk: 'Sensorstart' } ,'Sensor Change' : { cs: 'Výměna sensoru' @@ -2247,6 +2450,7 @@ function init() { ,ro: 'Schimbare senzor' ,bg: 'Смяна на сензор' ,hr: 'Promjena senzora' + ,dk: 'Sensor ombytning' } ,'Dexcom Sensor Start' : { cs: 'Spuštění sensoru' @@ -2258,6 +2462,7 @@ function init() { ,ro: 'Pornire senzor Dexcom' ,bg: 'Поставяне на Декском сензор' ,hr: 'Start Dexcom senzora' + ,dk: 'Dexcom sensor start' } ,'Dexcom Sensor Change' : { cs: 'Výměna sensoru' @@ -2269,6 +2474,7 @@ function init() { ,ro: 'Schimbare senzor Dexcom' ,bg: 'Смяна на Декском сензор' ,hr: 'Promjena Dexcom senzora' + ,dk: 'Dexcom sensor ombytning' } ,'Insulin Cartridge Change' : { cs: 'Výměna inzulínu' @@ -2280,6 +2486,7 @@ function init() { ,bg: 'Смяна на резервоар' ,hr: 'Promjena spremnika inzulina' ,sv: 'Insulinreservoarbyte' + ,dk: 'Skift insulin beholder' } ,'D.A.D. Alert' : { cs: 'D.A.D. Alert' @@ -2291,6 +2498,7 @@ function init() { ,bg: 'Сигнал от обучено куче' ,hr: 'Obavijest dijabetičkog psa' ,sv: 'Voff voff! (Diabeteshundalarm!)' + ,dk: 'Vuf Vuf! (Diabeteshundealarm!)' } ,'Glucose Reading' : { cs: 'Hodnota glykémie' @@ -2302,6 +2510,7 @@ function init() { ,ro: 'Valoare glicemie' ,bg: 'Кръвна захар' ,hr: 'Vrijednost GUK-a' + ,dk: 'Glukose aflæsning' } ,'Measurement Method' : { cs: 'Metoda měření' @@ -2313,6 +2522,7 @@ function init() { ,bg: 'Метод на измерване' ,hr: 'Metoda mjerenja' ,sv: 'Mätmetod' + ,dk: 'Målemetode' } ,'Meter' : { cs: 'Glukoměr' @@ -2324,6 +2534,7 @@ function init() { ,es: 'Glucómetro' ,bg: 'Глюкомер' ,hr: 'Glukometar' + ,dk: 'Glukosemeter' } ,'Insulin Given' : { cs: 'Inzulín' @@ -2335,6 +2546,7 @@ function init() { ,bg: 'Инсулин' ,hr: 'Količina iznulina' ,sv: 'Insulindos' + ,dk: 'Insulin dosis' } ,'Amount in grams' : { cs: 'Množství v gramech' @@ -2346,6 +2558,7 @@ function init() { ,ro: 'Cantitate în grame' ,bg: ' К-во в грамове' ,hr: 'Količina u gramima' + ,dk: 'Antal gram' } ,'Amount in units' : { cs: 'Množství v jednotkách' @@ -2357,6 +2570,7 @@ function init() { ,bg: 'К-во в единици' ,hr: 'Količina u jedinicama' ,sv: 'Antal enheter' + ,dk: 'Antal enheder' } ,'View all treatments' : { cs: 'Zobraz všechny ošetření' @@ -2368,6 +2582,7 @@ function init() { ,ro: 'Vezi toate evenimentele' ,bg: 'Преглед на всички събития' ,hr: 'Prikaži sve tretmane' + ,dk: 'Vis behandlinger' } ,'Enable Alarms' : { cs: 'Povolit alarmy' @@ -2378,6 +2593,7 @@ function init() { ,ro: 'Activează alarmele' ,bg: 'Активни аларми' ,hr: 'Aktiviraj alarme' + ,dk: 'Aktivere alarmer' } ,'When enabled an alarm may sound.' : { cs: 'Při povoleném alarmu zní zvuk' @@ -2388,6 +2604,7 @@ function init() { ,ro: 'Când este activ, poate suna o alarmă.' ,bg: 'Когато е активирано, алармата ще има звук' ,hr: 'Kad je aktiviran, alarm se može oglasiti' + ,dk: 'Når aktiveret kan alarm lyde' } ,'Urgent High Alarm' : { cs: 'Urgentní vysoká glykémie' @@ -2398,6 +2615,7 @@ function init() { ,ro: 'Alarmă urgentă hiper' ,bg: 'Много висока КЗ' ,hr: 'Hitni alarm za hiper' + ,dk: 'Høj grænse overskredet' } ,'High Alarm' : { cs: 'Vysoká glykémie' @@ -2408,6 +2626,7 @@ function init() { ,ro: 'Alarmă hiper' ,bg: 'Висока КЗ' ,hr: 'Alarm za hiper' + ,dk: 'Høj grænse overskredet' } ,'Low Alarm' : { cs: 'Nízká glykémie' @@ -2418,6 +2637,7 @@ function init() { ,ro: 'Alarmă hipo' ,bg: 'Ниска КЗ' ,hr: 'Alarm za hipo' + ,dk: 'Lav grænse overskredet' } ,'Urgent Low Alarm' : { cs: 'Urgentní nízká glykémie' @@ -2428,6 +2648,7 @@ function init() { ,ro: 'Alarmă urgentă hipo' ,bg: 'Много ниска КЗ' ,hr: 'Hitni alarm za hipo' + ,dk: 'Advarsel: Lav' } ,'Stale Data: Warn' : { cs: 'Zastaralá data' @@ -2438,6 +2659,7 @@ function init() { ,ro: 'Date învechite: alertă' ,bg: 'Стари данни' ,hr: 'Pažnja: Stari podaci' + ,dk: 'Advarsel: Gamle data' } ,'Stale Data: Urgent' : { cs: 'Zastaralá data urgentní' @@ -2449,6 +2671,7 @@ function init() { ,ro: 'Date învechite: urgent' ,bg: 'Много стари данни' ,hr: 'Hitno: Stari podaci' + ,dk: 'Advarsel: Gamle data' } ,'mins' : { cs: 'min' @@ -2460,6 +2683,7 @@ function init() { ,ro: 'min' ,bg: 'мин' ,hr: 'min' + ,dk: 'min' } ,'Night Mode' : { cs: 'Noční mód' @@ -2471,6 +2695,7 @@ function init() { ,ro: 'Mod nocturn' ,bg: 'Нощен режим' ,hr: 'Noćni način' + ,dk: 'Nat mode' } ,'When enabled the page will be dimmed from 10pm - 6am.' : { cs: 'Když je povoleno, obrazovka je ztlumena 22:00 - 6:00' @@ -2481,6 +2706,7 @@ function init() { ,ro: 'La activare va scădea iluminarea între 22 și 6' ,bg: 'Когато е активирано, страницата ще е затъмнена от 22-06ч' ,hr: 'Kad je uključen, stranica će biti zatamnjena od 22-06' + ,dk: 'Når aktiveret vil denne side nedtones fra 22:00-6:00' } ,'Enable' : { cs: 'Povoleno' @@ -2492,6 +2718,7 @@ function init() { ,ro: 'Activează' ,bg: 'Активно' ,hr: 'Aktiviraj' + ,dk: 'Aktivere' } ,'Show Raw BG Data' : { cs: 'Zobraz RAW data' @@ -2503,6 +2730,7 @@ function init() { ,bg: 'Показвай RAW данни' ,hr: 'Prikazuj sirove podatke o GUK-u' ,sv: 'Visa RAW-data' + ,dk: 'Vis rå data' } ,'Never' : { cs: 'Nikdy' @@ -2514,6 +2742,7 @@ function init() { ,ro: 'Niciodată' ,bg: 'Никога' ,hr: 'Nikad' + ,dk: 'Aldrig' } ,'Always' : { cs: 'Vždy' @@ -2525,6 +2754,7 @@ function init() { ,ro: 'Întotdeauna' ,bg: 'Винаги' ,hr: 'Uvijek' + ,dk: 'Altid' } ,'When there is noise' : { cs: 'Při šumu' @@ -2536,6 +2766,7 @@ function init() { ,ro: 'Atunci când este diferență' ,bg: 'Когато има шум' ,hr: 'Kad postoji šum' + ,dk: 'Når der er støj' } ,'When enabled small white dots will be disaplyed for raw BG data' : { cs: 'Když je povoleno, malé tečky budou zobrazeny pro RAW data' @@ -2546,6 +2777,7 @@ function init() { ,ro: 'La activare vor apărea puncte albe reprezentând citirea brută a glicemiei' ,bg: 'Когато е активирано, малки бели точки ще показват RAW данните' ,hr: 'Kad je omogućeno, male bijele točkice će prikazivati sirove podatke o GUK-u.' + ,dk: 'Ved aktivering vil små hvide prikker blive vist for rå BG tal' } ,'Custom Title' : { cs: 'Vlastní název stránky' @@ -2557,6 +2789,7 @@ function init() { ,ro: 'Titlu particularizat' ,bg: 'Име на страницата' ,hr: 'Vlastiti naziv' + ,dk: 'Egen titel' } ,'Theme' : { cs: 'Téma' @@ -2568,6 +2801,7 @@ function init() { ,bg: 'Тема' ,hr: 'Tema' ,sv: 'Tema' + ,dk: 'Tema' } ,'Default' : { cs: 'Výchozí' @@ -2579,6 +2813,7 @@ function init() { ,bg: 'Черно-бяла' ,hr: 'Default' ,sv: 'Standard' + ,dk: 'Standard' } ,'Colors' : { cs: 'Barevné' @@ -2589,6 +2824,7 @@ function init() { ,ro: 'Colorată' ,bg: 'Цветна' ,hr: 'Boje' + ,dk: 'Farver' } ,'Reset, and use defaults' : { cs: 'Vymaž a nastav výchozí hodnoty' @@ -2600,6 +2836,7 @@ function init() { ,ro: 'Resetează și folosește setările implicite' ,bg: 'Нулирай и използвай стандартните настройки' ,hr: 'Resetiraj i koristi defaultne vrijednosti' + ,dk: 'Returner til standardopsætning' } ,'Calibrations' : { cs: 'Kalibrace' @@ -2610,6 +2847,7 @@ function init() { ,ro: 'Calibrări' ,bg: 'Калибрации' ,hr: 'Kalibriranje' + ,dk: 'Kalibrering' } ,'Alarm Test / Smartphone Enable' : { cs: 'Test alarmu' @@ -2620,6 +2858,7 @@ function init() { ,ro: 'Teste alarme / Activează pe smartphone' ,bg: 'Тестване на алармата / Активно за мобилни телефони' ,hr: 'Alarm test / Aktiviraj smartphone' + ,dk: 'Alarm test / Smartphone aktiveret' } ,'Bolus Wizard' : { cs: 'Bolusový kalkulátor' @@ -2631,6 +2870,7 @@ function init() { ,ro: 'Calculator sugestie bolus' ,bg: 'Съветник при изчисление на болуса' ,hr: 'Bolus wizard' + ,dk: 'Bolusberegner' } ,'in the future' : { cs: 'v budoucnosti' @@ -2642,6 +2882,7 @@ function init() { ,ro: 'în viitor' ,bg: 'в бъдещето' ,hr: 'U budućnosti' + ,dk: 'fremtiden' } ,'time ago' : { cs: 'min zpět' @@ -2653,6 +2894,7 @@ function init() { ,ro: 'în trecut' ,bg: 'преди време' ,hr: 'prije' + ,dk: 'tid siden' } ,'hr ago' : { cs: 'hod zpět' @@ -2663,6 +2905,7 @@ function init() { ,ro: 'oră în trecut' ,bg: 'час по-рано' ,hr: 'sat unazad' + ,dk: 'Time siden' } ,'hrs ago' : { cs: 'hod zpět' @@ -2674,6 +2917,7 @@ function init() { ,ro: 'h în trecut' ,bg: 'часа по-рано' ,hr: 'sati unazad' + ,dk: 'Timer siden' } ,'min ago' : { cs: 'min zpět' @@ -2685,6 +2929,7 @@ function init() { ,ro: 'minut în trecut' ,bg: 'минута по-рано' ,hr: 'minuta unazad' + ,dk: 'minutter siden' } ,'mins ago' : { cs: 'min zpět' @@ -2696,6 +2941,7 @@ function init() { ,ro: 'minute în trecut' ,bg: 'минути по-рано' ,hr: 'minuta unazad' + ,dk: 'minutter siden' } ,'day ago' : { cs: 'den zpět' @@ -2707,6 +2953,7 @@ function init() { ,ro: 'zi în trecut' ,bg: 'ден по-рано' ,hr: 'dan unazad' + ,dk: 'dag siden' } ,'days ago' : { cs: 'dnů zpět' @@ -2718,6 +2965,7 @@ function init() { ,ro: 'zile în trecut' ,bg: 'дни по-рано' ,hr: 'dana unazad' + ,dk: 'dage siden' } ,'long ago' : { cs: 'dlouho zpět' @@ -2729,6 +2977,7 @@ function init() { ,ro: 'timp în trecut' ,bg: 'преди много време' ,hr: 'prije dosta vremena' + ,dk: 'længe siden' } ,'Clean' : { cs: 'Čistý' @@ -2740,6 +2989,7 @@ function init() { ,ro: 'Curat' ,bg: 'Чист' ,hr: 'Čisto' + ,dk: 'Rent' } ,'Light' : { cs: 'Lehký' @@ -2751,6 +3001,7 @@ function init() { ,ro: 'Ușor' ,bg: 'Лек' ,hr: 'Lagano' + ,dk: 'Let' } ,'Medium' : { cs: 'Střední' @@ -2762,6 +3013,7 @@ function init() { ,ro: 'Mediu' ,bg: 'Среден' ,hr: 'Srednje' + ,dk: 'Middel' } ,'Heavy' : { cs: 'Velký' @@ -2773,6 +3025,7 @@ function init() { ,ro: 'Puternic' ,bg: 'Висок' ,hr: 'Teško' + ,dk: 'Voldsom' } ,'Treatment type' : { cs: 'Typ ošetření' @@ -2784,6 +3037,7 @@ function init() { ,ro: 'Tip tratament' ,bg: 'Вид събитие' ,hr: 'Vrsta tretmana' + ,dk: 'Behandlingstype' } ,'Raw BG' : { cs: 'Glykémie z RAW dat' @@ -2795,6 +3049,7 @@ function init() { ,ro: 'Citire brută a glicemiei' ,bg: 'Непреработена КЗ' ,hr: 'Sirovi podaci o GUK-u' + ,dk: 'Råt BS' } ,'Device' : { cs: 'Zařízení' @@ -2806,6 +3061,7 @@ function init() { ,ro: 'Dispozitiv' ,bg: 'Устройство' ,hr: 'Uređaj' + ,dk: 'Enhed' } ,'Noise' : { cs: 'Šum' @@ -2817,6 +3073,7 @@ function init() { ,ro: 'Zgomot' ,bg: 'Шум' ,hr: 'Šum' + ,dk: 'Støj' } ,'Calibration' : { cs: 'Kalibrace' @@ -2828,6 +3085,7 @@ function init() { ,ro: 'Calibrare' ,bg: 'Калибрация' ,hr: 'Kalibriranje' + ,dk: 'Kalibrering' } ,'Show Plugins' : { cs: 'Zobrazuj pluginy' @@ -2838,6 +3096,7 @@ function init() { ,ro: 'Arată plugin-urile' ,bg: 'Покажи добавките' ,hr: 'Prikaži plugine' + ,dk: 'Vis plugins' } ,'About' : { cs: 'O aplikaci' @@ -2849,6 +3108,7 @@ function init() { ,bg: 'Относно' ,hr: 'O aplikaciji' ,sv: 'Om' + ,dk: 'Om' } ,'Value in' : { cs: 'Hodnota v' @@ -2860,6 +3120,7 @@ function init() { ,bg: 'Стойност в' ,hr: 'Vrijednost u' ,sv: 'Värde om' + ,dk: 'Værdi i' } ,'Carb Time' : { cs: 'Čas jídla' @@ -2869,6 +3130,7 @@ function init() { ,bg: 'ВХ действа след' ,hr: 'Vrijeme unosa UH' ,sv: 'Kolhydratstid' + ,dk: 'Kulhydratstid' } }; diff --git a/static/index.html b/static/index.html index 98c1f1bbdca..6678e90ffe5 100644 --- a/static/index.html +++ b/static/index.html @@ -110,6 +110,7 @@ +
    From 98d0b5dedd5a30aa63880c9e25a5dc0ff002244e Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 19 Aug 2015 01:26:13 -0700 Subject: [PATCH 623/937] allow the pills to wrap, lots of little adjustments --- lib/client/chart.js | 26 +++++++++++------------ lib/client/index.js | 19 ++++++----------- static/css/main.css | 51 +++++++++++++++++++-------------------------- 3 files changed, 40 insertions(+), 56 deletions(-) diff --git a/lib/client/chart.js b/lib/client/chart.js index 237557032d3..a88b9d7416c 100644 --- a/lib/client/chart.js +++ b/lib/client/chart.js @@ -5,7 +5,7 @@ var times = require('../times'); var DEBOUNCE_MS = 10 , UPDATE_TRANS_MS = 750 // milliseconds - , padding = { top: 0, right: 10, bottom: 30, left: 10 } + , padding = { bottom: 30 } ; function init (client, d3, $) { @@ -115,8 +115,7 @@ function init (client, d3, $) { // create svg and g to contain the chart contents chart.charts = d3.select('#chartContainer').append('svg') .append('g') - .attr('class', 'chartContainer') - .attr('transform', 'translate(' + padding.left + ',' + padding.top + ')'); + .attr('class', 'chartContainer'); chart.focus = chart.charts.append('g'); @@ -179,10 +178,9 @@ function init (client, d3, $) { // get current data range var dataRange = client.dataExtent(); - // get the entire container height and width subtracting the padding var chartContainerRect = chartContainer[0].getBoundingClientRect(); - var chartWidth = chartContainerRect.width - padding.left - padding.right; - var chartHeight = chartContainerRect.height - padding.top - padding.bottom; + var chartWidth = chartContainerRect.width; + var chartHeight = chartContainerRect.height - padding.bottom; // get the height of each chart based on its container size ratio var focusHeight = chart.focusHeight = chartHeight * .7; @@ -192,14 +190,14 @@ function init (client, d3, $) { var currentBrushExtent = createAdjustedRange(); // only redraw chart if chart size has changed - if ((client.prevChartWidth !== chartWidth) || (client.prevChartHeight !== chartHeight)) { + if ((chart.prevChartWidth !== chartWidth) || (chart.prevChartHeight !== chartHeight)) { - client.prevChartWidth = chartWidth; - client.prevChartHeight = chartHeight; + chart.prevChartWidth = chartWidth; + chart.prevChartHeight = chartHeight; //set the width and height of the SVG element - chart.charts.attr('width', chartWidth + padding.left + padding.right) - .attr('height', chartHeight + padding.top + padding.bottom); + chart.charts.attr('width', chartWidth) + .attr('height', chartHeight + padding.bottom); // ranges are based on the width and height available so reset chart.xScale.range([0, chartWidth]); @@ -296,7 +294,7 @@ function init (client, d3, $) { // add a y-axis line that opens up the brush extent from the context to the focus chart.focus.append('line') .attr('class', 'open-top') - .attr('stroke', 'black') + .attr('stroke', '#111') .attr('stroke-width', 2); // add a x-axis line that closes the the brush container on left side @@ -490,14 +488,14 @@ function init (client, d3, $) { .attr('x1', chart.xScale2(chart.brush.extent()[0])) .attr('y1', chart.focusHeight) .attr('x2', chart.xScale2(chart.brush.extent()[0])) - .attr('y2', client.prevChartHeight); + .attr('y2', chart.prevChartHeight); // transition open-right line to correct location chart.focus.select('.open-right') .attr('x1', chart.xScale2(new Date(chart.brush.extent()[1].getTime() + client.forecastTime))) .attr('y1', chart.focusHeight) .attr('x2', chart.xScale2(new Date(chart.brush.extent()[1].getTime() + client.forecastTime))) - .attr('y2', client.prevChartHeight); + .attr('y2', chart.prevChartHeight); chart.focus.select('.now-line') .transition() diff --git a/lib/client/index.js b/lib/client/index.js index 47614614d38..063a8d00460 100644 --- a/lib/client/index.js +++ b/lib/client/index.js @@ -90,7 +90,9 @@ client.init = function init(serverSettings, plugins) { }; client.bottomOfPills = function bottomOfPills ( ) { - return minorPills.offset().top + minorPills.height(); + var bottomOfMinorPills = minorPills.offset().top + minorPills.height(); + var bottomOfStatusPills = statusPills.offset().top + statusPills.height(); + return Math.max(bottomOfMinorPills, bottomOfStatusPills); }; function formatTime(time, compact) { @@ -289,17 +291,6 @@ client.init = function init(serverSettings, plugins) { //only shown plugins get a chance to update visualisations plugins.updateVisualisations(client.sbx); - - if (client.bottomOfPills() != preBottomOfPills) { - chart.update(false); - } - - var chartContainer = $('#chartContainer'); - - console.info('>>>>updatePlugins client.bottomOfPills()', client.bottomOfPills()); - chartContainer.css('top', (client.bottomOfPills() + 10) + 'px'); - chartContainer.find('svg').css('height', 'calc(100vh - ' + (client.bottomOfPills() + 10)+ 'px)'); - } function clearCurrentSGV ( ) { @@ -335,6 +326,8 @@ client.init = function init(serverSettings, plugins) { updatePlugins([], nowDate); } + $('#chartContainer').css('top', client.bottomOfPills() + 'px'); + updateTimeAgo(); chart.scroll(nowDate); @@ -580,7 +573,6 @@ client.init = function init(serverSettings, plugins) { updateClock(); updateTimeAgoSoon(); - function Dropdown(el) { this.ddmenuitem = 0; @@ -811,6 +803,7 @@ client.init = function init(serverSettings, plugins) { if (!isInitialData) { isInitialData = true; chart = client.chart = require('./chart')(client, d3, $); + brushed(); chart.update(true); } else { chart.update(false); diff --git a/static/css/main.css b/static/css/main.css index 40aa2177fcf..e32b0d306fd 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -40,15 +40,10 @@ body { .primary { font-family: 'Ubuntu', Helvetica, Arial, sans-serif; - height: 180px; vertical-align: middle; clear: both; } -.has-minor-pills .primary { - height: 200px; -} - .bgStatus { float: right; text-align: center; @@ -86,6 +81,7 @@ body { line-height: 90px; vertical-align: middle; border: none; + background: none; } .bgStatus .majorPills { @@ -94,8 +90,8 @@ body { .minorPills { font-size: 22px; - margin-top: 10px; z-index: 500; + white-space: normal; } .majorPills > span:not(:first-child) { @@ -110,21 +106,26 @@ body { white-space: nowrap; border-radius: 5px; border: 2px solid #808080; + background: #808080; + display: inline-block; + margin-bottom: 4px; + padding: 2px 0; } .pill em, .pill label { - padding-left: 2px; - padding-right: 2px; + padding: 2px; + white-space: nowrap; } .pill em { font-style: normal; font-weight: bold; + background: #000; + border-radius: 3px; } .pill label { color: #000; - background: #808080; } .pill.warn em { @@ -160,7 +161,6 @@ body { .status { color: #808080; font-size: 100px; - line-height: 100px; } .statusBox { text-align: center; @@ -168,7 +168,6 @@ body { } .statusPills { font-size: 20px; - line-height: 30px; } .loading .statusPills { @@ -183,7 +182,6 @@ body { .statusPills .pill em { color: #bdbdbd; background: #000; - border-radius: 4px 0 0 4px; } .statusPills .pill label { @@ -195,18 +193,22 @@ body { } #chartContainer { - left:0; - right:0; - bottom:0; - height:auto; + left: 0; + right: 0; + bottom: 0; + height: auto; font-size: 20px; background: #111; display: block; - position:absolute; + position: absolute; + margin: 2px; } #chartContainer svg { width: 100%; + height: 100%; + margin: 0; + padding: 0; } #silenceBtn { @@ -234,6 +236,7 @@ body { display: inline-block; border-radius: 2px; border: 1px solid #808080; + padding: 0; } .pill.rawbg em { @@ -241,6 +244,7 @@ body { background-color: black; display: block; font-size: 20px; + border-radius: 0; } .pill.rawbg label { @@ -359,13 +363,11 @@ body { .status { font-size: 70px; - line-height: 60px; padding-top: 8px; } .statusPills { font-size: 15px; - line-height: 40px; } .pill.upbat label { @@ -427,7 +429,6 @@ body { .status { font-size: 50px; - line-height: 35px; width: 250px; } } @@ -436,7 +437,6 @@ body { .primary { text-align: center; margin-bottom: 0; - height: 152px; } .bgStatus { @@ -505,7 +505,7 @@ body { .focus-range { position: absolute; - top: 80px; + top: 60px; left: auto; right: 10px; margin: 0; @@ -574,7 +574,6 @@ body { .status { font-size: 50px; - line-height: 40px; padding-top: 5px; } @@ -594,7 +593,6 @@ body { text-align: center; width: 220px; left: 0; - position: absolute; } .bgStatus .currentBG { @@ -636,11 +634,6 @@ body { display: block; } - .status { - position: absolute; - top: 90px; - } - .statusBox { width: 220px; } From a0b025d507fb4eca90870f1dc66b9b94769315cc Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 19 Aug 2015 01:33:29 -0700 Subject: [PATCH 624/937] fix bottomOfPills to work when there aren't offsets in the tests --- lib/client/index.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/client/index.js b/lib/client/index.js index 063a8d00460..dbe14106702 100644 --- a/lib/client/index.js +++ b/lib/client/index.js @@ -90,8 +90,9 @@ client.init = function init(serverSettings, plugins) { }; client.bottomOfPills = function bottomOfPills ( ) { - var bottomOfMinorPills = minorPills.offset().top + minorPills.height(); - var bottomOfStatusPills = statusPills.offset().top + statusPills.height(); + //the offset's might not exist for some tests + var bottomOfMinorPills = minorPills.offset() ? minorPills.offset().top + minorPills.height() : 0; + var bottomOfStatusPills = statusPills.offset() ? statusPills.offset().top + statusPills.height() : 0; return Math.max(bottomOfMinorPills, bottomOfStatusPills); }; From 1a470d25a8a66f05d1cf18738fde5223d7f3b17c Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 19 Aug 2015 01:48:19 -0700 Subject: [PATCH 625/937] adjust renderer since prevChartWidth moved to chart; and tweek to prevent pill truncation --- lib/client/index.js | 4 +--- lib/client/renderer.js | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/client/index.js b/lib/client/index.js index dbe14106702..5e5dc542c3c 100644 --- a/lib/client/index.js +++ b/lib/client/index.js @@ -273,8 +273,6 @@ client.init = function init(serverSettings, plugins) { function updatePlugins (sgvs, time) { var pluginBase = plugins.base(majorPills, minorPills, statusPills, bgStatus, client.tooltip); - var preBottomOfPills = client.bottomOfPills(); - client.sbx = sandbox.clientInit( client.settings , new Date(time).getTime() //make sure we send a timestamp @@ -327,7 +325,7 @@ client.init = function init(serverSettings, plugins) { updatePlugins([], nowDate); } - $('#chartContainer').css('top', client.bottomOfPills() + 'px'); + $('#chartContainer').css('top', (client.bottomOfPills() + 5) + 'px'); updateTimeAgo(); chart.scroll(nowDate); diff --git a/lib/client/renderer.js b/lib/client/renderer.js index a24890c42ee..bd1626c187b 100644 --- a/lib/client/renderer.js +++ b/lib/client/renderer.js @@ -26,7 +26,7 @@ function init (client, d3) { } var dotRadius = function(type) { - var radius = client.prevChartWidth > WIDTH_BIG_DOTS ? 4 : (client.prevChartWidth < WIDTH_SMALL_DOTS ? 2 : 3); + var radius = chart().prevChartWidth > WIDTH_BIG_DOTS ? 4 : (chart().prevChartWidth < WIDTH_SMALL_DOTS ? 2 : 3); if (type === 'mbg') { radius *= 2; } else if (type === 'forecast') { @@ -55,7 +55,7 @@ function init (client, d3) { renderer.bubbleScale = function bubbleScale ( ) { // a higher bubbleScale will produce smaller bubbles (it's not a radius like focusDotRadius) - return (client.prevChartWidth < WIDTH_SMALL_DOTS ? 4 : (client.prevChartWidth < WIDTH_BIG_DOTS ? 3 : 2)) * focusRangeAdjustment(); + return (chart().prevChartWidth < WIDTH_SMALL_DOTS ? 4 : (chart().prevChartWidth < WIDTH_BIG_DOTS ? 3 : 2)) * focusRangeAdjustment(); }; function isDexcom(device) { From 44caab378fd33fbbe12e23137abaea2041d649e3 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 19 Aug 2015 01:53:23 -0700 Subject: [PATCH 626/937] adjust the width of the open top line, to cover the bottom of the X axis --- lib/client/chart.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/client/chart.js b/lib/client/chart.js index a88b9d7416c..07a725b94ad 100644 --- a/lib/client/chart.js +++ b/lib/client/chart.js @@ -295,7 +295,7 @@ function init (client, d3, $) { chart.focus.append('line') .attr('class', 'open-top') .attr('stroke', '#111') - .attr('stroke-width', 2); + .attr('stroke-width', 15); // add a x-axis line that closes the the brush container on left side chart.focus.append('line') From 1f42e32eb88f7d517d87a0d940336baff44cc29f Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 19 Aug 2015 02:06:02 -0700 Subject: [PATCH 627/937] css clean up get rid of !important some hacks --- static/css/drawer.css | 9 --------- static/css/main.css | 21 --------------------- 2 files changed, 30 deletions(-) diff --git a/static/css/drawer.css b/static/css/drawer.css index ea6ae3379f2..fbf7ad9f325 100644 --- a/static/css/drawer.css +++ b/static/css/drawer.css @@ -246,15 +246,6 @@ h1, legend, padding-left: 12px; } -.icon-battery-25, -.icon-battery-50, -.icon-battery-75, -.icon-battery-100, -.icon-angle-double-up { - font-size: 21px !important; - padding-left: 9px !important; -} - #notification { display: none; font-size: 14px; diff --git a/static/css/main.css b/static/css/main.css index e32b0d306fd..b0f0fd7f2ac 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -181,15 +181,6 @@ body { .statusPills .pill em { color: #bdbdbd; - background: #000; -} - -.statusPills .pill label { - background: #808080; -} - -.pill.upbat label { - padding: 0 !important; } #chartContainer { @@ -370,10 +361,6 @@ body { font-size: 15px; } - .pill.upbat label { - font-size: 15px !important; - } - .focus-range { margin: 0; } @@ -499,10 +486,6 @@ body { font-size: 15px; } - .pill.upbat label { - font-size: 15px !important; - } - .focus-range { position: absolute; top: 60px; @@ -581,10 +564,6 @@ body { font-size: 15px; } - .pill.upbat label { - font-size: 15px !important; - } - } @media (max-height: 480px) and (max-width: 400px) { From 868952a987a42690852cb1699e5f3c2410a689ca Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 19 Aug 2015 10:27:49 -0700 Subject: [PATCH 628/937] adjust rawbg pill padding --- static/css/main.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/static/css/main.css b/static/css/main.css index b0f0fd7f2ac..f5fddb46811 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -236,11 +236,13 @@ body { display: block; font-size: 20px; border-radius: 0; + padding: 0 2px; } .pill.rawbg label { display: block; font-size: 14px; + padding: 0 2px; } .alarming .bgButton { From d4ab336733c9a6327d595f2420a60572fc183d15 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 19 Aug 2015 15:11:13 -0700 Subject: [PATCH 629/937] minor tweeks to prevent extra scroll bars in IE --- static/css/drawer.css | 12 ++++++------ static/css/main.css | 3 +++ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/static/css/drawer.css b/static/css/drawer.css index fbf7ad9f325..cd30fab0909 100644 --- a/static/css/drawer.css +++ b/static/css/drawer.css @@ -43,8 +43,9 @@ input[type=number]:invalid { } #drawer .actions a { - float: right; - padding: 10px 0; + display: block; + text-align: right; + padding: 10px 0; } #treatmentDrawer { @@ -137,14 +138,13 @@ input[type=number]:invalid { } #about { - margin-top: 1em; + margin-bottom: 55px; } #about div { - font-size: 0.75em; + font-size: 12px; } #about .links { - margin-top: 1em; - margin-bottom: 1em; + margin: 10px; } #about a { color: #fff; diff --git a/static/css/main.css b/static/css/main.css index f5fddb46811..891c60f4d99 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -12,6 +12,7 @@ body { font-family: 'Open Sans', Helvetica, Arial, sans-serif; background: #000; color: #bdbdbd; + overflow: hidden; } .container { @@ -193,6 +194,7 @@ body { display: block; position: absolute; margin: 2px; + overflow: hidden; } #chartContainer svg { @@ -285,6 +287,7 @@ body { background: white; border: 0; border-radius: 8px; + float: right; } .alarms { From 5ad6a2511b3b9942ef18f87ac4d9b73f5d978fc6 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 19 Aug 2015 15:11:55 -0700 Subject: [PATCH 630/937] fix more css indents --- static/css/drawer.css | 242 ++++++++++++++++++++-------------------- static/css/dropdown.css | 34 +++--- 2 files changed, 138 insertions(+), 138 deletions(-) diff --git a/static/css/drawer.css b/static/css/drawer.css index cd30fab0909..a3c4c3b0ce8 100644 --- a/static/css/drawer.css +++ b/static/css/drawer.css @@ -1,45 +1,45 @@ #drawer button, #treatmentDrawer button{ - font-size: 18px; - font-weight: bolder; - background-color: #ccc; - padding: 5px 10px; - border-radius: 5px; - border: 2px solid #aaa; - box-shadow: 2px 2px 0 #eee; - width: 100%; + font-size: 18px; + font-weight: bolder; + background-color: #ccc; + padding: 5px 10px; + border-radius: 5px; + border: 2px solid #aaa; + box-shadow: 2px 2px 0 #eee; + width: 100%; } #drawer { - background-color: #666; - border-left: 1px solid #999; - box-shadow: inset 4px 4px 5px 0 rgba(50, 50, 50, 0.75); - color: #eee; - display: none; - font-size: 16px; - height: 100%; - overflow-y: auto; - position: absolute; - margin-top: 45px; - right: -200px; - width: 300px; - top: 0; - z-index: 1; + background-color: #666; + border-left: 1px solid #999; + box-shadow: inset 4px 4px 5px 0 rgba(50, 50, 50, 0.75); + color: #eee; + display: none; + font-size: 16px; + height: 100%; + overflow-y: auto; + position: absolute; + margin-top: 45px; + right: -200px; + width: 300px; + top: 0; + z-index: 1; } #drawer i, #treatmentDrawer i { - opacity: 0.6; + opacity: 0.6; } #drawer a, #treatmentDrawer a { - color: white; + color: white; } input[type=number]:invalid { - background-color: #FFCCCC; + background-color: #FFCCCC; } #drawer .actions { - margin: 10px 2px; + margin: 10px 2px; } #drawer .actions a { @@ -49,20 +49,20 @@ input[type=number]:invalid { } #treatmentDrawer { - background-color: #666; - border-left: 1px solid #999; - box-shadow: inset 4px 4px 5px 0 rgba(50, 50, 50, 0.75); - color: #eee; - display: none; - font-size: 16px; - height: 100%; - overflow-y: auto; - position: absolute; - margin-top: 45px; - right: -200px; - width: 300px; - top: 0; - z-index: 1; + background-color: #666; + border-left: 1px solid #999; + box-shadow: inset 4px 4px 5px 0 rgba(50, 50, 50, 0.75); + color: #eee; + display: none; + font-size: 16px; + height: 100%; + overflow-y: auto; + position: absolute; + margin-top: 45px; + right: -200px; + width: 300px; + top: 0; + z-index: 1; } #treatmentDrawer input { @@ -98,7 +98,7 @@ input[type=number]:invalid { } #eventTime { - padding-bottom: 15px; + padding-bottom: 15px; } #eventTime > span { @@ -138,164 +138,164 @@ input[type=number]:invalid { } #about { - margin-bottom: 55px; + margin-bottom: 55px; } #about div { - font-size: 12px; + font-size: 12px; } #about .links { - margin: 10px; + margin: 10px; } #about a { - color: #fff; + color: #fff; } form { - margin: 0; - padding: 0 10px; + margin: 0; + padding: 0 10px; } .serverSettings { - display: none; + display: none; } fieldset { - margin: 0.5em 0 1em 0; + margin: 0.5em 0 1em 0; } legend { - font-size: 1.1em; + font-size: 1.1em; } dl { - margin: 0 0 1em 0; + margin: 0 0 1em 0; } dl:last-child { - margin-bottom: 0; + margin-bottom: 0; } dd { - margin-left: 0; + margin-left: 0; } dd.numbers input { - text-align: right; - width: 50px; + text-align: right; + width: 50px; } dd.numbers label { - display: block; - float: left; - width: 50px; + display: block; + float: left; + width: 50px; } h1, legend, #toolbar .customTitle, #about .appName, #about .version { - font-weight: bold; - text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5); + font-weight: bold; + text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5); } #about .appName { - font-size: 1em; + font-size: 1em; } #about .version { - font-size: 1.25em; - margin-left: 0.25em; + font-size: 1.25em; + margin-left: 0.25em; } #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; + 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; } #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; + 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; } #buttonbar { - padding-right: 10px; - height: 44px; - opacity: 0.75; - vertical-align: middle; - position: absolute; - right: 0; - z-index: 500; + padding-right: 10px; + height: 44px; + opacity: 0.75; + vertical-align: middle; + position: absolute; + right: 0; + z-index: 500; } #buttonbar a, #buttonbar i { - color: #ccc; - font-size: 16px; - height: 44px; - line-height: 44px; + color: #ccc; + font-size: 16px; + height: 44px; + line-height: 44px; } #buttonbar a { - float: left; - text-decoration: none; - width: 34px; + float: left; + text-decoration: none; + width: 34px; } #buttonbar i { - padding-left: 12px; + padding-left: 12px; } #notification { - display: none; - font-size: 14px; - position: absolute; + display: none; + font-size: 14px; + position: absolute; } #notification, #notification a { - overflow: hidden; + overflow: hidden; } #notification { - opacity: 0.75; - top: 4px; - z-index: 99; + opacity: 0.75; + top: 4px; + z-index: 99; } #notification.info a { - background: #00f; - color: #fff; + background: #00f; + color: #fff; } #notification.warn a { - background: #cc0; - color: #000; + background: #cc0; + color: #000; } #notification.success a { - background: #090; - color: #fff; + background: #090; + color: #fff; } #notification.urgent a { - background: #c00; - color: #fff; + background: #c00; + color: #fff; } #notification a { - background: #c00; - border-radius: 5px; - color: #fff; - display: block; - height: 20px; - padding: 0.5em; - text-decoration: none; + background: #c00; + border-radius: 5px; + color: #fff; + display: block; + height: 20px; + padding: 0.5em; + text-decoration: none; } #notification span { - margin-right: 0.25em; + margin-right: 0.25em; } #notification i { - float: right; - opacity: 0.5; - vertical-align: top; + float: right; + opacity: 0.5; + vertical-align: top; } .experiments a { - color: #fff; - font-size: 1.5em; + color: #fff; + font-size: 1.5em; } diff --git a/static/css/dropdown.css b/static/css/dropdown.css index 0c2f5ec4f3d..a2211b84983 100644 --- a/static/css/dropdown.css +++ b/static/css/dropdown.css @@ -1,28 +1,28 @@ .dropdown-menu { - font-size: 200%; - margin: 0; - padding: 0; - position: absolute; - visibility: hidden; - right: 8px; + font-size: 200%; + margin: 0; + padding: 0; + position: absolute; + visibility: hidden; + right: 8px; } .dropdown-menu li { - list-style: none; - float: none; - display: inline; + list-style: none; + float: none; + display: inline; } .dropdown-menu li a { - display: block; - text-decoration: none; - white-space: nowrap; - width: auto; - background: #BBBBBB; - color: #24313C; - padding: 2px 10px; + display: block; + text-decoration: none; + white-space: nowrap; + width: auto; + background: #BBBBBB; + color: #24313C; + padding: 2px 10px; } .dropdown-menu li a:hover { - background: #EEEEFF; + background: #EEEEFF; } From 2ce698216f5f555d43716f3681fde78f7cb7aa68 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 19 Aug 2015 16:47:37 -0700 Subject: [PATCH 631/937] adjust line-height to get pill borders just right --- static/css/main.css | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/static/css/main.css b/static/css/main.css index 891c60f4d99..50459110c9a 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -87,10 +87,12 @@ body { .bgStatus .majorPills { font-size: 30px; + line-height: 34px; } .minorPills { font-size: 22px; + line-height: 25px; z-index: 500; white-space: normal; } @@ -169,6 +171,7 @@ body { } .statusPills { font-size: 20px; + line-height: 23px; } .loading .statusPills { @@ -351,10 +354,12 @@ body { .bgStatus .majorPills { font-size: 20px; + line-height: 24px; } .bgStatus .minorPills { font-size: 16px; + line-height: 18px; } .status { @@ -364,6 +369,7 @@ body { .statusPills { font-size: 15px; + line-height: 17px; } .focus-range { @@ -413,10 +419,12 @@ body { .bgStatus .majorPills { font-size: 15px; + line-height: 17px; } .bgStatus .minorPills { font-size: 12px; + line-height: 13px; } .status { @@ -461,10 +469,12 @@ body { .bgStatus .majorPills { font-size: 15px; + line-height: 17px; } .bgStatus .minorPills { font-size: 12px; + line-height: 13px; } #silenceBtn a { @@ -489,6 +499,7 @@ body { display: inline; margin-left: auto; font-size: 15px; + line-height: 17px; } .focus-range { @@ -554,10 +565,12 @@ body { .bgStatus .majorPills { font-size: 15px; + line-height: 17px; } .bgStatus .minorPills { font-size: 12px; + line-height: 13px; } .status { @@ -567,6 +580,7 @@ body { .statusPills { font-size: 15px; + line-height: 17px; } } @@ -595,10 +609,12 @@ body { .bgStatus .majorPills { font-size: 15px; + line-height: 17px; } .bgStatus .majorPills { font-size: 12px; + line-height: 13px; } #silenceBtn { From fb231c84d4984af859ea43991ca22fbf36ad060c Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 19 Aug 2015 17:00:22 -0700 Subject: [PATCH 632/937] no more !important's --- static/css/main.css | 12 ++++++------ static/index.html | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/static/css/main.css b/static/css/main.css index 50459110c9a..d5ab2afda9d 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -131,12 +131,12 @@ body { color: #000; } -.pill.warn em { - color: yellow !important; +.container .pill.warn em { + color: yellow; } -.pill.urgent em { - color: red !important; +.container .pill.urgent em { + color: red; } .alarming-timeago #lastEntry.pill.warn { @@ -483,7 +483,7 @@ body { .status { padding-top: 0; - font-size: 20px !important; + font-size: 20px; } .statusBox { @@ -531,7 +531,7 @@ body { @media (max-height: 480px) { #toolbar { float: right; - height: auto !important; + height: auto; } #toolbar .customTitle { diff --git a/static/index.html b/static/index.html index 98c1f1bbdca..f200c42166d 100644 --- a/static/index.html +++ b/static/index.html @@ -25,9 +25,9 @@ + - From 167454e1c65d5510e94d0467b296f8d21bbddd0c Mon Sep 17 00:00:00 2001 From: jweismann Date: Thu, 20 Aug 2015 10:01:51 +0200 Subject: [PATCH 633/937] ups --- lib/language.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/language.js b/lib/language.js index ce35ad1c87c..e0fbe66b2b2 100644 --- a/lib/language.js +++ b/lib/language.js @@ -2595,7 +2595,7 @@ function init() { ,bg: 'Въпрос' ,hr: 'Pitanje' ,it: 'Domanda' - ,dk: 'Spørgsmål + ,dk: 'Spørgsmål' } ,'Announcement' : { bg: 'Известяване' From ebd48d9cbcbfab7bd0f3e4ff01eb00283ccd2289 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 20 Aug 2015 10:46:49 -0700 Subject: [PATCH 634/937] more line-height and font size pill tweeks --- static/css/main.css | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/static/css/main.css b/static/css/main.css index d5ab2afda9d..3be68b995b7 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -498,8 +498,8 @@ body { .statusPills { display: inline; margin-left: auto; - font-size: 15px; - line-height: 17px; + font-size: 12px; + line-height: 13px; } .focus-range { @@ -612,7 +612,7 @@ body { line-height: 17px; } - .bgStatus .majorPills { + .bgStatus .minorPills { font-size: 12px; line-height: 13px; } @@ -638,4 +638,10 @@ body { width: 220px; } + .statusPills { + display: inline; + margin-left: auto; + font-size: 12px; + line-height: 13px; + } } From c8293c00c504512b3f3a72f1ad6cca13afc12e0d Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 20 Aug 2015 10:58:00 -0700 Subject: [PATCH 635/937] adjust sort of languages --- static/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/index.html b/static/index.html index 23ca79330dc..f9d266d4456 100644 --- a/static/index.html +++ b/static/index.html @@ -103,6 +103,7 @@ + @@ -111,7 +112,6 @@ - From c8d9a0dda81f7592913b26995db2491a3b3f3762 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 20 Aug 2015 14:14:23 -0700 Subject: [PATCH 636/937] if there isn't a battery value, don't set the field --- lib/pebble.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/pebble.js b/lib/pebble.js index e96fe8af32f..34157a4a1fd 100644 --- a/lib/pebble.js +++ b/lib/pebble.js @@ -78,7 +78,10 @@ function prepareSGVs (req, sbx) { bgs[0].bgdelta = bgs[0].bgdelta.toFixed(1); } - bgs[0].battery = data.devicestatus && data.devicestatus.uploaderBattery && data.devicestatus.uploaderBattery.toString(); + if (data.devicestatus && data.devicestatus.uploaderBattery) { + bgs[0].battery = data.devicestatus.uploaderBattery.toString(); + } + if (req.iob) { var iobResult = iob.calcTotal(data.treatments, data.profile, Date.now()); if (iobResult) { From 70c226501cbafb76242f9fcc0e50c0e70a745935 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 20 Aug 2015 22:35:24 -0700 Subject: [PATCH 637/937] some pebble refactoring and a few more tests --- lib/pebble.js | 75 +++++++++++++++++++++++++++----------------- tests/pebble.test.js | 46 +++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 29 deletions(-) diff --git a/lib/pebble.js b/lib/pebble.js index 34157a4a1fd..0deffb221e4 100644 --- a/lib/pebble.js +++ b/lib/pebble.js @@ -35,10 +35,7 @@ function reverseAndSlice (entries, req) { } -function prepareSGVs (req, sbx) { - var bgs = []; - var data = sbx.data; - +function mapSGVs(req, sbx) { function scaleMgdlAPebbleLegacyHackThatWillNotGoAway (bg) { if (req.mmol) { return units.mgdlToMMOL(bg); @@ -47,49 +44,69 @@ function prepareSGVs (req, sbx) { } } - //for compatibility we're keeping battery and iob here, but they would be better somewhere else - if (data.sgvs.length > 0) { - var cal = sbx.lastEntry(sbx.data.cals); - - bgs = _.map(reverseAndSlice(data.sgvs, req), function transformSGV (sgv) { - var transformed = { - sgv: scaleMgdlAPebbleLegacyHackThatWillNotGoAway(sgv.mgdl) - , trend: directionToTrend(sgv.direction) - , direction: sgv.direction - , datetime: sgv.mills - }; - - if (req.rawbg && cal) { - transformed.filtered = sgv.filtered; - transformed.unfiltered = sgv.unfiltered; - transformed.noise = sgv.noise; - } + var cal = sbx.lastEntry(sbx.data.cals); - return transformed; - }); + return _.map(reverseAndSlice(sbx.data.sgvs, req), function transformSGV(sgv) { + var transformed = { + sgv: scaleMgdlAPebbleLegacyHackThatWillNotGoAway(sgv.mgdl), trend: directionToTrend(sgv.direction), direction: sgv.direction, datetime: sgv.mills + }; + + if (req.rawbg && cal) { + transformed.filtered = sgv.filtered; + transformed.unfiltered = sgv.unfiltered; + transformed.noise = sgv.noise; + } + + return transformed; + }); + +} + +function addExtraData (first, req, sbx) { + //for compatibility we're keeping battery and iob on the first bg, but they would be better somewhere else + var data = sbx.data; + + function addDelta() { var prev = data.sgvs.length >= 2 ? data.sgvs[data.sgvs.length - 2] : null; var current = sbx.lastSGVEntry(); //for legacy reasons we need to return a 0 for delta if it can't be calculated var deltaResult = delta.calc(prev, current, sbx); - bgs[0].bgdelta = deltaResult && deltaResult.scaled || 0; + first.bgdelta = deltaResult && deltaResult.scaled || 0; if (req.mmol) { - bgs[0].bgdelta = bgs[0].bgdelta.toFixed(1); + first.bgdelta = first.bgdelta.toFixed(1); } + } - if (data.devicestatus && data.devicestatus.uploaderBattery) { - bgs[0].battery = data.devicestatus.uploaderBattery.toString(); + function addBattery() { + if (data.devicestatus && data.devicestatus.uploaderBattery && data.devicestatus.uploaderBattery >= 0) { + first.battery = data.devicestatus.uploaderBattery.toString(); } + } + function addIOB() { if (req.iob) { var iobResult = iob.calcTotal(data.treatments, data.profile, Date.now()); if (iobResult) { - bgs[0].iob = iobResult.display; + first.iob = iobResult.display; } } } + addDelta(); + addBattery(); + addIOB(); +} + +function prepareBGs (req, sbx) { + if (sbx.data.sgvs.length === 0) { + return []; + } + + var bgs = mapSGVs(req, sbx); + addExtraData(bgs[0], req, sbx); + return bgs; } @@ -119,7 +136,7 @@ function pebble (req, res) { res.setHeader('content-type', 'application/json'); res.write(JSON.stringify({ status: [ {now: Date.now()} ] - , bgs: prepareSGVs(req, sbx) + , bgs: prepareBGs(req, sbx) , cals: prepareCals(req, sbx) })); diff --git a/tests/pebble.test.js b/tests/pebble.test.js index 876807866bc..f5ccf6060a2 100644 --- a/tests/pebble.test.js +++ b/tests/pebble.test.js @@ -165,11 +165,57 @@ describe('Pebble Endpoint', function ( ) { done( ); }); }); + + it('/pebble without battery', function (done) { + delete ctx.data.devicestatus.uploaderBattery; + request(this.app) + .get('/pebble') + .expect(200) + .end(function (err, res) { + var bgs = res.body.bgs; + bgs.length.should.equal(1); + should.not.exist(bgs[0].battery); + + res.body.cals.length.should.equal(0); + done( ); + }); + }); + + it('/pebble with a negative battery', function (done) { + ctx.data.devicestatus.uploaderBattery = -1; + request(this.app) + .get('/pebble') + .expect(200) + .end(function (err, res) { + var bgs = res.body.bgs; + bgs.length.should.equal(1); + should.not.exist(bgs[0].battery); + + res.body.cals.length.should.equal(0); + done( ); + }); + }); + + it('/pebble with a false battery', function (done) { + ctx.data.devicestatus.uploaderBattery = false; + request(this.app) + .get('/pebble') + .expect(200) + .end(function (err, res) { + var bgs = res.body.bgs; + bgs.length.should.equal(1); + should.not.exist(bgs[0].battery); + + res.body.cals.length.should.equal(0); + done( ); + }); + }); }); describe('Pebble Endpoint with Raw and IOB', function ( ) { var pebbleRaw = require('../lib/pebble'); before(function (done) { + ctx.data.devicestatus.uploaderBattery = 100; var envRaw = require('../env')( ); envRaw.settings.enable = 'rawbg iob'; this.appRaw = require('express')( ); From 94103c76ae89948df16758477faeffee522e87dc Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sat, 22 Aug 2015 10:38:28 +0300 Subject: [PATCH 638/937] Fixed to work with latest dev --- lib/pebble.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/pebble.js b/lib/pebble.js index e7ddbea9300..877c4764b30 100644 --- a/lib/pebble.js +++ b/lib/pebble.js @@ -5,8 +5,8 @@ var _ = require('lodash'); var sandbox = require('./sandbox')(); var units = require('./units')(); var iob = require('./plugins/iob')(); -var delta = require('./plugins/delta')(); var bwp = require('./plugins/boluswizardpreview')(); +var delta = require('./plugins/delta')(); var DIRECTIONS = { NONE: 0 @@ -92,13 +92,15 @@ function addExtraData (first, req, sbx) { if (iobResult) { first.iob = iobResult.display; } - + sbx.properties.iob = iobResult; var bwpResult = bwp.calc(sbx); + if (bwpResult) { - bgs[0].bwp = bwpResult.bolusEstimateDisplay; - bgs[0].bwpo = bwpResult.outcomeDisplay; + first.bwp = bwpResult.bolusEstimateDisplay; + first.bwpo = bwpResult.outcomeDisplay; } + } } From 1787eb3123b40911b86ea95500656b5f6b8a23b4 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 22 Aug 2015 10:36:02 -0700 Subject: [PATCH 639/937] some minor fixes and improvements for the profile editor --- lib/api/profile/index.js | 4 ++-- lib/profile.js | 2 +- static/css/main.css | 1 - static/css/profile.css | 25 +++++++++++++++++-------- static/profile/index.html | 24 ++++++++++++++---------- static/profile/js/profile.js | 33 +++++++++++++++++++++------------ 6 files changed, 55 insertions(+), 34 deletions(-) diff --git a/lib/api/profile/index.js b/lib/api/profile/index.js index c8f62323ddd..99295b05a57 100644 --- a/lib/api/profile/index.js +++ b/lib/api/profile/index.js @@ -41,7 +41,7 @@ function configure (app, wares, ctx) { console.log(err); } else { res.json(created); - console.log('Profile created'); + console.log('Profile created', created); } }); @@ -57,7 +57,7 @@ function configure (app, wares, ctx) { console.log(err); } else { res.json(created); - console.log('Profile saved'); + console.log('Profile saved', created); } }); diff --git a/lib/profile.js b/lib/profile.js index d1d80bbfbdd..7beff461a16 100644 --- a/lib/profile.js +++ b/lib/profile.js @@ -16,7 +16,7 @@ function storage (collection, ctx) { obj.created_at = (new Date( )).toISOString( ); } api().save(obj, function (err, doc) { - fn(err, doc.ops); + fn(err, obj); }); } diff --git a/static/css/main.css b/static/css/main.css index 3be68b995b7..ac92ef617c7 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -12,7 +12,6 @@ body { font-family: 'Open Sans', Helvetica, Arial, sans-serif; background: #000; color: #bdbdbd; - overflow: hidden; } .container { diff --git a/static/css/profile.css b/static/css/profile.css index 943b2642714..e5567da3bff 100644 --- a/static/css/profile.css +++ b/static/css/profile.css @@ -1,10 +1,19 @@ #profile-editor h1 { - position: absolute; - top: 0; - right: 0; - left: 50px; - margin-top: 0; - margin-right: auto; - margin-left: auto; - text-align: center; + text-align: left; + margin: 10px; +} + +#pe_form input[type="text"], #pe_form input[type="number"] { + width: 60px; +} + +#pe_submit { + font-size: 18px; + font-weight: bolder; + background-color: #ccc; + padding: 5px 10px; + border-radius: 5px; + border: 2px solid #aaa; + box-shadow: 2px 2px 0 #eee; + margin: 20px 0; } \ No newline at end of file diff --git a/static/profile/index.html b/static/profile/index.html index 3db92f1c66b..57694e6151f 100644 --- a/static/profile/index.html +++ b/static/profile/index.html @@ -31,17 +31,17 @@ -
    +
    - Status: Not loaded + Status: Not loaded
    -

    Nightscout

    -
    -

    Profile editor

    -
    -
    - -
    +

    Nightscout

    +
    + +
    +

    Profile editor

    +
    +
    General patient profile settings @@ -89,7 +89,7 @@

    Profile editor

    ( - use values specific to GI of food + use GI specific values ) @@ -128,6 +128,10 @@

    Profile editor

    Authentication status: +

    + Status: Not loaded +

    + diff --git a/static/profile/js/profile.js b/static/profile/js/profile.js index 6ab51dd55de..bcc648371bc 100644 --- a/static/profile/js/profile.js +++ b/static/profile/js/profile.js @@ -9,6 +9,7 @@ var c_profile = null; //some commonly used selectors + var peStatus = $('.pe_status'); var timezoneInput = $('#pe_timezone'); var timeInput = $('#pe_time'); var dateInput = $('#pe_date'); @@ -76,7 +77,7 @@ var mongoprofiles = []; // Fetch data from mongo - $('#pe_status').hide().text('Loading profile records ...').fadeIn('slow'); + peStatus.hide().text('Loading profile records ...').fadeIn('slow'); $.ajax('/api/v1/profile.json', { success: function (records) { c_profile = {}; @@ -99,18 +100,18 @@ } convertToRanges(c_profile); - $('#pe_status').hide().text('Values loaded.').fadeIn('slow'); + peStatus.hide().text('Values loaded.').fadeIn('slow'); mongoprofiles.unshift(c_profile); } else { c_profile = _.cloneDeep(defaultprofile); mongoprofiles.unshift(c_profile); - $('#pe_status').hide().text('Default values used.').fadeIn('slow'); + peStatus.hide().text('Default values used.').fadeIn('slow'); } }, error: function () { c_profile = _.cloneDeep(defaultprofile); mongoprofiles.unshift(c_profile); - $('#pe_status').hide().text('Error. Default values used.').fadeIn('slow'); + peStatus.hide().text('Error. Default values used.').fadeIn('slow'); } }).done(initeditor); @@ -433,7 +434,7 @@ GUIToObject(); if (new Date(c_profile.startDate) > new Date()) { alert('Date must be set in the past'); - $('#pe_status').hide().html('Wrong date').fadeIn('slow'); + peStatus.hide().html('Wrong date').fadeIn('slow'); return false; } @@ -458,10 +459,11 @@ adjustedProfile.startDate = adjustedProfile.startDate.toISOString( ); + console.info('saving profile'); if (submitButton.text().indexOf('Create new record')>-1) { if (mongoprofiles.length > 1 && (new Date(c_profile.startDate) <= new Date(mongoprofiles[1].validfrom))) { alert('Date must be greater than last record '+new Date(mongoprofiles[1].startDate)); - $('#pe_status').hide().html('Wrong date').fadeIn('slow'); + peStatus.hide().html('Wrong date').fadeIn('slow'); return false; } @@ -475,13 +477,17 @@ , headers: { 'api-secret': Nightscout.client.hashauth.hash() } - }).done(function postSuccess (response) { - console.info('profile saved', response); + }).done(function postSuccess (data, status) { + console.info('profile created', data); + peStatus.hide().text(status).fadeIn('slow'); //not using the adjustedProfile here (doesn't have the defaults other code needs) var newprofile = _.cloneDeep(c_profile); mongoprofiles.unshift(newprofile); initeditor(); + }).fail(function(xhr, status, errorThrown) { + console.error('Profile not saved', status, errorThrown); + peStatus.hide().text(status).fadeIn('slow'); }); } else { $.ajax({ @@ -491,12 +497,15 @@ , headers: { 'api-secret': Nightscout.client.hashauth.hash() } - }).done(function putSuccess (response) { - console.info('profile updated', response); - $('#pe_status').hide().text(response).fadeIn('slow'); + }).done(function putSuccess (data, status) { + console.info('profile updated', data); + peStatus.hide().text(status).fadeIn('slow'); + }).fail(function(xhr, status, errorThrown) { + console.error('Profile not saved', status, errorThrown); + peStatus.hide().text(status).fadeIn('slow'); }); } - + if (event) { event.preventDefault(); } From 5a1baebdfd4455010a344cc55f1f8fefaeff8021 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 22 Aug 2015 10:39:47 -0700 Subject: [PATCH 640/937] fix profile editor test --- tests/profileeditor.test.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/profileeditor.test.js b/tests/profileeditor.test.js index 632cff2ac43..6b643e3e1c2 100644 --- a/tests/profileeditor.test.js +++ b/tests/profileeditor.test.js @@ -85,6 +85,9 @@ describe('profile editor', function ( ) { opts.success([exampleProfile]); } fn(); + return { + fail: function () {} + } } }; }; From eda1fd7298be669c6da72498a6840ddbc2be3ae7 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 22 Aug 2015 17:02:44 -0700 Subject: [PATCH 641/937] adjust size is WIDTH_SMALL_DOTS limit since there is a little less padding we still want small dot in portrait mode for iPhone 6+ and Nexus 6 --- lib/client/renderer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/client/renderer.js b/lib/client/renderer.js index bd1626c187b..dc11c86e63e 100644 --- a/lib/client/renderer.js +++ b/lib/client/renderer.js @@ -3,7 +3,7 @@ var times = require('../times'); var DEFAULT_FOCUS = times.hours(3).msecs - , WIDTH_SMALL_DOTS = 400 + , WIDTH_SMALL_DOTS = 420 , WIDTH_BIG_DOTS = 800 , TOOLTIP_TRANS_MS = 200 // milliseconds , UPDATE_TRANS_MS = 750 // milliseconds From 647fceee5fe024b995125fdccc067ae9cb440e1d Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 22 Aug 2015 17:08:34 -0700 Subject: [PATCH 642/937] fix some issues found by codacy --- lib/profile.js | 3 ++- tests/profileeditor.test.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/profile.js b/lib/profile.js index 7beff461a16..2131b088642 100644 --- a/lib/profile.js +++ b/lib/profile.js @@ -15,7 +15,8 @@ function storage (collection, ctx) { if (!obj.created_at) { obj.created_at = (new Date( )).toISOString( ); } - api().save(obj, function (err, doc) { + api().save(obj, function (err) { + //id should be added for new docs fn(err, obj); }); } diff --git a/tests/profileeditor.test.js b/tests/profileeditor.test.js index 6b643e3e1c2..89d6db58ff1 100644 --- a/tests/profileeditor.test.js +++ b/tests/profileeditor.test.js @@ -87,7 +87,7 @@ describe('profile editor', function ( ) { fn(); return { fail: function () {} - } + }; } }; }; From e19af929c222a6190b9638beefe5aeb22b922b79 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 22 Aug 2015 17:59:30 -0700 Subject: [PATCH 643/937] use client settings instead of the base serverSettings resolves #822 --- static/profile/index.html | 2 +- .../js/{profile.js => profileeditor.js} | 25 ++++++++++--------- 2 files changed, 14 insertions(+), 13 deletions(-) rename static/profile/js/{profile.js => profileeditor.js} (95%) diff --git a/static/profile/index.html b/static/profile/index.html index 57694e6151f..54e51fe2c23 100644 --- a/static/profile/index.html +++ b/static/profile/index.html @@ -142,6 +142,6 @@

    Profile editor

    - + diff --git a/static/profile/js/profile.js b/static/profile/js/profileeditor.js similarity index 95% rename from static/profile/js/profile.js rename to static/profile/js/profileeditor.js index bcc648371bc..db568640be7 100644 --- a/static/profile/js/profile.js +++ b/static/profile/js/profileeditor.js @@ -5,6 +5,7 @@ var _ = window._; var moment = window.moment; var Nightscout = window.Nightscout; + var client = Nightscout.client; var c_profile = null; @@ -18,10 +19,10 @@ if (serverSettings === undefined) { console.error('server settings were not loaded, will not call init'); } else { - window.Nightscout.client.init(serverSettings, Nightscout.plugins); + client.init(serverSettings, Nightscout.plugins); } - var translate = Nightscout.client.translate; + var translate = client.translate; var defaultprofile = { //General values @@ -141,9 +142,9 @@ $('#pe_perGIvalues').change(switchStyle); // display status - $('#pe_units').text(serverSettings.settings.units); - $('#pe_timeformat').text(serverSettings.settings.timeFormat+'h'); - $('#pe_title').text(serverSettings.settings.customTitle); + $('#pe_units').text(client.settings.units); + $('#pe_timeformat').text(client.settings.timeFormat+'h'); + $('#pe_title').text(client.settings.customTitle); var lastvalidfrom = new Date(mongoprofiles[1] && mongoprofiles[1].startDate ? mongoprofiles[1].startDate : null); @@ -170,7 +171,7 @@ // Handling valid from date change function dateChanged(event) { - var newdate = new Date(Nightscout.client.utils.mergeInputTime(timeInput.val(), dateInput.val())); + var newdate = new Date(client.utils.mergeInputTime(timeInput.val(), dateInput.val())); if (mongoprofiles.length<2 || !mongoprofiles[1].startDate || mongoprofiles.length>=2 && new Date(mongoprofiles[1].startDate).getTime() === newdate.getTime()) { submitButton.text('Update record').css('display',''); timeInput.css({'background-color':'white'}); @@ -384,7 +385,7 @@ function GUIToObject() { c_profile.dia = parseFloat($('#pe_dia').val()); - c_profile.startDate = new Date(Nightscout.client.utils.mergeInputTime(timeInput.val(), dateInput.val())); + c_profile.startDate = new Date(client.utils.mergeInputTime(timeInput.val(), dateInput.val())); c_profile.carbs_hr = parseInt($('#pe_hr').val()); c_profile.delay = 20; c_profile.perGIvalues = $('#pe_perGIvalues').is(':checked'); @@ -427,7 +428,7 @@ function toDisplayTime (minfrommidnight) { var time = moment().startOf('day').add(minfrommidnight,'minutes'); - return serverSettings.settings.timeFormat === '24' ? time.format('HH:mm') : time.format('h:mm A'); + return client.settings.timeFormat === '24' ? time.format('HH:mm') : time.format('h:mm A'); } function profileSubmit(event) { @@ -438,12 +439,12 @@ return false; } - if (!Nightscout.client.hashauth.isAuthenticated()) { + if (!client.hashauth.isAuthenticated()) { alert(translate('Your device is not authenticated yet')); return false; } - c_profile.units = serverSettings.settings.units; + c_profile.units = client.settings.units; var adjustedProfile = _.cloneDeep(c_profile); @@ -475,7 +476,7 @@ , url: '/api/v1/profile/' , data: adjustedProfile , headers: { - 'api-secret': Nightscout.client.hashauth.hash() + 'api-secret': client.hashauth.hash() } }).done(function postSuccess (data, status) { console.info('profile created', data); @@ -495,7 +496,7 @@ , url: '/api/v1/profile/' , data: adjustedProfile , headers: { - 'api-secret': Nightscout.client.hashauth.hash() + 'api-secret': client.hashauth.hash() } }).done(function putSuccess (data, status) { console.info('profile updated', data); From 26fd7733b888c00fb3f950c5d212a31272cf19e7 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 22 Aug 2015 18:08:36 -0700 Subject: [PATCH 644/937] fix path for renamed file --- tests/profileeditor.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/profileeditor.test.js b/tests/profileeditor.test.js index 89d6db58ff1..ac6e3c95f29 100644 --- a/tests/profileeditor.test.js +++ b/tests/profileeditor.test.js @@ -111,7 +111,7 @@ describe('profile editor', function ( ) { }); benv.require(__dirname + '/../bundle/bundle.source.js'); - benv.require(__dirname + '/../static/profile/js/profile.js'); + benv.require(__dirname + '/../static/profile/js/profileeditor.js'); done(); }); From e30a5ae6036eb8c08eab096a17329ecc5e12b35b Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 22 Aug 2015 18:46:02 -0700 Subject: [PATCH 645/937] split example profiles out from the readme resolves #813 --- example-profiles.md | 77 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 example-profiles.md diff --git a/example-profiles.md b/example-profiles.md new file mode 100644 index 00000000000..717bc1df9dc --- /dev/null +++ b/example-profiles.md @@ -0,0 +1,77 @@ + + +**Table of Contents** + +- [Example Profiles](#example-profiles) + - [Simple profile](#simple-profile) + - [Profile can also use time periods for any field, for example:](#profile-can-also-use-time-periods-for-any-field-for-example) + + + +#Example Profiles + +These are only examples, make sure you update all fields to fit your needs + +##Simple profile + ```json + { + "dia": 3, + "carbs_hr": 20, + "carbratio": 30, + "sens": 100, + "basal": 0.125, + "target_low": 100, + "target_high": 120 + } + ``` + +##Profile can also use time periods for any field, for example: + + ```json + { + "carbratio": [ + { + "time": "00:00", + "value": 30 + }, + { + "time": "06:00", + "value": 25 + }, + { + "time": "14:00", + "value": 28 + } + ], + "basal": [ + { + "time": "00:00", + "value": 0.175 + }, + { + "time": "02:30", + "value": 0.125 + }, + { + "time": "05:00", + "value": 0.075 + }, + { + "time": "08:00", + "value": 0.100 + }, + { + "time": "14:00", + "value": 0.125 + }, + { + "time": "20:00", + "value": 0.175 + }, + { + "time": "22:00", + "value": 0.200 + } + ] + } + ``` From 460e14c275a089480c9d818330ac89e88dee608d Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 22 Aug 2015 18:47:40 -0700 Subject: [PATCH 646/937] forgot to pop from stash --- README.md | 69 +++---------------------------------------------------- 1 file changed, 3 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index 2ef4c243a90..1665d9b74c3 100644 --- a/README.md +++ b/README.md @@ -254,73 +254,8 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. ### Treatment Profile - Some of the [plugins](#plugins) make use of a treatment profile that is stored in Mongo. To use those plugins there should only be a single doc in the `profile` collection. + Some of the [plugins](#plugins) make use of a treatment profile that can be edited using the Profile Editor, see the link in the Settings drawer on your site. - Example Profile (change it to fit you): - - ```json - { - "dia": 3, - "carbs_hr": 20, - "carbratio": 30, - "sens": 100, - "basal": 0.125, - "target_low": 100, - "target_high": 120 - } - ``` - - Profile can also use time periods for any field, for example: - - ```json - { - "carbratio": [ - { - "time": "00:00", - "value": 30 - }, - { - "time": "06:00", - "value": 25 - }, - { - "time": "14:00", - "value": 28 - } - ], - "basal": [ - { - "time": "00:00", - "value": 0.175 - }, - { - "time": "02:30", - "value": 0.125 - }, - { - "time": "05:00", - "value": 0.075 - }, - { - "time": "08:00", - "value": 0.100 - }, - { - "time": "14:00", - "value": 0.125 - }, - { - "time": "20:00", - "value": 0.175 - }, - { - "time": "22:00", - "value": 0.200 - } - ] - } - ``` - Treatment Profile Fields: * `timezone` (Time Zone) - time zone local to the patient. *Should be set.* @@ -332,6 +267,8 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * `basal` The basal rate set on the pump. * `target_high` - Upper target for correction boluses. * `target_low` - Lower target for correction boluses. + + Some example profiles are [here](example-profiles.md) Additional information can be found [here](http://www.nightscout.info/wiki/labs/the-nightscout-iob-cob-website). From 9797040d6f9719c5e05a20d9202be3d3a90ab886 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 22 Aug 2015 18:51:47 -0700 Subject: [PATCH 647/937] remove outdated reference to database_configuration.json --- README.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/README.md b/README.md index 1665d9b74c3..5d6569d4b15 100644 --- a/README.md +++ b/README.md @@ -85,14 +85,9 @@ $ npm install #Usage The data being uploaded from the server to the client is from a -MongoDB server such as [mongolab][mongodb]. In order to access the -database, the appropriate credentials need to be filled into the -[JSON][json] file in the root directory. SGV data from the database -is assumed to have the following fields: date, sgv. Once all that is -ready, just host your web app on your service of choice. +MongoDB server such as [mongolab][mongodb]. [mongodb]: https://mongolab.com -[json]: https://github.com/rnpenguin/cgm-remote-monitor/blob/master/database_configuration.json [autoconfigure]: http://nightscout.github.io/pages/configure/ [mongostring]: http://nightscout.github.io/pages/mongostring/ [update-fork]: http://nightscout.github.io/pages/update-fork/ From e4eaf8159140b97fc312bfc2f767babe05620141 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 22 Aug 2015 19:10:15 -0700 Subject: [PATCH 648/937] don't link to the out-of-date .info site --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 5d6569d4b15..339673fc146 100644 --- a/README.md +++ b/README.md @@ -263,9 +263,7 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * `target_high` - Upper target for correction boluses. * `target_low` - Lower target for correction boluses. - Some example profiles are [here](example-profiles.md) - - Additional information can be found [here](http://www.nightscout.info/wiki/labs/the-nightscout-iob-cob-website). + Some example profiles are [here](example-profiles.md). ## Setting environment variables Easy to emulate on the commandline: From 6828a7382a99cec45a549c2f38dd6758f99b0901 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 23 Aug 2015 01:29:45 -0700 Subject: [PATCH 649/937] don't spam the console by logging the whole bundle at startup --- bundle/bundle.source.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index 4c39b094011..f9c9e195411 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -10,7 +10,7 @@ , plugins: require('../lib/plugins/')().registerClientDefaults() }; - console.info('Nightscout bundle ready', window.Nightscout); + console.info('Nightscout bundle ready'); })(); From 1779e619c18edc7a8e09fa47b78f9dbe19852301 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 23 Aug 2015 01:59:24 -0700 Subject: [PATCH 650/937] support for disabling default features use decodeURIComponent to support environments where spaces in env vars are a problem converted enable and alarmTypes to arrays --- README.md | 31 ++++-- env.js | 48 +-------- lib/api/index.js | 10 +- lib/settings.js | 124 ++++++++++++++++------- static/index.html | 2 +- tests/api.status.test.js | 6 +- tests/api.treatments.test.js | 2 +- tests/env.test.js | 10 -- tests/pebble.test.js | 2 +- tests/settings.test.js | 185 +++++++++++++++++++++-------------- 10 files changed, 237 insertions(+), 183 deletions(-) diff --git a/README.md b/README.md index 339673fc146..aa43bbe4209 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,8 @@ Community maintained fork of the - [Core](#core) - [Predefined values for your browser settings (optional)](#predefined-values-for-your-browser-settings-optional) - [Plugins](#plugins) + - [Default Plugins](#default-plugins) + - [Built-in/Example Plugins:](#built-inexample-plugins) - [Extended Settings](#extended-settings) - [Pushover](#pushover) - [IFTTT Maker](#ifttt-maker) @@ -119,7 +121,8 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. ### Features/Labs - * `ENABLE` - Used to enable optional features, expects a space delimited list such as: `careportal rawbg iob`, see [plugins](#plugins) below + * `ENABLE` - Used to enable optional features, expects a space delimited list, such as: `careportal rawbg iob`, see [plugins](#plugins) below + * `DISABLE` - Used to disable default features, expects a space delimited list, such as: `direction upbat`, see [plugins](#plugins) below * `API_SECRET` - A secret passphrase that must be at least 12 characters long, required to enable `POST` and `PUT`; also required for the Care Portal * `BG_HIGH` (`260`) - must be set using mg/dl units; the high BG outside the target range that is considered urgent * `BG_TARGET_TOP` (`180`) - must be set using mg/dl units; the top of the target range, also used to draw the line on the chart @@ -164,7 +167,23 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. The built-in/example plugins that are available by default are listed below. The plugins may still need to be enabled by adding to the `ENABLE` environment variable. - **Built-in/Example Plugins:** +#### Default Plugins + + These can be disabled by setting the `DISABLE` env var, for example `DISABLE="direction upbat"` + + * `delta` (BG Delta) - Calculates and displays the change between the last 2 BG values. + * `direction` (BG Direction) - Displays the trend direction. + * `upbat` (Uploader Battery) - Displays the most recent battery status from the uploader phone. + * `errorcodes` (CGM Error Codes) - Generates alarms for CGM codes `9` (hourglass) and `10` (???). + * `ar2` ([Forcasting using AR2 algorithm](https://github.com/nightscout/nightscout.github.io/wiki/Forecasting)) - Generates alarms based on forecasted values. + * Enabled by default if no thresholds are set **OR** `ALARM_TYPES` includes `predict`. + * Use [extended settings](#extended-settings) to adjust AR2 behavior: + * `AR2_USE_RAW` (`false`) - to forecast using `rawbg` values when standard values don't trigger an alarm. + * `AR2_CONE_FACTOR` (`2`) - to adjust size of cone, use `0` for a single line. + * `simplealarms` (Simple BG Alarms) - Uses `BG_HIGH`, `BG_TARGET_TOP`, `BG_TARGET_BOTTOM`, `BG_LOW` thresholds to generate alarms. + * Enabled by default if 1 of these thresholds is set **OR** `ALARM_TYPES` includes `simple`. + +#### Built-in/Example Plugins: * `rawbg` (Raw BG) - Calculates BG using sensor and calibration records from and displays an alternate BG values and noise levels. * `iob` (Insulin-on-Board) - Adds the IOB pill visualization in the client and calculates values that used by other plugins. Uses treatments with insulin doses and the `dia` and `sens` fields from the [treatment profile](#treatment-profile). @@ -179,14 +198,6 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * `CAGE_INFO` (`44`) - If time since last `Site Change` matches `CAGE_INFO`, user will be warned of upcoming cannula change * `CAGE_WARN` (`48`) - If time since last `Site Change` matches `CAGE_WARN`, user will be alarmed to to change the cannula * `CAGE_URGENT` (`72`) - If time since last `Site Change` matches `CAGE_URGENT`, user will be issued a persistent warning of overdue change. - * `delta` (BG Delta) - Calculates and displays the change between the last 2 BG values. **Enabled by default.** - * `direction` (BG Direction) - Displays the trend direction. **Enabled by default.** - * `upbat` (Uploader Battery) - Displays the most recent battery status from the uploader phone. **Enabled by default.** - * `ar2` ([Forcasting using AR2 algorithm](https://github.com/nightscout/nightscout.github.io/wiki/Forecasting)) - Generates alarms based on forecasted values. **Enabled by default.** Use [extended settings](#extended-settings) to adjust AR2 behavior: - * `AR2_USE_RAW` (`false`) - to forecast using `rawbg` values when standard values don't trigger an alarm. - * `AR2_CONE_FACTOR` (`2`) - to adjust size of cone, use `0` for a single line. - * `simplealarms` (Simple BG Alarms) - Uses `BG_HIGH`, `BG_TARGET_TOP`, `BG_TARGET_BOTTOM`, `BG_LOW` settings to generate alarms. - * `errorcodes` (CGM Error Codes) - Generates alarms for CGM codes `9` (hourglass) and `10` (???). **Enabled by default.** * `treatmentnotify` (Treatment Notifications) - Generates notifications when a treatment has been entered and snoozes alarms minutes after a treatment. Default snooze is 10 minutes, and can be set using the `TREATMENTNOTIFY_SNOOZE_MINS` [extended setting](#extended-settings). * `basal` (Basal Profile) - Adds the Basal pill visualization to display the basal rate for the current time. Also enables the `bwp` plugin to calculate correction temp basal suggestions. Uses the `basal` field from the [treatment profile](#treatment-profile). * `bridge` (Share2Nightscout bridge) - Glucose reading directly from the Share service, uses these extended settings: diff --git a/env.js b/env.js index bcc9878330d..1b4c94a43e2 100644 --- a/env.js +++ b/env.js @@ -26,8 +26,6 @@ function config ( ) { setMongo(); updateSettings(); - env.hasExtendedSetting = hasExtendedSetting; - return env; } @@ -113,26 +111,6 @@ function setMongo() { function updateSettings() { - var enable = readENV('ENABLE', ''); - - function anyEnabled (features) { - return _.findIndex(features, function (feature) { - return enable.indexOf(feature) > -1; - }) > -1; - } - - //don't require pushover to be enabled to preserve backwards compatibility if there are extendedSettings for it - if (hasExtendedSetting('PUSHOVER', process.env)) { - enable += ' pushover'; - } - - if (anyEnabled(['careportal', 'pushover', 'maker'])) { - enable += ' treatmentnotify'; - } - - //TODO: figure out something for default plugins, how can they be disabled? - enable += ' delta direction upbat errorcodes'; - var envNameOverrides = { UNITS: 'DISPLAY_UNITS' }; @@ -142,19 +120,8 @@ function updateSettings() { return readENV(envName); }); - //TODO: maybe get rid of ALARM_TYPES and only use enable? - if (env.settings.alarmTypes.indexOf('simple') > -1) { - enable = 'simplealarms ' + enable; - } - if (env.settings.alarmTypes.indexOf('predict') > -1) { - enable = 'ar2 ' + enable; - } - - env.settings.enable = enable; - env.settings.processRawSettings(); - //should always find extended settings last - env.extendedSettings = findExtendedSettings(enable, process.env); + env.extendedSettings = findExtendedSettings(process.env); } function readENV(varName, defaultValue) { @@ -170,23 +137,14 @@ function readENV(varName, defaultValue) { return value != null ? value : defaultValue; } -function hasExtendedSetting(prefix, envs) { - return _.find(envs, function (value, key) { - return key.indexOf(prefix + '_') >= 0 - || key.indexOf(prefix.toLowerCase() + '_') >= 0 - || key.indexOf('CUSTOMCONNSTR_' + prefix + '_') >= 0 - || key.indexOf('CUSTOMCONNSTR_' + prefix.toLowerCase() + '_') >= 0; - }) !== undefined; -} - -function findExtendedSettings (enables, envs) { +function findExtendedSettings (envs) { var extended = {}; function normalizeEnv (key) { return key.toUpperCase().replace('CUSTOMCONNSTR_', ''); } - enables.split(' ').forEach(function eachEnable(enable) { + _.forEach(env.settings.enable, function eachEnable(enable) { if (_.trim(enable)) { _.forIn(envs, function eachEnvPair (value, key) { var env = normalizeEnv(key); diff --git a/lib/api/index.js b/lib/api/index.js index 7329ab4faba..5e040eb4eca 100644 --- a/lib/api/index.js +++ b/lib/api/index.js @@ -1,9 +1,10 @@ 'use strict'; function create (env, ctx) { - var express = require('express'), - app = express( ) - ; + var _ = require('lodash') + , express = require('express') + , app = express( ) + ; var wares = require('../middleware/')(env); @@ -25,8 +26,7 @@ function create (env, ctx) { if (env.settings.enable) { app.extendedClientSettings = ctx.plugins && ctx.plugins.extendedClientSettings ? ctx.plugins.extendedClientSettings(env.extendedSettings) : {}; - env.settings.enable.toLowerCase().split(' ').forEach(function (value) { - var enable = value.trim(); + _.each(env.settings.enable, function (enable) { console.info('enabling feature:', enable); app.enable(enable); }); diff --git a/lib/settings.js b/lib/settings.js index 08e1dedf8b2..dbb6edf1490 100644 --- a/lib/settings.js +++ b/lib/settings.js @@ -12,7 +12,6 @@ function init ( ) { , customTitle: 'Nightscout' , theme: 'default' , alarmUrgentHigh: true - , alarmTypes: 'predict' , alarmHigh: true , alarmLow: true , alarmUrgentLow: true @@ -30,54 +29,108 @@ function init ( ) { } }; + settings.DEFAULT_FEATURES = ['delta', 'direction', 'upbat', 'errorcodes']; + var wasSet = []; function isSimple (value) { return typeof value !== 'function' && typeof value !== 'object'; } + function nameFromKey (key, nameType) { + return nameType === 'env' ? _.snakeCase(key).toUpperCase() : key; + } + function eachSettingAs (nameType) { - function topKeys (setter, keys) { + + function mapKeys (accessor, keys) { _.forIn(keys, function each (value, key) { if (isSimple(value)) { - var name = nameType === 'env' ? _.snakeCase(key).toUpperCase() : key; - var newValue = setter(name); + var newValue = accessor(nameFromKey(key, nameType)); if (newValue !== undefined) { wasSet.push(key); - keys[key] = setter(name); + keys[key] = newValue; } } }); } - return function allKeys (setter) { - topKeys(setter, settings); - - //for env vars thresholds are at the top level, they aren't set on the client (yet) - if (nameType === 'env') { - topKeys(setter, settings.thresholds); - } + return function allKeys (accessor) { + mapKeys(accessor, settings); + mapKeys(accessor, settings.thresholds); + enableAndDisableFeatures(accessor, nameType); }; } - function adjustShownPlugins ( ) { - //TODO: figure out something for some plugins to have them shown by default - if (settings.showPlugins !== '') { - settings.showPlugins += ' delta direction upbat'; - if (settings.showRawbg === 'always' || settings.showRawbg === 'noise') { - settings.showPlugins += ' rawbg'; + function enableAndDisableFeatures (accessor, nameType) { + + function getAndPrepare (key) { + var raw = accessor(nameFromKey(key, nameType)) || ''; + var cleaned = decodeURIComponent(raw).toLowerCase(); + return cleaned ? cleaned.split(' ') : []; + } + + function enableIf (feature, condition) { + if (condition) { + enable.push(feature); } } - } - function adjustAlarmTypes ( ) { - //if any threshold was set, and alarm types was not set default to simple - if (wasSet.indexOf('alarmTypes') === -1) { - var thresholdWasSet = _.findIndex(wasSet, function (name) { - return name.indexOf('bg') === 0; + function anyEnabled (features) { + return _.findIndex(features, function (feature) { + return enable.indexOf(feature) > -1; }) > -1; - settings.alarmTypes = thresholdWasSet ? 'simple' : 'predict'; } + + function prepareAlarmTypes ( ) { + var alarmTypes = _.filter(getAndPrepare('alarmTypes'), function onlyKnownTypes (type) { + return type === 'predict' || type === 'simple'; + }); + + if (alarmTypes.length === 0) { + var thresholdWasSet = _.findIndex(wasSet, function (name) { + return name.indexOf('bg') === 0; + }) > -1; + alarmTypes = thresholdWasSet ? ['simple'] : ['predict']; + } + + return alarmTypes; + } + + var enable = getAndPrepare('enable'); + var disable = getAndPrepare('disable'); + + settings.alarmTypes = prepareAlarmTypes(); + + //don't require pushover to be enabled to preserve backwards compatibility if there are extendedSettings for it + enableIf('pushover', accessor(nameFromKey('pushoverApiToken', nameType))); + + enableIf('treatmentnotify', anyEnabled(['careportal', 'pushover', 'maker'])); + + _.each(settings.DEFAULT_FEATURES, function eachDefault (feature) { + enableIf(feature, enable.indexOf(feature) < 0); + }); + + //TODO: maybe get rid of ALARM_TYPES and only use enable? + enableIf('simplealarms', settings.alarmTypes.indexOf('simple') > -1); + enableIf('ar2', settings.alarmTypes.indexOf('predict') > -1); + + if (disable.length > 0) { + console.info('disabling', disable); + } + + //all enabled feature, without any that have been disabled + settings.enable = _.difference(enable, disable); + + var thresholds = settings.thresholds; + + thresholds.bgHigh = Number(thresholds.bgHigh); + thresholds.bgTargetTop = Number(thresholds.bgTargetTop); + thresholds.bgTargetBottom = Number(thresholds.bgTargetBottom); + thresholds.bgLow = Number(thresholds.bgLow); + + verifyThresholds(); + adjustShownPlugins(); } function verifyThresholds() { @@ -105,18 +158,15 @@ function init ( ) { } } - settings.processRawSettings = function processRawSettings ( ) { - var thresholds = settings.thresholds; - - thresholds.bgHigh = Number(thresholds.bgHigh); - thresholds.bgTargetTop = Number(thresholds.bgTargetTop); - thresholds.bgTargetBottom = Number(thresholds.bgTargetBottom); - thresholds.bgLow = Number(thresholds.bgLow); - - verifyThresholds(); - adjustShownPlugins(); - adjustAlarmTypes(); - }; + function adjustShownPlugins ( ) { + //TODO: figure out something for some plugins to have them shown by default + if (settings.showPlugins !== '') { + settings.showPlugins += ' delta direction upbat'; + if (settings.showRawbg === 'always' || settings.showRawbg === 'noise') { + settings.showPlugins += ' rawbg'; + } + } + } function isEnabled (feature) { var enabled = false; diff --git a/static/index.html b/static/index.html index 629c774ad45..7b647eb47d3 100644 --- a/static/index.html +++ b/static/index.html @@ -50,7 +50,7 @@
    --- - - +
    From 0707f3f9322f10d653f2f79a6c52686097059909 Mon Sep 17 00:00:00 2001 From: Morten Skjelland Date: Sat, 29 Aug 2015 09:01:16 +0200 Subject: [PATCH 684/937] Translate to Norwegian --- lib/language.js | 264 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 262 insertions(+), 2 deletions(-) diff --git a/lib/language.js b/lib/language.js index 34e46c80e57..339ddbbd147 100644 --- a/lib/language.js +++ b/lib/language.js @@ -20,6 +20,7 @@ function init() { , { code: 'ro', language: 'Română' } , { code: 'es', language: 'Español' } , { code: 'sv', language: 'Svenska' } + , { code: 'nb', language: 'Norsk (Bokmål)' } ]; var translations = { @@ -35,6 +36,7 @@ function init() { ,hr: 'Slušanje na portu' ,it: 'Porta in ascolto' ,dk: 'Lytter på port' + ,nb: 'Lytter på port' } // Client ,'Mo' : { @@ -49,6 +51,7 @@ function init() { ,hr: 'Pon' ,it: 'Lun' ,dk: 'Man' + ,nb: 'Man' } ,'Tu' : { cs: 'Út' @@ -62,6 +65,7 @@ function init() { ,hr: 'Ut' ,it: 'Mar' ,dk: 'Tir' + ,nb: 'Tir' }, ',We' : { cs: 'St' @@ -75,6 +79,7 @@ function init() { ,hr: 'Sri' ,it: 'Mer' ,dk: 'Ons' + ,nb: 'Ons' } ,'Th' : { cs: 'Čt' @@ -88,6 +93,7 @@ function init() { ,hr: 'Čet' ,it: 'Gio' ,dk: 'Tor' + ,no: 'Tor' } ,'Fr' : { cs: 'Pá' @@ -101,6 +107,7 @@ function init() { ,hr: 'Pet' ,it: 'Ven' ,dk: 'Fre' + ,nb: 'Fre' } ,'Sa' : { cs: 'So' @@ -114,6 +121,7 @@ function init() { ,hr: 'Sub' ,it: 'Sab' ,dk: 'Lør' + ,nb: 'Lør' } ,'Su' : { cs: 'Ne' @@ -127,6 +135,7 @@ function init() { ,hr: 'Ned' ,it: 'Dom' ,dk: 'Søn' + ,nb: 'Søn' } ,'Monday' : { cs: 'Pondělí' @@ -140,6 +149,7 @@ function init() { ,hr: 'Ponedjeljak' ,it: 'Lunedì' ,dk: 'Mandag' + ,nb: 'Mandag' } ,'Tuesday' : { cs: 'Úterý' @@ -153,6 +163,7 @@ function init() { ,sv: 'Tisdag' ,it: 'Martedì' ,dk: 'Tirsdag' + ,nb: 'Tirsdag' } ,'Wednesday' : { cs: 'Středa' @@ -166,6 +177,7 @@ function init() { ,hr: 'Srijeda' ,it: 'Mercoledì' ,dk: 'Onsdag' + ,nb: 'Onsdag' } ,'Thursday' : { cs: 'Čtvrtek' @@ -179,6 +191,7 @@ function init() { ,hr: 'Četvrtak' ,it: 'Giovedì' ,dk: 'Torsdag' + ,nb: 'Torsdag' } ,'Friday' : { cs: 'Pátek' @@ -192,6 +205,7 @@ function init() { ,hr: 'Petak' ,it: 'Venerdì' ,dk: 'Fredag' + ,nb: 'Fredag' } ,'Saturday' : { cs: 'Sobota' @@ -205,6 +219,7 @@ function init() { ,sv: 'Lördag' ,it: 'Sabato' ,dk: 'Lørdag' + ,nb: 'Lørdag' } ,'Sunday' : { cs: 'Neděle' @@ -218,6 +233,7 @@ function init() { ,sv: 'Söndag' ,it: 'Domenica' ,dk: 'Søndag' + ,nb: 'Søndag' } ,'Category' : { cs: 'Kategorie' @@ -231,6 +247,7 @@ function init() { ,hr: 'Kategorija' ,it:'Categoria' ,dk: 'Kategori' + ,nb: 'Kategori' } ,'Subcategory' : { cs: 'Podkategorie' @@ -244,6 +261,7 @@ function init() { ,hr: 'Podkategorija' ,it: 'Sottocategoria' ,dk: 'Underkategori' + ,nb: 'Underkategori' } ,'Name' : { cs: 'Jméno' @@ -257,6 +275,7 @@ function init() { ,hr: 'Ime' ,it: 'Nome' ,dk: 'Navn' + ,nb: 'Navn' } ,'Today' : { cs: 'Dnes' @@ -270,6 +289,7 @@ function init() { ,sv: 'Idag' ,it: 'Oggi' ,dk: 'Idag' + ,nb: 'Idag' } ,'Last 2 days' : { cs: 'Poslední 2 dny' @@ -283,6 +303,7 @@ function init() { ,sv: 'Senaste 2 dagarna' ,it: 'Ultimi 2 giorni' ,dk: 'Sidste 2 dage' + ,nb: 'Siste 2 dager' } ,'Last 3 days' : { cs: 'Poslední 3 dny' @@ -296,6 +317,7 @@ function init() { ,hr: 'Posljednja 3 dana' ,it: 'Ultimi 3 giorni' ,dk: 'Sidste 3 dage' + ,nb: 'Siste 3 dager' } ,'Last week' : { cs: 'Poslední týden' @@ -309,6 +331,7 @@ function init() { ,sv: 'Senaste veckan' ,it: 'Settimana scorsa' ,dk: 'Sidste uge' + ,nb: 'Siste uke' } ,'Last 2 weeks' : { cs: 'Poslední 2 týdny' @@ -322,6 +345,7 @@ function init() { ,sv: 'Senaste 2 veckorna' ,it: 'Ultime 2 settimane' ,dk: 'Sidste 2 uger' + ,nb: 'Siste 2 uker' } ,'Last month' : { cs: 'Poslední měsíc' @@ -335,6 +359,7 @@ function init() { ,sv: 'Senaste månaden' ,it: 'Mese scorso' ,dk: 'Sidste måned' + ,nb: 'Siste måned' } ,'Last 3 months' : { cs: 'Poslední 3 měsíce' @@ -348,6 +373,7 @@ function init() { ,sv: 'Senaste 3 månaderna' ,it: 'Ultimi 3 mesi' ,dk: 'Sidste 3 måneder' + ,nb: 'Siste 3 måneder' } ,'From' : { cs: 'Od' @@ -361,6 +387,7 @@ function init() { ,hr: 'Od' ,it: 'Da' ,dk: 'Fra' + ,nb: 'Fra' } ,'To' : { cs: 'Do' @@ -374,6 +401,7 @@ function init() { ,sv: 'Till' ,it: 'A' ,dk: 'Til' + ,nb: 'Til' } ,'Notes' : { cs: 'Poznámky' @@ -387,6 +415,7 @@ function init() { ,hr: 'Bilješke' ,it: 'Note' ,dk: 'Noter' + ,nb: 'Notater' } ,'Food' : { cs: 'Jídlo' @@ -400,6 +429,7 @@ function init() { ,hr: 'Hrana' ,it: 'Cibo' ,dk: 'Mad' + ,nb: 'Mat' } ,'Insulin' : { cs: 'Inzulín' @@ -413,6 +443,7 @@ function init() { ,sv: 'Insulin' ,it: 'Insulina' ,dk: 'Insulin' + ,nb: 'Insulin' } ,'Carbs' : { cs: 'Sacharidy' @@ -426,6 +457,7 @@ function init() { ,sv: 'Kolhydrater' ,it: 'Carboidrati' ,dk: 'Kulhydrater' + ,nb: 'Karbohydrater' } ,'Notes contain' : { cs: 'Poznámky obsahují' @@ -439,6 +471,7 @@ function init() { ,sv: 'Notering innehåller' ,it: 'Contiene note' ,dk: 'Noter indeholder' + ,nb: 'Notater inneholder' } ,'Event type contains' : { cs: 'Typ události obsahuje' @@ -452,6 +485,7 @@ function init() { ,sv: 'Händelsen innehåller' ,it: 'Contiene evento' ,dk: 'Hændelsen indeholder' + ,nb: 'Hendelsen inneholder' } ,'Target bg range bottom' : { cs: 'Cílová glykémie spodní' @@ -465,6 +499,7 @@ function init() { ,sv: 'Gräns för nedre blodsockervärde' ,it: 'Limite inferiore della glicemia' ,dk: 'Nedre grænse for blodsukkerværdier' + ,nb: 'Nedre grense for blodsukkerverdier' } ,'top' : { cs: 'horní' @@ -478,6 +513,7 @@ function init() { ,sv: 'Toppen' ,it: 'Superiore' ,dk: 'Top' + ,nb: 'Topp' } ,'Show' : { cs: 'Zobraz' @@ -491,6 +527,7 @@ function init() { ,hr: 'Prikaži' ,it: 'Mostra' ,dk: 'Vis' + ,nb: 'Vis' } ,'Display' : { cs: 'Zobraz' @@ -504,6 +541,7 @@ function init() { ,sv: 'Visa' ,it: 'Schermo' ,dk: 'Vis' + ,nb: 'Vis' } ,'Loading' : { cs: 'Nahrávám' @@ -517,6 +555,7 @@ function init() { ,sv: 'Laddar' ,it: 'Sto Caricando' ,dk: 'Indlæser' + ,nb: 'Laster' } ,'Loading profile' : { cs: 'Nahrávám profil' @@ -530,6 +569,7 @@ function init() { ,hr: 'Učitavanje profila' ,it: 'Sto Caricando il profilo' ,dk: 'Indlæser profil' + ,nb: 'Leser profil' } ,'Loading status' : { cs: 'Nahrávám status' @@ -543,6 +583,7 @@ function init() { ,hr: 'Učitavanje statusa' ,it: 'Stato di caricamento' ,dk: 'Indlæsnings status' + ,nb: 'Leser status' } ,'Loading food database' : { cs: 'Nahrávám databázi jídel' @@ -556,6 +597,7 @@ function init() { ,hr: 'Učitavanje baze podataka o hrani' ,it: 'Carico dati alimenti' ,dk: 'Indlæser mad database' + ,nb: 'Leser matdatabase' } ,'not displayed' : { cs: 'není zobrazeno' @@ -569,6 +611,7 @@ function init() { ,sv: 'Visas ej' ,it: 'Non visualizzato' ,dk: 'Vises ikke' + ,nb: 'Vises ikke' } ,'Loading CGM data of' : { cs: 'Nahrávám CGM data' @@ -582,6 +625,7 @@ function init() { ,hr: 'Učitavanja podataka CGM-a' ,it: 'Carico dati CGM' ,dk: 'Indlæser CGM-data for' + ,nb: 'Leser CGM-data for' } ,'Loading treatments data of' : { cs: 'Nahrávám data ošetření' @@ -595,6 +639,7 @@ function init() { ,hr: 'Učitavanje podataka o tretmanu' ,it: 'Carico trattamenti dei dati di' ,dk: 'Indlæser data for' + ,nb: 'Leser behandlingsdata for' } ,'Processing data of' : { cs: 'Zpracovávám data' @@ -608,6 +653,7 @@ function init() { ,hr: 'Obrada podataka' ,it: 'Elaborazione dei dati di' ,dk: 'Behandler data for' + ,nb: 'Behandler data for' } ,'Portion' : { cs: 'Porce' @@ -621,6 +667,7 @@ function init() { ,sv: 'Portion' ,it: 'Porzione' ,dk: 'Portion' + ,nb: 'Porsjon' } ,'Size' : { cs: 'Rozměr' @@ -634,6 +681,7 @@ function init() { ,hr: 'Veličina' ,it: 'Formato' ,dk: 'Størrelse' + ,nb: 'Størrelse' } ,'(none)' : { cs: '(Prázdný)' @@ -647,6 +695,7 @@ function init() { ,hr: '(Prazno)' ,it: '(Nessuno)' ,dk: '(ingen)' + ,nb: '(ingen)' } ,'Result is empty' : { cs: 'Prázdný výsledek' @@ -660,6 +709,7 @@ function init() { ,hr: 'Prazan rezultat' ,it: 'Risultato vuoto' ,dk: 'Tomt resultat' + ,nb: 'Tomt resultat' } // ported reporting ,'Day to day' : { @@ -674,6 +724,7 @@ function init() { ,hr: 'Svakodnevno' ,it: 'Giorno per giorno' ,dk: 'Dag til dag' + ,nb: 'Dag til dag' } ,'Daily Stats' : { cs: 'Denní statistiky' @@ -687,6 +738,7 @@ function init() { ,hr: 'Dnevna statistika' ,it: 'Statistiche giornaliere' ,dk: 'Daglig statistik' + ,nb: 'Daglig statistikk' } ,'Percentile Chart' : { cs: 'Percentil' @@ -700,6 +752,7 @@ function init() { ,sv: 'Procentgraf' ,it: 'Grafico percentile' ,dk: 'Procentgraf' + ,nb: 'Prosentgraf' } ,'Distribution' : { cs: 'Rozložení' @@ -713,6 +766,7 @@ function init() { ,sv: 'Distribution' ,it: 'Distribuzione' ,dk: 'Distribution' + ,nb: 'Distribusjon' } ,'Hourly stats' : { cs: 'Statistika po hodinách' @@ -726,6 +780,7 @@ function init() { ,hr: 'Statistika po satu' ,it: 'Statistiche per ore' ,dk: 'Timestatistik' + ,nb: 'Timestatistikk' } ,'Weekly success' : { cs: 'Statistika po týdnech' @@ -739,6 +794,7 @@ function init() { ,sv: 'Veckoresultat' ,it: 'Statistiche settimanali' ,dk: 'Uge resultat' + ,nb: 'Ukeresultat' } ,'No data available' : { cs: 'Žádná dostupná data' @@ -752,6 +808,7 @@ function init() { ,sv: 'Data saknas' ,it: 'Dati non disponibili' ,dk: 'Mangler data' + ,nb: 'Mangler data' } ,'Low' : { cs: 'Nízká' @@ -765,6 +822,7 @@ function init() { ,hr: 'Nizak' ,it: 'Basso' ,dk: 'Lav' + ,nb: 'Lav' } ,'In Range' : { cs: 'V rozsahu' @@ -778,6 +836,7 @@ function init() { ,hr: 'U rasponu' ,it: 'In intervallo' ,dk: 'Indenfor intervallet' + ,nb: 'Innenfor intervallet' } ,'Period' : { cs: 'Období' @@ -791,6 +850,7 @@ function init() { ,hr: 'Period' ,it: 'Periodo' ,dk: 'Period' + ,nb: 'Periode' } ,'High' : { cs: 'Vysoká' @@ -804,6 +864,7 @@ function init() { ,hr: 'Visok' ,it: 'Alto' ,dk: 'Høj' + ,nb: 'Høy' } ,'Average' : { cs: 'Průměrná' @@ -817,6 +878,7 @@ function init() { ,hr: 'Prosjek' ,it: 'Media' ,dk: 'Gennemsnit' + ,nb: 'Gjennomsnitt' } ,'Low Quartile' : { cs: 'Nízký kvartil' @@ -830,6 +892,7 @@ function init() { ,sv: 'Nedre kvadranten' ,it: 'Quartile basso' ,dk: 'Nedre kvartil' + ,nb: 'Nedre kvartil' } ,'Upper Quartile' : { cs: 'Vysoký kvartil' @@ -843,6 +906,7 @@ function init() { ,sv: 'Övre kvadranten' ,it: 'Quartile alto' ,dk: 'Øvre kvartil' + ,nb: 'Øvre kvartil' } ,'Quartile' : { cs: 'Kvartil' @@ -856,6 +920,7 @@ function init() { ,sv: 'Kvadrant' ,it: 'Quartile' ,dk: 'Kvartil' + ,nb: 'Kvartil' } ,'Date' : { cs: 'Datum' @@ -869,6 +934,7 @@ function init() { ,hr: 'Datum' ,it: 'Data' ,dk: 'Dato' + ,nb: 'Dato' } ,'Normal' : { cs: 'Normální' @@ -882,6 +948,7 @@ function init() { ,hr: 'Normalno' ,it: 'Normale' ,dk: 'Normal' + ,nb: 'Normal' } ,'Median' : { cs: 'Medián' @@ -895,6 +962,7 @@ function init() { ,sv: 'Median' ,it: 'Mediana' ,dk: 'Median' + ,nb: 'Median' } ,'Readings' : { cs: 'Záznamů' @@ -908,6 +976,7 @@ function init() { ,hr: 'Vrijednosti' ,it: 'Valori' ,dk: 'Aflæsning' + ,nb: 'Avlesning' } ,'StDev' : { cs: 'St. odchylka' @@ -921,6 +990,7 @@ function init() { ,hr: 'Standardna devijacija' ,it: 'Deviazione standard' ,dk: 'Standard afvigelse' + ,nb: 'Standardavvik' } ,'Daily stats report' : { cs: 'Denní statistiky' @@ -934,6 +1004,7 @@ function init() { ,sv: 'Dygnsstatistik' ,it: 'Statistiche giornaliere' ,dk: 'Daglig statistik rapport' + ,nb: 'Daglig statistikkrapport' } ,'Glucose Percentile report' : { cs: 'Tabulka percentil glykémií' @@ -947,6 +1018,7 @@ function init() { ,hr: 'Izvješće o postotku GUK-a' ,it: 'Percentuale Glicemie' ,dk: 'Glukoserapport i procent' + ,nb: 'Glukoserapport i prosent' } ,'Glucose distribution' : { cs: 'Rozložení glykémií' @@ -960,6 +1032,7 @@ function init() { ,sv: 'Glukosdistribution' ,it: 'Distribuzione glicemie' ,dk: 'Glukosefordeling' + ,nb: 'Glukosefordeling' } ,'days total' : { cs: 'dní celkem' @@ -973,6 +1046,7 @@ function init() { ,hr: 'ukupno dana' ,it: 'Giorni totali' ,dk: 'antal dage' + ,nb: 'antall dager' } ,'Overall' : { cs: 'Celkem' @@ -986,6 +1060,7 @@ function init() { ,hr: 'Ukupno' ,it: 'Generale' ,dk: 'Overall' + ,nb: 'Generelt' } ,'Range' : { cs: 'Rozsah' @@ -999,6 +1074,7 @@ function init() { ,hr: 'Raspon' ,it: 'Intervallo' ,dk: 'Interval' + ,nb: 'Intervall' } ,'% of Readings' : { cs: '% záznamů' @@ -1012,6 +1088,7 @@ function init() { ,hr: '% očitanja' ,it: '% dei valori' ,dk: '% af aflæsningerne' + ,nb: '% af avlesningene' } ,'# of Readings' : { cs: 'počet záznamů' @@ -1025,6 +1102,7 @@ function init() { ,hr: 'broj očitanja' ,it: '# di valori' ,dk: 'Antal af aflæsninger' + ,nb: 'Antall avlesninger' } ,'Mean' : { cs: 'Střední hodnota' @@ -1038,6 +1116,7 @@ function init() { ,hr: 'Prosjek' ,it: 'Medio' ,dk: 'Gennemsnit' + ,nb: 'Gjennomsnitt' } ,'Standard Deviation' : { cs: 'Standardní odchylka' @@ -1051,6 +1130,7 @@ function init() { ,sv: 'Standardavvikelse' ,it: 'Deviazione Standard' ,dk: 'Standardafgivelse' + ,nb: 'Standardavvik' } ,'Max' : { cs: 'Max' @@ -1064,6 +1144,7 @@ function init() { ,hr: 'Max' ,it: 'Max' ,dk: 'Max' + ,nb: 'Max' } ,'Min' : { cs: 'Min' @@ -1077,6 +1158,7 @@ function init() { ,hr: 'Min' ,it: 'Min' ,dk: 'Min' + ,nb: 'Min' } ,'A1c estimation*' : { cs: 'Předpokládané HBA1c*' @@ -1090,6 +1172,7 @@ function init() { ,sv: 'Beräknat A1c-värde ' ,it: 'stima A1c' ,dk: 'Beregnet A1c-værdi ' + ,nb: 'Beregnet HbA1c' } ,'Weekly Success' : { cs: 'Týdenní úspěšnost' @@ -1103,6 +1186,7 @@ function init() { ,sv: 'Veckoresultat' ,it: 'Risultati settimanali' ,dk: 'Uge resultat' + ,nb: 'Ukeresultat' } ,'There is not sufficient data to run this report. Select more days.' : { cs: 'Není dostatek dat. Vyberte delší časové období.' @@ -1116,6 +1200,7 @@ function init() { ,sv: 'Data saknas för att köra rapport. Välj fler dagar.' ,it: 'Non ci sono dati sufficienti per eseguire questo rapporto. Selezionare più giorni.' ,dk: 'Der er utilstrækkeligt data til at generere rapporten. Vælg flere dage.' + ,nb: 'Der er ikke nok data til å lage rapporten. Velg flere dager.' } // food editor ,'Using stored API secret hash' : { @@ -1130,6 +1215,7 @@ function init() { ,sv: 'Använd hemlig API-nyckel' ,it: 'Stai utilizzando API hash segreta' ,dk: 'Anvender gemt API-nøgle' + ,nb: 'Bruker lagret API nøkkel' } ,'No API secret hash stored yet. You need to enter API secret.' : { cs: 'Není uložený žádný hash API hesla. Musíte zadat API heslo.' @@ -1143,6 +1229,7 @@ function init() { ,sv: 'Hemlig api-nyckel saknas. Du måste ange API hemlighet' ,it: 'No API hash segreto ancora memorizzato. È necessario inserire API segreto.' ,dk: 'Mangler API-nøgle. Du skal indtaste API hemmelighed' + ,nb: 'Mangler API nøkkel. Du må skrive inn API hemmelighet.' } ,'Database loaded' : { cs: 'Databáze načtena' @@ -1156,6 +1243,7 @@ function init() { ,sv: 'Databas laddad' ,it: 'Database caricato' ,dk: 'Database indlæst' + ,nb: 'Database lest' } ,'Error: Database failed to load' : { cs: 'Chyba při načítání databáze' @@ -1169,6 +1257,7 @@ function init() { ,sv: 'Error: Databas kan ej laddas' ,it: 'Errore: database non è stato caricato' ,dk: 'Fejl: Database kan ikke indlæses' + ,nb: 'Feil: Database kan ikke leses' } ,'Create new record' : { cs: 'Vytvořit nový záznam' @@ -1182,6 +1271,7 @@ function init() { ,sv: 'Skapa ny post' ,it: 'Crea nuovo registro' ,dk: 'Danner ny post' + ,nb: 'Lager ny registrering' } ,'Save record' : { cs: 'Uložit záznam' @@ -1195,6 +1285,7 @@ function init() { ,sv: 'Spara post' ,it: 'Salva Registro' ,dk: 'Gemmer post' + ,nb: 'Lagrer registrering' } ,'Portions' : { cs: 'Porcí' @@ -1208,6 +1299,7 @@ function init() { ,sv: 'Portion' ,it: 'Porzioni' ,dk: 'Portioner' + ,nb: 'Porsjoner' } ,'Unit' : { cs: 'Jedn' @@ -1221,6 +1313,7 @@ function init() { ,sv: 'Enhet' ,it: 'Unità' ,dk: 'Enheder' + ,nb: 'Enhet' } ,'GI' : { cs: 'GI' @@ -1234,6 +1327,7 @@ function init() { ,hr: 'GI' ,it: 'GI' ,dk: 'GI' + ,nb: 'GI' } ,'Edit record' : { cs: 'Upravit záznam' @@ -1247,6 +1341,7 @@ function init() { ,sv: 'Editera post' ,it: 'Modifica registro' ,dk: 'Editere post' + ,nb: 'Editere registrering' } ,'Delete record' : { cs: 'Smazat záznam' @@ -1260,6 +1355,7 @@ function init() { ,sv: 'Radera post' ,it: 'Cancella registro' ,dk: 'Slet post' + ,nb: 'Slette registrering' } ,'Move to the top' : { cs: 'Přesuň na začátek' @@ -1273,6 +1369,7 @@ function init() { ,hr: 'Premjesti na vrh' ,it: 'Spostare verso l\'alto' ,dk: 'Gå til toppen' + ,nb: 'Gå til toppen' } ,'Hidden' : { cs: 'Skrytý' @@ -1286,6 +1383,7 @@ function init() { ,hr: 'Skriveno' ,it: 'Nascosto' ,dk: 'Skjult' + ,nb: 'Skjult' } ,'Hide after use' : { cs: 'Skryj po použití' @@ -1299,6 +1397,7 @@ function init() { ,sv: 'Dölj efter användning' ,it: 'Nascondi dopo l\'uso' ,dk: 'Skjul efter brug' + ,nb: 'Skjul etter bruk' } ,'Your API secret must be at least 12 characters long' : { cs: 'Vaše API heslo musí mít alespoň 12 znaků' @@ -1312,6 +1411,7 @@ function init() { ,sv: 'Hemlig API-nyckel måsta innehålla 12 tecken' ,it: 'il vostro API secreto deve essere lungo almeno 12 caratteri' ,dk: 'Din API nøgle skal være mindst 12 tegn lang' + ,nb: 'Din API nøkkel må være minst 12 tegn lang' } ,'Bad API secret' : { cs: 'Chybné API heslo' @@ -1325,6 +1425,7 @@ function init() { ,sv: 'Felaktig API-nyckel' ,it: 'API secreto incorretto' ,dk: 'Forkert API-nøgle' + ,nb: 'Ugyldig API nøkkel' } ,'API secret hash stored' : { cs: 'Hash API hesla uložen' @@ -1338,6 +1439,7 @@ function init() { ,sv: 'Lagrad hemlig API-hash' ,it: 'Hash API secreto memorizzato' ,dk: 'Hemmelig API-hash gemt' + ,nb: 'API nøkkel lagret' } ,'Status' : { cs: 'Status' @@ -1351,6 +1453,7 @@ function init() { ,hr: 'Status' ,it: 'Stato' ,dk: 'Status' + ,nb: 'Status' } ,'Not loaded' : { cs: 'Nenačtený' @@ -1364,6 +1467,7 @@ function init() { ,sv: 'Ej laddad' ,it: 'Non caricato' ,dk: 'Ikke indlæst' + ,nb: 'Ikke lest' } ,'Food editor' : { cs: 'Editor jídel' @@ -1377,6 +1481,7 @@ function init() { ,sv: 'Födoämneseditor' ,it: 'Modifica alimenti' ,dk: 'Mad editor' + ,nb: 'Mat editor' } ,'Your database' : { cs: 'Vaše databáze' @@ -1390,6 +1495,7 @@ function init() { ,hr: 'Vaša baza podataka' ,it: 'Vostro database' ,dk: 'Din database' + ,nb: 'Din database' } ,'Filter' : { cs: 'Filtr' @@ -1403,6 +1509,7 @@ function init() { ,hr: 'Filter' ,it: 'Filtro' ,dk: 'Filter' + ,nb: 'Filter' } ,'Save' : { cs: 'Ulož' @@ -1416,6 +1523,7 @@ function init() { ,sv: 'Spara' ,it: 'Salva' ,dk: 'Gem' + ,nb: 'Lagre' } ,'Clear' : { cs: 'Vymaž' @@ -1429,6 +1537,7 @@ function init() { ,sv: 'Rensa' ,it: 'Pulisci' ,dk: 'Rense' + ,nb: 'Tøm' } ,'Record' : { cs: 'Záznam' @@ -1442,6 +1551,7 @@ function init() { ,hr: 'Zapis' ,it: 'Registro' ,dk: 'Post' + ,nb: 'Registrering' } ,'Quick picks' : { cs: 'Rychlý výběr' @@ -1455,6 +1565,7 @@ function init() { ,sv: 'Snabbval' ,it: 'Scelta rapida' ,dk: 'Hurtig valg' + ,nb: 'Hurtigvalg' } ,'Show hidden' : { cs: 'Zobraz skryté' @@ -1468,6 +1579,7 @@ function init() { ,sv: 'Visa dolda' ,it: 'Mostra nascosto' ,dk: 'Vis skjulte' + ,nb: 'Vis skjulte' } ,'Your API secret' : { cs: 'Vaše API heslo' @@ -1481,6 +1593,7 @@ function init() { ,hr: 'Vaš tajni API' ,it: 'Il tuo API secreto' ,dk: 'Din API-nøgle' + ,nb: 'Din API nøkkel' } ,'Store hash on this computer (Use only on private computers)' : { cs: 'Ulož hash na tomto počítači (používejte pouze na soukromých počítačích)' @@ -1494,6 +1607,7 @@ function init() { ,sv: 'Lagra hashvärde på denna dator (använd endast på privat dator)' ,it: 'Conservare hash su questo computer (utilizzare solo su computer privati)' ,dk: 'Gemme hash på denne computer (brug kun på privat computer)' + ,nb: 'Lagre hash på denne pc (bruk kun på privat pc)' } ,'Treatments' : { cs: 'Ošetření' @@ -1507,6 +1621,7 @@ function init() { ,hr: 'Tretmani' ,it: 'Somministrazione' ,dk: 'Behandling' + ,nb: 'Behandlinger' } ,'Time' : { cs: 'Čas' @@ -1520,6 +1635,7 @@ function init() { ,hr: 'Vrijeme' ,it: 'Tempo' ,dk: 'Tid' + ,nb: 'Tid' } ,'Event Type' : { cs: 'Typ události' @@ -1528,11 +1644,12 @@ function init() { ,fr: 'Type d\'événement' ,pt: 'Tipo de evento' ,sv: 'Händelsetyp' - ,ro: 'Eveniment' + ,ro: 'Tip eveniment' ,bg: 'Вид събитие' ,hr: 'Vrsta događaja' ,it: 'Tipo di evento' ,dk: 'Hændelsestype' + ,nb: 'Type' } ,'Blood Glucose' : { cs: 'Glykémie' @@ -1546,6 +1663,7 @@ function init() { ,hr: 'GUK' ,it: 'Glicemia' ,dk: 'Glukoseværdi' + ,nb: 'Blodsukker' } ,'Entered By' : { cs: 'Zadal' @@ -1559,6 +1677,7 @@ function init() { ,hr: 'Unos izvršio' ,it: 'inserito da' ,dk: 'Indtastet af' + ,nb: 'Lagt inn av' } ,'Delete this treatment?' : { cs: 'Vymazat toto ošetření?' @@ -1572,6 +1691,7 @@ function init() { ,sv: 'Ta bort händelse?' ,it: 'Eliminare questa somministrazione?' ,dk: 'Slet denne hændelse?' + ,nb: 'Slett denne hendelsen?' } ,'Carbs Given' : { cs: 'Sacharidů' @@ -1585,6 +1705,7 @@ function init() { ,sv: 'Antal kolhydrater' ,it: 'Carboidrati' ,dk: 'Antal kulhydrater' + ,nb: 'Karbo' } ,'Inzulin Given' : { cs: 'Inzulínu' @@ -1598,6 +1719,7 @@ function init() { ,sv: 'Insulin' ,it: 'Insulina' ,dk: 'Insulin' + ,nb: 'Insulin' } ,'Event Time' : { cs: 'Čas události' @@ -1611,6 +1733,7 @@ function init() { ,hr: 'Vrijeme događaja' ,it: 'Ora Evento' ,dk: 'Tidspunkt for hændelsen' + ,nb: 'Tidspunkt for hendelsen' } ,'Please verify that the data entered is correct' : { cs: 'Prosím zkontrolujte, zda jsou údaje zadány správně' @@ -1624,6 +1747,7 @@ function init() { ,sv: 'Vänligen verifiera att inlagd data är korrekt' ,it: 'Si prega di verificare che i dati inseriti sono corretti' ,dk: 'Venligst verificer at indtastet data er korrekt' + ,nb: 'Vennligst verifiser at inntastet data er korrekt' } ,'BG' : { cs: 'Glykémie' @@ -1637,6 +1761,7 @@ function init() { ,hr: 'GUK' ,it: 'Glicemia' ,dk: 'BS' + ,nb: 'BS' } ,'Use BG correction in calculation' : { cs: 'Použij korekci na glykémii' @@ -1650,6 +1775,7 @@ function init() { ,sv: 'Använd BS-korrektion för beräkning' ,it: 'Utilizzare la correzione Glicemia nei calcoli' ,dk: 'Anvend BS-korrektion før beregning' + ,nb: 'Bruk blodsukkerkorrigering i beregning' } ,'BG from CGM (autoupdated)' : { cs: 'Glykémie z CGM (automaticky aktualizovaná)' @@ -1663,6 +1789,7 @@ function init() { ,hr: 'GUK sa CGM-a (ažuriran automatski)' ,it: 'Glicemie da CGM (aggiornamento automatico)' ,dk: 'BS fra CGM (automatisk)' + ,nb: 'BS fra CGM (automatisk)' } ,'BG from meter' : { cs: 'Glykémie z glukoměru' @@ -1676,6 +1803,7 @@ function init() { ,hr: 'GUK s glukometra' ,it: 'Glicemie da glucometro' ,dk: 'BS fra blodsukkerapperat' + ,nb: 'BS fra blodsukkerapparat' } ,'Manual BG' : { cs: 'Ručně zadaná glykémie' @@ -1689,6 +1817,7 @@ function init() { ,sv: 'Manuellt BS' ,it: 'Inserisci Glicemia' ,dk: 'Manuelt BS' + ,nb: 'Manuelt BS' } ,'Quickpick' : { cs: 'Rychlý výběr' @@ -1702,6 +1831,7 @@ function init() { ,sv: 'Snabbval' ,it: 'Scelta rapida' ,dk: 'Hurtig snack' + ,nb: 'Hurtigvalg' } ,'or' : { cs: 'nebo' @@ -1715,6 +1845,7 @@ function init() { ,hr: 'ili' ,it: 'o' ,dk: 'eller' + ,nb: 'eller' } ,'Add from database' : { cs: 'Přidat z databáze' @@ -1728,6 +1859,7 @@ function init() { ,sv: 'Lägg till från databas' ,it: 'Aggiungi dal database' ,dk: 'Tilføj fra database' + ,nb: 'Legg til fra database' } ,'Use carbs correction in calculation' : { cs: 'Použij korekci na sacharidy' @@ -1741,6 +1873,7 @@ function init() { ,sv: 'Använd kolhydratkorrektion för beräkning' ,it: 'Utilizzare la correzione con carboidrati nel calcolo' ,dk: 'Benyt kulhydratkorrektion i beregning' + ,nb: 'Bruk karbohydratkorrigering i beregning' } ,'Use COB correction in calculation' : { cs: 'Použij korekci na COB' @@ -1754,6 +1887,7 @@ function init() { ,sv: 'Använd aktiva kolhydrater för beräkning' ,it: 'Utilizzare la correzione COB nel calcolo' ,dk: 'Benyt aktive kulhydrater i beregning' + ,nb: 'Benytt aktive karbohydrater i beregning' } ,'Use IOB in calculation' : { cs: 'Použij IOB ve výpočtu' @@ -1767,6 +1901,7 @@ function init() { ,sv: 'Använd aktivt insulin för beräkning' ,it: 'Utilizzare la correzione IOB nel calcolo' ,dk: 'Benyt aktivt insulin i beregningen' + ,nb: 'Bruk aktivt insulin i beregningen' } ,'Other correction' : { cs: 'Jiná korekce' @@ -1780,6 +1915,7 @@ function init() { ,sv: 'Övrig korrektion' ,it: 'Altre correzioni' ,dk: 'Øvrig korrektion' + ,nb: 'Annen korrigering' } ,'Rounding' : { cs: 'Zaokrouhlení' @@ -1793,6 +1929,7 @@ function init() { ,hr: 'Zaokruživanje' ,it: 'Arrotondamento' ,dk: 'Afrunding' + ,nb: 'Avrunding' } ,'Enter insulin correction in treatment' : { cs: 'Zahrň inzulín do záznamu ošetření' @@ -1806,6 +1943,7 @@ function init() { ,sv: 'Ange insulinkorrektion för händelse' ,it: 'Inserisci correzione insulina nella somministrazione' ,dk: 'Indtast insulionkorrektion' + ,nb: 'Task inn insulinkorrigering' } ,'Insulin needed' : { cs: 'Potřebný inzulín' @@ -1819,6 +1957,7 @@ function init() { ,sv: 'Beräknad insulinmängd' ,it: 'Insulina necessaria' ,dk: 'Insulin påkrævet' + ,nb: 'Insulin nødvendig' } ,'Carbs needed' : { cs: 'Potřebné sach' @@ -1832,6 +1971,7 @@ function init() { ,sv: 'Beräknad kolhydratmängd' ,it: 'Carboidrati necessari' ,dk: 'Kulhydrater påkrævet' + ,nb: 'Karbohydrater nødvendig' } ,'Carbs needed if Insulin total is negative value' : { cs: 'Chybějící sacharidy v případě, že výsledek je záporný' @@ -1845,6 +1985,7 @@ function init() { ,sv: 'Nödvändig kolhydratmängd för angiven insulinmängd' ,it: 'Carboidrati necessari se l\'insulina totale è un valore negativo' ,dk: 'Kulhydrater er nødvendige når total insulin mængde er negativ' + ,nb: 'Karbohydrater er nødvendige når total insulinmengde er negativ' } ,'Basal rate' : { cs: 'Bazál' @@ -1858,6 +1999,7 @@ function init() { ,sv: 'Basaldos' ,it: 'Velocità basale' ,dk: 'Basal rate' + ,nb: 'Basal' } ,'60 minutes earlier' : { cs: '60 min předem' @@ -1871,6 +2013,7 @@ function init() { ,hr: 'Prije 60 minuta' ,it: '60 minuti prima' ,dk: '60 min tidligere' + ,nb: '60 min tidligere' } ,'45 minutes earlier' : { cs: '45 min předem' @@ -1884,6 +2027,7 @@ function init() { ,hr: 'Prije 45 minuta' ,it: '45 minuti prima' ,dk: '45 min tidligere' + ,nb: '45 min tidligere' } ,'30 minutes earlier' : { cs: '30 min předem' @@ -1897,6 +2041,7 @@ function init() { ,hr: 'Prije 30 minuta' ,it: '30 minuti prima' ,dk: '30 min tidigere' + ,nb: '30 min tidigere' } ,'20 minutes earlier' : { cs: '20 min předem' @@ -1910,6 +2055,7 @@ function init() { ,hr: 'Prije 20 minuta' ,it: '20 minuti prima' ,dk: '20 min tidligere' + ,nb: '20 min tidligere' } ,'15 minutes earlier' : { cs: '15 min předem' @@ -1923,6 +2069,7 @@ function init() { ,hr: 'Prije 15 minuta' ,it: '15 minuti prima' ,dk: '15 min tidligere' + ,nb: '15 min tidligere' } ,'Time in minutes' : { cs: 'Čas v minutách' @@ -1936,6 +2083,7 @@ function init() { ,hr: 'Vrijeme u minutama' ,it: 'Tempo in minuti' ,dk: 'Tid i minutter' + ,nb: 'Tid i minutter' } ,'15 minutes later' : { cs: '15 min po' @@ -1948,6 +2096,7 @@ function init() { ,sv: '15 min senare' ,it: '15 minuti più tardi' ,dk: '15 min senere' + ,nb: '15 min senere' } ,'20 minutes later' : { cs: '20 min po' @@ -1961,6 +2110,7 @@ function init() { ,sv: '20 min senare' ,it: '20 minuti più tardi' ,dk: '20 min senere' + ,nb: '20 min senere' } ,'30 minutes later' : { cs: '30 min po' @@ -1974,6 +2124,7 @@ function init() { ,sv: '30 min senare' ,it: '30 minuti più tardi' ,dk: '30 min senere' + ,nb: '30 min senere' } ,'45 minutes later' : { cs: '45 min po' @@ -1987,6 +2138,7 @@ function init() { ,sv: '45 min senare' ,it: '45 minuti più tardi' ,dk: '45 min senere' + ,nb: '45 min senere' } ,'60 minutes later' : { cs: '60 min po' @@ -2000,6 +2152,7 @@ function init() { ,sv: '60 min senare' ,it: '60 minuti più tardi' ,dk: '60 min senere' + ,nb: '60 min senere' } ,'Additional Notes, Comments' : { cs: 'Dalši poznámky, komentáře' @@ -2013,6 +2166,7 @@ function init() { ,sv: 'Notering, övrigt' ,it: 'Note aggiuntive, commenti' ,dk: 'Ekstra noter, kommentarer' + ,nb: 'Ekstra notater, kommentarer' } ,'RETRO MODE' : { cs: 'V MINULOSTI' @@ -2026,6 +2180,7 @@ function init() { ,hr: 'Retrospektivni način' ,it: 'Modalità retrospettiva' ,dk: 'Retro mode' + ,nb: 'Retro mode' } ,'Now' : { cs: 'Nyní' @@ -2039,6 +2194,7 @@ function init() { ,hr: 'Sad' ,it: 'Adesso' ,dk: 'Nu' + ,nb: 'Nå' } ,'Other' : { cs: 'Jiný' @@ -2052,6 +2208,7 @@ function init() { ,hr: 'Drugo' ,it: 'Altro' ,dk: 'Øvrige' + ,nb: 'Annet' } ,'Submit Form' : { cs: 'Odeslat formulář' @@ -2065,6 +2222,7 @@ function init() { ,hr: 'Predaj obrazac' ,it: 'Inviare il modulo' ,dk: 'Gem hændelsen' + ,nb: 'Lagre' } ,'Profile editor' : { cs: 'Editor profilu' @@ -2078,6 +2236,7 @@ function init() { ,hr: 'Editor profila' ,it: 'Editor dei profili' ,dk: 'Profil editor' + ,nb: 'Profileditor' } ,'Reporting tool' : { cs: 'Výkazy' @@ -2091,6 +2250,7 @@ function init() { ,hr: 'Alat za prijavu' ,it: 'Strumento di reporting' ,dk: 'Rapporteringsværktøj' + ,nb: 'Rapporteringsverktøy' } ,'Add food from your database' : { cs: 'Přidat jidlo z Vaší databáze' @@ -2104,6 +2264,7 @@ function init() { ,sv: 'Lägg till livsmedel från databas' ,it: 'Aggiungere cibo al database' ,dk: 'Tilføj mad fra din database' + ,nb: 'Legg til mat fra din database' } ,'Reload database' : { cs: 'Znovu nahraj databázi' @@ -2117,6 +2278,7 @@ function init() { ,sv: 'Ladda om databas' ,it: 'Ricarica database' ,dk: 'Genindlæs databasen' + ,nb: 'Last inn databasen på nytt' } ,'Add' : { cs: 'Přidej' @@ -2130,6 +2292,7 @@ function init() { ,sv: 'Lägg till' ,it: 'Aggiungere' ,dk: 'Tilføj' + ,nb: 'Legg til' } ,'Unauthorized' : { cs: 'Neautorizováno' @@ -2143,6 +2306,7 @@ function init() { ,hr: 'Neautorizirano' ,it: 'non autorizzato' ,dk: 'Uautoriseret' + ,nb: 'Uautorisert' } ,'Entering record failed' : { cs: 'Vložení záznamu selhalo' @@ -2156,6 +2320,7 @@ function init() { ,sv: 'Lägga till post nekas' ,it: 'Voce del Registro fallita' ,dk: 'Tilføjelse af post fejlede' + ,nb: 'Lagring feilet' } ,'Device authenticated' : { cs: 'Zařízení ověřeno' @@ -2169,6 +2334,7 @@ function init() { ,hr: 'Uređaj autenticiran' ,it: 'Dispositivo autenticato' ,dk: 'Enhed godkendt' + ,nb: 'Enhet godkjent' } ,'Device not authenticated' : { cs: 'Zařízení není ověřeno' @@ -2182,6 +2348,7 @@ function init() { ,hr: 'Uređaj nije autenticiran' ,it: 'Dispositivo non autenticato' ,dk: 'Enhed ikke godkendt' + ,nb: 'Enhet ikke godkjent' } ,'Authentication status' : { cs: 'Stav ověření' @@ -2195,6 +2362,7 @@ function init() { ,sv: 'Autentiseringsstatus' ,it: 'Stato di autenticazione' ,dk: 'Autentifikationsstatus' + ,nb: 'Autentiseringsstatus' } ,'Authenticate' : { cs: 'Ověřit' @@ -2208,6 +2376,7 @@ function init() { ,hr: 'Autenticirati' ,it: 'Autenticare' ,dk: 'Godkende' + ,nb: 'Autentiser' } ,'Remove' : { cs: 'Vymazat' @@ -2221,6 +2390,7 @@ function init() { ,sv: 'Ta bort' ,it: 'Rimuovere' ,dk: 'Fjern' + ,nb: 'Slett' } ,'Your device is not authenticated yet' : { cs: 'Toto zařízení nebylo dosud ověřeno' @@ -2234,6 +2404,7 @@ function init() { ,sv: 'Din enhet är ej autentiserad' ,it: 'Il tuo dispositivo non è ancora stato autenticato' ,dk: 'Din enhed er ikke godkendt endnu' + ,nb: 'Din enhet er ikke godkjent enda' } ,'Sensor' : { cs: 'Senzor' @@ -2247,6 +2418,7 @@ function init() { ,hr: 'Senzor' ,it: 'Sensore' ,dk: 'Sensor' + ,nb: 'Sensor' } ,'Finger' : { cs: 'Glukoměr' @@ -2260,6 +2432,7 @@ function init() { ,hr: 'Prst' ,it: 'Dito' ,dk: 'Finger' + ,nb: 'Finger' } ,'Manual' : { cs: 'Ručně' @@ -2273,6 +2446,7 @@ function init() { ,hr: 'Ručno' ,it: 'Manuale' ,dk: 'Manuel' + ,nb: 'Manuell' } ,'Scale' : { cs: 'Měřítko' @@ -2286,6 +2460,7 @@ function init() { ,sv: 'Skala' ,it: 'Scala' ,dk: 'Skala' + ,nb: 'Skala' } ,'Linear' : { cs: 'lineární' @@ -2299,6 +2474,7 @@ function init() { ,hr: 'Linearno' ,it: 'Lineare' ,dk: 'Lineær' + ,nb: 'Lineær' } ,'Logarithmic' : { cs: 'logaritmické' @@ -2312,6 +2488,7 @@ function init() { ,hr: 'Logaritamski' ,it: 'Logaritmica' ,dk: 'Logaritmisk' + ,nb: 'Logaritmisk' } ,'Silence for 30 minutes' : { cs: 'Ztlumit na 30 minut' @@ -2325,6 +2502,7 @@ function init() { ,sv: 'Tyst i 30 min' ,it: 'Silenzio per 30 minuti' ,dk: 'Stilhed i 30 min' + ,nb: 'Stille i 30 min' } ,'Silence for 60 minutes' : { cs: 'Ztlumit na 60 minut' @@ -2338,6 +2516,7 @@ function init() { ,sv: 'Tyst i 60 min' ,it: 'Silenzio per 60 minuti' ,dk: 'Stilhed i 60 min' + ,nb: 'Stille i 60 min' } ,'Silence for 90 minutes' : { cs: 'Ztlumit na 90 minut' @@ -2351,6 +2530,7 @@ function init() { ,sv: 'Tyst i 90 min' ,it: 'Silenzio per 90 minuti' ,dk: 'Stilhed i 90 min' + ,nb: 'Stille i 90 min' } ,'Silence for 120 minutes' : { cs: 'Ztlumit na 120 minut' @@ -2364,6 +2544,7 @@ function init() { ,sv: 'Tyst i 120 min' ,it: 'Silenzio per 120 minuti' ,dk: 'Stilhed i 120 min' + ,nb: 'Stile i 120 min' } ,'3HR' : { cs: '3hod' @@ -2377,6 +2558,7 @@ function init() { ,hr: '3h' ,it: '3h' ,dk: '3tim' + ,nb: '3t' } ,'6HR' : { cs: '6hod' @@ -2390,6 +2572,7 @@ function init() { ,hr: '6h' ,it: '6h' ,dk: '6tim' + ,nb: '6t' } ,'12HR' : { cs: '12hod' @@ -2403,6 +2586,7 @@ function init() { ,hr: '12h' ,it: '12h' ,dk: '12tim' + ,nb: '12t' } ,'24HR' : { cs: '24hod' @@ -2416,6 +2600,7 @@ function init() { ,hr: '24h' ,it: '24h' ,dk: '24tim' + ,nb: '24t' } ,'Settings' : { cs: 'Nastavení' @@ -2429,6 +2614,7 @@ function init() { ,hr: 'Postavke' ,it: 'Impostazioni' ,dk: 'Opsætning' + ,nb: 'Innstillinger' } ,'Units' : { cs: 'Jednotky' @@ -2442,6 +2628,7 @@ function init() { ,sv: 'Enheter' ,it: 'Unità' ,dk: 'Enheder' + ,nb: 'Enheter' } ,'Date format' : { cs: 'Formát datumu' @@ -2455,6 +2642,7 @@ function init() { ,hr: 'Format datuma' ,it: 'Formato data' ,dk: 'dato format' + ,nb: 'datoformat' } ,'12 hours' : { cs: '12 hodin' @@ -2468,6 +2656,7 @@ function init() { ,hr: '12 sati' ,it: '12 ore' ,dk: '12 timer' + ,nb: '12 timer' } ,'24 hours' : { cs: '24 hodin' @@ -2481,6 +2670,7 @@ function init() { ,hr: '24 sata' ,it: '24 ore' ,dk: '24 timer' + ,nb: '24 timer' } ,'Log a Treatment' : { cs: 'Záznam ošetření' @@ -2494,6 +2684,7 @@ function init() { ,sv: 'Ange händelse' ,it: 'Somministrazione' ,dk: 'Log en hændelse' + ,nb: 'Logg en hendelse' } ,'BG Check' : { cs: 'Kontrola glykémie' @@ -2507,6 +2698,7 @@ function init() { ,hr: 'Kontrola GUK-a' ,it: 'Controllo Glicemia' ,dk: 'BS kontrol' + ,nb: 'Blodsukkerkontroll' } ,'Meal Bolus' : { cs: 'Bolus na jídlo' @@ -2520,6 +2712,7 @@ function init() { ,sv: 'Måltidsbolus' ,it: 'bolo pasto' ,dk: 'Måltidsbolus' + ,nb: 'Måltidsbolus' } ,'Snack Bolus' : { cs: 'Bolus na svačinu' @@ -2533,6 +2726,7 @@ function init() { ,hr: 'Bolus za užinu' ,it: 'Bolo merenda' ,dk: 'Mellemmåltidsbolus' + ,nb: 'Mellommåltidsbolus' } ,'Correction Bolus' : { cs: 'Bolus na glykémii' @@ -2546,6 +2740,7 @@ function init() { ,sv: 'Korrektionsbolus' ,it: 'Correzione bolo' ,dk: 'Korrektionsbolus' + ,nb: 'Korreksjonsbolus' } ,'Carb Correction' : { cs: 'Přídavek sacharidů' @@ -2559,6 +2754,7 @@ function init() { ,sv: 'Kolhydratskorrektion' ,it: 'Correzione carboidrati' ,dk: 'Kulhydratskorrektion' + ,nb: 'Karbohydratkorrigering' } ,'Note' : { cs: 'Poznámka' @@ -2572,6 +2768,7 @@ function init() { ,hr: 'Bilješka' ,it: 'Nota' ,dk: 'Note' + ,nb: 'Notat' } ,'Question' : { cs: 'Otázka' @@ -2585,6 +2782,7 @@ function init() { ,hr: 'Pitanje' ,it: 'Domanda' ,dk: 'Spørgsmål' + ,nb: 'Spørsmål' } ,'Announcement' : { bg: 'Известяване' @@ -2601,6 +2799,7 @@ function init() { ,hr: 'Aktivnost' ,it: 'Esercizio' ,dk: 'Træning' + ,nb: 'Trening' } ,'Pump Site Change' : { cs: 'Přepíchnutí kanyly' @@ -2614,6 +2813,7 @@ function init() { ,hr: 'Promjena seta' ,it: 'Cambio Ago' ,dk: 'Skift insulin infusionssted' + ,nb: 'Pumpebytte' } ,'Sensor Start' : { cs: 'Spuštění sensoru' @@ -2627,6 +2827,7 @@ function init() { ,hr: 'Start senzora' ,it: 'Avvio sensore' ,dk: 'Sensorstart' + ,nb: 'Sensorstart' } ,'Sensor Change' : { cs: 'Výměna sensoru' @@ -2640,6 +2841,7 @@ function init() { ,hr: 'Promjena senzora' ,it: 'Cambio sensore' ,dk: 'Sensor ombytning' + ,nb: 'Sensorbytte' } ,'Dexcom Sensor Start' : { cs: 'Spuštění sensoru' @@ -2653,6 +2855,7 @@ function init() { ,hr: 'Start Dexcom senzora' ,it: 'Avvio sensore Dexcom' ,dk: 'Dexcom sensor start' + ,nb: 'Dexcom sensor start' } ,'Dexcom Sensor Change' : { cs: 'Výměna sensoru' @@ -2666,6 +2869,7 @@ function init() { ,hr: 'Promjena Dexcom senzora' ,it: 'Cambio sensore Dexcom' ,dk: 'Dexcom sensor ombytning' + ,nb: 'Dexcom sensor bytte' } ,'Insulin Cartridge Change' : { cs: 'Výměna inzulínu' @@ -2679,6 +2883,7 @@ function init() { ,hr: 'Promjena spremnika inzulina' ,it: 'Cambio cartuccia insulina' ,dk: 'Skift insulin beholder' + ,nb: 'Skifte insulin beholder' } ,'D.A.D. Alert' : { cs: 'D.A.D. Alert' @@ -2692,6 +2897,7 @@ function init() { ,hr: 'Obavijest dijabetičkog psa' ,it: 'Allarme D.A.D.' ,dk: 'Vuf Vuf! (Diabeteshundealarm!)' + ,nb: 'Diabeteshundalarm' } ,'Glucose Reading' : { cs: 'Hodnota glykémie' @@ -2705,6 +2911,7 @@ function init() { ,hr: 'Vrijednost GUK-a' ,it: 'Lettura glicemie' ,dk: 'Glukose aflæsning' + ,nb: 'Blodsukkermåling' } ,'Measurement Method' : { cs: 'Metoda měření' @@ -2718,6 +2925,7 @@ function init() { ,hr: 'Metoda mjerenja' ,it: 'Metodo di misurazione' ,dk: 'Målemetode' + ,nb: 'Målemetode' } ,'Meter' : { cs: 'Glukoměr' @@ -2731,6 +2939,7 @@ function init() { ,hr: 'Glukometar' ,it: 'Glucometro' ,dk: 'Glukosemeter' + ,nb: 'Måleapparat' } ,'Insulin Given' : { cs: 'Inzulín' @@ -2739,11 +2948,12 @@ function init() { ,fr: 'Insuline donnée' ,pt: 'Insulina' ,sv: 'Insulindos' - ,ro: 'Insulină' + ,ro: 'Insulină administrată' ,bg: 'Инсулин' ,hr: 'Količina iznulina' ,it: 'Insulina' ,dk: 'Insulin dosis' + ,nb: 'Insulin' } ,'Amount in grams' : { cs: 'Množství v gramech' @@ -2757,6 +2967,7 @@ function init() { ,hr: 'Količina u gramima' ,it: 'Quantità in grammi' ,dk: 'Antal gram' + ,nb: 'Antall gram' } ,'Amount in units' : { cs: 'Množství v jednotkách' @@ -2770,6 +2981,7 @@ function init() { ,sv: 'Antal enheter' ,it: 'Quantità in unità' ,dk: 'Antal enheder' + ,nb: 'Antall enheter' } ,'View all treatments' : { cs: 'Zobraz všechny ošetření' @@ -2783,6 +2995,7 @@ function init() { ,hr: 'Prikaži sve tretmane' ,it: 'Visualizza tutti le somministrazioni' ,dk: 'Vis behandlinger' + ,nb: 'Vis behandlinger' } ,'Enable Alarms' : { cs: 'Povolit alarmy' @@ -2796,6 +3009,7 @@ function init() { ,hr: 'Aktiviraj alarme' ,it: 'Attiva Allarme' ,dk: 'Aktivere alarmer' + ,nb: 'Aktiver alarmer' } ,'When enabled an alarm may sound.' : { cs: 'Při povoleném alarmu zní zvuk' @@ -2809,6 +3023,7 @@ function init() { ,hr: 'Kad je aktiviran, alarm se može oglasiti' ,it: 'Quando si attiva un allarme acustico.' ,dk: 'Når aktiveret kan alarm lyde' + ,nb: 'Når aktivert er alarmer aktive' } ,'Urgent High Alarm' : { cs: 'Urgentní vysoká glykémie' @@ -2822,6 +3037,7 @@ function init() { ,hr: 'Hitni alarm za hiper' ,it: 'Allarme Urgente: Glicemia Molto Alta' ,dk: 'Høj grænse overskredet' + ,nb: 'Kritisk høy alarm' } ,'High Alarm' : { cs: 'Vysoká glykémie' @@ -2835,6 +3051,7 @@ function init() { ,hr: 'Alarm za hiper' ,it: 'Allarme: Glicemia Alta' ,dk: 'Høj grænse overskredet' + ,nb: 'Høy alarm' } ,'Low Alarm' : { cs: 'Nízká glykémie' @@ -2848,6 +3065,7 @@ function init() { ,hr: 'Alarm za hipo' ,it: 'Allarme Glicemia bassa' ,dk: 'Lav grænse overskredet' + ,nb: 'Lav alarm' } ,'Urgent Low Alarm' : { cs: 'Urgentní nízká glykémie' @@ -2861,6 +3079,7 @@ function init() { ,hr: 'Hitni alarm za hipo' ,it: 'Allarme Urgente. Glicemia Molto Bassa' ,dk: 'Advarsel: Lav' + ,nb: 'Kritisk lav alarm' } ,'Stale Data: Warn' : { cs: 'Zastaralá data' @@ -2874,6 +3093,7 @@ function init() { ,hr: 'Pažnja: Stari podaci' ,it: 'Dati obsoleti: Notifica' ,dk: 'Advarsel: Gamle data' + ,nb: 'Advarsel: Gamle data' } ,'Stale Data: Urgent' : { cs: 'Zastaralá data urgentní' @@ -2887,6 +3107,7 @@ function init() { ,hr: 'Hitno: Stari podaci' ,it: 'Dati obsoleti: Urgente' ,dk: 'Advarsel: Gamle data' + ,nb: 'Advarsel: Veldig gamle data' } ,'mins' : { cs: 'min' @@ -2900,6 +3121,7 @@ function init() { ,hr: 'min' ,it: 'min' ,dk: 'min' + ,nb: 'min' } ,'Night Mode' : { cs: 'Noční mód' @@ -2913,6 +3135,7 @@ function init() { ,hr: 'Noćni način' ,it: 'Modalità notte' ,dk: 'Nat mode' + ,nb: 'Nattmodus' } ,'When enabled the page will be dimmed from 10pm - 6am.' : { cs: 'Když je povoleno, obrazovka je ztlumena 22:00 - 6:00' @@ -2926,6 +3149,7 @@ function init() { ,hr: 'Kad je uključen, stranica će biti zatamnjena od 22-06' ,it: 'Attivandola, la pagina sarà oscurato da 10:00-06:00.' ,dk: 'Når aktiveret vil denne side nedtones fra 22:00-6:00' + ,nb: 'Når aktivert vil denne siden nedtones fra 22:00-06:00' } ,'Enable' : { cs: 'Povoleno' @@ -2939,6 +3163,7 @@ function init() { ,hr: 'Aktiviraj' ,it: 'Permettere' ,dk: 'Aktivere' + ,nb: 'Aktiver' } ,'Show Raw BG Data' : { cs: 'Zobraz RAW data' @@ -2952,6 +3177,7 @@ function init() { ,hr: 'Prikazuj sirove podatke o GUK-u' ,it: 'Mostra dati Raw BG' ,dk: 'Vis rå data' + ,nb: 'Vis rådata' } ,'Never' : { cs: 'Nikdy' @@ -2965,6 +3191,7 @@ function init() { ,hr: 'Nikad' ,it: 'Mai' ,dk: 'Aldrig' + ,nb: 'Aldri' } ,'Always' : { cs: 'Vždy' @@ -2978,6 +3205,7 @@ function init() { ,hr: 'Uvijek' ,it: 'Sempre' ,dk: 'Altid' + ,nb: 'Alltid' } ,'When there is noise' : { cs: 'Při šumu' @@ -2991,6 +3219,7 @@ function init() { ,hr: 'Kad postoji šum' ,it: 'Quando vi è rumore' ,dk: 'Når der er støj' + ,nb: 'Når det er støy' } ,'When enabled small white dots will be displayed for raw BG data' : { cs: 'Když je povoleno, malé tečky budou zobrazeny pro RAW data' @@ -3004,6 +3233,7 @@ function init() { ,hr: 'Kad je omogućeno, male bijele točkice će prikazivati sirove podatke o GUK-u.' ,it: 'Quando lo abiliti, visualizzerai piccoli puntini bianchi (raw BG data)' ,dk: 'Ved aktivering vil små hvide prikker blive vist for rå BG tal' + ,nb: 'Ved aktivering vil små hvite prikker bli vist for rå BG tall' } ,'Custom Title' : { cs: 'Vlastní název stránky' @@ -3017,6 +3247,7 @@ function init() { ,hr: 'Vlastiti naziv' ,it: 'Titolo personalizzato' ,dk: 'Egen titel' + ,nb: 'Egen tittel' } ,'Theme' : { cs: 'Téma' @@ -3030,6 +3261,7 @@ function init() { ,sv: 'Tema' ,it: 'Tema' ,dk: 'Tema' + ,nb: 'Tema' } ,'Default' : { cs: 'Výchozí' @@ -3043,6 +3275,7 @@ function init() { ,hr: 'Default' ,it: 'Predefinito' ,dk: 'Standard' + ,nb: 'Standard' } ,'Colors' : { cs: 'Barevné' @@ -3056,6 +3289,7 @@ function init() { ,hr: 'Boje' ,it: 'Colori' ,dk: 'Farver' + ,nb: 'Farger' } ,'Reset, and use defaults' : { cs: 'Vymaž a nastav výchozí hodnoty' @@ -3069,6 +3303,7 @@ function init() { ,hr: 'Resetiraj i koristi defaultne vrijednosti' ,it: 'Resetta e utilizza le impostazioni predefinite' ,dk: 'Returner til standardopsætning' + ,nb: 'Gjenopprett standardinnstillinger' } ,'Calibrations' : { cs: 'Kalibrace' @@ -3082,6 +3317,7 @@ function init() { ,hr: 'Kalibriranje' ,it: 'Calibrazioni' ,dk: 'Kalibrering' + ,nb: 'Kalibreringer' } ,'Alarm Test / Smartphone Enable' : { cs: 'Test alarmu' @@ -3095,6 +3331,7 @@ function init() { ,hr: 'Alarm test / Aktiviraj smartphone' ,it: 'Test Allarme / Abilita Smartphone' ,dk: 'Alarm test / Smartphone aktiveret' + ,nb: 'Alarmtest / Smartphone aktivering' } ,'Bolus Wizard' : { cs: 'Bolusový kalkulátor' @@ -3108,6 +3345,7 @@ function init() { ,hr: 'Bolus wizard' ,it: 'Bolo guidato' ,dk: 'Bolusberegner' + ,nb: 'Boluskalkulator' } ,'in the future' : { cs: 'v budoucnosti' @@ -3121,6 +3359,7 @@ function init() { ,hr: 'U budućnosti' ,it: 'nel futuro' ,dk: 'fremtiden' + ,nb: 'fremtiden' } ,'time ago' : { cs: 'min zpět' @@ -3134,6 +3373,7 @@ function init() { ,hr: 'prije' ,it: 'tempo fa' ,dk: 'tid siden' + ,nb: 'tid siden' } ,'hr ago' : { cs: 'hod zpět' @@ -3147,6 +3387,7 @@ function init() { ,hr: 'sat unazad' ,it: 'ora fa' ,dk: 'Time siden' + ,nb: 'Time siden' } ,'hrs ago' : { cs: 'hod zpět' @@ -3160,6 +3401,7 @@ function init() { ,hr: 'sati unazad' ,it: 'ore fa' ,dk: 'Timer siden' + ,nb: 'Timer siden' } ,'min ago' : { cs: 'min zpět' @@ -3173,6 +3415,7 @@ function init() { ,hr: 'minuta unazad' ,it: 'minuto fa' ,dk: 'minutter siden' + ,nb: 'minutter siden' } ,'mins ago' : { cs: 'min zpět' @@ -3186,6 +3429,7 @@ function init() { ,hr: 'minuta unazad' ,it: 'minuti fa' ,dk: 'minutter siden' + ,nb: 'minutter siden' } ,'day ago' : { cs: 'den zpět' @@ -3199,6 +3443,7 @@ function init() { ,hr: 'dan unazad' ,it: 'Giorno fa' ,dk: 'dag siden' + ,nb: 'dag siden' } ,'days ago' : { cs: 'dnů zpět' @@ -3212,6 +3457,7 @@ function init() { ,hr: 'dana unazad' ,it: 'giorni fa' ,dk: 'dage siden' + ,nb: 'dager siden' } ,'long ago' : { cs: 'dlouho zpět' @@ -3225,6 +3471,7 @@ function init() { ,hr: 'prije dosta vremena' ,it: 'Molto tempo fa' ,dk: 'længe siden' + ,nb: 'lenge siden' } ,'Clean' : { cs: 'Čistý' @@ -3238,6 +3485,7 @@ function init() { ,hr: 'Čisto' ,it: 'Pulito' ,dk: 'Rent' + ,nb: 'Rent' } ,'Light' : { cs: 'Lehký' @@ -3251,6 +3499,7 @@ function init() { ,hr: 'Lagano' ,it: 'Leggero' ,dk: 'Let' + ,nb: 'Lite' } ,'Medium' : { cs: 'Střední' @@ -3264,6 +3513,7 @@ function init() { ,hr: 'Srednje' ,it: 'Medio' ,dk: 'Middel' + ,nb: 'Middels' } ,'Heavy' : { cs: 'Velký' @@ -3277,6 +3527,7 @@ function init() { ,hr: 'Teško' ,it: 'Pesante' ,dk: 'Voldsom' + ,nb: 'Mye' } ,'Treatment type' : { cs: 'Typ ošetření' @@ -3290,6 +3541,7 @@ function init() { ,hr: 'Vrsta tretmana' ,it: 'Somministrazione' ,dk: 'Behandlingstype' + ,nb: 'Behandlingstype' } ,'Raw BG' : { cs: 'Glykémie z RAW dat' @@ -3303,6 +3555,7 @@ function init() { ,hr: 'Sirovi podaci o GUK-u' ,it: 'Raw BG' ,dk: 'Råt BS' + ,nb: 'RAW-BS' } ,'Device' : { cs: 'Zařízení' @@ -3316,6 +3569,7 @@ function init() { ,sv: 'Enhet' ,it: 'Dispositivo' ,dk: 'Enhed' + ,nb: 'Enhet' } ,'Noise' : { cs: 'Šum' @@ -3329,6 +3583,7 @@ function init() { ,hr: 'Šum' ,it: 'Rumore' ,dk: 'Støj' + ,nb: 'Støy' } ,'Calibration' : { cs: 'Kalibrace' @@ -3342,6 +3597,7 @@ function init() { ,hr: 'Kalibriranje' ,it: 'Calibratura' ,dk: 'Kalibrering' + ,nb: 'Kalibrering' } ,'Show Plugins' : { cs: 'Zobrazuj pluginy' @@ -3355,6 +3611,7 @@ function init() { ,sv: 'Visa tillägg' ,it: 'Mostra Plugin' ,dk: 'Vis plugins' + ,nb: 'Vis plugins' } ,'About' : { cs: 'O aplikaci' @@ -3368,6 +3625,7 @@ function init() { ,sv: 'Om' ,it: 'Informazioni' ,dk: 'Om' + ,nb: 'Om' } ,'Value in' : { cs: 'Hodnota v' @@ -3381,6 +3639,7 @@ function init() { ,sv: 'Värde om' ,it: 'Valore in' ,dk: 'Værdi i' + ,nb: 'Verdi i' } ,'Carb Time' : { cs: 'Čas jídla' @@ -3392,6 +3651,7 @@ function init() { ,sv: 'Kolhydratstid' ,it: 'Tempo' ,dk: 'Kulhydratstid' + ,nb: 'Karbohydrattid' } ,'Language' : { cs: 'Jazyk' From 2904fd42e195868b5b447630a2d1a86bfb6b072c Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 29 Aug 2015 10:43:12 -0700 Subject: [PATCH 685/937] add cache buster to verifyauth; fix a codacy issue --- lib/api/index.js | 2 +- lib/api/verifyauth.js | 7 +++---- lib/hashauth.js | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/api/index.js b/lib/api/index.js index 551d2958c14..268122db8ca 100644 --- a/lib/api/index.js +++ b/lib/api/index.js @@ -46,7 +46,7 @@ function create (env, ctx) { app.use('/', require('./profile/')(app, wares, ctx)); app.use('/', require('./devicestatus/')(app, wares, ctx)); app.use('/', require('./notifications-api')(app, wares, ctx)); - app.use('/', require('./verifyauth')(app, wares, env)); + app.use('/', require('./verifyauth')(app, env)); // Status app.use('/', require('./status')(app, wares, env)); diff --git a/lib/api/verifyauth.js b/lib/api/verifyauth.js index 169e7ec7fef..8e7a6378e2c 100644 --- a/lib/api/verifyauth.js +++ b/lib/api/verifyauth.js @@ -2,12 +2,11 @@ var consts = require('../constants'); -function configure (app, wares, env) { +function configure (app, env) { var express = require('express'), api = express.Router( ); - function config_authed (app, api, wares) { - + function config_authed (api) { api.get('/verifyauth', function(req, res) { var api_secret = env.api_secret; var secret = req.params.secret ? req.params.secret : req.header('api-secret'); @@ -17,7 +16,7 @@ function configure (app, wares, env) { } if (app.enabled('api')) { - config_authed(app, api, wares); + config_authed(api); } return api; diff --git a/lib/hashauth.js b/lib/hashauth.js index 55d53a8332b..63a182be2a9 100644 --- a/lib/hashauth.js +++ b/lib/hashauth.js @@ -19,7 +19,7 @@ hashauth.init = function init(client, $) { hashauth.verifyAuthentication = function verifyAuthentication(next) { $.ajax({ method: 'GET' - , url: '/api/v1/verifyauth' + , url: '/api/v1/verifyauth?t=' + Date.now() //cache buster , headers: { 'api-secret': hashauth.apisecrethash } From 1f69b68b4805e6895b31ae07c98f19e5ef59fe75 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 29 Aug 2015 11:07:10 -0700 Subject: [PATCH 686/937] language sort --- lib/language.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/language.js b/lib/language.js index 339ddbbd147..2c54bc88c56 100644 --- a/lib/language.js +++ b/lib/language.js @@ -9,20 +9,20 @@ function init() { language.languages = [ { code: 'bg', language: 'Български език' } - , { code: 'hr', language: 'Hrvatski' } , { code: 'cz', language: 'Čeština' } , { code: 'dk', language: 'Dansk' } + , { code: 'de', language: 'Deutsch' } , { code: 'en', language: 'English' } + , { code: 'es', language: 'Español' } , { code: 'fr', language: 'Français' } - , { code: 'de', language: 'Deutsch' } + , { code: 'hr', language: 'Hrvatski' } , { code: 'it', language: 'Italiano' } + , { code: 'nb', language: 'Norsk (Bokmål)' } , { code: 'pt', language: 'Português (Brasil)' } , { code: 'ro', language: 'Română' } - , { code: 'es', language: 'Español' } , { code: 'sv', language: 'Svenska' } - , { code: 'nb', language: 'Norsk (Bokmål)' } ]; - + var translations = { // Server 'Listening on port' : { @@ -1774,8 +1774,8 @@ function init() { ,hr: 'Koristi korekciju GUK-a u izračunu' ,sv: 'Använd BS-korrektion för beräkning' ,it: 'Utilizzare la correzione Glicemia nei calcoli' - ,dk: 'Anvend BS-korrektion før beregning' - ,nb: 'Bruk blodsukkerkorrigering i beregning' + ,dk: 'Anvend BS-korrektion før beregning' + ,nb: 'Bruk blodsukkerkorrigering i beregning' } ,'BG from CGM (autoupdated)' : { cs: 'Glykémie z CGM (automaticky aktualizovaná)' @@ -1819,7 +1819,7 @@ function init() { ,dk: 'Manuelt BS' ,nb: 'Manuelt BS' } - ,'Quickpick' : { + ,'Quickpick' : { cs: 'Rychlý výběr' ,de: 'Schnellauswahl' ,es: 'Selección rápida' @@ -1833,7 +1833,7 @@ function init() { ,dk: 'Hurtig snack' ,nb: 'Hurtigvalg' } - ,'or' : { + ,'or' : { cs: 'nebo' ,de: 'oder' ,es: 'o' @@ -2403,8 +2403,8 @@ function init() { ,hr: 'Vaš uređaj još nije autenticiran' ,sv: 'Din enhet är ej autentiserad' ,it: 'Il tuo dispositivo non è ancora stato autenticato' - ,dk: 'Din enhed er ikke godkendt endnu' - ,nb: 'Din enhet er ikke godkjent enda' + ,dk: 'Din enhed er ikke godkendt endnu' + ,nb: 'Din enhet er ikke godkjent enda' } ,'Sensor' : { cs: 'Senzor' From 4747a7a360edbad19f498c2a401405a777ab9de0 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 29 Aug 2015 11:26:36 -0700 Subject: [PATCH 687/937] d3 is in the bundle, so don't try to include it via bower --- static/index.html | 1 - 1 file changed, 1 deletion(-) diff --git a/static/index.html b/static/index.html index 631822009d8..4574917292d 100644 --- a/static/index.html +++ b/static/index.html @@ -269,7 +269,6 @@ - From aeceffb5d297ddd4bf59a26193a3654112540204 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 29 Aug 2015 11:58:09 -0700 Subject: [PATCH 688/937] fix bug that caused bottom of chart to be cropped till next update --- lib/client/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/client/index.js b/lib/client/index.js index 520068aa816..5c72761355a 100644 --- a/lib/client/index.js +++ b/lib/client/index.js @@ -327,9 +327,9 @@ client.init = function init(serverSettings, plugins) { } } + updateHeader(); + updateTimeAgo(); if (chart.prevChartHeight) { - updateHeader(); - updateTimeAgo(); chart.scroll(nowDate); } From b484f86af1b52c3ef8532795a2fe118eed837d18 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 25 Aug 2015 13:49:03 -0700 Subject: [PATCH 689/937] beta2 bump --- bower.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index 323527e266e..09f505e7f2e 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "nightscout", - "version": "0.8.0-beta1", + "version": "0.8.0-beta2", "dependencies": { "angularjs": "1.3.0-beta.19", "bootstrap": "~3.2.0", diff --git a/package.json b/package.json index bc483fa76d7..44659ca5f2a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Nightscout", - "version": "0.8.0-beta1", + "version": "0.8.0-beta2", "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": "AGPL3", "author": "Nightscout Team", From 06753b7fa9603c1cc8c342cceb8abcd282ced870 Mon Sep 17 00:00:00 2001 From: Ben West Date: Sat, 29 Aug 2015 14:02:13 -0700 Subject: [PATCH 690/937] quick stab at walk_prop --- lib/entries.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/lib/entries.js b/lib/entries.js index 5fbfafd6efe..797022ef424 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -10,6 +10,8 @@ var ObjectID = require('mongodb').ObjectID; \**********/ function find_sgv_query (opts) { + ['date', 'sgv'].forEach(function iter_prop (key) { + }); if (opts && opts.find && opts.find.sgv) { Object.keys(opts.find.sgv).forEach(function (key) { var is_keyword = /^\$/g; @@ -21,6 +23,21 @@ function find_sgv_query (opts) { return opts; } +function walk_prop (prop, typer) { + function iter (opts) { + if (opts && opts.find && opts.find[prop]) { + Object.keys(opts.find[prop]).forEach(function (key) { + var is_keyword = /^\$/g; + if (is_keyword.test(key)) { + opts.find[prop][key] = typer(opts.find[prop][key]); + } + }); + } + return opts; + } + return iter; +} + var TWO_DAYS = 172800000; function storage(env, ctx) { From f35bba3b20f700d7a9086729df4a7e337ef624ce Mon Sep 17 00:00:00 2001 From: Ben West Date: Sat, 29 Aug 2015 14:04:28 -0700 Subject: [PATCH 691/937] quick stab at walk_prop --- lib/entries.js | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/lib/entries.js b/lib/entries.js index 797022ef424..7ac9d77b30a 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -10,16 +10,9 @@ var ObjectID = require('mongodb').ObjectID; \**********/ function find_sgv_query (opts) { - ['date', 'sgv'].forEach(function iter_prop (key) { - }); - if (opts && opts.find && opts.find.sgv) { - Object.keys(opts.find.sgv).forEach(function (key) { - var is_keyword = /^\$/g; - if (is_keyword.test(key)) { - opts.find.sgv[key] = parseInt(opts.find.sgv[key]); - } - }); - } + + opts = walk_prop('date', parseInt)(opts); + opts = walk_prop('sgv', parseInt)(opts); return opts; } From cf46c4910dff4c9bcf31daae27882bfd557be502 Mon Sep 17 00:00:00 2001 From: Ben West Date: Sat, 29 Aug 2015 14:41:30 -0700 Subject: [PATCH 692/937] convert all leaf nodes for sgv and date to int --- lib/entries.js | 6 ++++++ package.json | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/entries.js b/lib/entries.js index 7ac9d77b30a..efe10447782 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -2,6 +2,7 @@ var es = require('event-stream'); var sgvdata = require('sgvdata'); +var traverse = require('traverse'); var ObjectID = require('mongodb').ObjectID; /**********\ @@ -19,12 +20,17 @@ function find_sgv_query (opts) { function walk_prop (prop, typer) { function iter (opts) { if (opts && opts.find && opts.find[prop]) { + traverse(opts.find[prop]).forEach(function (x) { + this.update(typer(x)); + }); + /* Object.keys(opts.find[prop]).forEach(function (key) { var is_keyword = /^\$/g; if (is_keyword.test(key)) { opts.find[prop][key] = typer(opts.find[prop][key]); } }); + */ } return opts; } diff --git a/package.json b/package.json index 44659ca5f2a..21198271cd3 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "bower": "^1.3.8", "browserify-express": "^0.1.4", "compression": "^1.4.2", + "d3": "^3.5.6", "errorhandler": "^1.1.1", "event-stream": "~3.1.5", "express": "^4.6.1", @@ -72,7 +73,7 @@ "sgvdata": "git://github.com/ktind/sgvdata.git#wip/protobuf", "share2nightscout-bridge": "git://github.com/bewest/share2nightscout-bridge.git#wip/generalize", "socket.io": "^1.3.5", - "d3": "^3.5.6" + "traverse": "^0.6.6" }, "devDependencies": { "istanbul": "~0.3.5", From 1ed8ab6883ba52c6a7a0efe5781fd9f5e18a3917 Mon Sep 17 00:00:00 2001 From: Ben West Date: Sat, 29 Aug 2015 14:49:57 -0700 Subject: [PATCH 693/937] apply to leaf nodes only --- lib/entries.js | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/lib/entries.js b/lib/entries.js index efe10447782..5c1a8d427da 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -21,16 +21,10 @@ function walk_prop (prop, typer) { function iter (opts) { if (opts && opts.find && opts.find[prop]) { traverse(opts.find[prop]).forEach(function (x) { - this.update(typer(x)); - }); - /* - Object.keys(opts.find[prop]).forEach(function (key) { - var is_keyword = /^\$/g; - if (is_keyword.test(key)) { - opts.find[prop][key] = typer(opts.find[prop][key]); + if (this.isLeaf) { + this.update(typer(x)); } }); - */ } return opts; } @@ -53,6 +47,7 @@ function storage(env, ctx) { if (query._id && query._id.length) { query._id = ObjectID(query._id); } + console.log('QUERY', query); return query; } From 94abbd079d1c529e04b78b8c1ecbc051b144b5cf Mon Sep 17 00:00:00 2001 From: Ben West Date: Sat, 29 Aug 2015 15:04:17 -0700 Subject: [PATCH 694/937] add a generic spec builder --- lib/entries.js | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/lib/entries.js b/lib/entries.js index 5c1a8d427da..abc25746682 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -10,13 +10,29 @@ var ObjectID = require('mongodb').ObjectID; * Encapsulate persistent storage of sgv entries. \**********/ -function find_sgv_query (opts) { +function build_query (opts) { - opts = walk_prop('date', parseInt)(opts); - opts = walk_prop('sgv', parseInt)(opts); + opts = walker({ date: parseInt, sgv: parseInt })(opts); return opts; } +function walker (spec) { + var fns = [ ]; + for (var prop in spec) { + var typer = spec[prop]; + fns.push(walk_prop(prop, typer)); + } + + function exec (obj) { + var fn; + while (fn = fns.shift( )) { + obj = fn(obj); + } + return obj; + } + return exec +} + function walk_prop (prop, typer) { function iter (opts) { if (opts && opts.find && opts.find[prop]) { @@ -39,7 +55,7 @@ function storage(env, ctx) { var with_collection = ctx.store.with_collection(env.mongo_collection); function find_options (opts) { - var finder = find_sgv_query(opts); + var finder = build_query(opts); var query = finder && finder.find ? finder.find : { }; if (!query.date && !query.dateString) { query.date = { $gte: Date.now( ) - ( TWO_DAYS * 2 ) }; @@ -47,7 +63,6 @@ function storage(env, ctx) { if (query._id && query._id.length) { query._id = ObjectID(query._id); } - console.log('QUERY', query); return query; } From 2180f0c310f9ec43163a1d14e879db4f24acd935 Mon Sep 17 00:00:00 2001 From: Ben West Date: Sat, 29 Aug 2015 16:29:30 -0700 Subject: [PATCH 695/937] find_options: replace with module version --- lib/entries.js | 52 +---------------------------------- lib/query.js | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 51 deletions(-) create mode 100644 lib/query.js diff --git a/lib/entries.js b/lib/entries.js index abc25746682..1b529b45b47 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -2,7 +2,7 @@ var es = require('event-stream'); var sgvdata = require('sgvdata'); -var traverse = require('traverse'); +var find_options = require('./query'); var ObjectID = require('mongodb').ObjectID; /**********\ @@ -10,62 +10,12 @@ var ObjectID = require('mongodb').ObjectID; * Encapsulate persistent storage of sgv entries. \**********/ -function build_query (opts) { - - opts = walker({ date: parseInt, sgv: parseInt })(opts); - return opts; -} - -function walker (spec) { - var fns = [ ]; - for (var prop in spec) { - var typer = spec[prop]; - fns.push(walk_prop(prop, typer)); - } - - function exec (obj) { - var fn; - while (fn = fns.shift( )) { - obj = fn(obj); - } - return obj; - } - return exec -} - -function walk_prop (prop, typer) { - function iter (opts) { - if (opts && opts.find && opts.find[prop]) { - traverse(opts.find[prop]).forEach(function (x) { - if (this.isLeaf) { - this.update(typer(x)); - } - }); - } - return opts; - } - return iter; -} - -var TWO_DAYS = 172800000; function storage(env, ctx) { // TODO: Code is a little redundant. var with_collection = ctx.store.with_collection(env.mongo_collection); - function find_options (opts) { - var finder = build_query(opts); - var query = finder && finder.find ? finder.find : { }; - if (!query.date && !query.dateString) { - query.date = { $gte: Date.now( ) - ( TWO_DAYS * 2 ) }; - } - if (query._id && query._id.length) { - query._id = ObjectID(query._id); - } - return query; - } - // query for entries from storage function list (opts, fn) { with_collection(function (err, collection) { diff --git a/lib/query.js b/lib/query.js new file mode 100644 index 00000000000..9996f0b57f0 --- /dev/null +++ b/lib/query.js @@ -0,0 +1,74 @@ +'use strict'; + +var traverse = require('traverse'); +var ObjectID = require('mongodb').ObjectID; + +var TWO_DAYS = 172800000; + +function default_options (opts) { + if (!opts) { + opts = { }; + } + if (opts) { + // if (!'laxDate' in opts) { opts.laxDate = false; } + if (!'deltaAgo' in opts) { + opts.deltaAgo = ( TWO_DAYS * 2 ); + } + if (!'walker' in opts) { + opts.walker = { date: parseInt, sgv: parseInt }; + } + } + return opts; +} + +function create (params, opts) { + opts = default_options(opts); + var finder = walker(opts.walker)(params); + var query = finder && finder.find ? finder.find : { }; + if (!query.date && !query.dateString) { + // if (!laxDate) { } + query.date = { $gte: Date.now( ) - opts.deltaAgo }; + } + if (query._id && query._id.length) { + query._id = ObjectID(query._id); + } + return query; +} + +function walker (spec) { + var fns = [ ]; + for (var prop in spec) { + var typer = spec[prop]; + fns.push(walk_prop(prop, typer)); + } + + function exec (obj) { + var fn; + while (fn = fns.shift( )) { + obj = fn(obj); + } + return obj; + } + return exec +} + +function walk_prop (prop, typer) { + function iter (opts) { + if (opts && opts.find && opts.find[prop]) { + traverse(opts.find[prop]).forEach(function (x) { + if (this.isLeaf) { + this.update(typer(x)); + } + }); + } + return opts; + } + return iter; +} + +walker.walk_prop = walk_prop; +create.walker = walker; +create.default_options = default_options; + +exports = module.exports = create; + From dec04cf18172f0193e1e03aaf0fc3c822b7656aa Mon Sep 17 00:00:00 2001 From: Ben West Date: Sat, 29 Aug 2015 17:12:33 -0700 Subject: [PATCH 696/937] fix options passed to walker/query module --- lib/query.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/query.js b/lib/query.js index 9996f0b57f0..57d6427724f 100644 --- a/lib/query.js +++ b/lib/query.js @@ -6,15 +6,14 @@ var ObjectID = require('mongodb').ObjectID; var TWO_DAYS = 172800000; function default_options (opts) { - if (!opts) { - opts = { }; - } + opts = opts || { }; if (opts) { - // if (!'laxDate' in opts) { opts.laxDate = false; } - if (!'deltaAgo' in opts) { + var keys = [null].concat(Object.keys(opts)); + // if (keys.indexOf('laxDate') < 1) { opts.laxDate = false; } + if (keys.indexOf('deltaAgo') < 1) { opts.deltaAgo = ( TWO_DAYS * 2 ); } - if (!'walker' in opts) { + if (keys.indexOf('walker') < 1) { opts.walker = { date: parseInt, sgv: parseInt }; } } @@ -37,10 +36,11 @@ function create (params, opts) { function walker (spec) { var fns = [ ]; - for (var prop in spec) { + var keys = Object.keys(spec); + keys.forEach(function config (prop) { var typer = spec[prop]; fns.push(walk_prop(prop, typer)); - } + }); function exec (obj) { var fn; From 9472983845a17d4006eaf243681f2654e8592104 Mon Sep 17 00:00:00 2001 From: Ben West Date: Sat, 29 Aug 2015 17:19:59 -0700 Subject: [PATCH 697/937] missing semicolon --- lib/query.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/query.js b/lib/query.js index 57d6427724f..862bd14c8b7 100644 --- a/lib/query.js +++ b/lib/query.js @@ -49,7 +49,7 @@ function walker (spec) { } return obj; } - return exec + return exec; } function walk_prop (prop, typer) { From bd5820f9cc1b19cc594ca255d01f2aef7be0a674 Mon Sep 17 00:00:00 2001 From: Ben West Date: Sat, 29 Aug 2015 17:37:33 -0700 Subject: [PATCH 698/937] augment for @stavior's data --- lib/entries.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/entries.js b/lib/entries.js index 1b529b45b47..3c2aadb7631 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -43,7 +43,7 @@ function storage(env, ctx) { // now just stitch them all together limit.call(collection - .find(find_options(opts)) + .find(find_options(opts, {walker: storage.query})) .sort(sort( )) ).toArray(toArray); }); @@ -142,6 +142,11 @@ function storage(env, ctx) { return api; } +storage.query = { date: parseInt, sgv: parseInt, + filtered: parseInt, unfiltered: parseInt, + rssi: parseInt, noise: parseInt + }; + // expose module storage.storage = storage; module.exports = storage; From e1ae38cef5a01de55b8ba324578ed290abecce11 Mon Sep 17 00:00:00 2001 From: Ben West Date: Sat, 29 Aug 2015 17:45:57 -0700 Subject: [PATCH 699/937] hat tip to @jasoncalabrese, now with mbg --- lib/entries.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/entries.js b/lib/entries.js index 3c2aadb7631..06730e1ad92 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -142,10 +142,14 @@ function storage(env, ctx) { return api; } -storage.query = { date: parseInt, sgv: parseInt, - filtered: parseInt, unfiltered: parseInt, - rssi: parseInt, noise: parseInt - }; +storage.query = { date: parseInt + , sgv: parseInt + , filtered: parseInt + , unfiltered: parseInt + , rssi: parseInt + , noise: parseInt + , mbg: parseInt + }; // expose module storage.storage = storage; From f6a16394191f63185909e46d6118ea7c39a1536b Mon Sep 17 00:00:00 2001 From: Ben West Date: Sat, 29 Aug 2015 18:06:00 -0700 Subject: [PATCH 700/937] make codacy happier --- lib/query.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/query.js b/lib/query.js index 862bd14c8b7..62b49a7a6fd 100644 --- a/lib/query.js +++ b/lib/query.js @@ -44,7 +44,8 @@ function walker (spec) { function exec (obj) { var fn; - while (fn = fns.shift( )) { + while (fns.length > 0) { + fn = fns.shift( ); obj = fn(obj); } return obj; From 8f02a6a97d5685d5381b813b1d8a9ba6da12f4e9 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 29 Aug 2015 18:40:34 -0700 Subject: [PATCH 701/937] added query walker to the treatment api, for insulin, carbs, and glucose --- lib/entries.js | 24 ++++++++++++++---------- lib/query.js | 10 ++++++++-- lib/treatments.js | 20 +++++++++++++++----- 3 files changed, 37 insertions(+), 17 deletions(-) diff --git a/lib/entries.js b/lib/entries.js index 06730e1ad92..12db42dd12a 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -43,7 +43,7 @@ function storage(env, ctx) { // now just stitch them all together limit.call(collection - .find(find_options(opts, {walker: storage.query})) + .find(find_options(opts, storage.queryOpts)) .sort(sort( )) ).toArray(toArray); }); @@ -51,7 +51,7 @@ function storage(env, ctx) { function remove (opts, fn) { with_collection(function (err, collection) { - collection.remove(find_options(opts), fn); + collection.remove(find_options(opts, storage.queryOpts), fn); }); } @@ -142,14 +142,18 @@ function storage(env, ctx) { return api; } -storage.query = { date: parseInt - , sgv: parseInt - , filtered: parseInt - , unfiltered: parseInt - , rssi: parseInt - , noise: parseInt - , mbg: parseInt - }; +storage.queryOpts = { + walker: { + date: parseInt + , sgv: parseInt + , filtered: parseInt + , unfiltered: parseInt + , rssi: parseInt + , noise: parseInt + , mbg: parseInt + } + , useISO: false +}; // expose module storage.storage = storage; diff --git a/lib/query.js b/lib/query.js index 62b49a7a6fd..6bfad2f00b5 100644 --- a/lib/query.js +++ b/lib/query.js @@ -24,9 +24,15 @@ function create (params, opts) { opts = default_options(opts); var finder = walker(opts.walker)(params); var query = finder && finder.find ? finder.find : { }; - if (!query.date && !query.dateString) { + var dateField = opts.dateField || 'date'; + var dateValue = query[dateField]; + + if (!dateValue && !query.dateString) { // if (!laxDate) { } - query.date = { $gte: Date.now( ) - opts.deltaAgo }; + var minDate = Date.now( ) - opts.deltaAgo; + query[dateField] = { + $gte: opts.useISO ? new Date(minDate).toISOString() : minDate + }; } if (query._id && query._id.length) { query._id = ObjectID(query._id); diff --git a/lib/treatments.js b/lib/treatments.js index 500aa52de97..1b630ec7a06 100644 --- a/lib/treatments.js +++ b/lib/treatments.js @@ -1,5 +1,7 @@ 'use strict'; +var find_options = require('./query'); + function storage (env, ctx) { function create (obj, fn) { @@ -33,11 +35,10 @@ function storage (env, ctx) { } function list (opts, fn) { - function find ( ) { - return opts && opts.find ? opts.find : { }; - } - - return ctx.store.limit.call(api().find(find( )).sort(opts && opts.sort || {created_at: -1}), opts).toArray(fn); + return ctx.store.limit.call(api() + .find(find_options(opts, storage.queryOpts)) + .sort(opts && opts.sort || {created_at: -1}), opts) + .toArray(fn); } function api ( ) { @@ -102,5 +103,14 @@ function prepareData(obj) { return results; } +storage.queryOpts = { + walker: { + insulin: parseInt + , carbs: parseInt + , glucose: parseInt + } + , dateField: 'created_at' +}; + module.exports = storage; From f970ac0b08b7e0974095abc7bd9ada8091594081 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 29 Aug 2015 18:46:57 -0700 Subject: [PATCH 702/937] changed option from useISO to useEpoch --- lib/entries.js | 2 +- lib/query.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/entries.js b/lib/entries.js index 12db42dd12a..5f2a85a5c7d 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -152,7 +152,7 @@ storage.queryOpts = { , noise: parseInt , mbg: parseInt } - , useISO: false + , useEpoch: true }; // expose module diff --git a/lib/query.js b/lib/query.js index 6bfad2f00b5..6833c68ea0f 100644 --- a/lib/query.js +++ b/lib/query.js @@ -31,7 +31,7 @@ function create (params, opts) { // if (!laxDate) { } var minDate = Date.now( ) - opts.deltaAgo; query[dateField] = { - $gte: opts.useISO ? new Date(minDate).toISOString() : minDate + $gte: opts.useEpoch ? minDate : new Date(minDate).toISOString() }; } if (query._id && query._id.length) { From 0a1b0fbe110263d45f09ab1462d015c4a9060cfb Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 29 Aug 2015 19:01:31 -0700 Subject: [PATCH 703/937] query refactoring --- lib/query.js | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/lib/query.js b/lib/query.js index 6833c68ea0f..8a5697c686a 100644 --- a/lib/query.js +++ b/lib/query.js @@ -16,27 +16,37 @@ function default_options (opts) { if (keys.indexOf('walker') < 1) { opts.walker = { date: parseInt, sgv: parseInt }; } + + opts.dateField = opts.dateField || 'date'; } return opts; } -function create (params, opts) { - opts = default_options(opts); - var finder = walker(opts.walker)(params); - var query = finder && finder.find ? finder.find : { }; - var dateField = opts.dateField || 'date'; - var dateValue = query[dateField]; +function enforceDateFilter (query, opts) { + var dateValue = query[opts.dateField]; if (!dateValue && !query.dateString) { - // if (!laxDate) { } var minDate = Date.now( ) - opts.deltaAgo; - query[dateField] = { + query[opts.dateField] = { $gte: opts.useEpoch ? minDate : new Date(minDate).toISOString() }; } +} + +function updateIdQuery(query) { if (query._id && query._id.length) { query._id = ObjectID(query._id); } +} + +function create (params, opts) { + opts = default_options(opts); + var finder = walker(opts.walker)(params); + var query = finder && finder.find ? finder.find : { }; + + enforceDateFilter(query, opts); + updateIdQuery(query); + return query; } From 36821c6c324c6cd3401266b56088fe0b0003dcbf Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 29 Aug 2015 19:02:18 -0700 Subject: [PATCH 704/937] space --- lib/query.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/query.js b/lib/query.js index 8a5697c686a..f6513051ae4 100644 --- a/lib/query.js +++ b/lib/query.js @@ -33,7 +33,7 @@ function enforceDateFilter (query, opts) { } } -function updateIdQuery(query) { +function updateIdQuery (query) { if (query._id && query._id.length) { query._id = ObjectID(query._id); } From a61cfdc31813991158f63245b35959c82c2a492b Mon Sep 17 00:00:00 2001 From: Ben West Date: Sat, 29 Aug 2015 19:16:44 -0700 Subject: [PATCH 705/937] try some docs --- lib/query.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/lib/query.js b/lib/query.js index 6833c68ea0f..2a22e4fe76c 100644 --- a/lib/query.js +++ b/lib/query.js @@ -4,7 +4,16 @@ var traverse = require('traverse'); var ObjectID = require('mongodb').ObjectID; var TWO_DAYS = 172800000; +/** + * @module query + * Assist in translating objects from query-string representation into + * mongo-style queries by performing type translation. + */ +/** + * Default options for query. + * Interpret and return the options to use for building our query. + */ function default_options (opts) { opts = opts || { }; if (opts) { @@ -20,6 +29,17 @@ function default_options (opts) { return opts; } +/** + * @param QueryParams params Object returned by qs.parse or https://github.com/hapijs/qs + * @param BuilderOpts opts Options for how to translate types. + * + * Allows performing logic described by a model's attributes. + * Specifically, we try to ensure that all queries have some kind of query + * body to filter the rows mongodb will spool. The defaults, such as name and + * representation of a date field can be configured via the `opts` passed in. + * + * @returns Object An object which can be passed to `mongodb.find( )` + */ function create (params, opts) { opts = default_options(opts); var finder = walker(opts.walker)(params); From 4c12eea50852afbeab6a657ccba70ada86366290 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 29 Aug 2015 19:26:00 -0700 Subject: [PATCH 706/937] added some more query examples --- swagger.yaml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/swagger.yaml b/swagger.yaml index f849bc883a8..aa240697244 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -107,7 +107,12 @@ paths: parameters: - name: find in: query - description: The query used to find entries, support nested query syntax, for example `find[created_at][$gte]=2015-08-27&find[eventType]=Carb+Correction`. All find paramertes are interpreted as strings. + description: + The query used to find entries, supports nested query syntax. Examples + `find[insulin][$gte]=3` + `find[carb][$gte]=100` + `find[eventType]=Correction+Bolus` + All find paramertes are interpreted as strings. required: false type: string - name: count From f96a86a5ebed7a2a8d8f8774268d765ff32d0c41 Mon Sep 17 00:00:00 2001 From: Ben West Date: Sat, 29 Aug 2015 19:48:56 -0700 Subject: [PATCH 707/937] add some comments/docs --- lib/query.js | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/lib/query.js b/lib/query.js index c85a23ba709..f157cc4ee1b 100644 --- a/lib/query.js +++ b/lib/query.js @@ -25,18 +25,32 @@ function default_options (opts) { if (opts) { var keys = [null].concat(Object.keys(opts)); + // default at least TWO_DAYS of data + // TODO: discuss/consensus on right value/ENV? if (keys.indexOf('deltaAgo') < 1) { opts.deltaAgo = ( TWO_DAYS * 2 ); } + + // default at `date` and `sgv` properties are both int-types. if (keys.indexOf('walker') < 1) { opts.walker = { date: parseInt, sgv: parseInt }; } + // The default field to constrain is called 'date' for entries module. + // Allow other models/backends to use other fields names. opts.dateField = opts.dateField || 'date'; } return opts; } +/** + * Enforce rule that says that the query must express some constraint on the + * configured `dateField` or against the field named `dateString`. If the + * configured option `useEpoch` is set, the naive JS epoch is used, otherwise + * ISO 8601 is used. The rule ensures that records must have a date field + * with a date and time greater than or equal to the configured `deltaAgo` + * option, (`opts.deltaAgo`). + */ function enforceDateFilter (query, opts) { var dateValue = query[opts.dateField]; @@ -48,6 +62,10 @@ function enforceDateFilter (query, opts) { } } +/** + * Helper to set ObjectID type for `_id` queries. + * Forces anything named `_id` to be the `ObjectID` type. + */ function updateIdQuery (query) { if (query._id && query._id.length) { query._id = ObjectID(query._id); @@ -65,54 +83,95 @@ function updateIdQuery (query) { * * @returns Object An object which can be passed to `mongodb.find( )` */ - function create (params, opts) { + // setup default options for what/how to do things opts = default_options(opts); + // Build the iterator, pass it our initial params to et the results. var finder = walker(opts.walker)(params); + // Get the final query to pass to mongodb. var query = finder && finder.find ? finder.find : { }; + // Ensure some kind of sane date constraint tied to an index is expressed in the query. enforceDateFilter(query, opts); + // Help queries for _id. updateIdQuery(query); + // Ready for mongodb.find( ) and friends. return query; } +/** + * Configure a single iterator given a specification of named mapped to types. + * @params Object spec A simple mapping of field names to function to create that type. + * + * Example spec: { sgv: parseInt } + * @returns function Function will translate types expressed in query. + */ function walker (spec) { + // empty queue var fns = [ ]; + + // for each key/value pair in the spec var keys = Object.keys(spec); keys.forEach(function config (prop) { var typer = spec[prop]; + // add function from walk_prop to the queue fns.push(walk_prop(prop, typer)); }); + /** + * Execute all configured mappings in single step. + * @param Object obj QueryString object + * @returns Object for mongodb queries, with fields set to appropriate type + described by previous mapping. + */ function exec (obj) { var fn; + // for each mapping in the queue while (fns.length > 0) { fn = fns.shift( ); + // do each mapping obj = fn(obj); } return obj; } + // return a function that can execute the configured queue of translations return exec; } +/** + * Given a name and a type, return a function which will transform any value + * on a leaf-node into that type. + * @param String prop Property name to to translate. + * @param function typer Function to convert to type, eg `parseInt` + */ function walk_prop (prop, typer) { function iter (opts) { + // This is specifically configured to match the `find` convention in our REST API. + // Query parameters are the ones attached to the `find` object. if (opts && opts.find && opts.find[prop]) { + // Traverse any query elements associated with this property. traverse(opts.find[prop]).forEach(function (x) { + // In Mongo queries, the leaf nodes are always the values to search for. + // Ignore any interstitial arrays/objects to represent + // greater-than-or-equal-to, etc. if (this.isLeaf) { + // Leaf nodes should be converted to this type. this.update(typer(x)); } }); } + // Return opts after modifying in place. return opts; } return iter; } +// attach helpers and utilities to main function for testing walker.walk_prop = walk_prop; create.walker = walker; create.default_options = default_options; +// expose module as single high level function exports = module.exports = create; From a4b8606fd4c4cf6eaa8026069201f1853cf971e9 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 30 Aug 2015 00:16:31 -0700 Subject: [PATCH 708/937] ensure there are indexes for some more fields --- lib/entries.js | 2 +- lib/treatments.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/entries.js b/lib/entries.js index 5f2a85a5c7d..752a2d666b6 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -138,7 +138,7 @@ function storage(env, ctx) { api.remove = remove; api.persist = persist; api.getEntry = getEntry; - api.indexedFields = [ 'date', 'type', 'sgv', 'sysTime', 'dateString' ]; + api.indexedFields = [ 'date', 'type', 'sgv', 'mbg', 'sysTime', 'dateString' ]; return api; } diff --git a/lib/treatments.js b/lib/treatments.js index 1b630ec7a06..055f4c6547f 100644 --- a/lib/treatments.js +++ b/lib/treatments.js @@ -47,7 +47,7 @@ function storage (env, ctx) { api.list = list; api.create = create; - api.indexedFields = ['created_at', 'eventType']; + api.indexedFields = ['created_at', 'eventType', 'insulin', 'carbs', 'glucose']; return api; } From a4d341f24a3eb743b2c6ddb7e5fa1e38e98e67eb Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 30 Aug 2015 00:17:33 -0700 Subject: [PATCH 709/937] use the typer for simple fields too --- lib/query.js | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/lib/query.js b/lib/query.js index f157cc4ee1b..43e4da34b3c 100644 --- a/lib/query.js +++ b/lib/query.js @@ -96,6 +96,7 @@ function create (params, opts) { // Help queries for _id. updateIdQuery(query); + console.info('query:', query); // Ready for mongodb.find( ) and friends. return query; } @@ -150,16 +151,21 @@ function walk_prop (prop, typer) { // This is specifically configured to match the `find` convention in our REST API. // Query parameters are the ones attached to the `find` object. if (opts && opts.find && opts.find[prop]) { - // Traverse any query elements associated with this property. - traverse(opts.find[prop]).forEach(function (x) { - // In Mongo queries, the leaf nodes are always the values to search for. - // Ignore any interstitial arrays/objects to represent - // greater-than-or-equal-to, etc. - if (this.isLeaf) { - // Leaf nodes should be converted to this type. - this.update(typer(x)); - } - }); + if (typeof opts.find[prop] === 'string') { + //simple string property, no need to traverse + opts.find[prop] = typer(opts.find[prop]); + } else { + // Traverse any query elements associated with this property. + traverse(opts.find[prop]).forEach(function (x) { + // In Mongo queries, the leaf nodes are always the values to search for. + // Ignore any interstitial arrays/objects to represent + // greater-than-or-equal-to, etc. + if (this.isLeaf) { + // Leaf nodes should be converted to this type. + this.update(typer(x)); + } + }); + } } // Return opts after modifying in place. return opts; From 12ec46822ccd6a012737dad9d694cb7906e3907b Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 30 Aug 2015 00:18:06 -0700 Subject: [PATCH 710/937] add some example queries to the readme --- README.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 822919b4ad7..fe85ee1a0b5 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ Community maintained fork of the - [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) @@ -116,9 +117,21 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. The Nightscout API enables direct access to your DData without the need for direct Mongo access. You can find CGM data in `/api/v1/entries`, Care Portal Treatments in `/api/v1/treatments`, and Treatment Profiles in `/api/v1/profile`. The server status and settings are available from `/api/v1/status.json`. -Also, by using Swagger you can also generate client code to make working with the Nightscout API. -After deploying your site, you can learn more about the Nightscout API looking at https://YOUR-SITE.com/api-docs.html or reviewing [swagger.yaml](swagger.yaml). +By default the `/entries` and `/treatments` APIs limit results to the the most recent 10 values from the last 2 days. +You can get many more results, by using the `count`, `date`, `dateString`, and `created_at` parameters, depending on the type of data you're looking for. + +#### Example Queries + +(replace `http://localhost:1337` with your base url, YOUR-SITE) + + * 100's: `http://localhost:1337/api/v1/entries.json?find[sgv]=100` + * BGs between 2 days: `http://localhost:1337/api/v1/entries/sgv.json?find[dateString][$gte]=2015-08-28&find[dateString][$lte]=2015-08-30` + * Juice Box corrections in a year: `http://localhost:1337/api/v1/treatments.json?count=1000&find[carbs]=15&find[eventType]=Carb+Correction&find[created_at][$gte]=2015` + * Boluses over 2U: `http://localhost:1337/api/v1/treatments.json?find[insulin][$gte]=2` + +The API is Swagger enabled, so you can generate client code to make working with the API easy. +To learn more about the Nightscout API, visit https://YOUR-SITE.com/api-docs.html or review [swagger.yaml](swagger.yaml). ## Environment From 01d0d314d8effa0c63de77686096798bd57df4b8 Mon Sep 17 00:00:00 2001 From: Ben West Date: Sun, 30 Aug 2015 00:21:25 -0700 Subject: [PATCH 711/937] expose to treatments With @MilosKazak and @jasoncalabrese. --- lib/query.js | 5 +++++ lib/treatments.js | 1 + 2 files changed, 6 insertions(+) diff --git a/lib/query.js b/lib/query.js index f157cc4ee1b..817e1f3a1be 100644 --- a/lib/query.js +++ b/lib/query.js @@ -167,9 +167,14 @@ function walk_prop (prop, typer) { return iter; } +function parseRegEx (str) { + return new RegExp(str); +} + // attach helpers and utilities to main function for testing walker.walk_prop = walk_prop; create.walker = walker; +create.parseRegEx = parseRegEx; create.default_options = default_options; // expose module as single high level function diff --git a/lib/treatments.js b/lib/treatments.js index 1b630ec7a06..b0327a3a658 100644 --- a/lib/treatments.js +++ b/lib/treatments.js @@ -108,6 +108,7 @@ storage.queryOpts = { insulin: parseInt , carbs: parseInt , glucose: parseInt + , notes: find_options.parseRegEx } , dateField: 'created_at' }; From 257cb4a58f03ffd7bc1d2cbb326eb5f65ffaddae Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Thu, 27 Aug 2015 00:11:22 +0200 Subject: [PATCH 712/937] extracted needed files --- README.md | 1 + env.js | 3 +++ lib/api/index.js | 2 ++ lib/api/treatments/index.js | 27 +++++++++++++++++++++++++-- lib/treatments.js | 34 +++++++++++++++++++++++++++------- 5 files changed, 58 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index fe85ee1a0b5..86766140681 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,7 @@ To learn more about the Nightscout API, visit https://YOUR-SITE.com/api-docs.htm * `BG_LOW` (`55`) - must be set using mg/dl units; the low BG outside the target range that is considered urgent * `ALARM_TYPES` (`simple` if any `BG_`* ENV's are set, otherwise `predict`) - currently 2 alarm types are supported, and can be used independently or combined. The `simple` alarm type only compares the current BG to `BG_` thresholds above, the `predict` alarm type uses highly tuned formula that forecasts where the BG is going based on it's trend. `predict` **DOES NOT** currently use any of the `BG_`* ENV's * `BASE_URL` - Used for building links to your sites api, ie pushover callbacks, usually the URL of your Nightscout site you may want https instead of http + * `TREATMENTS_AUTH` (`off`) - possible values `on` or `off`. When on device must be authenticated by entering `API_SECRET` to create treatments ### Core diff --git a/env.js b/env.js index c12c4c32a4a..cd6f103dd8f 100644 --- a/env.js +++ b/env.js @@ -26,6 +26,9 @@ function config ( ) { setMongo(); updateSettings(); + // require authorization for entering treatments + env.treatments_auth = readENV('TREATMENTS_AUTH',false); + return env; } diff --git a/lib/api/index.js b/lib/api/index.js index 268122db8ca..a409801161a 100644 --- a/lib/api/index.js +++ b/lib/api/index.js @@ -34,6 +34,8 @@ function create (env, ctx) { app.set('title', [app.get('name'), 'API', app.get('version')].join(' ')); + app.set('treatments_auth', env.treatments_auth); + // Start setting up routes if (app.enabled('api')) { // experiments diff --git a/lib/api/treatments/index.js b/lib/api/treatments/index.js index 71687d4aec1..cec3a375fab 100644 --- a/lib/api/treatments/index.js +++ b/lib/api/treatments/index.js @@ -24,17 +24,40 @@ function configure (app, wares, ctx) { function config_authed (app, api, wares, ctx) { - api.post('/treatments/', /*TODO: auth disabled for now, need to get login figured out... wares.verifyAuthorization, */ function(req, res) { + function post_response(req, res) { var treatment = req.body; ctx.treatments.create(treatment, function (err, created) { + if (err) + res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', err); + else + res.json(created); + }); + } + if (app.treatments_auth) + api.post('/treatments/', wares.verifyAuthorization, post_response); + else + api.post('/treatments/', post_response); + + api.delete('/treatments/:_id', wares.verifyAuthorization, function(req, res) { + ctx.treatments.remove(req.params._id, function ( ) { + res.json({ }); + }); + }); + + // update record + api.put('/treatments/', wares.verifyAuthorization, function(req, res) { + var data = req.body; + ctx.treatments.save(data, function (err, created) { if (err) { res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', err); + console.log('Error saving treatment'); + console.log(err); } else { res.json(created); + console.log('Treatment saved'); } }); }); - } if (app.enabled('api') && app.enabled('careportal')) { diff --git a/lib/treatments.js b/lib/treatments.js index 3adf50fbb87..94d7c3cba92 100644 --- a/lib/treatments.js +++ b/lib/treatments.js @@ -3,6 +3,19 @@ var find_options = require('./query'); function storage (env, ctx) { + var ObjectID = require('mongodb').ObjectID; + + // allow regexp search + // /api/v1/treatments.json?find[notes]=/sometext/i + function find_query (opts) { + var reg; + ['notes','eventType'].forEach(function(d) { + if (opts && opts.find && opts.find[d] && (reg=/\/(.*)\/(.*)/.exec(opts.find[d]))) { + opts.find[d] = new RegExp(reg[1],reg[2]); + } + }); + return opts; + } function create (obj, fn) { @@ -41,13 +54,25 @@ function storage (env, ctx) { .toArray(fn); } + function remove (_id, fn) { + return api( ).remove({ "_id": new ObjectID(_id) }, fn); + } + + function save (obj, fn) { + obj._id = new ObjectID(obj._id); + api().save(obj, fn); + } + + function api ( ) { return ctx.store.db.collection(env.treatments_collection); } api.list = list; api.create = create; - api.indexedFields = ['created_at', 'eventType', 'insulin', 'carbs', 'glucose']; + api.indexedFields = ['created_at', 'eventType', 'insulin', 'carbs', 'glucose', 'boluscalc.foods._id', 'notes']; + api.remove = remove; + api.save = save; return api; } @@ -59,11 +84,6 @@ function prepareData(obj) { , preBolusCarbs: '' }; - obj.glucose = Number(obj.glucose); - obj.carbs = Number(obj.carbs); - obj.insulin = Number(obj.insulin); - obj.preBolus = Number(obj.preBolus); - var eventTime; if (obj.eventTime) { eventTime = new Date(obj.eventTime); @@ -94,7 +114,7 @@ function prepareData(obj) { deleteIfEmpty('notes'); deleteIfEmpty('preBolus'); - if (obj.glucose === 0 || isNaN(obj.glucose)) { + if (!obj.glucose || obj.glucose === 0) { delete obj.glucose; delete obj.glucoseType; delete obj.units; From 5951797c4a0c086c195dbaf6ded524b31a703905 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Thu, 27 Aug 2015 15:06:52 +0200 Subject: [PATCH 713/937] added report files --- static/report/index.html | 245 ++++++++++ static/report/js/calibrations.js | 279 +++++++++++ static/report/js/dailystats.js | 116 +++++ static/report/js/daytoday.js | 367 ++++++++++++++ static/report/js/flotcandle.js | 85 ++++ static/report/js/glucosedistribution.js | 99 ++++ static/report/js/hourlystats.js | 106 +++++ static/report/js/percentile.js | 152 ++++++ static/report/js/report.js | 608 ++++++++++++++++++++++++ static/report/js/success.js | 178 +++++++ static/report/js/time.js | 318 +++++++++++++ static/report/js/treatments.js | 154 ++++++ 12 files changed, 2707 insertions(+) create mode 100644 static/report/index.html create mode 100644 static/report/js/calibrations.js create mode 100644 static/report/js/dailystats.js create mode 100644 static/report/js/daytoday.js create mode 100644 static/report/js/flotcandle.js create mode 100644 static/report/js/glucosedistribution.js create mode 100644 static/report/js/hourlystats.js create mode 100644 static/report/js/percentile.js create mode 100644 static/report/js/report.js create mode 100644 static/report/js/success.js create mode 100644 static/report/js/time.js create mode 100644 static/report/js/treatments.js diff --git a/static/report/index.html b/static/report/index.html new file mode 100644 index 00000000000..d04be3aac72 --- /dev/null +++ b/static/report/index.html @@ -0,0 +1,245 @@ + + + + Nightscout reporting + + + + +

    Nightscout

    +
      + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + From: + To: + Today + Last 2 days + Last 3 days + Last week + Last 2 weeks + Last month + Last 3 months +
    + Category: + Subcategory: + Name: +
    + + + Food: +
    + Notes contain: +
    + Event type contains: +
    + Mo + Tu + We + Th + Fr + Sa + Su +
    + Target bg range bottom: + + top: + +
    + +
    +
    +
    + Display: + Insulin + Carbs + Notes + Food + Raw + IOB + COB +  Size: + +
    + Scale: + + Linear + + Logarithmic +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/report/js/calibrations.js b/static/report/js/calibrations.js new file mode 100644 index 00000000000..c66275e4968 --- /dev/null +++ b/static/report/js/calibrations.js @@ -0,0 +1,279 @@ +function report_calibrations(datastorage,daystoshow,options) { + var padding = { top: 15, right: 15, bottom: 30, left: 70 }; + var treatments = []; + Object.keys(daystoshow).forEach(function (day) { + treatments = treatments.concat(datastorage[day].treatments.filter(function (t) { + if (t.eventType == "Sensor Start") return true; + if (t.eventType == "Sensor Change") return true; + return false; + })); + }); + + var cals = []; + Object.keys(daystoshow).forEach(function (day) { + cals = cals.concat(datastorage[day].cal); + }); + + var sgvs = []; + Object.keys(daystoshow).forEach(function (day) { + sgvs = sgvs.concat(datastorage[day].sgv); + }); + + var mbgs = []; + Object.keys(daystoshow).forEach(function (day) { + mbgs = mbgs.concat(datastorage[day].mbg); + }); + mbgs.forEach(function (mbg) { calibrations_calcmbg(mbg); }); + + + var events = treatments.concat(cals).concat(mbgs).sort(function(a, b) { return a.x - b.x; }); + + var colors = ['Aqua','Blue','Brown','Chartreuse','Coral','CornflowerBlue','DarkCyan','DarkMagenta','DarkOrange','Fuchsia','Green','Yellow']; + var colorindex = 0; + var html = ''; + var lastmbg = null; + for (var i=0; i'; + }; + + html += '
    '; + e.bgcolor = colors[colorindex]; + if (e.eventType) + html += ''+translate(e.eventType)+':
    '; + else if (typeof e.device !== 'undefined') { + html += ' '; + html += 'MBG: ' + e.y + ' Raw: '+e.raw+'
    '; + lastmbg = e; + e.cals = []; + e.checked = false; + } else if (typeof e.scale !== 'undefined') { + html += 'CAL: ' + ' Scale: ' + e.scale.toFixed(2) + ' Intercept: ' + e.intercept.toFixed(0) + ' Slope: ' + e.slope.toFixed(2) + '
    '; + if (lastmbg) lastmbg.cals.push(e); + } else html += JSON.stringify(e); + html += '
    '; + + $('#calibrations-list').html(html); + + // select last 3 mbgs + var maxcals = 3; + for (var i=events.length-1; i>0; i--) { + if (typeof events[i].device !== 'undefined') { + events[i].checked = true; + $('#calibrations-'+i).prop('checked',true); + if (--maxcals<1) break; + } + } + calibrations_drawelements(); + + $('.calibrations-checkbox').change(calibrations_checkboxevent); + + function calibrations_checkboxevent(event) { + var index = $(this).attr('index'); + events[index].checked = $(this).is(':checked'); + calibrations_drawelements(); + event.preventDefault(); + } + + function calibrations_drawelements() { + calibrations_drawChart(); + for (var i=0; i5*60*1000) { + console.log('Last SGV too old for MBG. Time diff: '+((mbg.x-lastsgv.x)/1000/60).toFixed(1)+' min',mbg); + } else { + mbg.raw = lastsgv.filtered || lastsgv.unfiltered; + } + } else { + console.log('Last entry not found for MBG ',mbg); + } + } + + function calibrations_drawmbg(mbg,color) { + if (mbg.raw) { + calibration_context.append('circle') + .attr('cx', xScale2(mbg.y) + padding.left) + .attr('cy', yScale2(mbg.raw) + padding.top) + .attr('fill', color) + .style('opacity', 1) + .attr('stroke-width', 1) + .attr('stroke', 'black') + .attr('r', 5); + } + } + + function calibrations_findlatest(date,storage) { + var last = null; + var time = date.getTime(); + for (var i=0; i time) + return last; + last = storage[i]; + } + return last; + } + +} diff --git a/static/report/js/dailystats.js b/static/report/js/dailystats.js new file mode 100644 index 00000000000..18df7786ea0 --- /dev/null +++ b/static/report/js/dailystats.js @@ -0,0 +1,116 @@ + function report_dailystats(datastorage,daystoshow,options) { + var todo = []; +// var data = o[0]; +// var days = 7; +// var config = { low: convertBg(low), high: convertBg(high) }; // options.targetLow options.targetHigh +// var sevendaysago = Date.now() - days.days(); + var report = $("#dailystats-report"); + var minForDay, maxForDay; + var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec"]; + +// data = data.filter(function(record) { +// return "bgValue" in record && /\d+/.test(record.bgValue.toString()); +// }).filter(function(record) { +// return record.displayTime > sevendaysago; +// }); + report.empty(); + var table = $(''); + report.append(table); + var thead = $(""); + $("").appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + thead.appendTo(table); + + Object.keys(daystoshow).forEach(function (day) { + var tr = $(""); + var dayInQuestion = new Date(day); + + var daysRecords = datastorage[day].statsrecords; + + if (daysRecords.length == 0) { + $("").appendTo(tr); + $('').appendTo(tr); + table.append(tr); + return;; + } + + minForDay = daysRecords[0].sgv; + maxForDay = daysRecords[0].sgv; + var stats = daysRecords.reduce(function(out, record) { + record.sgv = parseFloat(record.sgv); + if (record.sgv < options.targetLow) { + out.lows++; + } else if (record.sgv < options.targetHigh) { + out.normal++; + } else { + out.highs++; + } + if (minForDay > record.sgv) minForDay = record.sgv; + if (maxForDay < record.sgv) maxForDay = record.sgv; + return out; + }, { + lows: 0, + normal: 0, + highs: 0 + }); + var bgValues = daysRecords.map(function(r) { return r.sgv; }); + $("").appendTo(tr); + + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + + table.append(tr); + var inrange = [ + { + label: translate('Low'), + data: Math.floor(stats.lows * 1000 / daysRecords.length) / 10 + }, + { + label: translate('In range'), + data: Math.floor(stats.normal * 1000 / daysRecords.length) / 10 + }, + { + label: translate('High'), + data: Math.floor(stats.highs * 1000 / daysRecords.length) / 10 + } + ]; + $.plot( + "#dailystat-chart-" + day.toString(), + inrange, + { + series: { + pie: { + show: true + } + }, + colors: ["#f88", "#8f8", "#ff8"] + } + ); + }); + + setTimeout(function() { + todo.forEach(function(fn) { + fn(); + }); + }, 50); + } \ No newline at end of file diff --git a/static/report/js/daytoday.js b/static/report/js/daytoday.js new file mode 100644 index 00000000000..371628d15dc --- /dev/null +++ b/static/report/js/daytoday.js @@ -0,0 +1,367 @@ + function report_daytoday(datastorage,daystoshow,options) { + var translate = Nightscout.language.translate; + + var padding = { top: 15, right: 15, bottom: 30, left: 35 }; + var + FORMAT_TIME_12 = '%I' + , FORMAT_TIME_24 = '%H'; + + for (day in daystoshow) { + drawChart(day,datastorage[day],options); + } + + function getTimeFormat() { + var timeFormat = FORMAT_TIME_12; + if (serverSettings.defaults.timeFormat == '24') { + timeFormat = FORMAT_TIME_24; + } + return timeFormat; + } + + function drawChart(day,data,options) { + var tickValues + , charts + , context + , xScale2, yScale2 + , yInsulinScale, yCarbsScale + , xAxis2, yAxis2 + , dateFn = function (d) { return new Date(d.date) } + , foodtexts = 0; + + // Tick Values + if (options.scale == SCALE_LOG) { + if (serverSettings.units == 'mmol') { + tickValues = [ + 2.0 + , 3.0 + , options.targetLow + , 6.0 + , 8.0 + , options.targetHigh + , 16.0 + , 22.0 + ]; + } else { + tickValues = [ + 40 + , 60 + , options.targetLow + , 120 + , 160 + , options.targetHigh + , 250 + , 400 + ]; + } + } else { + if (serverSettings.units == 'mmol') { + tickValues = [ + 2.0 + , 4.0 + , 6.0 + , 8.0 + , 10.0 + , 12.0 + , 14.0 + , 16.0 + , 18.0 + , 20.0 + , 22.0 + ]; + } else { + tickValues = [ + 40 + , 80 + , 120 + , 160 + , 200 + , 240 + , 280 + , 320 + , 360 + , 400 + ]; + } + } + + // create svg and g to contain the chart contents + charts = d3.select('#'+containerprefix+day).html( + ''+ + localeDate(day)+ + '
    ' + ).append('svg'); + + charts.append("rect") + .attr("width", "100%") + .attr("height", "100%") + .attr("fill", "WhiteSmoke"); + + context = charts.append('g'); + + // define the parts of the axis that aren't dependent on width or height + xScale2 = d3.time.scale() + .domain(d3.extent(data.sgv, function (d) { return d.date; })); + + if (options.scale == SCALE_LOG) { + yScale2 = d3.scale.log() + .domain([scaleBg(36), scaleBg(420)]); + } else { + yScale2 = d3.scale.linear() + .domain([scaleBg(36), scaleBg(420)]); + } + + yInsulinScale = d3.scale.linear() + .domain([0, options.maxInsulinValue*2]); + + yCarbsScale = d3.scale.linear() + .domain([0, options.maxCarbsValue*1.25]); + + xAxis2 = d3.svg.axis() + .scale(xScale2) + .tickFormat(d3.time.format(getTimeFormat(true))) + .ticks(24) + .orient('bottom'); + + yAxis2 = d3.svg.axis() + .scale(yScale2) + .tickFormat(d3.format('d')) + .tickValues(tickValues) + .orient('left'); + + // get current data range + var dataRange = d3.extent(data.sgv, dateFn); + + // get the entire container height and width subtracting the padding + var chartWidth = options.width - padding.left - padding.right; + var chartHeight = options.height - padding.top - padding.bottom; + + //set the width and height of the SVG element + charts.attr('width', options.width) + .attr('height', options.height); + + // ranges are based on the width and height available so reset + xScale2.range([0, chartWidth]); + yScale2.range([chartHeight,0]); + yInsulinScale.range([chartHeight,0]); + yCarbsScale.range([chartHeight,0]); + + // add target BG rect + context.append('rect') + .attr('x', xScale2(dataRange[0])+padding.left) + .attr('y', yScale2(options.targetHigh)+padding.top) + .attr('width', xScale2(dataRange[1]- xScale2(dataRange[0]))) + .attr('height', yScale2(options.targetLow)-yScale2(options.targetHigh)) + .style('fill', '#D6FFD6') + .attr('stroke', 'grey'); + + // create the x axis container + context.append('g') + .attr('class', 'x axis'); + + // create the y axis container + context.append('g') + .attr('class', 'y axis'); + + context.select('.y') + .attr('transform', 'translate(' + (/*chartWidth + */ padding.left) + ',' + padding.top + ')') + .style('stroke', 'black') + .style('shape-rendering', 'crispEdges') + .style('fill', 'none') + .call(yAxis2); + + // if first run then just display axis with no transition + context.select('.x') + .attr('transform', 'translate(' + padding.left + ',' + (chartHeight + padding.top) + ')') + .style('stroke', 'black') + .style('shape-rendering', 'crispEdges') + .style('fill', 'none') + .call(xAxis2); + + for (var li in tickValues) { + context.append('line') + .attr('class', 'high-line') + .attr('x1', xScale2(dataRange[0])+padding.left) + .attr('y1', yScale2(tickValues[li])+padding.top) + .attr('x2', xScale2(dataRange[1])+padding.left) + .attr('y2', yScale2(tickValues[li])+padding.top) + .style('stroke-dasharray', ('3, 3')) + .attr('stroke', 'grey'); + } + + // bind up the context chart data to an array of circles + var contextCircles = context.selectAll('circle') + .data(data.sgv); + + function prepareContextCircles(sel) { + var badData = []; + sel.attr('cx', function (d) { + return xScale2(d.date) + padding.left; + }) + .attr('cy', function (d) { + if (isNaN(d.sgv)) { + badData.push(d); + return yScale2(scaleBg(450) + padding.top); + } else { + return yScale2(d.sgv) + padding.top; + } + }) + .attr('fill', function (d) { + if (d.color == 'gray' && !options.raw) { + return 'transparent'; + } + return d.color; + }) + .style('opacity', function (d) { 0.5 }) + .attr('stroke-width', function (d) {if (d.type == 'mbg') return 2; else return 0; }) + .attr('stroke', function (d) { return 'black'; }) + .attr('r', function(d) { if (d.type == 'mbg') { return 4; } else { return 2; }}); + + if (badData.length > 0) { + console.warn("Bad Data: isNaN(sgv)", badData); + } + return sel; + } + + // if new circle then just display + prepareContextCircles(contextCircles.enter().append('circle')); + + contextCircles.exit() + .remove(); + + var to = moment(day).add(1, 'days') //.add(new Date().getTimezoneOffset(), 'minutes'); + var from = moment(day); //.add(new Date().getTimezoneOffset(), 'minutes'); + var iobpolyline = '', cobpolyline = ''; + for (var dt=from; dt < to; dt.add(5, 'minutes')) { + if (options.iob) { + var iob = Nightscout.plugins('iob').calcTotal(data.treatments,Nightscout.profile,dt.toDate()).iob; + if (dt!=from) { + iobpolyline += ', '; + } + iobpolyline += (xScale2(dt) + padding.left) + ',' + (yInsulinScale(iob) + padding.top) + ' '; + } + if (options.cob) { + var cob = Nightscout.plugins('cob').cobTotal(data.treatments,Nightscout.profile,dt.toDate()).cob; +console.log(dt.toDate().toLocaleTimeString(),cob); + if (dt!=from) { + cobpolyline += ', '; + } + cobpolyline += (xScale2(dt.toDate()) + padding.left) + ',' + (yCarbsScale(cob) + padding.top) + ' '; + } + } + if (options.iob) { + context.append('polyline') + .attr('stroke', 'blue') + .attr('opacity', '0.5') + .attr('fill-opacity', '0.1') + .attr('points',iobpolyline); + } + if (options.cob) { + context.append('polyline') + .attr('stroke', 'red') + .attr('opacity', '0.5') + .attr('fill-opacity', '0.1') + .attr('points',cobpolyline); + } + + data.treatments.forEach(function (treatment) { + if (treatment.boluscalc && treatment.boluscalc.foods && treatment.boluscalc.foods.length > 0 || treatment.notes) { + var lastfoodtext = foodtexts; + var drawpointer = false; + if (treatment.boluscalc && treatment.boluscalc.foods && treatment.boluscalc.foods.length > 0 && options.food) { + var foods = treatment.boluscalc.foods; + for (var fi=0; fi y){ + ctx.beginPath(); + ctx.moveTo(lineX,y); + ctx.lineTo(lineX,lowY); + ctx.closePath(); + ctx.stroke(); + } + } + } + + $.plot.plugins.push({ + init: init, + options: options, + name: 'candle', + version: '1.0' + }); +})(jQuery); \ No newline at end of file diff --git a/static/report/js/glucosedistribution.js b/static/report/js/glucosedistribution.js new file mode 100644 index 00000000000..16ac13d5d0b --- /dev/null +++ b/static/report/js/glucosedistribution.js @@ -0,0 +1,99 @@ + function report_glucosedistribution(datastorage,daystoshow,options) { + var Statician = ss; + var report = $("#glucosedistribution-report"); + report.empty(); + var minForDay, maxForDay; + var stats = []; + var table = $('
    '+translate('Date')+''+translate('Low')+''+translate('Normal')+''+translate('High')+''+translate('Readings')+''+translate('Min')+''+translate('Max')+''+translate('StDev')+''+translate('25%')+''+translate('Median')+''+translate('75%')+'
    ").appendTo(tr); + $("" + localeDate(dayInQuestion) + "'+translate('No data available')+'
    " + localeDate(dayInQuestion) + "" + Math.floor((100 * stats.lows) / daysRecords.length) + "%" + Math.floor((100 * stats.normal) / daysRecords.length) + "%" + Math.floor((100 * stats.highs) / daysRecords.length) + "%" + daysRecords.length +"" + minForDay +"" + maxForDay +"" + Math.floor(ss.standard_deviation(bgValues)) + "" + ss.quantile(bgValues, 0.25) + "" + ss.quantile(bgValues, 0.5) + "" + ss.quantile(bgValues, 0.75) + "
    '); + var thead = $(""); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + thead.appendTo(table); + + var data = []; + var days = 0; + Object.keys(daystoshow).forEach(function (day) { + data = data.concat(datastorage[day].statsrecords); + days++; + }); + + $('#glucosedistribution-days').text(days+' '+translate('days total')); + + ['Low', 'Normal', 'High'].forEach(function(range) { + var tr = $(""); + var rangeRecords = data.filter(function(r) { + if (range == "Low") { + return r.sgv > 0 && r.sgv < options.targetLow; + } else if (range == "Normal") { + return r.sgv >= options.targetLow && r.sgv < options.targetHigh; + } else { + return r.sgv >= options.targetHigh; + } + }); + stats.push(rangeRecords.length); + rangeRecords.sort(function(a,b) { + return a.sgv - b.sgv; + }); + var localBgs = rangeRecords.map(function(r) { return r.sgv; }).filter(function(bg) { return !!bg; }); + + var midpoint = Math.floor(rangeRecords.length / 2); + //var statistics = ss.(new Statician(rangeRecords.map(function(r) { return r.sgv; }))).stats; + + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + if (rangeRecords.length > 0) { + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + } else { + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + } + + table.append(tr); + }); + + var tr = $(""); + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + if (data.length > 0) { + var localBgs = data.map(function(r) { return r.sgv; }).filter(function(bg) { return !!bg; }); + var mgDlBgs = data.map(function(r) { return r.bgValue; }).filter(function(bg) { return !!bg; }); + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + } else { + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + } + table.append(tr); + report.append(table); + + setTimeout(function() { + $.plot( + "#glucosedistribution-overviewchart", + stats, + { + series: { + pie: { + show: true + } + }, + colors: ["#f88", "#8f8", "#ff8"] + } + ); + }); + } diff --git a/static/report/js/hourlystats.js b/static/report/js/hourlystats.js new file mode 100644 index 00000000000..23dd1ce6949 --- /dev/null +++ b/static/report/js/hourlystats.js @@ -0,0 +1,106 @@ + function report_hourlystats(datastorage,daystoshow,options) { + var report = $("#hourlystats-report"); + var stats = []; + var pivotedByHour = {}; + + var data = []; + var days = 0; + Object.keys(daystoshow).forEach(function (day) { + data = data.concat(datastorage[day].statsrecords); + days++; + }); + + for (var i = 0; i < 24; i++) { + pivotedByHour[i] = []; + } + data.forEach(function(record) { + var d = new Date(record.displayTime); + pivotedByHour[d.getHours()].push(record); + }); + var table = $("
    '+translate('Range')+''+translate('% of Readings')+''+translate('# of Readings')+''+translate('Mean')+''+translate('Median')+''+translate('Standard Deviation')+''+translate('A1c estimation*')+'
    " + translate(range) + ": " + Math.floor(100 * rangeRecords.length / data.length) + "%" + rangeRecords.length + "" + Math.floor(10*Statician.mean(localBgs))/10 + "" + rangeRecords[midpoint].sgv + "" + Math.floor(Statician.standard_deviation(localBgs)*10)/10 + " N/AN/AN/A
    "+translate("Overall")+": " + data.length + "" + Math.round(10*ss.mean(localBgs))/10 + "" + Math.round(10*ss.quantile(localBgs, 0.5))/10+ "" + Math.round(ss.standard_deviation(localBgs)*10)/10 + "
    " + Math.round(10*(ss.mean(mgDlBgs)+46.7)/28.7)/10 + "%DCCT | " +Math.round(((ss.mean(mgDlBgs)+46.7)/28.7 - 2.15)*10.929) + "IFCC
    N/AN/AN/AN/A
    "); + var thead = $(""); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + thead.appendTo(table); + + [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23].forEach(function(hour) { + var tr = $(""); + var display = hour % 12; + if (hour === 0) { + display = "12"; + } + display += ":00 "; + if (hour >= 12) { + display += "PM"; + } else { + display += "AM"; + } + + var avg = Math.floor(pivotedByHour[hour].map(function(r) { return r.sgv; }).reduce(function(o,v){ return o+v; }, 0) / pivotedByHour[hour].length); + var d = new Date(hour.hours()); + // d.setHours(hour); + // d.setMinutes(0); + // d.setSeconds(0); + // d.setMilliseconds(0); + + var dev = ss.standard_deviation(pivotedByHour[hour].map(function(r) { return r.sgv; })); + stats.push([ + new Date(d), + ss.quantile(pivotedByHour[hour].map(function(r) { return r.sgv; }), 0.25), + ss.quantile(pivotedByHour[hour].map(function(r) { return r.sgv; }), 0.75), + avg - dev, + avg + dev + // Math.min.apply(Math, pivotedByHour[hour].map(function(r) { return r.sgv; })), + // Math.max.apply(Math, pivotedByHour[hour].map(function(r) { return r.sgv; })) + ]); + var tmp; + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + table.append(tr); + }); + + report.empty(); + report.append(table); + + $.plot( + "#hourlystats-overviewchart", + [{ + data:stats, + candle:true + }], + { + series: { + candle: true, + lines: false //Somehow it draws lines if you dont disable this. Should investigate and fix this ;) + }, + xaxis: { + mode: "time", + timeFormat: "%h:00", + min: 0, + max: (24).hours()-(1).seconds() + }, + yaxis: { + min: 0, + max: serverSettings.units == 'mmol' ? 22: 400, + show: true + }, + grid: { + show: true + } + } + ); + } \ No newline at end of file diff --git a/static/report/js/percentile.js b/static/report/js/percentile.js new file mode 100644 index 00000000000..3a22cf19519 --- /dev/null +++ b/static/report/js/percentile.js @@ -0,0 +1,152 @@ + function report_percentile(datastorage,daystoshow,options) { + var Statician = ss; + var window = 30; //minute-window should be a divisor of 60 + + var data = []; + var days = 0; + Object.keys(daystoshow).forEach(function (day) { + data = data.concat(datastorage[day].statsrecords); + days++; + }); + + var bins = []; + for (hour = 0; hour < 24; hour++) { + for (minute = 0; minute < 60; minute = minute + window) { + var date = new Date(); + date.setHours(hour); + date.setMinutes(minute); + var readings = data.filter(function(record) { + var recdate = new Date(record.displayTime); + return recdate.getHours() == hour && recdate.getMinutes() >= minute && + recdate.getMinutes() < minute + window;; + }); + readings = readings.map(function(record) { + return record.sgv; + }); + bins.push([date, readings]); + //console.log(date + " - " + readings.length); + //readings.forEach(function(x){console.log(x)}); + } + } + dat10 = bins.map(function(bin) { + return [bin[0], ss.quantile(bin[1], 0.1)]; + }); + dat25 = bins.map(function(bin) { + return [bin[0], ss.quantile(bin[1], 0.25)]; + }); + dat50 = bins.map(function(bin) { + return [bin[0], ss.quantile(bin[1], 0.5)]; + }); + dat75 = bins.map(function(bin) { + return [bin[0], ss.quantile(bin[1], 0.75)]; + }); + dat90 = bins.map(function(bin) { + return [bin[0], ss.quantile(bin[1], 0.9)]; + }); + high = options.targetHigh; + low = options.targetLow; + //dat50.forEach(function(x){console.log(x[0] + " - " + x[1])}); + $.plot( + "#percentile-chart", [{ + label: translate("Median"), + data: dat50, + id: 'c50', + color: "#000000", + points: { + show: false + }, + lines: { + show: true, + //fill: true + } + }, { + label: "25%/75% "+translate("percentile"), + data: dat25, + id: 'c25', + color: "#000055", + points: { + show: false + }, + lines: { + show: true, + fill: true + }, + fillBetween: 'c50' + }, { + data: dat75, + id: 'c75', + color: "#000055", + points: { + show: false + }, + lines: { + show: true, + fill: true + }, + fillBetween: 'c50' + }, { + label: "10%/90% "+translate("percentile"), + data: dat10, + id: 'c10', + color: "#a0a0FF", + points: { + show: false + }, + lines: { + show: true, + fill: true + }, + fillBetween: 'c25' + }, { + data: dat90, + id: 'c90', + color: "#a0a0FF", + points: { + show: false + }, + lines: { + show: true, + fill: true + }, + fillBetween: 'c75' + }, { + label: translate("High"), + data: [], + color: '#FFFF00', + }, { + label: translate("Low"), + data: [], + color: '#FF0000', + }], { + xaxis: { + mode: "time", + timezone: "browser", + timeformat: "%H:%M", + tickColor: "#555", + }, + yaxis: { + min: 0, + max: serverSettings.units == 'mmol' ? 22: 400, + tickColor: "#555", + }, + grid: { + markings: [{ + color: '#FF0000', + lineWidth: 2, + yaxis: { + from: low, + to: low + } + }, { + color: '#FFFF00', + lineWidth: 2, + yaxis: { + from: high, + to: high + } + }], + //hoverable: true + } + } + ); + } diff --git a/static/report/js/report.js b/static/report/js/report.js new file mode 100644 index 00000000000..b63828ab9aa --- /dev/null +++ b/static/report/js/report.js @@ -0,0 +1,608 @@ + 'use strict'; + + var translate = Nightscout.language.translate; + + var maxInsulinValue = 0 + ,maxCarbsValue = 0; + var containerprefix = 'chart-'; + var maxdays = 3 * 31; + var datastorage = {}; + var daystoshow = {}; + + var targetBGdefault = { + "mg/dl": { low: 72, high: 180 }, + "mmol": { low: 4, high: 10 } + }; + + var + ONE_MIN_IN_MS = 60000 + , SIX_MINS_IN_MS = 360000; + + var + SCALE_LINEAR = 0 + , SCALE_LOG = 1; + + + var categories = []; + var foodlist = []; + + + + function rawIsigToRawBg(entry, cal) { + var raw = 0 + , unfiltered = parseInt(entry.unfiltered) || 0 + , filtered = parseInt(entry.filtered) || 0 + , sgv = entry.y + , scale = parseFloat(cal.scale) || 0 + , intercept = parseFloat(cal.intercept) || 0 + , slope = parseFloat(cal.slope) || 0; + + if (slope == 0 || unfiltered == 0 || scale == 0) { + raw = 0; + } else if (filtered == 0 || sgv < 40) { + raw = scale * (unfiltered - intercept) / slope; + } else { + var ratio = scale * (filtered - intercept) / slope / sgv; + raw = scale * ( unfiltered - intercept) / slope / ratio; + } + return Math.round(raw); + } + + function sgvToColor(sgv,options) { + var color = 'darkgreen'; + + if (sgv > options.targetHigh) { + color = 'red'; + } else if (sgv < options.targetLow) { + color = 'red'; + } + + return color; + } + + $('#info').html(''+translate('Loading profile')+' ...'); + $.ajax('/api/v1/profile', { + success: function (record) { + Nightscout.profile.loadData(record); + } + }).done(function() { + $('#info').html(''+translate('Loading food database')+' ...'); + $.ajax('/api/v1/food/regular.json', { + success: function (records) { + records.forEach(function (r) { + foodlist.push(r); + if (r.category && !categories[r.category]) categories[r.category] = {}; + if (r.category && r.subcategory) categories[r.category][r.subcategory] = true; + }); + fillForm(); + } + }).done(function() { + $('#info').html(''); + $('.presetdates').click(function(e) { var days = $(this).attr('days'); setDataRange(e,days); }); + + $('#rp_show').click(show); + $('#rp_food').change(function (event) { + event.preventDefault(); + $('#rp_enablefood').prop('checked',true); + }); + $('#rp_notes').change(function (event) { + event.preventDefault(); + $('#rp_enablenotes').prop('checked',true); + }); + + $('#rp_targetlow').val(targetBGdefault[serverSettings.units].low); + $('#rp_targethigh').val(targetBGdefault[serverSettings.units].high); + + $('.menutab').click(switchtab); + + setDataRange(null,7); + + }); + }); + + function show(event) { + var options = { + width: 1000, + height: 300, + targetLow: 3.5, + targetHigh: 10, + raw: true, + notes: true, + food: true, + insulin: true, + carbs: true, + iob : true, + cob : true, + scale: SCALE_LINEAR + }; + + options.targetLow = parseFloat($('#rp_targetlow').val().replace(',','.')); + options.targetHigh = parseFloat($('#rp_targethigh').val().replace(',','.')); + options.raw = $('#rp_optionsraw').is(':checked'); + options.iob = $('#rp_optionsiob').is(':checked'); + options.cob = $('#rp_optionscob').is(':checked'); + options.notes = $('#rp_optionsnotes').is(':checked'); + options.food = $('#rp_optionsfood').is(':checked'); + options.insulin = $('#rp_optionsinsulin').is(':checked'); + options.carbs = $('#rp_optionscarbs').is(':checked'); + options.scale = $('#rp_linear').is(':checked') ? SCALE_LINEAR : SCALE_LOG; + options.width = parseInt($('#rp_size :selected').attr('x')); + options.height = parseInt($('#rp_size :selected').attr('y')); + + var matchesneeded = 0; + + // date range + function datefilter() { + if ($('#rp_enabledate').is(':checked')) { + matchesneeded++; + var from = moment($('#rp_from').val()); + var to = moment($('#rp_to').val()); + + while (from <= to) { + if (daystoshow[from.format('YYYY-MM-DD')]) daystoshow[from.format('YYYY-MM-DD')]++; + else daystoshow[from.format('YYYY-MM-DD')] = 1; + from.add(1, 'days'); + } + } + console.log('Dayfilter: ',daystoshow); + foodfilter(); + } + + //food filter + function foodfilter() { + if ($('#rp_enablefood').is(':checked')) { + matchesneeded++; + var _id = $('#rp_food').val(); + if (_id) { + var treatmentData; + var tquery = '?find[boluscalc.foods._id]='+_id; + $.ajax('/api/v1/treatments.json'+tquery, { + success: function (xhr) { + treatmentData = xhr.map(function (treatment) { + return moment(treatment.mills).format('YYYY-MM-DD'); + }); + // unique it + treatmentData = $.grep(treatmentData, function(v, k){ + return $.inArray(v ,treatmentData) === k; + }); + treatmentData.sort(function(a, b) { return a > b; }); + } + }).done(function () { + console.log('Foodfilter: ',treatmentData); + for (var d=0; d b; }); + } + }).done(function () { + console.log('Notesfilter: ',treatmentData); + for (var d=0; d b; }); + } + }).done(function () { + console.log('Eventtypefilter: ',treatmentData); + for (var d=0; d'+translate('Loading')+' ...'); + $('#charts').html(''); + for (var d in daystoshow) { + if (daystoshow[d]==matchesneeded) { + if (displayeddays < maxdays) { + $('#charts').append($('
    ')); + loadData(d,options); + displayeddays++; + } else { + $('#charts').append($('
    '+d+' '+translate('not displayed')+'.
    ')); + } + } else { + delete daystoshow[d]; + } + } + if (displayeddays==0) { + $('#charts').html(''+translate('Result is empty')+''); + $('#info').empty(); + } + } + + $('#rp_show').css('display','none'); + daystoshow = {}; + datefilter(); + if (event) event.preventDefault(); + } + + function showreports(options) { + // wait for all loads + for (var d in daystoshow) { + if (!datastorage[d]) return; // all data not loaded yet + } + + ['daytoday','dailystats','percentile','glucosedistribution','hourlystats','success','treatments','calibrations'].forEach(function (chart) { + // jquery plot doesn't draw to hidden div + $('#'+chart+'-placeholder').css('display',''); + eval('report_'+chart+'(datastorage,daystoshow,options);'); + if (!$('#'+chart).hasClass('selected')) + $('#'+chart+'-placeholder').css('display','none'); + }); + + $('#info').html(''); + $('#rp_show').css('display',''); + } + + function setDataRange(event,days) { + $('#rp_to').val(moment().format('YYYY-MM-DD')); + $('#rp_from').val(moment().add(-days+1, 'days').format('YYYY-MM-DD')); + + if (event) event.preventDefault(); + } + + function switchtab(event) { + var id = $(this).attr('id'); + + $('.menutab').removeClass('selected'); + $('#'+id).addClass('selected'); + + $('.tabplaceholder').css('display','none'); + $('#'+id+'-placeholder').css('display',''); + + } + + function localeDate(day) { + var ret = + [translate("Sunday"),translate("Monday"),translate("Tuesday"),translate("Wednesday"),translate("Thursday"),translate("Friday"),translate("Saturday")][new Date(day).getDay()]; + ret += ' '; + ret += new Date(day).toLocaleDateString(); + return ret; + } + + function localeDateTime(day) { + var ret = new Date(day).toLocaleDateString() + ' ' + new Date(day).toLocaleTimeString(); + return ret; + } + + function loadData(day,options) { + // check for loaded data + if (datastorage[day] && day != moment().format('YYYY-MM-DD')) { + showreports(options); + return; + } + // patientData = [actual, predicted, mbg, treatment, cal, devicestatusData]; + var data = {}; + var cgmData = [] + , mbgData = [] + , treatmentData = [] + , calData = [] + ; + var dt = new Date(day); + var from = dt.getTime() + dt.getTimezoneOffset() * 60 * 1000; + var to = from + 1000 * 60 * 60 * 24; + var query = '?find[date][$gte]='+from+'&find[date][$lt]='+to+'&count=10000'; + + $('#'+containerprefix+day).html(''+translate('Loading CGM data of')+' '+day+' ...'); + $.ajax('/api/v1/entries.json'+query, { + success: function (xhr) { + xhr.forEach(function (element) { + if (element) { + if (element.mbg) { + mbgData.push({ + y: element.mbg + , x: element.date + , d: element.dateString + , device: element.device + }); + } else if (element.sgv) { + cgmData.push({ + y: element.sgv + , x: element.date + , d: element.dateString + , device: element.device + , filtered: element.filtered + , unfiltered: element.unfiltered + , noise: element.noise + , rssi: element.rssi + , sgv: element.sgv + }); + } else if (element.type == 'cal') { + calData.push({ + x: element.date + , d: element.dateString + , scale: element.scale + , intercept: element.intercept + , slope: element.slope + }); + } + } + }); + // sometimes cgm contains duplicates. uniq it. + data.sgv = cgmData.slice(); + data.sgv.sort(function(a, b) { return a.x - b.x; }); + var lastDate = 0; + data.sgv = data.sgv.filter(function(d) { + var ok = (lastDate + ONE_MIN_IN_MS) < d.x; + lastDate = d.x; + return ok; + }); + data.mbg = mbgData.slice(); + data.mbg.sort(function(a, b) { return a.x - b.x; }); + data.cal = calData.slice(); + data.cal.sort(function(a, b) { return a.x - b.x; }); + } + }).done(function () { + $('#'+containerprefix+day).html(''+translate('Loading treatments data of')+' '+day+' ...'); + var tquery = '?find[created_at][$gte]='+new Date(from).toISOString()+'&find[created_at][$lt]='+new Date(to).toISOString(); + $.ajax('/api/v1/treatments.json'+tquery, { + success: function (xhr) { + treatmentData = xhr.map(function (treatment) { + var timestamp = new Date(treatment.timestamp || treatment.created_at); + treatment.mills = timestamp.getTime(); + return treatment; + }); + data.treatments = treatmentData.slice(); + data.treatments.sort(function(a, b) { return a.mills - b.mills; }); + } + }).done(function () { + $('#'+containerprefix+day).html(''+translate('Processing data of')+' '+day+' ...'); + processData(data,day,options); + }); + + }); + } + + function processData(data,day,options) { + // treatments + data.treatments.forEach(function (d) { + if (parseFloat(d.insulin) > maxInsulinValue) maxInsulinValue = parseFloat(d.insulin); + if (parseFloat(d.carbs) > maxCarbsValue) maxCarbsValue = parseFloat(d.carbs); + }); + + var cal = data.cal[data.cal.length-1]; + var temp1 = [ ]; + if (cal) { + temp1 = data.sgv.map(function (entry) { + var noise = entry.noise || 0; + var rawBg = rawIsigToRawBg(entry, cal); + return { x: entry.x, date: new Date(entry.x - 2 * 1000), y: rawBg, sgv: scaleBg(rawBg), color: 'gray', type: 'rawbg', filtered: entry.filtered, unfiltered: entry.unfiltered } + }).filter(function(entry) { return entry.y > 0}); + } + var temp2 = data.sgv.map(function (obj) { + return { x: obj.x, date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), color: sgvToColor(scaleBg(obj.y),options), type: 'sgv', noise: obj.noise, filtered: obj.filtered, unfiltered: obj.unfiltered} + }); + data.sgv = [].concat(temp1, temp2); + + //Add MBG's also, pretend they are SGV's + data.sgv = data.sgv.concat(data.mbg.map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), color: 'red', type: 'mbg', device: obj.device } })); + + // make sure data range will be exactly 24h + var from = new Date(new Date(day).getTime() + (new Date().getTimezoneOffset()*60*1000)); + var to = new Date(from.getTime() + 1000 * 60 * 60 * 24); + data.sgv.push({ date: from, y: 40, sgv: 40, color: 'transparent', type: 'rawbg'}); + data.sgv.push({ date: to, y: 40, sgv: 40, color: 'transparent', type: 'rawbg'}); + + // clear error data. we don't need it to display them + data.sgv = data.sgv.filter(function (d) { + if (d.y < 39) return false; + return true; + }); + + //delete data.cal; + //delete data.mbg; + + // for other reports + data.statsrecords = data.sgv.filter(function(r) { + if (r.type) return r.type == 'sgv'; + else return true; + }).map(function (r) { + var ret = {}; + ret.sgv = parseFloat(r.sgv); + ret.bgValue = parseInt(r.y); + ret.displayTime = r.date; + return ret; + }); + + + datastorage[day] = data; + options.maxInsulinValue = maxInsulinValue; + options.maxCarbsValue = maxCarbsValue; + showreports(options); + } + + // Filtering food code + // ------------------- + var categories = []; + var foodlist = []; + var filter = { + category: '' + , subcategory: '' + , name: '' + }; + + function fillForm(event) { + $('#rp_category').empty().append(new Option(translate('(none)'),'')); + for (var s in categories) { + $('#rp_category').append(new Option(s,s)); + } + filter.category = ''; + fillSubcategories(); + + $('#rp_category').change(fillSubcategories); + $('#rp_subcategory').change(doFilter); + $('#rp_name').on('input',doFilter); + + if (event) event.preventDefault(); + return false; + } + + function fillSubcategories(event) { + if (event) { + event.preventDefault(); + } + filter.category = $('#rp_category').val(); + filter.subcategory = ''; + $('#rp_subcategory').empty().append(new Option(translate('(none)'),'')); + if (filter.category != '') { + for (var s in categories[filter.category]) { + $('#rp_subcategory').append(new Option(s,s)); + } + } + doFilter(); + } + + function doFilter(event) { + if (event) { + filter.category = $('#rp_category').val(); + filter.subcategory = $('#rp_subcategory').val(); + filter.name = $('#rp_name').val(); + } + $('#rp_food').empty(); + for (var i=0; i 0 ? (totalBG / closeBGs.length) : 450; + } + + var treatmentGlucose = null; + + if (treatment.glucose && isNaN(treatment.glucose)) { + console.warn('found an invalid glucose value', treatment); + } else { + if (treatment.glucose && treatment.units && serverSettings.units) { + if (treatment.units != serverSettings.units) { + console.info('found mismatched glucose units, converting ' + treatment.units + ' into ' + serverSettings.units, treatment); + if (treatment.units == 'mmol') { + //BG is in mmol and display in mg/dl + treatmentGlucose = Math.round(treatment.glucose * 18) + } else { + //BG is in mg/dl and display in mmol + treatmentGlucose = scaleBg(treatment.glucose); + } + } else { + treatmentGlucose = treatment.glucose; + } + } else if (treatment.glucose) { + //no units, assume everything is the same + console.warn('found an glucose value with any units, maybe from an old version?', treatment); + treatmentGlucose = treatment.glucose; + } + } + + return treatmentGlucose || scaleBg(calcBGByTime(treatment.mills)); + } + + function scaleBg(bg) { + if (serverSettings.units === 'mmol') { + return Nightscout.units.mgdlToMMOL(bg); + } else { + return bg; + } + } diff --git a/static/report/js/success.js b/static/report/js/success.js new file mode 100644 index 00000000000..85311b42624 --- /dev/null +++ b/static/report/js/success.js @@ -0,0 +1,178 @@ +function report_success(datastorage,daystoshow,options) { + var low = parseInt(options.targetLow), + high = parseInt(options.targetHigh); + + var data = []; + var days = 0; + Object.keys(daystoshow).forEach(function (day) { + data = data.concat(datastorage[day].statsrecords); + days++; + }); + + var now = Date.now(); + var period = (7).days(); + var firstDataPoint = data.reduce(function(min, record) { + return Math.min(min, record.displayTime); + }, Number.MAX_VALUE); + if (firstDataPoint < 1390000000000) firstDataPoint = 1390000000000; + var quarters = Math.floor((Date.now() - firstDataPoint) / period); + + var grid = $("#success-grid"); + grid.empty(); + var table = $("
    '+translate('Time')+''+translate('Readings')+''+translate('Average')+''+translate('Min')+''+translate('Quartile')+' 25'+translate('Median')+''+translate('Quartile')+' 75'+translate('Max')+''+translate('Standard Deviation')+'
    " + display + "" + pivotedByHour[hour].length + " (" + Math.floor(100 * pivotedByHour[hour].length / data.length) + "%)" + avg + "" + Math.min.apply(Math, pivotedByHour[hour].map(function(r) { return r.sgv; })) + "" + ((tmp=ss.quantile(pivotedByHour[hour].map(function(r) { return r.sgv; }), 0.25)) ? tmp.toFixed(1) : 0 ) + "" + ((tmp=ss.quantile(pivotedByHour[hour].map(function(r) { return r.sgv; }), 0.5)) ? tmp.toFixed(1) : 0 ) + "" + ((tmp=ss.quantile(pivotedByHour[hour].map(function(r) { return r.sgv; }), 0.75)) ? tmp.toFixed(1) : 0 ) + "" + Math.max.apply(Math, pivotedByHour[hour].map(function(r) { return r.sgv; })) + "" + Math.floor(dev*10)/10 + "
    "); + + if (quarters == 0) { + // insufficent data + grid.append("

    "+translate("There is not sufficient data to run this report. Select more days.")+"

    "); + return; + } + + var dim = function(n) { + var a = []; + for (i = 0; i < n; i++) { + a[i]=0; + } + return a; + } + var sum = function(a) { + return a.reduce(function(sum,v) { + return sum+v; + }, 0); + } + var averages = { + percentLow: 0, + percentInRange: 0, + percentHigh: 0, + standardDeviation: 0, + lowerQuartile: 0, + upperQuartile: 0, + average: 0 + }; + try { + quarters = dim(quarters).map(function(blank, n) { + var starting = new Date(now - (n+1) * period), + ending = new Date(now - n * period); + return { + starting: starting, + ending: ending, + records: data.filter(function(record) { + return record.displayTime > starting && record.displayTime <= ending; + }) + }; + }).filter(function(quarter) { + return quarter.records.length > 0; + }).map(function(quarter, ix, all) { + var bgValues = quarter.records.map(function(record) { + return record.sgv; + }); + quarter.standardDeviation = ss.standard_deviation(bgValues); + quarter.average = bgValues.length > 0? (sum(bgValues) / bgValues.length): "N/A"; + quarter.lowerQuartile = ss.quantile(bgValues, 0.25); + quarter.upperQuartile = ss.quantile(bgValues, 0.75); + quarter.numberLow = bgValues.filter(function(bg) { + return bg < low; + }).length; + quarter.numberHigh = bgValues.filter(function(bg) { + return bg >= high; + }).length; + quarter.numberInRange = bgValues.length - (quarter.numberHigh + quarter.numberLow); + + quarter.percentLow = (quarter.numberLow / bgValues.length) * 100; + quarter.percentInRange = (quarter.numberInRange / bgValues.length) * 100; + quarter.percentHigh = (quarter.numberHigh / bgValues.length) * 100; + + averages.percentLow += quarter.percentLow / all.length; + averages.percentInRange += quarter.percentInRange / all.length; + averages.percentHigh += quarter.percentHigh / all.length; + averages.lowerQuartile += quarter.lowerQuartile / all.length; + averages.upperQuartile += quarter.upperQuartile / all.length; + averages.average += quarter.average / all.length; + averages.standardDeviation += quarter.standardDeviation / all.length; + return quarter; + }); + } catch (e) { + console.log(e); + } + + var lowComparison = function(quarter, averages, field, invert) { + if (quarter[field] < averages[field] * 0.8) { + return (invert? "bad": "good"); + } else if (quarter[field] > averages[field] * 1.2) { + return (invert? "good": "bad"); + } else { + return ""; + } + } + + var lowQuartileEvaluation = function(quarter, averages) { + if (quarter.lowerQuartile < low) { + return "bad"; + } else { + return lowComparison(quarter, averages, "lowerQuartile"); + } + } + + var upperQuartileEvaluation = function(quarter, averages) { + if (quarter.upperQuartile > high) { + return "bad"; + } else { + return lowComparison(quarter, averages, "upperQuartile"); + } + } + + var averageEvaluation = function(quarter, averages) { + if (quarter.average > high) { + return "bad"; + } else if (quarter.average < low) { + return "bad"; + } else { + return lowComparison(quarter, averages, "average", true); + } + } + + table.append(""); + table.append("" + quarters.filter(function(quarter) { + return quarter.records.length > 0; + }).map(function(quarter) { + var INVERT = true; + return "" + [ + quarter.starting.format("M d Y") + " - " + quarter.ending.format("M d Y"), + { + klass: lowComparison(quarter, averages, "percentLow"), + text: Math.round(quarter.percentLow) + "%" + }, + { + klass: lowComparison(quarter, averages, "percentInRange", INVERT), + text: Math.round(quarter.percentInRange) + "%" + }, + { + klass: lowComparison(quarter, averages, "percentHigh"), + text: Math.round(quarter.percentHigh) + "%" + }, + { + klass: lowComparison(quarter, averages, "standardDeviation"), + text: (quarter.standardDeviation > 10? Math.round(quarter.standardDeviation): quarter.standardDeviation.toFixed(1)) + }, + { + klass: lowQuartileEvaluation(quarter, averages), + text: quarter.lowerQuartile + }, + { + klass: lowComparison(quarter, averages, "average"), + text: quarter.average.toFixed(1) + }, + { + klass: upperQuartileEvaluation(quarter, averages), + text: quarter.upperQuartile + } + ].map(function(v) { + if (typeof v == "object") { + return ""; + } else { + return ""; + } + }).join("") + ""; + }).join("") + ""); + table.appendTo(grid); + +} \ No newline at end of file diff --git a/static/report/js/time.js b/static/report/js/time.js new file mode 100644 index 00000000000..0b34d7d47bf --- /dev/null +++ b/static/report/js/time.js @@ -0,0 +1,318 @@ +if (!("milliseconds" in Number.prototype)) + Number.prototype.milliseconds = function() { return this; }; +if (!("seconds" in Number.prototype)) + Number.prototype.seconds = function() { return this.milliseconds() * 1000; }; +if (!("minutes" in Number.prototype)) + Number.prototype.minutes = function() { return this.seconds() * 60; }; +if (!("hours" in Number.prototype)) + Number.prototype.hours = function() { return this.minutes() * 60; }; +if (!("days" in Number.prototype)) + Number.prototype.days = function() { return this.hours() * 24; }; +if (!("weeks" in Number.prototype)) + Number.prototype.weeks = function() { return this.days() * 7; }; +if (!("months" in Number.prototype)) + Number.prototype.months = function() { return this.days() * 30; }; + +if (!("toDays" in Number.prototype)) + Number.prototype.toDays = function() { return this.toHours() / 24; }; +if (!("toHours" in Number.prototype)) + Number.prototype.toHours = function() { return this.toMinutes() / 60; }; +if (!("toMinutes" in Number.prototype)) + Number.prototype.toMinutes = function() { return this.toSeconds() / 60; }; +if (!("toSeconds" in Number.prototype)) + Number.prototype.toSeconds = function() { return this.toMilliseconds() / 1000; }; +if (!("toMilliseconds" in Number.prototype)) + Number.prototype.toMilliseconds = function() { return this; }; + +Date.prototype.format = function(format) { + // discuss at: http://phpjs.org/functions/date/ + // original by: Carlos R. L. Rodrigues (http://www.jsfromhell.com) + // original by: gettimeofday + // parts by: Peter-Paul Koch (http://www.quirksmode.org/js/beat.html) + // improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // improved by: MeEtc (http://yass.meetcweb.com) + // improved by: Brad Touesnard + // improved by: Tim Wiel + // improved by: Bryan Elliott + // improved by: David Randall + // improved by: Theriault + // improved by: Theriault + // improved by: Brett Zamir (http://brett-zamir.me) + // improved by: Theriault + // improved by: Thomas Beaucourt (http://www.webapp.fr) + // improved by: JT + // improved by: Theriault + // improved by: Rafał Kukawski (http://blog.kukawski.pl) + // improved by: Theriault + // input by: Brett Zamir (http://brett-zamir.me) + // input by: majak + // input by: Alex + // input by: Martin + // input by: Alex Wilson + // input by: Haravikk + // bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // bugfixed by: majak + // bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // bugfixed by: Brett Zamir (http://brett-zamir.me) + // bugfixed by: omid (http://phpjs.org/functions/380:380#comment_137122) + // bugfixed by: Chris (http://www.devotis.nl/) + // note: Uses global: php_js to store the default timezone + // note: Although the function potentially allows timezone info (see notes), it currently does not set + // note: per a timezone specified by date_default_timezone_set(). Implementers might use + // note: this.php_js.currentTimezoneOffset and this.php_js.currentTimezoneDST set by that function + // note: in order to adjust the dates in this function (or our other date functions!) accordingly + // example 1: date('H:m:s \\m \\i\\s \\m\\o\\n\\t\\h', 1062402400); + // returns 1: '09:09:40 m is month' + // example 2: date('F j, Y, g:i a', 1062462400); + // returns 2: 'September 2, 2003, 2:26 am' + // example 3: date('Y W o', 1062462400); + // returns 3: '2003 36 2003' + // example 4: x = date('Y m d', (new Date()).getTime()/1000); + // example 4: (x+'').length == 10 // 2009 01 09 + // returns 4: true + // example 5: date('W', 1104534000); + // returns 5: '53' + // example 6: date('B t', 1104534000); + // returns 6: '999 31' + // example 7: date('W U', 1293750000.82); // 2010-12-31 + // returns 7: '52 1293750000' + // example 8: date('W', 1293836400); // 2011-01-01 + // returns 8: '52' + // example 9: date('W Y-m-d', 1293974054); // 2011-01-02 + // returns 9: '52 2011-01-02' + + var that = this, timestamp = this; + var jsdate, f; + // Keep this here (works, but for code commented-out below for file size reasons) + // var tal= []; + var txt_words = [ + 'Sun', 'Mon', 'Tues', 'Wednes', 'Thurs', 'Fri', 'Satur', + 'January', 'February', 'March', 'April', 'May', 'June', + 'July', 'August', 'September', 'October', 'November', 'December' + ]; + // trailing backslash -> (dropped) + // a backslash followed by any character (including backslash) -> the character + // empty string -> empty string + var formatChr = /\\?(.?)/gi; + var formatChrCb = function(t, s) { + return f[t] ? f[t]() : s; + }; + var _pad = function(n, c) { + n = String(n); + while (n.length < c) { + n = '0' + n; + } + return n; + }; + f = { + // Day + d: function() { // Day of month w/leading 0; 01..31 + return _pad(f.j(), 2); + }, + D: function() { // Shorthand day name; Mon...Sun + return f.l() + .slice(0, 3); + }, + j: function() { // Day of month; 1..31 + return jsdate.getDate(); + }, + l: function() { // Full day name; Monday...Sunday + return txt_words[f.w()] + 'day'; + }, + N: function() { // ISO-8601 day of week; 1[Mon]..7[Sun] + return f.w() || 7; + }, + S: function() { // Ordinal suffix for day of month; st, nd, rd, th + var j = f.j(); + var i = j % 10; + if (i <= 3 && parseInt((j % 100) / 10, 10) == 1) { + i = 0; + } + return ['st', 'nd', 'rd'][i - 1] || 'th'; + }, + w: function() { // Day of week; 0[Sun]..6[Sat] + return jsdate.getDay(); + }, + z: function() { // Day of year; 0..365 + var a = new Date(f.Y(), f.n() - 1, f.j()); + var b = new Date(f.Y(), 0, 1); + return Math.round((a - b) / 864e5); + }, + + // Week + W: function() { // ISO-8601 week number + var a = new Date(f.Y(), f.n() - 1, f.j() - f.N() + 3); + var b = new Date(a.getFullYear(), 0, 4); + return _pad(1 + Math.round((a - b) / 864e5 / 7), 2); + }, + + // Month + F: function() { // Full month name; January...December + return txt_words[6 + f.n()]; + }, + m: function() { // Month w/leading 0; 01...12 + return _pad(f.n(), 2); + }, + M: function() { // Shorthand month name; Jan...Dec + return f.F() + .slice(0, 3); + }, + n: function() { // Month; 1...12 + return jsdate.getMonth() + 1; + }, + t: function() { // Days in month; 28...31 + return (new Date(f.Y(), f.n(), 0)) + .getDate(); + }, + + // Year + L: function() { // Is leap year?; 0 or 1 + var j = f.Y(); + return j % 4 === 0 & j % 100 !== 0 | j % 400 === 0; + }, + o: function() { // ISO-8601 year + var n = f.n(); + var W = f.W(); + var Y = f.Y(); + return Y + (n === 12 && W < 9 ? 1 : n === 1 && W > 9 ? -1 : 0); + }, + Y: function() { // Full year; e.g. 1980...2010 + return jsdate.getFullYear(); + }, + y: function() { // Last two digits of year; 00...99 + return f.Y() + .toString() + .slice(-2); + }, + + // Time + a: function() { // am or pm + return jsdate.getHours() > 11 ? 'pm' : 'am'; + }, + A: function() { // AM or PM + return f.a() + .toUpperCase(); + }, + B: function() { // Swatch Internet time; 000..999 + var H = jsdate.getUTCHours() * 36e2; + // Hours + var i = jsdate.getUTCMinutes() * 60; + // Minutes + var s = jsdate.getUTCSeconds(); // Seconds + return _pad(Math.floor((H + i + s + 36e2) / 86.4) % 1e3, 3); + }, + g: function() { // 12-Hours; 1..12 + return f.G() % 12 || 12; + }, + G: function() { // 24-Hours; 0..23 + return jsdate.getHours(); + }, + h: function() { // 12-Hours w/leading 0; 01..12 + return _pad(f.g(), 2); + }, + H: function() { // 24-Hours w/leading 0; 00..23 + return _pad(f.G(), 2); + }, + i: function() { // Minutes w/leading 0; 00..59 + return _pad(jsdate.getMinutes(), 2); + }, + s: function() { // Seconds w/leading 0; 00..59 + return _pad(jsdate.getSeconds(), 2); + }, + u: function() { // Microseconds; 000000-999000 + return _pad(jsdate.getMilliseconds() * 1000, 6); + }, + + // Timezone + e: function() { // Timezone identifier; e.g. Atlantic/Azores, ... + // The following works, but requires inclusion of the very large + // timezone_abbreviations_list() function. + /* return that.date_default_timezone_get(); + */ + throw 'Not supported (see source code of date() for timezone on how to add support)'; + }, + I: function() { // DST observed?; 0 or 1 + // Compares Jan 1 minus Jan 1 UTC to Jul 1 minus Jul 1 UTC. + // If they are not equal, then DST is observed. + var a = new Date(f.Y(), 0); + // Jan 1 + var c = Date.UTC(f.Y(), 0); + // Jan 1 UTC + var b = new Date(f.Y(), 6); + // Jul 1 + var d = Date.UTC(f.Y(), 6); // Jul 1 UTC + return ((a - c) !== (b - d)) ? 1 : 0; + }, + O: function() { // Difference to GMT in hour format; e.g. +0200 + var tzo = jsdate.getTimezoneOffset(); + var a = Math.abs(tzo); + return (tzo > 0 ? '-' : '+') + _pad(Math.floor(a / 60) * 100 + a % 60, 4); + }, + P: function() { // Difference to GMT w/colon; e.g. +02:00 + var O = f.O(); + return (O.substr(0, 3) + ':' + O.substr(3, 2)); + }, + T: function() { // Timezone abbreviation; e.g. EST, MDT, ... + // The following works, but requires inclusion of the very + // large timezone_abbreviations_list() function. + /* var abbr, i, os, _default; + if (!tal.length) { + tal = that.timezone_abbreviations_list(); + } + if (that.php_js && that.php_js.default_timezone) { + _default = that.php_js.default_timezone; + for (abbr in tal) { + for (i = 0; i < tal[abbr].length; i++) { + if (tal[abbr][i].timezone_id === _default) { + return abbr.toUpperCase(); + } + } + } + } + for (abbr in tal) { + for (i = 0; i < tal[abbr].length; i++) { + os = -jsdate.getTimezoneOffset() * 60; + if (tal[abbr][i].offset === os) { + return abbr.toUpperCase(); + } + } + } + */ + return 'UTC'; + }, + Z: function() { // Timezone offset in seconds (-43200...50400) + return -jsdate.getTimezoneOffset() * 60; + }, + + // Full Date/Time + c: function() { // ISO-8601 date. + return 'Y-m-d\\TH:i:sP'.replace(formatChr, formatChrCb); + }, + r: function() { // RFC 2822 + return 'D, d M Y H:i:s O'.replace(formatChr, formatChrCb); + }, + U: function() { // Seconds since UNIX epoch + return jsdate / 1000 | 0; + } + }; + this.date = function(format, timestamp) { + that = this; + jsdate = (timestamp === undefined ? new Date() : // Not provided + (timestamp instanceof Date) ? new Date(timestamp) : // JS Date() + new Date(timestamp * 1000) // UNIX timestamp (auto-convert to int) + ); + return format.replace(formatChr, formatChrCb); + }; + return this.date(format, timestamp); +} + +// http://stackoverflow.com/questions/11887934/check-if-daylight-saving-time-is-in-effect-and-if-it-is-for-how-many-hours +Date.prototype.stdTimezoneOffset = function() { + var jan = new Date(this.getFullYear(), 0, 1); + var jul = new Date(this.getFullYear(), 6, 1); + return Math.max(jan.getTimezoneOffset(), jul.getTimezoneOffset()); +}; + +Date.prototype.dst = function() { + return this.getTimezoneOffset() < this.stdTimezoneOffset(); +}; \ No newline at end of file diff --git a/static/report/js/treatments.js b/static/report/js/treatments.js new file mode 100644 index 00000000000..0f2f5ab0eef --- /dev/null +++ b/static/report/js/treatments.js @@ -0,0 +1,154 @@ + function report_treatments(datastorage,daystoshow,options) { + var icon_remove = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACrElEQVQ4T42Ty2sTQRzHv5tmk2yyjRNtpfZhL8V6s2KoUNC2XqwgaCsVQcGiFqpHi0c9iRdR/ANE9KR40FIQX4cueKoPaKFoLdSYNtE0abKT1+5s9iW7aUMiHtzTzO7v85md+c6PA4DrHbsPCKIgOWO1pA7dT6YXnXH949SE/F63pqwZtRrO+SCKgjQ5NUV+azpmHj2krMwaJC4c8Erj+/eRyloMMwWFKgbn1nC3ervlK1evkXBLGBZT8SOewotnTylTNLdgeg/pDgZDC2cPHSR8bB22DVC9hFe0SG/H0xFXcHlykjRHRDBWgJcZSCY38Xx2lhqMnRYE34Px/sN9vlQWeoHBAx2yXsRruVAVuFsIBaSJ8+eJGPaBqQV4NROJjTzez89jLBoFn6FgybQL54wS3uTyVDFQ3cL2IYpBv3RhdJSIIQ80tQyv7gEqJvS8AmUlBs7UXPhtjtZgh3UFNYngk86NHCfNAg9dMwHVBPu+CpsVkTXKeJeVG+AGgTOZ3tt6MSKKjy+NjEBjFrR4ElZmA4pdxstMFsyyJu6tZZ7Ux9vwB6EAL50ZGiRECEPPUOixVTRxHlicgSVWxEdZpuZWfNuS2hk48NjwMIkIYZglBnV5Cbqtws/5IaAJmsfCglrEl2y2QeKmEBJ80tixKmxrFpSVr0gV0viQoxho2YUuPohmeFD22PiklLC4ma5JuBvdrfLJI0dJd0s7bM0ES8aR/BXDXGaTskqlL+D3Lwy0tZEePoAd4EA5YF4tYymdonfjmQh3s6dTPjU4SHYGwjAKecSXFyGlM1TdytntE56T+ts7SC/vhw3gm6njc2Kd3vm5Ub1IwQAvnYhGiZpYw1wiWYPrIw7wnBTt7CLOOwdmut14kQQvqt24tfK/utGR6LaF+iRqMf4N/O/8D28HiiCRYqzAAAAAAElFTkSuQmCC"; + var icon_edit = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABEUlEQVQ4jZ3MMUsCYQDG8ee8IySQbNCLyyEKG/RLNAXicqvQcAeNLrcFLlE0+xHuNpt8wy04rrYm8Q4HQRE56BSC3lSqU1BwCoxM39dnffj9BWyxXvVeEzvtctBwHyRebNu2Nk2lzMlrgJB+qBEeTByiKYpihl+fIO8jTI9PDJEVF1+K2iw+M6PhDuyag4NkQi/c3FkCK5Z3ZbM76qLltpCbn+vXxq0FABsDy9hzPdBvqvtXvvXzrw1swmsDLPjfACteGeDBfwK8+FdgGwwAIgC0ncsjxGRSH/eiPBgAJADY2z8sJ4JBfNBsDqlADVYMANIzKalv/bHaefKsTH9iPFb8ISsGAJym0+Qinz3jQktbAHcxvx3559eSAAAAAElFTkSuQmCC"; + + var table = '
    "+translate("Period")+""+translate("Low")+""+translate("In Range")+""+translate("High")+""+translate("Standard Deviation")+""+translate("Low Quartile")+""+translate("Average")+""+translate("Upper Quartile")+"
    " + v.text + "" + v + "
    '; + table += ''; + + Object.keys(daystoshow).forEach(function (day) { + table += ''; + var treatments = datastorage[day].treatments; + for (var t=0; t'; + table += ' '; + table += ''; + table += ''; + table += ''; + table += ''; + table += ''; + table += ''; + table += ''; + table += ''; + table += ''; + + table += ''; + } + }); + $('#treatments-report').html(table); + $('.deleteTreatment').click(deleteTreatment); + $('.editTreatment').click(editTreatment); + } + + function deleteTreatment(event) { + var data = JSON.parse($(this).attr('data')); + var day = $(this).attr('day'); + + var ok = window.confirm( + translate('Delete this treatment?')+'\n' + + '\n'+translate('Event Type')+': ' + data.eventType + + (data.glucose ? '\n'+translate('Blood Glucose')+': ' + data.glucose : '')+ + (data.glucoseType ? '\n'+translate('Method')+': ' + data.glucoseType : '')+ + (data.carbs ? '\n'+translate('Carbs Given')+': ' + data.carbs : '' )+ + (data.insulin ? '\n'+translate('Insulin Given')+': ' + data.insulin : '')+ + (data.preBolus ? '\n'+translate('Pre Bolus')+': ' + data.preBolus : '')+ + (data.notes ? '\n'+translate('Notes')+': ' + data.notes : '' )+ + (data.enteredBy ? '\n'+translate('Entered By')+': ' + data.enteredBy : '' )+ + ('\n'+translate('Event Time')+': ' + new Date(data.created_at).toLocaleString()) + ); + + if (ok) { + deleteTreatmentRecord(data._id); + delete datastorage[day]; + show(); + } + if (event) event.preventDefault(); + return false; + } + + function deleteTreatmentRecord(_id) { + if (!Nightscout.auth.isAuthenticated()) { + alert(translate('Your device is not authenticated yet')); + return false; + } + + var xhr = new XMLHttpRequest(); + xhr.open('DELETE', '/api/v1/treatments/'+_id, true); + xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); + xhr.setRequestHeader('api-secret', Nightscout.auth.hash()); + xhr.onload = function () { + if (xhr.statusText!='OK') { + alert(translate('Deleting record failed')); + } + } + xhr.send(null); + return true; + } + + function editTreatment(event) { + var data = JSON.parse($(this).attr('data')); + var day = $(this).attr('day'); + + $( '#rp_edittreatmentdialog' ).dialog({ + width: 350 + , height: 500 + , buttons: [ + { text: translate('Save'), + class: 'leftButton', + click: function() { + data.eventType = $('#rp_eventType').val(); + data.glucose = $('#rp_glucoseValue').val(); + data.glucoseType = $('#rp_edittreatmentdialog').find('input[name=rp_bginput]:checked').val(); + data.carbs = $('#rp_carbsGiven').val(); + data.insulin = $('#rp_insulinGiven').val(); + data.notes = $('#rp_adnotes').val(); + data.enteredBy = $('#rp_enteredBy').val(); + data.created_at = Nightscout.utils.mergeInputTime($('#rp_eventTimeValue').val(), $('#rp_eventDateValue').val()); + $( this ).dialog( "close" ); + saveTreatmentRecord(data); + delete datastorage[day]; + show(); + } + }, + { text: translate('Cancel'), + click: function () { $( this ).dialog( "close" ); } + } + ] + , open : function(ev, ui) { + $(this).parent().css('box-shadow', '20px 20px 20px 0px black'); + $(this).parent().find('.ui-dialog-buttonset' ).css({'width':'100%','text-align':'right'}) + $(this).parent().find('button:contains("'+translate('Save')+'")').css({'float':'left'}); + $('#rp_eventType').val(translate(data.eventType)); + $('#rp_glucoseValue').val(data.glucose ? data.glucose : '').attr('placeholder', translate('Value in') + ' ' + serverSettings.units); + $('#rp_bgfromsensor').prop('checked', data.glucoseType === 'Sensor'); + $('#rp_bgfrommeter').prop('checked', data.glucoseType === 'Finger'); + $('#rp_bgmanual').prop('checked', data.glucoseType === 'Manual'); + $('#rp_carbsGiven').val(data.carbs ? data.carbs : ''); + $('#rp_insulinGiven').val(data.insulin ? data.insulin : ''); + $('#rp_adnotes').val(data.notes ? data.notes : ''); + $('#rp_enteredBy').val(data.enteredBy ? data.enteredBy : ''); + $('#rp_eventTimeValue').val(moment(data.created_at).format('HH:mm')); + $('#rp_eventDateValue').val(moment(data.created_at).format('YYYY-MM-DD')); + $('#rp_eventType').focus(); + } + + }); + + if (event) event.preventDefault(); + return false; + } + + function saveTreatmentRecord(data) { + if (!Nightscout.auth.isAuthenticated()) { + alert(translate('Your device is not authenticated yet')); + return false; + } + + + var dataJson = JSON.stringify(data, null, ' '); + + var xhr = new XMLHttpRequest(); + xhr.open('PUT', '/api/v1/treatments/', true); + xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); + xhr.setRequestHeader('api-secret', Nightscout.auth.hash()); + xhr.onload = function () { + if (xhr.statusText!='OK') { + alert(translate('Saving record failed')); + } + } + xhr.send(dataJson); + return true; + } + From e94aa852db717ab86dfb953bce7a3858d011b910 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Thu, 27 Aug 2015 16:52:32 +0200 Subject: [PATCH 714/937] make it at least load without errors --- bower.json | 2 + static/css/report.css | 112 ++++++++++++++++++ static/report/index.html | 6 +- static/report/js/dailystats.js | 8 +- static/report/js/daytoday.js | 35 +++--- static/report/js/glucosedistribution.js | 4 + static/report/js/hourlystats.js | 4 + static/report/js/percentile.js | 10 +- static/report/js/report.js | 149 ++++++++++++++---------- static/report/js/success.js | 4 + static/report/js/treatments.js | 6 +- 11 files changed, 249 insertions(+), 91 deletions(-) create mode 100644 static/css/report.css diff --git a/bower.json b/bower.json index 09f505e7f2e..74e91242d6e 100644 --- a/bower.json +++ b/bower.json @@ -8,6 +8,8 @@ "jQuery-Storage-API": "~1.7.2", "tipsy-jmalonzo": "~1.0.1", "jquery-ui": "~1.11.3", + "jquery-flot": "0.8.3", + "simple-statistics": "0.7.0", "swagger-ui": "~2.1.2" }, "resolutions": { diff --git a/static/css/report.css b/static/css/report.css new file mode 100644 index 00000000000..d311c8d4b58 --- /dev/null +++ b/static/css/report.css @@ -0,0 +1,112 @@ +@import url("//fonts.googleapis.com/css?family=Ubuntu:300,400,500,700,300italic,400italic,500italic,700italic"); +@import url("//fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,300,400,600,700,800"); +@import url("/glyphs/css/fontello.css"); + + +html, body { + height: 100%; + margin: 0; + padding: 0; +} +body { + font-family: 'Open Sans', Helvetica, Arial, sans-serif; + background: white; + color: black; +} +.inlinepiechart { + width: 2.0in; + height: 0.9in; +} + +#glucosedistribution-overviewchart { + width: 3.0in; + height: 3in; + float:left; +} +#percentile-chart { + width: 100%; + height: 100%; +} +#hourlystats-overviewchart { + width: 100%; + min-width: 6.5in; + height: 5in; +} +#success-placeholder table td { + border: 1px #ccc solid; + margin: 0; + padding: 1px; +} + +#success-placeholder td.bad { + background-color: #fcc; +} + +#success-placeholder td.good { + background-color: #cfc; +} + +#success-placeholder th:first-child { + width: 30%; +} +#success-placeholder th { + width: 10%; +} +#success-placeholder table { + width: 100%; +} + +table.centeraligned td{ + text-align:center; +} +table.centeraligned th{ + text-align:center; +} +#dailystats-placeholder td.tdborder { + width:80px; + border: 1px #ccc solid; + margin: 0; + padding: 1px; +} +#glucosedistribution-placeholder td.tdborder { + width:80px; + border: 1px #ccc solid; + margin: 0; + padding: 1px; +} + +ul#tabnav { /* general settings */ +text-align: left; /* set to left, right or center */ +margin: 1em 0 1em 0; /* set margins as desired */ +font: bold 11px verdana, arial, sans-serif; /* set font as desired */ +border-bottom: 1px solid #6c6; /* set border COLOR as desired */ +list-style-type: none; +padding: 3px 10px 3px 10px; /* THIRD number must change with respect to padding-top (X) below */ +} + +ul#tabnav li { /* do not change */ +display: inline; +} + +ul#tabnav li{ /* settings for all tab links */ +padding: 3px 4px; /* set padding (tab size) as desired; FIRST number must change with respect to padding-top (X) above */ +border: 1px solid #6c6; /* set border COLOR as desired; usually matches border color specified in #tabnav */ +background-color: #cfc; /* set unselected tab background color as desired */ +color: #666; /* set unselected tab link color as desired */ +margin-right: 0px; /* set additional spacing between tabs as desired */ +text-decoration: none; +border-bottom: none; +} + +ul#tabnav li.selected { /* settings selected tab */ +background: #fff; /* set desired selected color */ +} + +ul#tabnav li:hover { /* settings for hover effect */ +background: #9f9; /* set desired hover color */ +cursor:pointer; +} + +tr.border_bottom td { + border-bottom:1pt solid #eee; +} diff --git a/static/report/index.html b/static/report/index.html index d04be3aac72..76caed95fd7 100644 --- a/static/report/index.html +++ b/static/report/index.html @@ -217,8 +217,7 @@

    Calibrations

    - - + @@ -227,9 +226,6 @@

    Calibrations

    - - - diff --git a/static/report/js/dailystats.js b/static/report/js/dailystats.js index 18df7786ea0..ea34bf9cb93 100644 --- a/static/report/js/dailystats.js +++ b/static/report/js/dailystats.js @@ -1,4 +1,8 @@ function report_dailystats(datastorage,daystoshow,options) { + var Nightscout = window.Nightscout; + var client = Nightscout.client; + var translate = client.translate; + var todo = []; // var data = o[0]; // var days = 7; @@ -39,7 +43,7 @@ if (daysRecords.length == 0) { $("
    ").appendTo(tr); + $("").appendTo(tr); $('').appendTo(tr); table.append(tr); return;; @@ -67,7 +71,7 @@ var bgValues = daysRecords.map(function(r) { return r.sgv; }); $("").appendTo(tr); - $("").appendTo(tr); + $("").appendTo(tr); $("").appendTo(tr); $("").appendTo(tr); $("").appendTo(tr); diff --git a/static/report/js/daytoday.js b/static/report/js/daytoday.js index 371628d15dc..41ec2aaad64 100644 --- a/static/report/js/daytoday.js +++ b/static/report/js/daytoday.js @@ -1,6 +1,10 @@ function report_daytoday(datastorage,daystoshow,options) { - var translate = Nightscout.language.translate; - + var Nightscout = window.Nightscout; + var client = Nightscout.client; + var translate = client.translate; + var profile = client.sbx.data.profile; + var scaledTreatmentBG = Nightscout.reports.scaledTreatmentBG; + var padding = { top: 15, right: 15, bottom: 30, left: 35 }; var FORMAT_TIME_12 = '%I' @@ -12,7 +16,7 @@ function getTimeFormat() { var timeFormat = FORMAT_TIME_12; - if (serverSettings.defaults.timeFormat == '24') { + if (Nightscout.client.settings.timeFormat == '24') { timeFormat = FORMAT_TIME_24; } return timeFormat; @@ -29,8 +33,8 @@ , foodtexts = 0; // Tick Values - if (options.scale == SCALE_LOG) { - if (serverSettings.units == 'mmol') { + if (options.scale == Nightscout.reports.SCALE_LOG) { + if (client.settings.units == 'mmol') { tickValues = [ 2.0 , 3.0 @@ -54,7 +58,7 @@ ]; } } else { - if (serverSettings.units == 'mmol') { + if (client.settings.units == 'mmol') { tickValues = [ 2.0 , 4.0 @@ -85,9 +89,9 @@ } // create svg and g to contain the chart contents - charts = d3.select('#'+containerprefix+day).html( + charts = d3.select('#'+Nightscout.reports.containerprefix+day).html( ''+ - localeDate(day)+ + Nightscout.reports.localeDate(day)+ '
    ' ).append('svg'); @@ -102,12 +106,12 @@ xScale2 = d3.time.scale() .domain(d3.extent(data.sgv, function (d) { return d.date; })); - if (options.scale == SCALE_LOG) { + if (options.scale == Nightscout.reports.SCALE_LOG) { yScale2 = d3.scale.log() - .domain([scaleBg(36), scaleBg(420)]); + .domain([client.utils.scaleMgdl(36), client.utils.scaleMgdl(420)]); } else { yScale2 = d3.scale.linear() - .domain([scaleBg(36), scaleBg(420)]); + .domain([client.utils.scaleMgdl(36), client.utils.scaleMgdl(420)]); } yInsulinScale = d3.scale.linear() @@ -200,7 +204,7 @@ .attr('cy', function (d) { if (isNaN(d.sgv)) { badData.push(d); - return yScale2(scaleBg(450) + padding.top); + return yScale2(client.utils.scaleMgdl(450) + padding.top); } else { return yScale2(d.sgv) + padding.top; } @@ -233,15 +237,14 @@ var iobpolyline = '', cobpolyline = ''; for (var dt=from; dt < to; dt.add(5, 'minutes')) { if (options.iob) { - var iob = Nightscout.plugins('iob').calcTotal(data.treatments,Nightscout.profile,dt.toDate()).iob; + var iob = Nightscout.plugins('iob').calcTotal(data.treatments,profile,dt.toDate()).iob; if (dt!=from) { iobpolyline += ', '; } iobpolyline += (xScale2(dt) + padding.left) + ',' + (yInsulinScale(iob) + padding.top) + ' '; } if (options.cob) { - var cob = Nightscout.plugins('cob').cobTotal(data.treatments,Nightscout.profile,dt.toDate()).cob; -console.log(dt.toDate().toLocaleTimeString(),cob); + var cob = Nightscout.plugins('cob').cobTotal(data.treatments,profile,dt.toDate()).cob; if (dt!=from) { cobpolyline += ', '; } @@ -308,7 +311,7 @@ console.log(dt.toDate().toLocaleTimeString(),cob); } if (treatment.carbs && options.carbs) { - var ic = Nightscout.profile.getCarbRatio(new Date(treatment.mills)); + var ic = profile.getCarbRatio(new Date(treatment.mills)); context.append('rect') .attr('y',yCarbsScale(treatment.carbs)) .attr('height', chartHeight-yCarbsScale(treatment.carbs)) diff --git a/static/report/js/glucosedistribution.js b/static/report/js/glucosedistribution.js index 16ac13d5d0b..36addc1c5c1 100644 --- a/static/report/js/glucosedistribution.js +++ b/static/report/js/glucosedistribution.js @@ -1,4 +1,8 @@ function report_glucosedistribution(datastorage,daystoshow,options) { + var Nightscout = window.Nightscout; + var client = Nightscout.client; + var translate = client.translate; + var Statician = ss; var report = $("#glucosedistribution-report"); report.empty(); diff --git a/static/report/js/hourlystats.js b/static/report/js/hourlystats.js index 23dd1ce6949..4ad375d7805 100644 --- a/static/report/js/hourlystats.js +++ b/static/report/js/hourlystats.js @@ -1,4 +1,8 @@ function report_hourlystats(datastorage,daystoshow,options) { + var Nightscout = window.Nightscout; + var client = Nightscout.client; + var translate = client.translate; + var report = $("#hourlystats-report"); var stats = []; var pivotedByHour = {}; diff --git a/static/report/js/percentile.js b/static/report/js/percentile.js index 3a22cf19519..77e9374a4ef 100644 --- a/static/report/js/percentile.js +++ b/static/report/js/percentile.js @@ -1,6 +1,10 @@ function report_percentile(datastorage,daystoshow,options) { + var Nightscout = window.Nightscout; + var client = Nightscout.client; + var translate = client.translate; + var Statician = ss; - var window = 30; //minute-window should be a divisor of 60 + var minutewindow = 30; //minute-window should be a divisor of 60 var data = []; var days = 0; @@ -11,14 +15,14 @@ var bins = []; for (hour = 0; hour < 24; hour++) { - for (minute = 0; minute < 60; minute = minute + window) { + for (minute = 0; minute < 60; minute = minute + minutewindow) { var date = new Date(); date.setHours(hour); date.setMinutes(minute); var readings = data.filter(function(record) { var recdate = new Date(record.displayTime); return recdate.getHours() == hour && recdate.getMinutes() >= minute && - recdate.getMinutes() < minute + window;; + recdate.getMinutes() < minute + minutewindow; }); readings = readings.map(function(record) { return record.sgv; diff --git a/static/report/js/report.js b/static/report/js/report.js index b63828ab9aa..a6f9f17df63 100644 --- a/static/report/js/report.js +++ b/static/report/js/report.js @@ -1,10 +1,24 @@ +(function () { 'use strict'; - - var translate = Nightscout.language.translate; + //for the tests window isn't the global object + var $ = window.$; + var _ = window._; + var moment = window.moment; + var Nightscout = window.Nightscout; + var client = Nightscout.client; + + Nightscout.reports = Nightscout.reports || {}; + + if (serverSettings === undefined) { + console.error('server settings were not loaded, will not call init'); + } else { + client.init(serverSettings, Nightscout.plugins); + } + + var translate = client.translate; var maxInsulinValue = 0 ,maxCarbsValue = 0; - var containerprefix = 'chart-'; var maxdays = 3 * 31; var datastorage = {}; var daystoshow = {}; @@ -18,9 +32,9 @@ ONE_MIN_IN_MS = 60000 , SIX_MINS_IN_MS = 360000; - var - SCALE_LINEAR = 0 - , SCALE_LOG = 1; + Nightscout.reports.SCALE_LINEAR = 0; + Nightscout.reports.SCALE_LOG = 1; + Nightscout.reports.containerprefix = 'chart-'; var categories = []; @@ -60,44 +74,56 @@ return color; } - $('#info').html(''+translate('Loading profile')+' ...'); - $.ajax('/api/v1/profile', { - success: function (record) { - Nightscout.profile.loadData(record); + $('#info').html(''+translate('Loading food database')+' ...'); + $.ajax('/api/v1/food/regular.json', { + success: function foodLoadSuccess(records) { + records.forEach(function (r) { + foodlist.push(r); + if (r.category && !categories[r.category]) categories[r.category] = {}; + if (r.category && r.subcategory) categories[r.category][r.subcategory] = true; + }); + fillForm(); } }).done(function() { - $('#info').html(''+translate('Loading food database')+' ...'); - $.ajax('/api/v1/food/regular.json', { - success: function (records) { - records.forEach(function (r) { - foodlist.push(r); - if (r.category && !categories[r.category]) categories[r.category] = {}; - if (r.category && r.subcategory) categories[r.category][r.subcategory] = true; - }); - fillForm(); - } - }).done(function() { - $('#info').html(''); - $('.presetdates').click(function(e) { var days = $(this).attr('days'); setDataRange(e,days); }); - - $('#rp_show').click(show); - $('#rp_food').change(function (event) { - event.preventDefault(); - $('#rp_enablefood').prop('checked',true); - }); - $('#rp_notes').change(function (event) { - event.preventDefault(); - $('#rp_enablenotes').prop('checked',true); - }); - - $('#rp_targetlow').val(targetBGdefault[serverSettings.units].low); - $('#rp_targethigh').val(targetBGdefault[serverSettings.units].high); - - $('.menutab').click(switchtab); + $('#info').html(''); + $('.presetdates').click(function(e) { var days = $(this).attr('days'); setDataRange(e,days); }); - setDataRange(null,7); - - }); + $('#rp_show').click(show); + $('#rp_food').change(function (event) { + event.preventDefault(); + $('#rp_enablefood').prop('checked',true); + }); + $('#rp_notes').change(function (event) { + event.preventDefault(); + $('#rp_enablenotes').prop('checked',true); + }); + + $('#rp_targetlow').val(targetBGdefault[client.settings.units].low); + $('#rp_targethigh').val(targetBGdefault[client.settings.units].high); + + $('.menutab').click(switchtab); + + setDataRange(null,7); + }).fail(function() { + $('#info').html(''); + $('.presetdates').click(function(e) { var days = $(this).attr('days'); setDataRange(e,days); }); + + $('#rp_show').click(show); + $('#rp_food').change(function (event) { + event.preventDefault(); + $('#rp_enablefood').prop('checked',true); + }); + $('#rp_notes').change(function (event) { + event.preventDefault(); + $('#rp_enablenotes').prop('checked',true); + }); + + $('#rp_targetlow').val(targetBGdefault[client.settings.units].low); + $('#rp_targethigh').val(targetBGdefault[client.settings.units].high); + + $('.menutab').click(switchtab); + + setDataRange(null,7); }); function show(event) { @@ -113,7 +139,7 @@ carbs: true, iob : true, cob : true, - scale: SCALE_LINEAR + scale: Nightscout.reports.SCALE_LINEAR }; options.targetLow = parseFloat($('#rp_targetlow').val().replace(',','.')); @@ -125,7 +151,7 @@ options.food = $('#rp_optionsfood').is(':checked'); options.insulin = $('#rp_optionsinsulin').is(':checked'); options.carbs = $('#rp_optionscarbs').is(':checked'); - options.scale = $('#rp_linear').is(':checked') ? SCALE_LINEAR : SCALE_LOG; + options.scale = $('#rp_linear').is(':checked') ? Nightscout.reports.SCALE_LINEAR : Nightscout.reports.SCALE_LOG; options.width = parseInt($('#rp_size :selected').attr('x')); options.height = parseInt($('#rp_size :selected').attr('y')); @@ -328,7 +354,7 @@ } - function localeDate(day) { + Nightscout.reports.localeDate = function localeDate(day) { var ret = [translate("Sunday"),translate("Monday"),translate("Tuesday"),translate("Wednesday"),translate("Thursday"),translate("Friday"),translate("Saturday")][new Date(day).getDay()]; ret += ' '; @@ -336,7 +362,7 @@ return ret; } - function localeDateTime(day) { + Nightscout.reports.localeDateTime = function localeDateTime(day) { var ret = new Date(day).toLocaleDateString() + ' ' + new Date(day).toLocaleTimeString(); return ret; } @@ -359,7 +385,7 @@ var to = from + 1000 * 60 * 60 * 24; var query = '?find[date][$gte]='+from+'&find[date][$lt]='+to+'&count=10000'; - $('#'+containerprefix+day).html(''+translate('Loading CGM data of')+' '+day+' ...'); + $('#'+Nightscout.reports.containerprefix+day).html(''+translate('Loading CGM data of')+' '+day+' ...'); $.ajax('/api/v1/entries.json'+query, { success: function (xhr) { xhr.forEach(function (element) { @@ -396,6 +422,7 @@ }); // sometimes cgm contains duplicates. uniq it. data.sgv = cgmData.slice(); +console.log(data.sgv); data.sgv.sort(function(a, b) { return a.x - b.x; }); var lastDate = 0; data.sgv = data.sgv.filter(function(d) { @@ -409,7 +436,7 @@ data.cal.sort(function(a, b) { return a.x - b.x; }); } }).done(function () { - $('#'+containerprefix+day).html(''+translate('Loading treatments data of')+' '+day+' ...'); + $('#'+Nightscout.reports.containerprefix+day).html(''+translate('Loading treatments data of')+' '+day+' ...'); var tquery = '?find[created_at][$gte]='+new Date(from).toISOString()+'&find[created_at][$lt]='+new Date(to).toISOString(); $.ajax('/api/v1/treatments.json'+tquery, { success: function (xhr) { @@ -422,7 +449,7 @@ data.treatments.sort(function(a, b) { return a.mills - b.mills; }); } }).done(function () { - $('#'+containerprefix+day).html(''+translate('Processing data of')+' '+day+' ...'); + $('#'+Nightscout.reports.containerprefix+day).html(''+translate('Processing data of')+' '+day+' ...'); processData(data,day,options); }); @@ -442,16 +469,16 @@ temp1 = data.sgv.map(function (entry) { var noise = entry.noise || 0; var rawBg = rawIsigToRawBg(entry, cal); - return { x: entry.x, date: new Date(entry.x - 2 * 1000), y: rawBg, sgv: scaleBg(rawBg), color: 'gray', type: 'rawbg', filtered: entry.filtered, unfiltered: entry.unfiltered } + return { x: entry.x, date: new Date(entry.x - 2 * 1000), y: rawBg, sgv: client.utils.scaleMgdl(rawBg), color: 'gray', type: 'rawbg', filtered: entry.filtered, unfiltered: entry.unfiltered } }).filter(function(entry) { return entry.y > 0}); } var temp2 = data.sgv.map(function (obj) { - return { x: obj.x, date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), color: sgvToColor(scaleBg(obj.y),options), type: 'sgv', noise: obj.noise, filtered: obj.filtered, unfiltered: obj.unfiltered} + return { x: obj.x, date: new Date(obj.x), y: obj.y, sgv: client.utils.scaleMgdl(obj.y), color: sgvToColor(client.utils.scaleMgdl(obj.y),options), type: 'sgv', noise: obj.noise, filtered: obj.filtered, unfiltered: obj.unfiltered} }); data.sgv = [].concat(temp1, temp2); //Add MBG's also, pretend they are SGV's - data.sgv = data.sgv.concat(data.mbg.map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), color: 'red', type: 'mbg', device: obj.device } })); + data.sgv = data.sgv.concat(data.mbg.map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: client.utils.scaleMgdl(obj.y), color: 'red', type: 'mbg', device: obj.device } })); // make sure data range will be exactly 24h var from = new Date(new Date(day).getTime() + (new Date().getTimezoneOffset()*60*1000)); @@ -550,7 +577,7 @@ if (event) event.preventDefault(); } - function scaledTreatmentBG(treatment,data) { + Nightscout.reports.scaledTreatmentBG = function scaledTreatmentBG(treatment,data) { var SIX_MINS_IN_MS = 360000; @@ -576,15 +603,15 @@ if (treatment.glucose && isNaN(treatment.glucose)) { console.warn('found an invalid glucose value', treatment); } else { - if (treatment.glucose && treatment.units && serverSettings.units) { - if (treatment.units != serverSettings.units) { - console.info('found mismatched glucose units, converting ' + treatment.units + ' into ' + serverSettings.units, treatment); + if (treatment.glucose && treatment.units && client.settings.units) { + if (treatment.units != client.settings.units) { + console.info('found mismatched glucose units, converting ' + treatment.units + ' into ' + client.settings.units, treatment); if (treatment.units == 'mmol') { //BG is in mmol and display in mg/dl treatmentGlucose = Math.round(treatment.glucose * 18) } else { //BG is in mg/dl and display in mmol - treatmentGlucose = scaleBg(treatment.glucose); + treatmentGlucose = client.utils.scaleMgdl(treatment.glucose); } } else { treatmentGlucose = treatment.glucose; @@ -596,13 +623,7 @@ } } - return treatmentGlucose || scaleBg(calcBGByTime(treatment.mills)); + return treatmentGlucose || client.utils.scaleMgdl(calcBGByTime(treatment.mills)); } - function scaleBg(bg) { - if (serverSettings.units === 'mmol') { - return Nightscout.units.mgdlToMMOL(bg); - } else { - return bg; - } - } +})(); \ No newline at end of file diff --git a/static/report/js/success.js b/static/report/js/success.js index 85311b42624..20a120d892f 100644 --- a/static/report/js/success.js +++ b/static/report/js/success.js @@ -1,4 +1,8 @@ function report_success(datastorage,daystoshow,options) { + var Nightscout = window.Nightscout; + var client = Nightscout.client; + var translate = client.translate; + var low = parseInt(options.targetLow), high = parseInt(options.targetHigh); diff --git a/static/report/js/treatments.js b/static/report/js/treatments.js index 0f2f5ab0eef..91649ea01c7 100644 --- a/static/report/js/treatments.js +++ b/static/report/js/treatments.js @@ -1,4 +1,8 @@ function report_treatments(datastorage,daystoshow,options) { + var Nightscout = window.Nightscout; + var client = Nightscout.client; + var translate = client.translate; + var icon_remove = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACrElEQVQ4T42Ty2sTQRzHv5tmk2yyjRNtpfZhL8V6s2KoUNC2XqwgaCsVQcGiFqpHi0c9iRdR/ANE9KR40FIQX4cueKoPaKFoLdSYNtE0abKT1+5s9iW7aUMiHtzTzO7v85md+c6PA4DrHbsPCKIgOWO1pA7dT6YXnXH949SE/F63pqwZtRrO+SCKgjQ5NUV+azpmHj2krMwaJC4c8Erj+/eRyloMMwWFKgbn1nC3ervlK1evkXBLGBZT8SOewotnTylTNLdgeg/pDgZDC2cPHSR8bB22DVC9hFe0SG/H0xFXcHlykjRHRDBWgJcZSCY38Xx2lhqMnRYE34Px/sN9vlQWeoHBAx2yXsRruVAVuFsIBaSJ8+eJGPaBqQV4NROJjTzez89jLBoFn6FgybQL54wS3uTyVDFQ3cL2IYpBv3RhdJSIIQ80tQyv7gEqJvS8AmUlBs7UXPhtjtZgh3UFNYngk86NHCfNAg9dMwHVBPu+CpsVkTXKeJeVG+AGgTOZ3tt6MSKKjy+NjEBjFrR4ElZmA4pdxstMFsyyJu6tZZ7Ux9vwB6EAL50ZGiRECEPPUOixVTRxHlicgSVWxEdZpuZWfNuS2hk48NjwMIkIYZglBnV5Cbqtws/5IaAJmsfCglrEl2y2QeKmEBJ80tixKmxrFpSVr0gV0viQoxho2YUuPohmeFD22PiklLC4ma5JuBvdrfLJI0dJd0s7bM0ES8aR/BXDXGaTskqlL+D3Lwy0tZEePoAd4EA5YF4tYymdonfjmQh3s6dTPjU4SHYGwjAKecSXFyGlM1TdytntE56T+ts7SC/vhw3gm6njc2Kd3vm5Ub1IwQAvnYhGiZpYw1wiWYPrIw7wnBTt7CLOOwdmut14kQQvqt24tfK/utGR6LaF+iRqMf4N/O/8D28HiiCRYqzAAAAAAElFTkSuQmCC"; var icon_edit = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABEUlEQVQ4jZ3MMUsCYQDG8ee8IySQbNCLyyEKG/RLNAXicqvQcAeNLrcFLlE0+xHuNpt8wy04rrYm8Q4HQRE56BSC3lSqU1BwCoxM39dnffj9BWyxXvVeEzvtctBwHyRebNu2Nk2lzMlrgJB+qBEeTByiKYpihl+fIO8jTI9PDJEVF1+K2iw+M6PhDuyag4NkQi/c3FkCK5Z3ZbM76qLltpCbn+vXxq0FABsDy9hzPdBvqvtXvvXzrw1swmsDLPjfACteGeDBfwK8+FdgGwwAIgC0ncsjxGRSH/eiPBgAJADY2z8sJ4JBfNBsDqlADVYMANIzKalv/bHaefKsTH9iPFb8ISsGAJym0+Qinz3jQktbAHcxvx3559eSAAAAAElFTkSuQmCC"; @@ -6,7 +10,7 @@ table += ''; Object.keys(daystoshow).forEach(function (day) { - table += ''; + table += ''; var treatments = datastorage[day].treatments; for (var t=0; t Date: Fri, 28 Aug 2015 18:28:42 +0200 Subject: [PATCH 715/937] some renames --- static/report/index.html | 2 +- static/report/js/report.js | 64 ++++++++++++++++++++------------------ 2 files changed, 34 insertions(+), 32 deletions(-) diff --git a/static/report/index.html b/static/report/index.html index 76caed95fd7..e8442d93be0 100644 --- a/static/report/index.html +++ b/static/report/index.html @@ -6,7 +6,7 @@ -

    Nightscout

    +

    Nightscout reporting

      diff --git a/static/report/js/report.js b/static/report/js/report.js index a6f9f17df63..a18f2b20c6d 100644 --- a/static/report/js/report.js +++ b/static/report/js/report.js @@ -14,7 +14,7 @@ } else { client.init(serverSettings, Nightscout.plugins); } - + var translate = client.translate; var maxInsulinValue = 0 @@ -37,8 +37,8 @@ Nightscout.reports.containerprefix = 'chart-'; - var categories = []; - var foodlist = []; + var food_categories = []; + var food_list = []; @@ -78,11 +78,11 @@ $.ajax('/api/v1/food/regular.json', { success: function foodLoadSuccess(records) { records.forEach(function (r) { - foodlist.push(r); - if (r.category && !categories[r.category]) categories[r.category] = {}; - if (r.category && r.subcategory) categories[r.category][r.subcategory] = true; + food_list.push(r); + if (r.category && !food_categories[r.category]) food_categories[r.category] = {}; + if (r.category && r.subcategory) food_categories[r.category][r.subcategory] = true; }); - fillForm(); + fillFoodForm(); } }).done(function() { $('#info').html(''); @@ -101,7 +101,7 @@ $('#rp_targetlow').val(targetBGdefault[client.settings.units].low); $('#rp_targethigh').val(targetBGdefault[client.settings.units].high); - $('.menutab').click(switchtab); + $('.menutab').click(switchreport_handler); setDataRange(null,7); }).fail(function() { @@ -121,7 +121,7 @@ $('#rp_targetlow').val(targetBGdefault[client.settings.units].low); $('#rp_targethigh').val(targetBGdefault[client.settings.units].high); - $('.menutab').click(switchtab); + $('.menutab').click(switchreport_handler); setDataRange(null,7); }); @@ -343,7 +343,7 @@ if (event) event.preventDefault(); } - function switchtab(event) { + function switchreport_handler(event) { var id = $(this).attr('id'); $('.menutab').removeClass('selected'); @@ -386,8 +386,10 @@ var query = '?find[date][$gte]='+from+'&find[date][$lt]='+to+'&count=10000'; $('#'+Nightscout.reports.containerprefix+day).html(''+translate('Loading CGM data of')+' '+day+' ...'); +console.log('/api/v1/entries.json'+query); $.ajax('/api/v1/entries.json'+query, { success: function (xhr) { +console.log(xhr); xhr.forEach(function (element) { if (element) { if (element.mbg) { @@ -516,31 +518,31 @@ console.log(data.sgv); // Filtering food code // ------------------- - var categories = []; - var foodlist = []; + var food_categories = []; + var food_list = []; var filter = { category: '' , subcategory: '' , name: '' }; - function fillForm(event) { + function fillFoodForm(event) { $('#rp_category').empty().append(new Option(translate('(none)'),'')); - for (var s in categories) { + for (var s in food_categories) { $('#rp_category').append(new Option(s,s)); } filter.category = ''; - fillSubcategories(); + fillFoodSubcategories(); - $('#rp_category').change(fillSubcategories); - $('#rp_subcategory').change(doFilter); - $('#rp_name').on('input',doFilter); + $('#rp_category').change(fillFoodSubcategories); + $('#rp_subcategory').change(doFoodFilter); + $('#rp_name').on('input',doFoodFilter); if (event) event.preventDefault(); return false; } - function fillSubcategories(event) { + function fillFoodSubcategories(event) { if (event) { event.preventDefault(); } @@ -548,30 +550,30 @@ console.log(data.sgv); filter.subcategory = ''; $('#rp_subcategory').empty().append(new Option(translate('(none)'),'')); if (filter.category != '') { - for (var s in categories[filter.category]) { + for (var s in food_categories[filter.category]) { $('#rp_subcategory').append(new Option(s,s)); } } - doFilter(); + doFoodFilter(); } - function doFilter(event) { + function doFoodFilter(event) { if (event) { filter.category = $('#rp_category').val(); filter.subcategory = $('#rp_subcategory').val(); filter.name = $('#rp_name').val(); } $('#rp_food').empty(); - for (var i=0; i Date: Fri, 28 Aug 2015 21:24:00 +0200 Subject: [PATCH 716/937] make reports plugins --- bundle/bundle.source.js | 1 + .../js => lib/report_plugins}/calibrations.js | 33 +- .../js => lib/report_plugins}/dailystats.js | 22 +- .../js => lib/report_plugins}/daytoday.js | 30 +- .../report_plugins}/glucosedistribution.js | 17 +- .../js => lib/report_plugins}/hourlystats.js | 17 +- lib/report_plugins/index.js | 45 +++ .../js => lib/report_plugins}/percentile.js | 35 +- .../js => lib/report_plugins}/success.js | 105 +++--- .../js => lib/report_plugins}/treatments.js | 89 +++-- lib/report_plugins/utils.js | 73 +++++ static/report/index.html | 8 - static/report/js/report.js | 310 +++++++----------- 13 files changed, 477 insertions(+), 308 deletions(-) rename {static/report/js => lib/report_plugins}/calibrations.js (94%) rename {static/report/js => lib/report_plugins}/dailystats.js (87%) rename {static/report/js => lib/report_plugins}/daytoday.js (95%) rename {static/report/js => lib/report_plugins}/glucosedistribution.js (93%) rename {static/report/js => lib/report_plugins}/hourlystats.js (93%) create mode 100644 lib/report_plugins/index.js rename {static/report/js => lib/report_plugins}/percentile.js (82%) rename {static/report/js => lib/report_plugins}/success.js (64%) rename {static/report/js => lib/report_plugins}/treatments.js (70%) create mode 100644 lib/report_plugins/utils.js diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index f9c9e195411..0a6eaee0058 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -8,6 +8,7 @@ window.Nightscout = { client: require('../lib/client') , plugins: require('../lib/plugins/')().registerClientDefaults() + , report_plugins: require('../lib/report_plugins/')() }; console.info('Nightscout bundle ready'); diff --git a/static/report/js/calibrations.js b/lib/report_plugins/calibrations.js similarity index 94% rename from static/report/js/calibrations.js rename to lib/report_plugins/calibrations.js index c66275e4968..02692de8236 100644 --- a/static/report/js/calibrations.js +++ b/lib/report_plugins/calibrations.js @@ -1,4 +1,19 @@ -function report_calibrations(datastorage,daystoshow,options) { +'use strict'; + +var calibrations = { + name: 'calibrations' + , label: 'Calibrations' + , pluginType: 'report' +}; + +function init() { + return calibrations; +} + +module.exports = init; + + +calibrations.report = function report_calibrations(datastorage,daystoshow,options) { var padding = { top: 15, right: 15, bottom: 30, left: 70 }; var treatments = []; Object.keys(daystoshow).forEach(function (day) { @@ -147,17 +162,13 @@ function report_calibrations(datastorage,daystoshow,options) { .domain([0,400000]); var xAxis2 = d3.svg.axis() - .scale(xScale2) -// .tickFormat(d3.time.format(getTimeFormat(true))) - .ticks(10) - .orient('bottom'); + .scale(xScale2) + .ticks(10) + .orient('bottom'); - yAxis2 = d3.svg.axis() - .scale(yScale2) -// .tickFormat(d3.format('d')) -// .tickValues(tickValues) -// .orient('right'); - .orient('left'); + var yAxis2 = d3.svg.axis() + .scale(yScale2) + .orient('left'); // get current data range var dataRange = [0,maxBG]; diff --git a/static/report/js/dailystats.js b/lib/report_plugins/dailystats.js similarity index 87% rename from static/report/js/dailystats.js rename to lib/report_plugins/dailystats.js index ea34bf9cb93..7fd3b48756e 100644 --- a/static/report/js/dailystats.js +++ b/lib/report_plugins/dailystats.js @@ -1,7 +1,23 @@ - function report_dailystats(datastorage,daystoshow,options) { +'use strict'; + +var dailystats = { + name: 'dailystats' + , label: 'Dailystats' + , pluginType: 'report' +}; + +function init() { + return dailystats; +} + +module.exports = init; + + +dailystats.report = function report_dailystats(datastorage,daystoshow,options) { var Nightscout = window.Nightscout; var client = Nightscout.client; var translate = client.translate; + var report_plugins = Nightscout.report_plugins; var todo = []; // var data = o[0]; @@ -43,7 +59,7 @@ if (daysRecords.length == 0) { $("
    ").appendTo(tr); + $("").appendTo(tr); $('').appendTo(tr); table.append(tr); return;; @@ -71,7 +87,7 @@ var bgValues = daysRecords.map(function(r) { return r.sgv; }); $("").appendTo(tr); - $("").appendTo(tr); + $("").appendTo(tr); $("").appendTo(tr); $("").appendTo(tr); $("").appendTo(tr); diff --git a/static/report/js/daytoday.js b/lib/report_plugins/daytoday.js similarity index 95% rename from static/report/js/daytoday.js rename to lib/report_plugins/daytoday.js index 41ec2aaad64..1ca56a6be8c 100644 --- a/static/report/js/daytoday.js +++ b/lib/report_plugins/daytoday.js @@ -1,16 +1,32 @@ - function report_daytoday(datastorage,daystoshow,options) { +'use strict'; + +var daytoday = { + name: 'daytoday' + , label: 'Daytoday' + , pluginType: 'report' +}; + +function init() { + return daytoday; +} + +module.exports = init; + + +daytoday.report = function report_daytoday(datastorage,daystoshow,options) { var Nightscout = window.Nightscout; var client = Nightscout.client; var translate = client.translate; var profile = client.sbx.data.profile; - var scaledTreatmentBG = Nightscout.reports.scaledTreatmentBG; + var report_plugins = Nightscout.report_plugins; + var scaledTreatmentBG = report_plugins.utils.scaledTreatmentBG; var padding = { top: 15, right: 15, bottom: 30, left: 35 }; var FORMAT_TIME_12 = '%I' , FORMAT_TIME_24 = '%H'; - for (day in daystoshow) { + for (var day in daystoshow) { drawChart(day,datastorage[day],options); } @@ -33,7 +49,7 @@ , foodtexts = 0; // Tick Values - if (options.scale == Nightscout.reports.SCALE_LOG) { + if (options.scale == report_plugins.getProperty('SCALE_LOG')) { if (client.settings.units == 'mmol') { tickValues = [ 2.0 @@ -89,9 +105,9 @@ } // create svg and g to contain the chart contents - charts = d3.select('#'+Nightscout.reports.containerprefix+day).html( + charts = d3.select('#'+report_plugins.getProperty('containerprefix')+day).html( ''+ - Nightscout.reports.localeDate(day)+ + report_plugins.utils.localeDate(day)+ '
    ' ).append('svg'); @@ -106,7 +122,7 @@ xScale2 = d3.time.scale() .domain(d3.extent(data.sgv, function (d) { return d.date; })); - if (options.scale == Nightscout.reports.SCALE_LOG) { + if (options.scale == report_plugins.getProperty('SCALE_LOG')) { yScale2 = d3.scale.log() .domain([client.utils.scaleMgdl(36), client.utils.scaleMgdl(420)]); } else { diff --git a/static/report/js/glucosedistribution.js b/lib/report_plugins/glucosedistribution.js similarity index 93% rename from static/report/js/glucosedistribution.js rename to lib/report_plugins/glucosedistribution.js index 36addc1c5c1..ab7fa77ef3a 100644 --- a/static/report/js/glucosedistribution.js +++ b/lib/report_plugins/glucosedistribution.js @@ -1,4 +1,19 @@ - function report_glucosedistribution(datastorage,daystoshow,options) { +'use strict'; + +var glucosedistribution = { + name: 'glucosedistribution' + , label: 'Glucose distribution' + , pluginType: 'report' +}; + +function init() { + return glucosedistribution; +} + +module.exports = init; + + +glucosedistribution.report = function report_glucosedistribution(datastorage,daystoshow,options) { var Nightscout = window.Nightscout; var client = Nightscout.client; var translate = client.translate; diff --git a/static/report/js/hourlystats.js b/lib/report_plugins/hourlystats.js similarity index 93% rename from static/report/js/hourlystats.js rename to lib/report_plugins/hourlystats.js index 4ad375d7805..3a9cc33434d 100644 --- a/static/report/js/hourlystats.js +++ b/lib/report_plugins/hourlystats.js @@ -1,4 +1,19 @@ - function report_hourlystats(datastorage,daystoshow,options) { +'use strict'; + +var hourlystats = { + name: 'hourlystats' + , label: 'Hourly stats' + , pluginType: 'report' +}; + +function init() { + return hourlystats; +} + +module.exports = init; + + +hourlystats.report = function report_hourlystats(datastorage,daystoshow,options) { var Nightscout = window.Nightscout; var client = Nightscout.client; var translate = client.translate; diff --git a/lib/report_plugins/index.js b/lib/report_plugins/index.js new file mode 100644 index 00000000000..f7ff151e9ab --- /dev/null +++ b/lib/report_plugins/index.js @@ -0,0 +1,45 @@ +'use strict'; + +var _ = require('lodash'); + +function init() { + + var properties = {} + , allPlugins = [ + require('./daytoday')() + , require('./dailystats')() + , require('./glucosedistribution')() + , require('./hourlystats')() + , require('./percentile')() + , require('./success')() + , require('./calibrations')() + , require('./treatments')() + ]; + + function plugins(name) { + if (name) { + return _.find(allPlugins, {name: name}); + } else { + return plugins; + } + } + + plugins.eachPlugin = function eachPlugin(f) { + _.each(allPlugins, f); + }; + + plugins.setProperty = function setProperty(p, val) { + properties[p] = val; + }; + + plugins.getProperty = function getProperty(p) { + return properties[p]; + }; + + plugins.utils = require('./utils')(); + + return plugins(); + +} + +module.exports = init; \ No newline at end of file diff --git a/static/report/js/percentile.js b/lib/report_plugins/percentile.js similarity index 82% rename from static/report/js/percentile.js rename to lib/report_plugins/percentile.js index 77e9374a4ef..d0f781bb741 100644 --- a/static/report/js/percentile.js +++ b/lib/report_plugins/percentile.js @@ -1,4 +1,19 @@ - function report_percentile(datastorage,daystoshow,options) { +'use strict'; + +var percentile = { + name: 'percentile' + , label: 'Percentile' + , pluginType: 'report' +}; + +function init() { + return percentile; +} + +module.exports = init; + + +percentile.report = function report_percentile(datastorage,daystoshow,options) { var Nightscout = window.Nightscout; var client = Nightscout.client; var translate = client.translate; @@ -14,8 +29,8 @@ }); var bins = []; - for (hour = 0; hour < 24; hour++) { - for (minute = 0; minute < 60; minute = minute + minutewindow) { + for (var hour = 0; hour < 24; hour++) { + for (var minute = 0; minute < 60; minute = minute + minutewindow) { var date = new Date(); date.setHours(hour); date.setMinutes(minute); @@ -32,23 +47,23 @@ //readings.forEach(function(x){console.log(x)}); } } - dat10 = bins.map(function(bin) { + var dat10 = bins.map(function(bin) { return [bin[0], ss.quantile(bin[1], 0.1)]; }); - dat25 = bins.map(function(bin) { + var dat25 = bins.map(function(bin) { return [bin[0], ss.quantile(bin[1], 0.25)]; }); - dat50 = bins.map(function(bin) { + var dat50 = bins.map(function(bin) { return [bin[0], ss.quantile(bin[1], 0.5)]; }); - dat75 = bins.map(function(bin) { + var dat75 = bins.map(function(bin) { return [bin[0], ss.quantile(bin[1], 0.75)]; }); - dat90 = bins.map(function(bin) { + var dat90 = bins.map(function(bin) { return [bin[0], ss.quantile(bin[1], 0.9)]; }); - high = options.targetHigh; - low = options.targetLow; + var high = options.targetHigh; + var low = options.targetLow; //dat50.forEach(function(x){console.log(x[0] + " - " + x[1])}); $.plot( "#percentile-chart", [{ diff --git a/static/report/js/success.js b/lib/report_plugins/success.js similarity index 64% rename from static/report/js/success.js rename to lib/report_plugins/success.js index 20a120d892f..6e0d9ec037e 100644 --- a/static/report/js/success.js +++ b/lib/report_plugins/success.js @@ -1,4 +1,19 @@ -function report_success(datastorage,daystoshow,options) { +'use strict'; + +var success = { + name: 'success' + , label: 'Success' + , pluginType: 'report' +}; + +function init() { + return success; +} + +module.exports = init; + + +success.report = function report_success(datastorage,daystoshow,options) { var Nightscout = window.Nightscout; var client = Nightscout.client; var translate = client.translate; @@ -33,7 +48,7 @@ function report_success(datastorage,daystoshow,options) { var dim = function(n) { var a = []; - for (i = 0; i < n; i++) { + for (var i = 0; i < n; i++) { a[i]=0; } return a; @@ -52,51 +67,47 @@ function report_success(datastorage,daystoshow,options) { upperQuartile: 0, average: 0 }; - try { - quarters = dim(quarters).map(function(blank, n) { - var starting = new Date(now - (n+1) * period), - ending = new Date(now - n * period); - return { - starting: starting, - ending: ending, - records: data.filter(function(record) { - return record.displayTime > starting && record.displayTime <= ending; - }) - }; - }).filter(function(quarter) { - return quarter.records.length > 0; - }).map(function(quarter, ix, all) { - var bgValues = quarter.records.map(function(record) { - return record.sgv; - }); - quarter.standardDeviation = ss.standard_deviation(bgValues); - quarter.average = bgValues.length > 0? (sum(bgValues) / bgValues.length): "N/A"; - quarter.lowerQuartile = ss.quantile(bgValues, 0.25); - quarter.upperQuartile = ss.quantile(bgValues, 0.75); - quarter.numberLow = bgValues.filter(function(bg) { - return bg < low; - }).length; - quarter.numberHigh = bgValues.filter(function(bg) { - return bg >= high; - }).length; - quarter.numberInRange = bgValues.length - (quarter.numberHigh + quarter.numberLow); - - quarter.percentLow = (quarter.numberLow / bgValues.length) * 100; - quarter.percentInRange = (quarter.numberInRange / bgValues.length) * 100; - quarter.percentHigh = (quarter.numberHigh / bgValues.length) * 100; - - averages.percentLow += quarter.percentLow / all.length; - averages.percentInRange += quarter.percentInRange / all.length; - averages.percentHigh += quarter.percentHigh / all.length; - averages.lowerQuartile += quarter.lowerQuartile / all.length; - averages.upperQuartile += quarter.upperQuartile / all.length; - averages.average += quarter.average / all.length; - averages.standardDeviation += quarter.standardDeviation / all.length; - return quarter; - }); - } catch (e) { - console.log(e); - } + quarters = dim(quarters).map(function(blank, n) { + var starting = new Date(now - (n+1) * period), + ending = new Date(now - n * period); + return { + starting: starting, + ending: ending, + records: data.filter(function(record) { + return record.displayTime > starting && record.displayTime <= ending; + }) + }; + }).filter(function(quarter) { + return quarter.records.length > 0; + }).map(function(quarter, ix, all) { + var bgValues = quarter.records.map(function(record) { + return record.sgv; + }); + quarter.standardDeviation = ss.standard_deviation(bgValues); + quarter.average = bgValues.length > 0? (sum(bgValues) / bgValues.length): "N/A"; + quarter.lowerQuartile = ss.quantile(bgValues, 0.25); + quarter.upperQuartile = ss.quantile(bgValues, 0.75); + quarter.numberLow = bgValues.filter(function(bg) { + return bg < low; + }).length; + quarter.numberHigh = bgValues.filter(function(bg) { + return bg >= high; + }).length; + quarter.numberInRange = bgValues.length - (quarter.numberHigh + quarter.numberLow); + + quarter.percentLow = (quarter.numberLow / bgValues.length) * 100; + quarter.percentInRange = (quarter.numberInRange / bgValues.length) * 100; + quarter.percentHigh = (quarter.numberHigh / bgValues.length) * 100; + + averages.percentLow += quarter.percentLow / all.length; + averages.percentInRange += quarter.percentInRange / all.length; + averages.percentHigh += quarter.percentHigh / all.length; + averages.lowerQuartile += quarter.lowerQuartile / all.length; + averages.upperQuartile += quarter.upperQuartile / all.length; + averages.average += quarter.average / all.length; + averages.standardDeviation += quarter.standardDeviation / all.length; + return quarter; + }); var lowComparison = function(quarter, averages, field, invert) { if (quarter[field] < averages[field] * 0.8) { diff --git a/static/report/js/treatments.js b/lib/report_plugins/treatments.js similarity index 70% rename from static/report/js/treatments.js rename to lib/report_plugins/treatments.js index 91649ea01c7..dcef65abb22 100644 --- a/static/report/js/treatments.js +++ b/lib/report_plugins/treatments.js @@ -1,41 +1,20 @@ - function report_treatments(datastorage,daystoshow,options) { - var Nightscout = window.Nightscout; - var client = Nightscout.client; - var translate = client.translate; +'use strict'; - var icon_remove = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACrElEQVQ4T42Ty2sTQRzHv5tmk2yyjRNtpfZhL8V6s2KoUNC2XqwgaCsVQcGiFqpHi0c9iRdR/ANE9KR40FIQX4cueKoPaKFoLdSYNtE0abKT1+5s9iW7aUMiHtzTzO7v85md+c6PA4DrHbsPCKIgOWO1pA7dT6YXnXH949SE/F63pqwZtRrO+SCKgjQ5NUV+azpmHj2krMwaJC4c8Erj+/eRyloMMwWFKgbn1nC3ervlK1evkXBLGBZT8SOewotnTylTNLdgeg/pDgZDC2cPHSR8bB22DVC9hFe0SG/H0xFXcHlykjRHRDBWgJcZSCY38Xx2lhqMnRYE34Px/sN9vlQWeoHBAx2yXsRruVAVuFsIBaSJ8+eJGPaBqQV4NROJjTzez89jLBoFn6FgybQL54wS3uTyVDFQ3cL2IYpBv3RhdJSIIQ80tQyv7gEqJvS8AmUlBs7UXPhtjtZgh3UFNYngk86NHCfNAg9dMwHVBPu+CpsVkTXKeJeVG+AGgTOZ3tt6MSKKjy+NjEBjFrR4ElZmA4pdxstMFsyyJu6tZZ7Ux9vwB6EAL50ZGiRECEPPUOixVTRxHlicgSVWxEdZpuZWfNuS2hk48NjwMIkIYZglBnV5Cbqtws/5IaAJmsfCglrEl2y2QeKmEBJ80tixKmxrFpSVr0gV0viQoxho2YUuPohmeFD22PiklLC4ma5JuBvdrfLJI0dJd0s7bM0ES8aR/BXDXGaTskqlL+D3Lwy0tZEePoAd4EA5YF4tYymdonfjmQh3s6dTPjU4SHYGwjAKecSXFyGlM1TdytntE56T+ts7SC/vhw3gm6njc2Kd3vm5Ub1IwQAvnYhGiZpYw1wiWYPrIw7wnBTt7CLOOwdmut14kQQvqt24tfK/utGR6LaF+iRqMf4N/O/8D28HiiCRYqzAAAAAAElFTkSuQmCC"; - var icon_edit = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABEUlEQVQ4jZ3MMUsCYQDG8ee8IySQbNCLyyEKG/RLNAXicqvQcAeNLrcFLlE0+xHuNpt8wy04rrYm8Q4HQRE56BSC3lSqU1BwCoxM39dnffj9BWyxXvVeEzvtctBwHyRebNu2Nk2lzMlrgJB+qBEeTByiKYpihl+fIO8jTI9PDJEVF1+K2iw+M6PhDuyag4NkQi/c3FkCK5Z3ZbM76qLltpCbn+vXxq0FABsDy9hzPdBvqvtXvvXzrw1swmsDLPjfACteGeDBfwK8+FdgGwwAIgC0ncsjxGRSH/eiPBgAJADY2z8sJ4JBfNBsDqlADVYMANIzKalv/bHaefKsTH9iPFb8ISsGAJym0+Qinz3jQktbAHcxvx3559eSAAAAAElFTkSuQmCC"; +var treatments = { + name: 'treatments' + , label: 'Treatments' + , pluginType: 'report' +}; - var table = '
    '+translate('Time')+''+translate('Event Type')+''+translate('Blood Glucose')+''+translate('Insulin')+''+translate('Carbs')+''+translate('Entered By')+''+translate('Notes')+'
    '+localeDate(day)+'
    '+(new Date(tr.created_at).toLocaleTimeString().replace(/([\d]+:[\d]{2})(:[\d]{2})(.*)/, "$1$3"))+''+(tr.eventType ? tr.eventType : '')+''+(tr.glucose ? tr.glucose + ' ('+translate(tr.glucoseType)+')' : '')+''+(tr.insulin ? tr.insulin : '')+''+(tr.carbs ? tr.carbs : '')+''+(tr.enteredBy ? tr.enteredBy : '')+''+(tr.notes ? tr.notes : '')+'
    ").appendTo(tr); - $("" + localeDate(dayInQuestion) + "" + Nightscout.reports.localeDate(dayInQuestion) + "'+translate('No data available')+'
    " + localeDate(dayInQuestion) + "" + Nightscout.reports.localeDate(dayInQuestion) + "" + Math.floor((100 * stats.lows) / daysRecords.length) + "%" + Math.floor((100 * stats.normal) / daysRecords.length) + "%" + Math.floor((100 * stats.highs) / daysRecords.length) + "%
    '+translate('Time')+''+translate('Event Type')+''+translate('Blood Glucose')+''+translate('Insulin')+''+translate('Carbs')+''+translate('Entered By')+''+translate('Notes')+'
    '+localeDate(day)+'
    '+Nightscout.reports.localeDate(day)+'
    ").appendTo(tr); - $("" + Nightscout.reports.localeDate(dayInQuestion) + "" + report_plugins.utils.localeDate(dayInQuestion) + "'+translate('No data available')+'
    " + Nightscout.reports.localeDate(dayInQuestion) + "" + report_plugins.utils.localeDate(dayInQuestion) + "" + Math.floor((100 * stats.lows) / daysRecords.length) + "%" + Math.floor((100 * stats.normal) / daysRecords.length) + "%" + Math.floor((100 * stats.highs) / daysRecords.length) + "%
    '; - table += ''; - - Object.keys(daystoshow).forEach(function (day) { - table += ''; - var treatments = datastorage[day].treatments; - for (var t=0; t'; - table += ' '; - table += ''; - table += ''; - table += ''; - table += ''; - table += ''; - table += ''; - table += ''; - table += ''; - table += ''; - - table += ''; - } - }); - $('#treatments-report').html(table); - $('.deleteTreatment').click(deleteTreatment); - $('.editTreatment').click(editTreatment); - } - +function init() { + return treatments; +} + +module.exports = init; + + +treatments.report = function report_treatments(datastorage,daystoshow,options) { + function deleteTreatment(event) { var data = JSON.parse($(this).attr('data')); var day = $(this).attr('day'); @@ -156,3 +135,41 @@ return true; } + var Nightscout = window.Nightscout; + var client = Nightscout.client; + var translate = client.translate; + var report_plugins = Nightscout.report_plugins; + + var icon_remove = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACrElEQVQ4T42Ty2sTQRzHv5tmk2yyjRNtpfZhL8V6s2KoUNC2XqwgaCsVQcGiFqpHi0c9iRdR/ANE9KR40FIQX4cueKoPaKFoLdSYNtE0abKT1+5s9iW7aUMiHtzTzO7v85md+c6PA4DrHbsPCKIgOWO1pA7dT6YXnXH949SE/F63pqwZtRrO+SCKgjQ5NUV+azpmHj2krMwaJC4c8Erj+/eRyloMMwWFKgbn1nC3ervlK1evkXBLGBZT8SOewotnTylTNLdgeg/pDgZDC2cPHSR8bB22DVC9hFe0SG/H0xFXcHlykjRHRDBWgJcZSCY38Xx2lhqMnRYE34Px/sN9vlQWeoHBAx2yXsRruVAVuFsIBaSJ8+eJGPaBqQV4NROJjTzez89jLBoFn6FgybQL54wS3uTyVDFQ3cL2IYpBv3RhdJSIIQ80tQyv7gEqJvS8AmUlBs7UXPhtjtZgh3UFNYngk86NHCfNAg9dMwHVBPu+CpsVkTXKeJeVG+AGgTOZ3tt6MSKKjy+NjEBjFrR4ElZmA4pdxstMFsyyJu6tZZ7Ux9vwB6EAL50ZGiRECEPPUOixVTRxHlicgSVWxEdZpuZWfNuS2hk48NjwMIkIYZglBnV5Cbqtws/5IaAJmsfCglrEl2y2QeKmEBJ80tixKmxrFpSVr0gV0viQoxho2YUuPohmeFD22PiklLC4ma5JuBvdrfLJI0dJd0s7bM0ES8aR/BXDXGaTskqlL+D3Lwy0tZEePoAd4EA5YF4tYymdonfjmQh3s6dTPjU4SHYGwjAKecSXFyGlM1TdytntE56T+ts7SC/vhw3gm6njc2Kd3vm5Ub1IwQAvnYhGiZpYw1wiWYPrIw7wnBTt7CLOOwdmut14kQQvqt24tfK/utGR6LaF+iRqMf4N/O/8D28HiiCRYqzAAAAAAElFTkSuQmCC"; + var icon_edit = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABEUlEQVQ4jZ3MMUsCYQDG8ee8IySQbNCLyyEKG/RLNAXicqvQcAeNLrcFLlE0+xHuNpt8wy04rrYm8Q4HQRE56BSC3lSqU1BwCoxM39dnffj9BWyxXvVeEzvtctBwHyRebNu2Nk2lzMlrgJB+qBEeTByiKYpihl+fIO8jTI9PDJEVF1+K2iw+M6PhDuyag4NkQi/c3FkCK5Z3ZbM76qLltpCbn+vXxq0FABsDy9hzPdBvqvtXvvXzrw1swmsDLPjfACteGeDBfwK8+FdgGwwAIgC0ncsjxGRSH/eiPBgAJADY2z8sJ4JBfNBsDqlADVYMANIzKalv/bHaefKsTH9iPFb8ISsGAJym0+Qinz3jQktbAHcxvx3559eSAAAAAElFTkSuQmCC"; + + var table = '
    '+translate('Time')+''+translate('Event Type')+''+translate('Blood Glucose')+''+translate('Insulin')+''+translate('Carbs')+''+translate('Entered By')+''+translate('Notes')+'
    '+Nightscout.reports.localeDate(day)+'
    '+(new Date(tr.created_at).toLocaleTimeString().replace(/([\d]+:[\d]{2})(:[\d]{2})(.*)/, "$1$3"))+''+(tr.eventType ? tr.eventType : '')+''+(tr.glucose ? tr.glucose + ' ('+translate(tr.glucoseType)+')' : '')+''+(tr.insulin ? tr.insulin : '')+''+(tr.carbs ? tr.carbs : '')+''+(tr.enteredBy ? tr.enteredBy : '')+''+(tr.notes ? tr.notes : '')+'
    '; + table += ''; + + Object.keys(daystoshow).forEach(function (day) { + table += ''; + var treatments = datastorage[day].treatments; + for (var t=0; t'; + table += ' '; + table += ''; + table += ''; + table += ''; + table += ''; + table += ''; + table += ''; + table += ''; + table += ''; + table += ''; + + table += ''; + } + }); + $('#treatments-report').html(table); + $('.deleteTreatment').click(deleteTreatment); + $('.editTreatment').click(editTreatment); +} + diff --git a/lib/report_plugins/utils.js b/lib/report_plugins/utils.js new file mode 100644 index 00000000000..b4235fcbce5 --- /dev/null +++ b/lib/report_plugins/utils.js @@ -0,0 +1,73 @@ +'use strict'; + +var utils = { }; + +function init( ) { + return utils; +} + +module.exports = init; + +utils.localeDate = function localeDate(day) { + var translate = Nightscout.client.translate; + var ret = + [translate("Sunday"),translate("Monday"),translate("Tuesday"),translate("Wednesday"),translate("Thursday"),translate("Friday"),translate("Saturday")][new Date(day).getDay()]; + ret += ' '; + ret += new Date(day).toLocaleDateString(); + return ret; +} + +utils.localeDateTime = function localeDateTime(day) { + var ret = new Date(day).toLocaleDateString() + ' ' + new Date(day).toLocaleTimeString(); + return ret; +} + +utils.scaledTreatmentBG = function scaledTreatmentBG(treatment,data) { + var client = Nightscout.client; + + var SIX_MINS_IN_MS = 360000; + + function calcBGByTime(time) { + var closeBGs = data.filter(function(d) { + if (!d.y) { + return false; + } else { + return Math.abs((new Date(d.date)).getTime() - time) <= SIX_MINS_IN_MS; + } + }); + + var totalBG = 0; + closeBGs.forEach(function(d) { + totalBG += Number(d.y); + }); + + return totalBG > 0 ? (totalBG / closeBGs.length) : 450; + } + + var treatmentGlucose = null; + + if (treatment.glucose && isNaN(treatment.glucose)) { + console.warn('found an invalid glucose value', treatment); + } else { + if (treatment.glucose && treatment.units && client.settings.units) { + if (treatment.units != client.settings.units) { + console.info('found mismatched glucose units, converting ' + treatment.units + ' into ' + client.settings.units, treatment); + if (treatment.units == 'mmol') { + //BG is in mmol and display in mg/dl + treatmentGlucose = Math.round(treatment.glucose * 18) + } else { + //BG is in mg/dl and display in mmol + treatmentGlucose = client.utils.scaleMgdl(treatment.glucose); + } + } else { + treatmentGlucose = treatment.glucose; + } + } else if (treatment.glucose) { + //no units, assume everything is the same + console.warn('found an glucose value with any units, maybe from an old version?', treatment); + treatmentGlucose = treatment.glucose; + } + } + + return treatmentGlucose || client.utils.scaleMgdl(calcBGByTime(treatment.mills)); +} diff --git a/static/report/index.html b/static/report/index.html index e8442d93be0..2ab71406553 100644 --- a/static/report/index.html +++ b/static/report/index.html @@ -228,14 +228,6 @@

    Calibrations

    - - - - - - - - diff --git a/static/report/js/report.js b/static/report/js/report.js index a18f2b20c6d..fd4d41195ca 100644 --- a/static/report/js/report.js +++ b/static/report/js/report.js @@ -6,15 +6,14 @@ var moment = window.moment; var Nightscout = window.Nightscout; var client = Nightscout.client; + var report_plugins = Nightscout.report_plugins; - Nightscout.reports = Nightscout.reports || {}; - if (serverSettings === undefined) { console.error('server settings were not loaded, will not call init'); } else { client.init(serverSettings, Nightscout.plugins); } - + var translate = client.translate; var maxInsulinValue = 0 @@ -32,14 +31,74 @@ ONE_MIN_IN_MS = 60000 , SIX_MINS_IN_MS = 360000; - Nightscout.reports.SCALE_LINEAR = 0; - Nightscout.reports.SCALE_LOG = 1; - Nightscout.reports.containerprefix = 'chart-'; + report_plugins.setProperty('SCALE_LINEAR', 0); + report_plugins.setProperty('SCALE_LOG', 1); + report_plugins.setProperty('containerprefix', 'chart-'); - + // ****** FOOD CODE START ****** var food_categories = []; var food_list = []; + var filter = { + category: '' + , subcategory: '' + , name: '' + }; + + function fillFoodForm(event) { + $('#rp_category').empty().append(new Option(translate('(none)'),'')); + for (var s in food_categories) { + $('#rp_category').append(new Option(s,s)); + } + filter.category = ''; + fillFoodSubcategories(); + + $('#rp_category').change(fillFoodSubcategories); + $('#rp_subcategory').change(doFoodFilter); + $('#rp_name').on('input',doFoodFilter); + + if (event) event.preventDefault(); + return false; + } + + function fillFoodSubcategories(event) { + if (event) { + event.preventDefault(); + } + filter.category = $('#rp_category').val(); + filter.subcategory = ''; + $('#rp_subcategory').empty().append(new Option(translate('(none)'),'')); + if (filter.category != '') { + for (var s in food_categories[filter.category]) { + $('#rp_subcategory').append(new Option(s,s)); + } + } + doFoodFilter(); + } + + function doFoodFilter(event) { + if (event) { + filter.category = $('#rp_category').val(); + filter.subcategory = $('#rp_subcategory').val(); + filter.name = $('#rp_name').val(); + } + $('#rp_food').empty(); + for (var i=0; i'+translate('Loading food database')+' ...'); - $.ajax('/api/v1/food/regular.json', { - success: function foodLoadSuccess(records) { - records.forEach(function (r) { - food_list.push(r); - if (r.category && !food_categories[r.category]) food_categories[r.category] = {}; - if (r.category && r.subcategory) food_categories[r.category][r.subcategory] = true; - }); - fillFoodForm(); - } - }).done(function() { - $('#info').html(''); - $('.presetdates').click(function(e) { var days = $(this).attr('days'); setDataRange(e,days); }); - - $('#rp_show').click(show); - $('#rp_food').change(function (event) { - event.preventDefault(); - $('#rp_enablefood').prop('checked',true); + $('#info').html(''+translate('Loading food database')+' ...'); + $.ajax('/api/v1/food/regular.json', { + success: function foodLoadSuccess(records) { + records.forEach(function (r) { + food_list.push(r); + if (r.category && !food_categories[r.category]) food_categories[r.category] = {}; + if (r.category && r.subcategory) food_categories[r.category][r.subcategory] = true; }); - $('#rp_notes').change(function (event) { - event.preventDefault(); - $('#rp_enablenotes').prop('checked',true); - }); - - $('#rp_targetlow').val(targetBGdefault[client.settings.units].low); - $('#rp_targethigh').val(targetBGdefault[client.settings.units].high); - - $('.menutab').click(switchreport_handler); + fillFoodForm(); + } + }).done(function() { + $('#info').html(''); + $('.presetdates').click(function(e) { var days = $(this).attr('days'); setDataRange(e,days); }); - setDataRange(null,7); - }).fail(function() { - $('#info').html(''); - $('.presetdates').click(function(e) { var days = $(this).attr('days'); setDataRange(e,days); }); + $('#rp_show').click(show); + $('#rp_food').change(function (event) { + event.preventDefault(); + $('#rp_enablefood').prop('checked',true); + }); + $('#rp_notes').change(function (event) { + event.preventDefault(); + $('#rp_enablenotes').prop('checked',true); + }); + + $('#rp_targetlow').val(targetBGdefault[client.settings.units].low); + $('#rp_targethigh').val(targetBGdefault[client.settings.units].high); + + $('.menutab').click(switchreport_handler); - $('#rp_show').click(show); - $('#rp_food').change(function (event) { - event.preventDefault(); - $('#rp_enablefood').prop('checked',true); - }); - $('#rp_notes').change(function (event) { - event.preventDefault(); - $('#rp_enablenotes').prop('checked',true); - }); - - $('#rp_targetlow').val(targetBGdefault[client.settings.units].low); - $('#rp_targethigh').val(targetBGdefault[client.settings.units].high); - - $('.menutab').click(switchreport_handler); + setDataRange(null,7); + }).fail(function() { + $('#info').html(''); + $('.presetdates').click(function(e) { var days = $(this).attr('days'); setDataRange(e,days); }); - setDataRange(null,7); + $('#rp_show').click(show); + $('#rp_food').change(function (event) { + event.preventDefault(); + $('#rp_enablefood').prop('checked',true); + }); + $('#rp_notes').change(function (event) { + event.preventDefault(); + $('#rp_enablenotes').prop('checked',true); }); + + $('#rp_targetlow').val(targetBGdefault[client.settings.units].low); + $('#rp_targethigh').val(targetBGdefault[client.settings.units].high); + + $('.menutab').click(switchreport_handler); + + setDataRange(null,7); + }); function show(event) { var options = { @@ -139,7 +198,7 @@ carbs: true, iob : true, cob : true, - scale: Nightscout.reports.SCALE_LINEAR + scale: report_plugins.getProperty('SCALE_LINEAR') }; options.targetLow = parseFloat($('#rp_targetlow').val().replace(',','.')); @@ -151,7 +210,7 @@ options.food = $('#rp_optionsfood').is(':checked'); options.insulin = $('#rp_optionsinsulin').is(':checked'); options.carbs = $('#rp_optionscarbs').is(':checked'); - options.scale = $('#rp_linear').is(':checked') ? Nightscout.reports.SCALE_LINEAR : Nightscout.reports.SCALE_LOG; + options.scale = $('#rp_linear').is(':checked') ? report_plugins.getProperty('SCALE_LINEAR') : report_plugins.getProperty('SCALE_LOG'); options.width = parseInt($('#rp_size :selected').attr('x')); options.height = parseInt($('#rp_size :selected').attr('y')); @@ -323,7 +382,7 @@ for (var d in daystoshow) { if (!datastorage[d]) return; // all data not loaded yet } - +/* ['daytoday','dailystats','percentile','glucosedistribution','hourlystats','success','treatments','calibrations'].forEach(function (chart) { // jquery plot doesn't draw to hidden div $('#'+chart+'-placeholder').css('display',''); @@ -331,6 +390,14 @@ if (!$('#'+chart).hasClass('selected')) $('#'+chart+'-placeholder').css('display','none'); }); +*/ + report_plugins.eachPlugin(function (plugin) { + // jquery plot doesn't draw to hidden div + $('#'+plugin.name+'-placeholder').css('display',''); + plugin.report(datastorage,daystoshow,options); + if (!$('#'+plugin.name).hasClass('selected')) + $('#'+plugin.name+'-placeholder').css('display','none'); + }); $('#info').html(''); $('#rp_show').css('display',''); @@ -354,19 +421,6 @@ } - Nightscout.reports.localeDate = function localeDate(day) { - var ret = - [translate("Sunday"),translate("Monday"),translate("Tuesday"),translate("Wednesday"),translate("Thursday"),translate("Friday"),translate("Saturday")][new Date(day).getDay()]; - ret += ' '; - ret += new Date(day).toLocaleDateString(); - return ret; - } - - Nightscout.reports.localeDateTime = function localeDateTime(day) { - var ret = new Date(day).toLocaleDateString() + ' ' + new Date(day).toLocaleTimeString(); - return ret; - } - function loadData(day,options) { // check for loaded data if (datastorage[day] && day != moment().format('YYYY-MM-DD')) { @@ -385,7 +439,7 @@ var to = from + 1000 * 60 * 60 * 24; var query = '?find[date][$gte]='+from+'&find[date][$lt]='+to+'&count=10000'; - $('#'+Nightscout.reports.containerprefix+day).html(''+translate('Loading CGM data of')+' '+day+' ...'); + $('#'+report_plugins.getProperty('containerprefix')+day).html(''+translate('Loading CGM data of')+' '+day+' ...'); console.log('/api/v1/entries.json'+query); $.ajax('/api/v1/entries.json'+query, { success: function (xhr) { @@ -438,7 +492,7 @@ console.log(data.sgv); data.cal.sort(function(a, b) { return a.x - b.x; }); } }).done(function () { - $('#'+Nightscout.reports.containerprefix+day).html(''+translate('Loading treatments data of')+' '+day+' ...'); + $('#'+report_plugins.getProperty('containerprefix')+day).html(''+translate('Loading treatments data of')+' '+day+' ...'); var tquery = '?find[created_at][$gte]='+new Date(from).toISOString()+'&find[created_at][$lt]='+new Date(to).toISOString(); $.ajax('/api/v1/treatments.json'+tquery, { success: function (xhr) { @@ -451,7 +505,7 @@ console.log(data.sgv); data.treatments.sort(function(a, b) { return a.mills - b.mills; }); } }).done(function () { - $('#'+Nightscout.reports.containerprefix+day).html(''+translate('Processing data of')+' '+day+' ...'); + $('#'+report_plugins.getProperty('containerprefix')+day).html(''+translate('Processing data of')+' '+day+' ...'); processData(data,day,options); }); @@ -516,116 +570,4 @@ console.log(data.sgv); showreports(options); } - // Filtering food code - // ------------------- - var food_categories = []; - var food_list = []; - var filter = { - category: '' - , subcategory: '' - , name: '' - }; - - function fillFoodForm(event) { - $('#rp_category').empty().append(new Option(translate('(none)'),'')); - for (var s in food_categories) { - $('#rp_category').append(new Option(s,s)); - } - filter.category = ''; - fillFoodSubcategories(); - - $('#rp_category').change(fillFoodSubcategories); - $('#rp_subcategory').change(doFoodFilter); - $('#rp_name').on('input',doFoodFilter); - - if (event) event.preventDefault(); - return false; - } - - function fillFoodSubcategories(event) { - if (event) { - event.preventDefault(); - } - filter.category = $('#rp_category').val(); - filter.subcategory = ''; - $('#rp_subcategory').empty().append(new Option(translate('(none)'),'')); - if (filter.category != '') { - for (var s in food_categories[filter.category]) { - $('#rp_subcategory').append(new Option(s,s)); - } - } - doFoodFilter(); - } - - function doFoodFilter(event) { - if (event) { - filter.category = $('#rp_category').val(); - filter.subcategory = $('#rp_subcategory').val(); - filter.name = $('#rp_name').val(); - } - $('#rp_food').empty(); - for (var i=0; i 0 ? (totalBG / closeBGs.length) : 450; - } - - var treatmentGlucose = null; - - if (treatment.glucose && isNaN(treatment.glucose)) { - console.warn('found an invalid glucose value', treatment); - } else { - if (treatment.glucose && treatment.units && client.settings.units) { - if (treatment.units != client.settings.units) { - console.info('found mismatched glucose units, converting ' + treatment.units + ' into ' + client.settings.units, treatment); - if (treatment.units == 'mmol') { - //BG is in mmol and display in mg/dl - treatmentGlucose = Math.round(treatment.glucose * 18) - } else { - //BG is in mg/dl and display in mmol - treatmentGlucose = client.utils.scaleMgdl(treatment.glucose); - } - } else { - treatmentGlucose = treatment.glucose; - } - } else if (treatment.glucose) { - //no units, assume everything is the same - console.warn('found an glucose value with any units, maybe from an old version?', treatment); - treatmentGlucose = treatment.glucose; - } - } - - return treatmentGlucose || client.utils.scaleMgdl(calcBGByTime(treatment.mills)); - } - })(); \ No newline at end of file From a837f2262f25ed836033ab0f382489735a0395aa Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Fri, 28 Aug 2015 21:52:52 +0200 Subject: [PATCH 717/937] removed tabulators, fixed idents --- lib/report_plugins/calibrations.js | 492 ++++++++--------- lib/report_plugins/dailystats.js | 216 ++++---- lib/report_plugins/daytoday.js | 645 +++++++++++----------- lib/report_plugins/glucosedistribution.js | 192 +++---- lib/report_plugins/hourlystats.js | 198 ++++--- lib/report_plugins/percentile.js | 308 +++++------ lib/report_plugins/success.js | 260 ++++----- lib/report_plugins/treatments.js | 148 ++--- 8 files changed, 1203 insertions(+), 1256 deletions(-) diff --git a/lib/report_plugins/calibrations.js b/lib/report_plugins/calibrations.js index 02692de8236..bdb14f9c7a1 100644 --- a/lib/report_plugins/calibrations.js +++ b/lib/report_plugins/calibrations.js @@ -15,153 +15,121 @@ module.exports = init; calibrations.report = function report_calibrations(datastorage,daystoshow,options) { var padding = { top: 15, right: 15, bottom: 30, left: 70 }; - var treatments = []; - Object.keys(daystoshow).forEach(function (day) { - treatments = treatments.concat(datastorage[day].treatments.filter(function (t) { - if (t.eventType == "Sensor Start") return true; - if (t.eventType == "Sensor Change") return true; - return false; - })); - }); - - var cals = []; - Object.keys(daystoshow).forEach(function (day) { - cals = cals.concat(datastorage[day].cal); - }); - - var sgvs = []; - Object.keys(daystoshow).forEach(function (day) { - sgvs = sgvs.concat(datastorage[day].sgv); - }); - - var mbgs = []; - Object.keys(daystoshow).forEach(function (day) { - mbgs = mbgs.concat(datastorage[day].mbg); - }); - mbgs.forEach(function (mbg) { calibrations_calcmbg(mbg); }); - - - var events = treatments.concat(cals).concat(mbgs).sort(function(a, b) { return a.x - b.x; }); - - var colors = ['Aqua','Blue','Brown','Chartreuse','Coral','CornflowerBlue','DarkCyan','DarkMagenta','DarkOrange','Fuchsia','Green','Yellow']; - var colorindex = 0; - var html = '
    '+translate('Time')+''+translate('Event Type')+''+translate('Blood Glucose')+''+translate('Insulin')+''+translate('Carbs')+''+translate('Entered By')+''+translate('Notes')+'
    '+report_plugins.utils.localeDate(day)+'
    '+(new Date(tr.created_at).toLocaleTimeString().replace(/([\d]+:[\d]{2})(:[\d]{2})(.*)/, "$1$3"))+''+(tr.eventType ? tr.eventType : '')+''+(tr.glucose ? tr.glucose + ' ('+translate(tr.glucoseType)+')' : '')+''+(tr.insulin ? tr.insulin : '')+''+(tr.carbs ? tr.carbs : '')+''+(tr.enteredBy ? tr.enteredBy : '')+''+(tr.notes ? tr.notes : '')+'
    '; - var lastmbg = null; - for (var i=0; i'; - }; - - html += '
    '; - e.bgcolor = colors[colorindex]; - if (e.eventType) - html += ''+translate(e.eventType)+':
    '; - else if (typeof e.device !== 'undefined') { - html += ' '; - html += 'MBG: ' + e.y + ' Raw: '+e.raw+'
    '; - lastmbg = e; - e.cals = []; - e.checked = false; - } else if (typeof e.scale !== 'undefined') { - html += 'CAL: ' + ' Scale: ' + e.scale.toFixed(2) + ' Intercept: ' + e.intercept.toFixed(0) + ' Slope: ' + e.slope.toFixed(2) + '
    '; - if (lastmbg) lastmbg.cals.push(e); - } else html += JSON.stringify(e); - html += '
    '; - - $('#calibrations-list').html(html); - - // select last 3 mbgs - var maxcals = 3; - for (var i=events.length-1; i>0; i--) { - if (typeof events[i].device !== 'undefined') { - events[i].checked = true; - $('#calibrations-'+i).prop('checked',true); - if (--maxcals<1) break; - } - } - calibrations_drawelements(); - - $('.calibrations-checkbox').change(calibrations_checkboxevent); - - function calibrations_checkboxevent(event) { - var index = $(this).attr('index'); - events[index].checked = $(this).is(':checked'); - calibrations_drawelements(); - event.preventDefault(); - } - - function calibrations_drawelements() { - calibrations_drawChart(); - for (var i=0; i'; + e.bgcolor = colors[colorindex]; + if (e.eventType) + html += ''+translate(e.eventType)+':
    '; + else if (typeof e.device !== 'undefined') { + html += ' '; + html += 'MBG: ' + e.y + ' Raw: '+e.raw+'
    '; + lastmbg = e; + e.cals = []; + e.checked = false; + } else if (typeof e.scale !== 'undefined') { + html += 'CAL: ' + ' Scale: ' + e.scale.toFixed(2) + ' Intercept: ' + e.intercept.toFixed(0) + ' Slope: ' + e.slope.toFixed(2) + '
    '; + if (lastmbg) lastmbg.cals.push(e); + } else html += JSON.stringify(e); + html += ''; + }; + + html += ''; + + $('#calibrations-list').html(html); + + // select last 3 mbgs + var maxcals = 3; + for (var i=events.length-1; i>0; i--) { + if (typeof events[i].device !== 'undefined') { + events[i].checked = true; + $('#calibrations-'+i).prop('checked',true); + if (--maxcals<1) break; + } + } + calibrations_drawelements(); + + $('.calibrations-checkbox').change(calibrations_checkboxevent); + + function calibrations_checkboxevent(event) { + var index = $(this).attr('index'); + events[index].checked = $(this).is(':checked'); + calibrations_drawelements(); + event.preventDefault(); + } + + function calibrations_drawelements() { + calibrations_drawChart(); + for (var i=0; i5*60*1000) { - console.log('Last SGV too old for MBG. Time diff: '+((mbg.x-lastsgv.x)/1000/60).toFixed(1)+' min',mbg); - } else { - mbg.raw = lastsgv.filtered || lastsgv.unfiltered; - } - } else { - console.log('Last entry not found for MBG ',mbg); - } - } - - function calibrations_drawmbg(mbg,color) { - if (mbg.raw) { - calibration_context.append('circle') - .attr('cx', xScale2(mbg.y) + padding.left) - .attr('cy', yScale2(mbg.raw) + padding.top) - .attr('fill', color) - .style('opacity', 1) - .attr('stroke-width', 1) - .attr('stroke', 'black') - .attr('r', 5); - } - } - - function calibrations_findlatest(date,storage) { - var last = null; - var time = date.getTime(); - for (var i=0; i time) - return last; - last = storage[i]; - } - return last; - } - + // get current data range + var dataRange = [0,maxBG]; + var width = 600; + var height = 500; + + // get the entire container height and width subtracting the padding + var chartWidth = width - padding.left - padding.right; + var chartHeight = height - padding.top - padding.bottom; + + //set the width and height of the SVG element + charts.attr('width', width) + .attr('height', height) + + // ranges are based on the width and height available so reset + xScale2.range([0, chartWidth]); + yScale2.range([chartHeight,0]); + + // create the x axis container + calibration_context.append('g') + .attr('class', 'x axis'); + + // create the y axis container + calibration_context.append('g') + .attr('class', 'y axis'); + + calibration_context.select('.y') + .attr('transform', 'translate(' + (/*chartWidth + */ padding.left) + ',' + padding.top + ')') + .style('stroke', 'black') + .style('shape-rendering', 'crispEdges') + .style('fill', 'none') + .call(yAxis2); + + // if first run then just display axis with no transition + calibration_context.select('.x') + .attr('transform', 'translate(' + padding.left + ',' + (chartHeight + padding.top) + ')') + .style('stroke', 'black') + .style('shape-rendering', 'crispEdges') + .style('fill', 'none') + .call(xAxis2); + + [50000,100000,150000,200000,250000,300000,350000,400000].forEach(function (li) { + calibration_context.append('line') + .attr('class', 'high-line') + .attr('x1', xScale2(dataRange[0])+padding.left) + .attr('y1', yScale2(li)+padding.top) + .attr('x2', xScale2(dataRange[1])+padding.left) + .attr('y2', yScale2(li)+padding.top) + .style('stroke-dasharray', ('3, 3')) + .attr('stroke', 'grey'); + }); + [50,100,150,200,250,300,350,400,450,500].forEach(function (li) { + calibration_context.append('line') + .attr('class', 'high-line') + .attr('x1', xScale2(li)+padding.left) + .attr('y1', padding.top) + .attr('x2', xScale2(li)+padding.left) + .attr('y2', chartHeight+padding.top) + .style('stroke-dasharray', ('3, 3')) + .attr('stroke', 'grey'); + }); + } + + function calibrations_drawcal(cal,color) { + var y1 = 50000; + var x1 = cal.scale * (y1 - cal.intercept) / cal.slope; + var y2 = 400000; + var x2 = cal.scale * (y2 - cal.intercept) / cal.slope; + calibration_context.append('line') + .attr('x1', xScale2(x1)+padding.left) + .attr('y1', yScale2(y1)+padding.top) + .attr('x2', xScale2(x2)+padding.left) + .attr('y2', yScale2(y2)+padding.top) + .style('stroke-width', 3) + .attr('stroke', color); + } + + function calibrations_calcmbg(mbg) { + var lastsgv = calibrations_findlatest(new Date(mbg.x),sgvs); + + if (lastsgv) { + if (mbg.x-lastsgv.x>5*60*1000) { + console.log('Last SGV too old for MBG. Time diff: '+((mbg.x-lastsgv.x)/1000/60).toFixed(1)+' min',mbg); + } else { + mbg.raw = lastsgv.filtered || lastsgv.unfiltered; + } + } else { + console.log('Last entry not found for MBG ',mbg); + } + } + + function calibrations_drawmbg(mbg,color) { + if (mbg.raw) { + calibration_context.append('circle') + .attr('cx', xScale2(mbg.y) + padding.left) + .attr('cy', yScale2(mbg.raw) + padding.top) + .attr('fill', color) + .style('opacity', 1) + .attr('stroke-width', 1) + .attr('stroke', 'black') + .attr('r', 5); + } + } + + function calibrations_findlatest(date,storage) { + var last = null; + var time = date.getTime(); + for (var i=0; i time) + return last; + last = storage[i]; + } + return last; + } } diff --git a/lib/report_plugins/dailystats.js b/lib/report_plugins/dailystats.js index 7fd3b48756e..58f9010e1b9 100644 --- a/lib/report_plugins/dailystats.js +++ b/lib/report_plugins/dailystats.js @@ -12,125 +12,115 @@ function init() { module.exports = init; - dailystats.report = function report_dailystats(datastorage,daystoshow,options) { - var Nightscout = window.Nightscout; - var client = Nightscout.client; - var translate = client.translate; - var report_plugins = Nightscout.report_plugins; + var Nightscout = window.Nightscout; + var client = Nightscout.client; + var translate = client.translate; + var report_plugins = Nightscout.report_plugins; - var todo = []; -// var data = o[0]; -// var days = 7; -// var config = { low: convertBg(low), high: convertBg(high) }; // options.targetLow options.targetHigh -// var sevendaysago = Date.now() - days.days(); - var report = $("#dailystats-report"); - var minForDay, maxForDay; - var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec"]; + var todo = []; + var report = $("#dailystats-report"); + var minForDay, maxForDay; + var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec"]; -// data = data.filter(function(record) { -// return "bgValue" in record && /\d+/.test(record.bgValue.toString()); -// }).filter(function(record) { -// return record.displayTime > sevendaysago; -// }); - report.empty(); - var table = $(''); - report.append(table); - var thead = $(""); - $("").appendTo(thead); - $('').appendTo(thead); - $('').appendTo(thead); - $('').appendTo(thead); - $('').appendTo(thead); - $('').appendTo(thead); - $('').appendTo(thead); - $('').appendTo(thead); - $('').appendTo(thead); - $('').appendTo(thead); - $('').appendTo(thead); - $('').appendTo(thead); - thead.appendTo(table); + report.empty(); + var table = $('
    '+translate('Date')+''+translate('Low')+''+translate('Normal')+''+translate('High')+''+translate('Readings')+''+translate('Min')+''+translate('Max')+''+translate('StDev')+''+translate('25%')+''+translate('Median')+''+translate('75%')+'
    '); + report.append(table); + var thead = $(""); + $("").appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + thead.appendTo(table); - Object.keys(daystoshow).forEach(function (day) { - var tr = $(""); - var dayInQuestion = new Date(day); + Object.keys(daystoshow).forEach(function (day) { + var tr = $(""); + var dayInQuestion = new Date(day); - var daysRecords = datastorage[day].statsrecords; - - if (daysRecords.length == 0) { - $("").appendTo(tr); - $('').appendTo(tr); - table.append(tr); - return;; - } + var daysRecords = datastorage[day].statsrecords; + + if (daysRecords.length == 0) { + $("").appendTo(tr); + $('').appendTo(tr); + table.append(tr); + return;; + } - minForDay = daysRecords[0].sgv; - maxForDay = daysRecords[0].sgv; - var stats = daysRecords.reduce(function(out, record) { - record.sgv = parseFloat(record.sgv); - if (record.sgv < options.targetLow) { - out.lows++; - } else if (record.sgv < options.targetHigh) { - out.normal++; - } else { - out.highs++; - } - if (minForDay > record.sgv) minForDay = record.sgv; - if (maxForDay < record.sgv) maxForDay = record.sgv; - return out; - }, { - lows: 0, - normal: 0, - highs: 0 - }); - var bgValues = daysRecords.map(function(r) { return r.sgv; }); - $("").appendTo(tr); + minForDay = daysRecords[0].sgv; + maxForDay = daysRecords[0].sgv; + var stats = daysRecords.reduce(function(out, record) { + record.sgv = parseFloat(record.sgv); + if (record.sgv < options.targetLow) { + out.lows++; + } else if (record.sgv < options.targetHigh) { + out.normal++; + } else { + out.highs++; + } + if (minForDay > record.sgv) minForDay = record.sgv; + if (maxForDay < record.sgv) maxForDay = record.sgv; + return out; + }, { + lows: 0, + normal: 0, + highs: 0 + }); + var bgValues = daysRecords.map(function(r) { return r.sgv; }); + $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); - table.append(tr); - var inrange = [ - { - label: translate('Low'), - data: Math.floor(stats.lows * 1000 / daysRecords.length) / 10 - }, - { - label: translate('In range'), - data: Math.floor(stats.normal * 1000 / daysRecords.length) / 10 - }, - { - label: translate('High'), - data: Math.floor(stats.highs * 1000 / daysRecords.length) / 10 - } - ]; - $.plot( - "#dailystat-chart-" + day.toString(), - inrange, - { - series: { - pie: { - show: true - } - }, - colors: ["#f88", "#8f8", "#ff8"] - } - ); - }); + table.append(tr); + var inrange = [ + { + label: translate('Low'), + data: Math.floor(stats.lows * 1000 / daysRecords.length) / 10 + }, + { + label: translate('In range'), + data: Math.floor(stats.normal * 1000 / daysRecords.length) / 10 + }, + { + label: translate('High'), + data: Math.floor(stats.highs * 1000 / daysRecords.length) / 10 + } + ]; + $.plot( + "#dailystat-chart-" + day.toString(), + inrange, + { + series: { + pie: { + show: true + } + }, + colors: ["#f88", "#8f8", "#ff8"] + } + ); + }); - setTimeout(function() { - todo.forEach(function(fn) { - fn(); - }); - }, 50); - } \ No newline at end of file + setTimeout(function() { + todo.forEach(function(fn) { + fn(); + }); + }, 50); +} diff --git a/lib/report_plugins/daytoday.js b/lib/report_plugins/daytoday.js index 1ca56a6be8c..8f5e41f84f9 100644 --- a/lib/report_plugins/daytoday.js +++ b/lib/report_plugins/daytoday.js @@ -14,373 +14,372 @@ module.exports = init; daytoday.report = function report_daytoday(datastorage,daystoshow,options) { - var Nightscout = window.Nightscout; - var client = Nightscout.client; - var translate = client.translate; - var profile = client.sbx.data.profile; - var report_plugins = Nightscout.report_plugins; - var scaledTreatmentBG = report_plugins.utils.scaledTreatmentBG; + var Nightscout = window.Nightscout; + var client = Nightscout.client; + var translate = client.translate; + var profile = client.sbx.data.profile; + var report_plugins = Nightscout.report_plugins; + var scaledTreatmentBG = report_plugins.utils.scaledTreatmentBG; + + var padding = { top: 15, right: 15, bottom: 30, left: 35 }; + var + FORMAT_TIME_12 = '%I' + , FORMAT_TIME_24 = '%H'; - var padding = { top: 15, right: 15, bottom: 30, left: 35 }; - var - FORMAT_TIME_12 = '%I' - , FORMAT_TIME_24 = '%H'; - - for (var day in daystoshow) { - drawChart(day,datastorage[day],options); - } - - function getTimeFormat() { - var timeFormat = FORMAT_TIME_12; - if (Nightscout.client.settings.timeFormat == '24') { - timeFormat = FORMAT_TIME_24; - } - return timeFormat; - } + for (var day in daystoshow) { + drawChart(day,datastorage[day],options); + } - function drawChart(day,data,options) { - var tickValues - , charts - , context - , xScale2, yScale2 - , yInsulinScale, yCarbsScale - , xAxis2, yAxis2 - , dateFn = function (d) { return new Date(d.date) } - , foodtexts = 0; + function getTimeFormat() { + var timeFormat = FORMAT_TIME_12; + if (Nightscout.client.settings.timeFormat == '24') { + timeFormat = FORMAT_TIME_24; + } + return timeFormat; + } + + function drawChart(day,data,options) { + var tickValues + , charts + , context + , xScale2, yScale2 + , yInsulinScale, yCarbsScale + , xAxis2, yAxis2 + , dateFn = function (d) { return new Date(d.date) } + , foodtexts = 0; - // Tick Values - if (options.scale == report_plugins.getProperty('SCALE_LOG')) { - if (client.settings.units == 'mmol') { - tickValues = [ - 2.0 - , 3.0 - , options.targetLow - , 6.0 - , 8.0 - , options.targetHigh - , 16.0 - , 22.0 - ]; - } else { - tickValues = [ - 40 - , 60 - , options.targetLow - , 120 - , 160 - , options.targetHigh - , 250 - , 400 - ]; - } + // Tick Values + if (options.scale == report_plugins.getProperty('SCALE_LOG')) { + if (client.settings.units == 'mmol') { + tickValues = [ + 2.0 + , 3.0 + , options.targetLow + , 6.0 + , 8.0 + , options.targetHigh + , 16.0 + , 22.0 + ]; } else { - if (client.settings.units == 'mmol') { - tickValues = [ - 2.0 - , 4.0 - , 6.0 - , 8.0 - , 10.0 - , 12.0 - , 14.0 - , 16.0 - , 18.0 - , 20.0 - , 22.0 - ]; - } else { - tickValues = [ - 40 - , 80 - , 120 - , 160 - , 200 - , 240 - , 280 - , 320 - , 360 - , 400 - ]; - } + tickValues = [ + 40 + , 60 + , options.targetLow + , 120 + , 160 + , options.targetHigh + , 250 + , 400 + ]; } + } else { + if (client.settings.units == 'mmol') { + tickValues = [ + 2.0 + , 4.0 + , 6.0 + , 8.0 + , 10.0 + , 12.0 + , 14.0 + , 16.0 + , 18.0 + , 20.0 + , 22.0 + ]; + } else { + tickValues = [ + 40 + , 80 + , 120 + , 160 + , 200 + , 240 + , 280 + , 320 + , 360 + , 400 + ]; + } + } - // create svg and g to contain the chart contents - charts = d3.select('#'+report_plugins.getProperty('containerprefix')+day).html( - ''+ - report_plugins.utils.localeDate(day)+ - '
    ' - ).append('svg'); + // create svg and g to contain the chart contents + charts = d3.select('#'+report_plugins.getProperty('containerprefix')+day).html( + ''+ + report_plugins.utils.localeDate(day)+ + '
    ' + ).append('svg'); - charts.append("rect") - .attr("width", "100%") - .attr("height", "100%") - .attr("fill", "WhiteSmoke"); - - context = charts.append('g'); + charts.append("rect") + .attr("width", "100%") + .attr("height", "100%") + .attr("fill", "WhiteSmoke"); + + context = charts.append('g'); - // define the parts of the axis that aren't dependent on width or height - xScale2 = d3.time.scale() - .domain(d3.extent(data.sgv, function (d) { return d.date; })); + // define the parts of the axis that aren't dependent on width or height + xScale2 = d3.time.scale() + .domain(d3.extent(data.sgv, function (d) { return d.date; })); - if (options.scale == report_plugins.getProperty('SCALE_LOG')) { - yScale2 = d3.scale.log() - .domain([client.utils.scaleMgdl(36), client.utils.scaleMgdl(420)]); - } else { - yScale2 = d3.scale.linear() - .domain([client.utils.scaleMgdl(36), client.utils.scaleMgdl(420)]); - } + if (options.scale == report_plugins.getProperty('SCALE_LOG')) { + yScale2 = d3.scale.log() + .domain([client.utils.scaleMgdl(36), client.utils.scaleMgdl(420)]); + } else { + yScale2 = d3.scale.linear() + .domain([client.utils.scaleMgdl(36), client.utils.scaleMgdl(420)]); + } - yInsulinScale = d3.scale.linear() - .domain([0, options.maxInsulinValue*2]); + yInsulinScale = d3.scale.linear() + .domain([0, options.maxInsulinValue*2]); - yCarbsScale = d3.scale.linear() - .domain([0, options.maxCarbsValue*1.25]); + yCarbsScale = d3.scale.linear() + .domain([0, options.maxCarbsValue*1.25]); - xAxis2 = d3.svg.axis() - .scale(xScale2) - .tickFormat(d3.time.format(getTimeFormat(true))) - .ticks(24) - .orient('bottom'); + xAxis2 = d3.svg.axis() + .scale(xScale2) + .tickFormat(d3.time.format(getTimeFormat(true))) + .ticks(24) + .orient('bottom'); - yAxis2 = d3.svg.axis() - .scale(yScale2) - .tickFormat(d3.format('d')) - .tickValues(tickValues) - .orient('left'); + yAxis2 = d3.svg.axis() + .scale(yScale2) + .tickFormat(d3.format('d')) + .tickValues(tickValues) + .orient('left'); - // get current data range - var dataRange = d3.extent(data.sgv, dateFn); + // get current data range + var dataRange = d3.extent(data.sgv, dateFn); - // get the entire container height and width subtracting the padding - var chartWidth = options.width - padding.left - padding.right; - var chartHeight = options.height - padding.top - padding.bottom; - - //set the width and height of the SVG element - charts.attr('width', options.width) - .attr('height', options.height); - - // ranges are based on the width and height available so reset - xScale2.range([0, chartWidth]); - yScale2.range([chartHeight,0]); - yInsulinScale.range([chartHeight,0]); - yCarbsScale.range([chartHeight,0]); + // get the entire container height and width subtracting the padding + var chartWidth = options.width - padding.left - padding.right; + var chartHeight = options.height - padding.top - padding.bottom; - // add target BG rect - context.append('rect') - .attr('x', xScale2(dataRange[0])+padding.left) - .attr('y', yScale2(options.targetHigh)+padding.top) - .attr('width', xScale2(dataRange[1]- xScale2(dataRange[0]))) - .attr('height', yScale2(options.targetLow)-yScale2(options.targetHigh)) - .style('fill', '#D6FFD6') - .attr('stroke', 'grey'); + //set the width and height of the SVG element + charts.attr('width', options.width) + .attr('height', options.height); + + // ranges are based on the width and height available so reset + xScale2.range([0, chartWidth]); + yScale2.range([chartHeight,0]); + yInsulinScale.range([chartHeight,0]); + yCarbsScale.range([chartHeight,0]); - // create the x axis container - context.append('g') - .attr('class', 'x axis'); + // add target BG rect + context.append('rect') + .attr('x', xScale2(dataRange[0])+padding.left) + .attr('y', yScale2(options.targetHigh)+padding.top) + .attr('width', xScale2(dataRange[1]- xScale2(dataRange[0]))) + .attr('height', yScale2(options.targetLow)-yScale2(options.targetHigh)) + .style('fill', '#D6FFD6') + .attr('stroke', 'grey'); - // create the y axis container - context.append('g') - .attr('class', 'y axis'); + // create the x axis container + context.append('g') + .attr('class', 'x axis'); - context.select('.y') - .attr('transform', 'translate(' + (/*chartWidth + */ padding.left) + ',' + padding.top + ')') - .style('stroke', 'black') - .style('shape-rendering', 'crispEdges') - .style('fill', 'none') - .call(yAxis2); + // create the y axis container + context.append('g') + .attr('class', 'y axis'); - // if first run then just display axis with no transition - context.select('.x') - .attr('transform', 'translate(' + padding.left + ',' + (chartHeight + padding.top) + ')') - .style('stroke', 'black') - .style('shape-rendering', 'crispEdges') - .style('fill', 'none') - .call(xAxis2); + context.select('.y') + .attr('transform', 'translate(' + (/*chartWidth + */ padding.left) + ',' + padding.top + ')') + .style('stroke', 'black') + .style('shape-rendering', 'crispEdges') + .style('fill', 'none') + .call(yAxis2); - for (var li in tickValues) { - context.append('line') - .attr('class', 'high-line') - .attr('x1', xScale2(dataRange[0])+padding.left) - .attr('y1', yScale2(tickValues[li])+padding.top) - .attr('x2', xScale2(dataRange[1])+padding.left) - .attr('y2', yScale2(tickValues[li])+padding.top) - .style('stroke-dasharray', ('3, 3')) - .attr('stroke', 'grey'); - } + // if first run then just display axis with no transition + context.select('.x') + .attr('transform', 'translate(' + padding.left + ',' + (chartHeight + padding.top) + ')') + .style('stroke', 'black') + .style('shape-rendering', 'crispEdges') + .style('fill', 'none') + .call(xAxis2); - // bind up the context chart data to an array of circles - var contextCircles = context.selectAll('circle') - .data(data.sgv); + for (var li in tickValues) { + context.append('line') + .attr('class', 'high-line') + .attr('x1', xScale2(dataRange[0])+padding.left) + .attr('y1', yScale2(tickValues[li])+padding.top) + .attr('x2', xScale2(dataRange[1])+padding.left) + .attr('y2', yScale2(tickValues[li])+padding.top) + .style('stroke-dasharray', ('3, 3')) + .attr('stroke', 'grey'); + } - function prepareContextCircles(sel) { - var badData = []; - sel.attr('cx', function (d) { - return xScale2(d.date) + padding.left; - }) - .attr('cy', function (d) { - if (isNaN(d.sgv)) { - badData.push(d); - return yScale2(client.utils.scaleMgdl(450) + padding.top); - } else { - return yScale2(d.sgv) + padding.top; - } - }) - .attr('fill', function (d) { - if (d.color == 'gray' && !options.raw) { - return 'transparent'; - } - return d.color; - }) - .style('opacity', function (d) { 0.5 }) - .attr('stroke-width', function (d) {if (d.type == 'mbg') return 2; else return 0; }) - .attr('stroke', function (d) { return 'black'; }) - .attr('r', function(d) { if (d.type == 'mbg') { return 4; } else { return 2; }}); + // bind up the context chart data to an array of circles + var contextCircles = context.selectAll('circle') + .data(data.sgv); - if (badData.length > 0) { - console.warn("Bad Data: isNaN(sgv)", badData); + function prepareContextCircles(sel) { + var badData = []; + sel.attr('cx', function (d) { + return xScale2(d.date) + padding.left; + }) + .attr('cy', function (d) { + if (isNaN(d.sgv)) { + badData.push(d); + return yScale2(client.utils.scaleMgdl(450) + padding.top); + } else { + return yScale2(d.sgv) + padding.top; } - return sel; - } + }) + .attr('fill', function (d) { + if (d.color == 'gray' && !options.raw) { + return 'transparent'; + } + return d.color; + }) + .style('opacity', function (d) { 0.5 }) + .attr('stroke-width', function (d) {if (d.type == 'mbg') return 2; else return 0; }) + .attr('stroke', function (d) { return 'black'; }) + .attr('r', function(d) { if (d.type == 'mbg') { return 4; } else { return 2; }}); - // if new circle then just display - prepareContextCircles(contextCircles.enter().append('circle')); + if (badData.length > 0) { + console.warn("Bad Data: isNaN(sgv)", badData); + } + return sel; + } - contextCircles.exit() - .remove(); + // if new circle then just display + prepareContextCircles(contextCircles.enter().append('circle')); - var to = moment(day).add(1, 'days') //.add(new Date().getTimezoneOffset(), 'minutes'); - var from = moment(day); //.add(new Date().getTimezoneOffset(), 'minutes'); - var iobpolyline = '', cobpolyline = ''; - for (var dt=from; dt < to; dt.add(5, 'minutes')) { - if (options.iob) { - var iob = Nightscout.plugins('iob').calcTotal(data.treatments,profile,dt.toDate()).iob; - if (dt!=from) { - iobpolyline += ', '; - } - iobpolyline += (xScale2(dt) + padding.left) + ',' + (yInsulinScale(iob) + padding.top) + ' '; - } - if (options.cob) { - var cob = Nightscout.plugins('cob').cobTotal(data.treatments,profile,dt.toDate()).cob; - if (dt!=from) { - cobpolyline += ', '; - } - cobpolyline += (xScale2(dt.toDate()) + padding.left) + ',' + (yCarbsScale(cob) + padding.top) + ' '; + contextCircles.exit() + .remove(); + + var to = moment(day).add(1, 'days') //.add(new Date().getTimezoneOffset(), 'minutes'); + var from = moment(day); //.add(new Date().getTimezoneOffset(), 'minutes'); + var iobpolyline = '', cobpolyline = ''; + for (var dt=from; dt < to; dt.add(5, 'minutes')) { + if (options.iob) { + var iob = Nightscout.plugins('iob').calcTotal(data.treatments,profile,dt.toDate()).iob; + if (dt!=from) { + iobpolyline += ', '; } + iobpolyline += (xScale2(dt) + padding.left) + ',' + (yInsulinScale(iob) + padding.top) + ' '; } - if (options.iob) { - context.append('polyline') - .attr('stroke', 'blue') - .attr('opacity', '0.5') - .attr('fill-opacity', '0.1') - .attr('points',iobpolyline); - } if (options.cob) { - context.append('polyline') - .attr('stroke', 'red') - .attr('opacity', '0.5') - .attr('fill-opacity', '0.1') - .attr('points',cobpolyline); - } + var cob = Nightscout.plugins('cob').cobTotal(data.treatments,profile,dt.toDate()).cob; + if (dt!=from) { + cobpolyline += ', '; + } + cobpolyline += (xScale2(dt.toDate()) + padding.left) + ',' + (yCarbsScale(cob) + padding.top) + ' '; + } + } + if (options.iob) { + context.append('polyline') + .attr('stroke', 'blue') + .attr('opacity', '0.5') + .attr('fill-opacity', '0.1') + .attr('points',iobpolyline); + } + if (options.cob) { + context.append('polyline') + .attr('stroke', 'red') + .attr('opacity', '0.5') + .attr('fill-opacity', '0.1') + .attr('points',cobpolyline); + } - data.treatments.forEach(function (treatment) { - if (treatment.boluscalc && treatment.boluscalc.foods && treatment.boluscalc.foods.length > 0 || treatment.notes) { - var lastfoodtext = foodtexts; - var drawpointer = false; - if (treatment.boluscalc && treatment.boluscalc.foods && treatment.boluscalc.foods.length > 0 && options.food) { - var foods = treatment.boluscalc.foods; - for (var fi=0; fi 0 || treatment.notes) { + var lastfoodtext = foodtexts; + var drawpointer = false; + if (treatment.boluscalc && treatment.boluscalc.foods && treatment.boluscalc.foods.length > 0 && options.food) { + var foods = treatment.boluscalc.foods; + for (var fi=0; fi'); - var thead = $(""); - $('').appendTo(thead); - $('').appendTo(thead); - $('').appendTo(thead); - $('').appendTo(thead); - $('').appendTo(thead); - $('').appendTo(thead); - $('').appendTo(thead); - thead.appendTo(table); + var Statician = ss; + var report = $("#glucosedistribution-report"); + report.empty(); + var minForDay, maxForDay; + var stats = []; + var table = $('
    '+translate('Date')+''+translate('Low')+''+translate('Normal')+''+translate('High')+''+translate('Readings')+''+translate('Min')+''+translate('Max')+''+translate('StDev')+''+translate('25%')+''+translate('Median')+''+translate('75%')+'
    ").appendTo(tr); - $("" + report_plugins.utils.localeDate(dayInQuestion) + "'+translate('No data available')+'").appendTo(tr); + $("" + report_plugins.utils.localeDate(dayInQuestion) + "'+translate('No data available')+'
    " + report_plugins.utils.localeDate(dayInQuestion) + "" + Math.floor((100 * stats.lows) / daysRecords.length) + "%" + Math.floor((100 * stats.normal) / daysRecords.length) + "%" + Math.floor((100 * stats.highs) / daysRecords.length) + "%" + daysRecords.length +"" + minForDay +"" + maxForDay +"" + Math.floor(ss.standard_deviation(bgValues)) + "" + ss.quantile(bgValues, 0.25) + "" + ss.quantile(bgValues, 0.5) + "" + ss.quantile(bgValues, 0.75) + "" + report_plugins.utils.localeDate(dayInQuestion) + "" + Math.floor((100 * stats.lows) / daysRecords.length) + "%" + Math.floor((100 * stats.normal) / daysRecords.length) + "%" + Math.floor((100 * stats.highs) / daysRecords.length) + "%" + daysRecords.length +"" + minForDay +"" + maxForDay +"" + Math.floor(ss.standard_deviation(bgValues)) + "" + ss.quantile(bgValues, 0.25) + "" + ss.quantile(bgValues, 0.5) + "" + ss.quantile(bgValues, 0.75) + "
    '+translate('Range')+''+translate('% of Readings')+''+translate('# of Readings')+''+translate('Mean')+''+translate('Median')+''+translate('Standard Deviation')+''+translate('A1c estimation*')+'
    '); + var thead = $(""); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + thead.appendTo(table); - var data = []; - var days = 0; - Object.keys(daystoshow).forEach(function (day) { - data = data.concat(datastorage[day].statsrecords); - days++; - }); - - $('#glucosedistribution-days').text(days+' '+translate('days total')); - - ['Low', 'Normal', 'High'].forEach(function(range) { - var tr = $(""); - var rangeRecords = data.filter(function(r) { - if (range == "Low") { - return r.sgv > 0 && r.sgv < options.targetLow; - } else if (range == "Normal") { - return r.sgv >= options.targetLow && r.sgv < options.targetHigh; - } else { - return r.sgv >= options.targetHigh; - } - }); - stats.push(rangeRecords.length); - rangeRecords.sort(function(a,b) { - return a.sgv - b.sgv; - }); - var localBgs = rangeRecords.map(function(r) { return r.sgv; }).filter(function(bg) { return !!bg; }); + var data = []; + var days = 0; + Object.keys(daystoshow).forEach(function (day) { + data = data.concat(datastorage[day].statsrecords); + days++; + }); + + $('#glucosedistribution-days').text(days+' '+translate('days total')); + + ['Low', 'Normal', 'High'].forEach(function(range) { + var tr = $(""); + var rangeRecords = data.filter(function(r) { + if (range == "Low") { + return r.sgv > 0 && r.sgv < options.targetLow; + } else if (range == "Normal") { + return r.sgv >= options.targetLow && r.sgv < options.targetHigh; + } else { + return r.sgv >= options.targetHigh; + } + }); + stats.push(rangeRecords.length); + rangeRecords.sort(function(a,b) { + return a.sgv - b.sgv; + }); + var localBgs = rangeRecords.map(function(r) { return r.sgv; }).filter(function(bg) { return !!bg; }); - var midpoint = Math.floor(rangeRecords.length / 2); - //var statistics = ss.(new Statician(rangeRecords.map(function(r) { return r.sgv; }))).stats; + var midpoint = Math.floor(rangeRecords.length / 2); + //var statistics = ss.(new Statician(rangeRecords.map(function(r) { return r.sgv; }))).stats; - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); - if (rangeRecords.length > 0) { - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); - } else { - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); - } + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + if (rangeRecords.length > 0) { + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + } else { + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + } - table.append(tr); - }); - - var tr = $(""); - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); - if (data.length > 0) { - var localBgs = data.map(function(r) { return r.sgv; }).filter(function(bg) { return !!bg; }); - var mgDlBgs = data.map(function(r) { return r.bgValue; }).filter(function(bg) { return !!bg; }); - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); - } else { - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); - } - table.append(tr); - report.append(table); + table.append(tr); + }); + + var tr = $(""); + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + if (data.length > 0) { + var localBgs = data.map(function(r) { return r.sgv; }).filter(function(bg) { return !!bg; }); + var mgDlBgs = data.map(function(r) { return r.bgValue; }).filter(function(bg) { return !!bg; }); + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + } else { + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + } + table.append(tr); + report.append(table); - setTimeout(function() { - $.plot( - "#glucosedistribution-overviewchart", - stats, - { - series: { - pie: { - show: true - } - }, - colors: ["#f88", "#8f8", "#ff8"] - } - ); - }); - } + setTimeout(function() { + $.plot( + "#glucosedistribution-overviewchart", + stats, + { + series: { + pie: { + show: true + } + }, + colors: ["#f88", "#8f8", "#ff8"] + } + ); + }); +} diff --git a/lib/report_plugins/hourlystats.js b/lib/report_plugins/hourlystats.js index 3a9cc33434d..ee3c536bd22 100644 --- a/lib/report_plugins/hourlystats.js +++ b/lib/report_plugins/hourlystats.js @@ -14,112 +14,106 @@ module.exports = init; hourlystats.report = function report_hourlystats(datastorage,daystoshow,options) { - var Nightscout = window.Nightscout; - var client = Nightscout.client; - var translate = client.translate; + var Nightscout = window.Nightscout; + var client = Nightscout.client; + var translate = client.translate; - var report = $("#hourlystats-report"); - var stats = []; - var pivotedByHour = {}; + var report = $("#hourlystats-report"); + var stats = []; + var pivotedByHour = {}; - var data = []; - var days = 0; - Object.keys(daystoshow).forEach(function (day) { - data = data.concat(datastorage[day].statsrecords); - days++; - }); - - for (var i = 0; i < 24; i++) { - pivotedByHour[i] = []; - } - data.forEach(function(record) { - var d = new Date(record.displayTime); - pivotedByHour[d.getHours()].push(record); - }); - var table = $("
    '+translate('Range')+''+translate('% of Readings')+''+translate('# of Readings')+''+translate('Mean')+''+translate('Median')+''+translate('Standard Deviation')+''+translate('A1c estimation*')+'
    " + translate(range) + ": " + Math.floor(100 * rangeRecords.length / data.length) + "%" + rangeRecords.length + "" + Math.floor(10*Statician.mean(localBgs))/10 + "" + rangeRecords[midpoint].sgv + "" + Math.floor(Statician.standard_deviation(localBgs)*10)/10 + " N/AN/AN/A " + translate(range) + ": " + Math.floor(100 * rangeRecords.length / data.length) + "%" + rangeRecords.length + "" + Math.floor(10*Statician.mean(localBgs))/10 + "" + rangeRecords[midpoint].sgv + "" + Math.floor(Statician.standard_deviation(localBgs)*10)/10 + " N/AN/AN/A
    "+translate("Overall")+": " + data.length + "" + Math.round(10*ss.mean(localBgs))/10 + "" + Math.round(10*ss.quantile(localBgs, 0.5))/10+ "" + Math.round(ss.standard_deviation(localBgs)*10)/10 + "
    " + Math.round(10*(ss.mean(mgDlBgs)+46.7)/28.7)/10 + "%DCCT | " +Math.round(((ss.mean(mgDlBgs)+46.7)/28.7 - 2.15)*10.929) + "IFCC
    N/AN/AN/AN/A
    "+translate("Overall")+": " + data.length + "" + Math.round(10*ss.mean(localBgs))/10 + "" + Math.round(10*ss.quantile(localBgs, 0.5))/10+ "" + Math.round(ss.standard_deviation(localBgs)*10)/10 + "
    " + Math.round(10*(ss.mean(mgDlBgs)+46.7)/28.7)/10 + "%DCCT | " +Math.round(((ss.mean(mgDlBgs)+46.7)/28.7 - 2.15)*10.929) + "IFCC
    N/AN/AN/AN/A
    "); - var thead = $(""); - $('').appendTo(thead); - $('').appendTo(thead); - $('').appendTo(thead); - $('').appendTo(thead); - $('').appendTo(thead); - $('').appendTo(thead); - $('').appendTo(thead); - $('').appendTo(thead); - $('').appendTo(thead); - thead.appendTo(table); + var data = []; + var days = 0; + Object.keys(daystoshow).forEach(function (day) { + data = data.concat(datastorage[day].statsrecords); + days++; + }); + + for (var i = 0; i < 24; i++) { + pivotedByHour[i] = []; + } + data.forEach(function(record) { + var d = new Date(record.displayTime); + pivotedByHour[d.getHours()].push(record); + }); + var table = $("
    '+translate('Time')+''+translate('Readings')+''+translate('Average')+''+translate('Min')+''+translate('Quartile')+' 25'+translate('Median')+''+translate('Quartile')+' 75'+translate('Max')+''+translate('Standard Deviation')+'
    "); + var thead = $(""); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + thead.appendTo(table); - [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23].forEach(function(hour) { - var tr = $(""); - var display = hour % 12; - if (hour === 0) { - display = "12"; - } - display += ":00 "; - if (hour >= 12) { - display += "PM"; - } else { - display += "AM"; - } + [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23].forEach(function(hour) { + var tr = $(""); + var display = hour % 12; + if (hour === 0) { + display = "12"; + } + display += ":00 "; + if (hour >= 12) { + display += "PM"; + } else { + display += "AM"; + } - var avg = Math.floor(pivotedByHour[hour].map(function(r) { return r.sgv; }).reduce(function(o,v){ return o+v; }, 0) / pivotedByHour[hour].length); - var d = new Date(hour.hours()); - // d.setHours(hour); - // d.setMinutes(0); - // d.setSeconds(0); - // d.setMilliseconds(0); + var avg = Math.floor(pivotedByHour[hour].map(function(r) { return r.sgv; }).reduce(function(o,v){ return o+v; }, 0) / pivotedByHour[hour].length); + var d = new Date(hour.hours()); - var dev = ss.standard_deviation(pivotedByHour[hour].map(function(r) { return r.sgv; })); - stats.push([ - new Date(d), - ss.quantile(pivotedByHour[hour].map(function(r) { return r.sgv; }), 0.25), - ss.quantile(pivotedByHour[hour].map(function(r) { return r.sgv; }), 0.75), - avg - dev, - avg + dev - // Math.min.apply(Math, pivotedByHour[hour].map(function(r) { return r.sgv; })), - // Math.max.apply(Math, pivotedByHour[hour].map(function(r) { return r.sgv; })) - ]); - var tmp; - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); - table.append(tr); - }); + var dev = ss.standard_deviation(pivotedByHour[hour].map(function(r) { return r.sgv; })); + stats.push([ + new Date(d), + ss.quantile(pivotedByHour[hour].map(function(r) { return r.sgv; }), 0.25), + ss.quantile(pivotedByHour[hour].map(function(r) { return r.sgv; }), 0.75), + avg - dev, + avg + dev + ]); + var tmp; + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + $("").appendTo(tr); + table.append(tr); + }); - report.empty(); - report.append(table); + report.empty(); + report.append(table); - $.plot( - "#hourlystats-overviewchart", - [{ - data:stats, - candle:true - }], - { - series: { - candle: true, - lines: false //Somehow it draws lines if you dont disable this. Should investigate and fix this ;) - }, - xaxis: { - mode: "time", - timeFormat: "%h:00", - min: 0, - max: (24).hours()-(1).seconds() - }, - yaxis: { - min: 0, - max: serverSettings.units == 'mmol' ? 22: 400, - show: true - }, - grid: { - show: true - } - } - ); - } \ No newline at end of file + $.plot( + "#hourlystats-overviewchart", + [{ + data:stats, + candle:true + }], + { + series: { + candle: true, + lines: false //Somehow it draws lines if you dont disable this. Should investigate and fix this ;) + }, + xaxis: { + mode: "time", + timeFormat: "%h:00", + min: 0, + max: (24).hours()-(1).seconds() + }, + yaxis: { + min: 0, + max: serverSettings.units == 'mmol' ? 22: 400, + show: true + }, + grid: { + show: true + } + } + ); +} \ No newline at end of file diff --git a/lib/report_plugins/percentile.js b/lib/report_plugins/percentile.js index d0f781bb741..ec71828116c 100644 --- a/lib/report_plugins/percentile.js +++ b/lib/report_plugins/percentile.js @@ -14,158 +14,158 @@ module.exports = init; percentile.report = function report_percentile(datastorage,daystoshow,options) { - var Nightscout = window.Nightscout; - var client = Nightscout.client; - var translate = client.translate; + var Nightscout = window.Nightscout; + var client = Nightscout.client; + var translate = client.translate; - var Statician = ss; - var minutewindow = 30; //minute-window should be a divisor of 60 - - var data = []; - var days = 0; - Object.keys(daystoshow).forEach(function (day) { - data = data.concat(datastorage[day].statsrecords); - days++; - }); - - var bins = []; - for (var hour = 0; hour < 24; hour++) { - for (var minute = 0; minute < 60; minute = minute + minutewindow) { - var date = new Date(); - date.setHours(hour); - date.setMinutes(minute); - var readings = data.filter(function(record) { - var recdate = new Date(record.displayTime); - return recdate.getHours() == hour && recdate.getMinutes() >= minute && - recdate.getMinutes() < minute + minutewindow; - }); - readings = readings.map(function(record) { - return record.sgv; - }); - bins.push([date, readings]); - //console.log(date + " - " + readings.length); - //readings.forEach(function(x){console.log(x)}); - } - } - var dat10 = bins.map(function(bin) { - return [bin[0], ss.quantile(bin[1], 0.1)]; - }); - var dat25 = bins.map(function(bin) { - return [bin[0], ss.quantile(bin[1], 0.25)]; - }); - var dat50 = bins.map(function(bin) { - return [bin[0], ss.quantile(bin[1], 0.5)]; - }); - var dat75 = bins.map(function(bin) { - return [bin[0], ss.quantile(bin[1], 0.75)]; - }); - var dat90 = bins.map(function(bin) { - return [bin[0], ss.quantile(bin[1], 0.9)]; - }); - var high = options.targetHigh; - var low = options.targetLow; - //dat50.forEach(function(x){console.log(x[0] + " - " + x[1])}); - $.plot( - "#percentile-chart", [{ - label: translate("Median"), - data: dat50, - id: 'c50', - color: "#000000", - points: { - show: false - }, - lines: { - show: true, - //fill: true - } - }, { - label: "25%/75% "+translate("percentile"), - data: dat25, - id: 'c25', - color: "#000055", - points: { - show: false - }, - lines: { - show: true, - fill: true - }, - fillBetween: 'c50' - }, { - data: dat75, - id: 'c75', - color: "#000055", - points: { - show: false - }, - lines: { - show: true, - fill: true - }, - fillBetween: 'c50' - }, { - label: "10%/90% "+translate("percentile"), - data: dat10, - id: 'c10', - color: "#a0a0FF", - points: { - show: false - }, - lines: { - show: true, - fill: true - }, - fillBetween: 'c25' - }, { - data: dat90, - id: 'c90', - color: "#a0a0FF", - points: { - show: false - }, - lines: { - show: true, - fill: true - }, - fillBetween: 'c75' - }, { - label: translate("High"), - data: [], - color: '#FFFF00', - }, { - label: translate("Low"), - data: [], - color: '#FF0000', - }], { - xaxis: { - mode: "time", - timezone: "browser", - timeformat: "%H:%M", - tickColor: "#555", - }, - yaxis: { - min: 0, - max: serverSettings.units == 'mmol' ? 22: 400, - tickColor: "#555", - }, - grid: { - markings: [{ - color: '#FF0000', - lineWidth: 2, - yaxis: { - from: low, - to: low - } - }, { - color: '#FFFF00', - lineWidth: 2, - yaxis: { - from: high, - to: high - } - }], - //hoverable: true - } - } - ); - } + var Statician = ss; + var minutewindow = 30; //minute-window should be a divisor of 60 + + var data = []; + var days = 0; + Object.keys(daystoshow).forEach(function (day) { + data = data.concat(datastorage[day].statsrecords); + days++; + }); + + var bins = []; + for (var hour = 0; hour < 24; hour++) { + for (var minute = 0; minute < 60; minute = minute + minutewindow) { + var date = new Date(); + date.setHours(hour); + date.setMinutes(minute); + var readings = data.filter(function(record) { + var recdate = new Date(record.displayTime); + return recdate.getHours() == hour && recdate.getMinutes() >= minute && + recdate.getMinutes() < minute + minutewindow; + }); + readings = readings.map(function(record) { + return record.sgv; + }); + bins.push([date, readings]); + //console.log(date + " - " + readings.length); + //readings.forEach(function(x){console.log(x)}); + } + } + var dat10 = bins.map(function(bin) { + return [bin[0], ss.quantile(bin[1], 0.1)]; + }); + var dat25 = bins.map(function(bin) { + return [bin[0], ss.quantile(bin[1], 0.25)]; + }); + var dat50 = bins.map(function(bin) { + return [bin[0], ss.quantile(bin[1], 0.5)]; + }); + var dat75 = bins.map(function(bin) { + return [bin[0], ss.quantile(bin[1], 0.75)]; + }); + var dat90 = bins.map(function(bin) { + return [bin[0], ss.quantile(bin[1], 0.9)]; + }); + var high = options.targetHigh; + var low = options.targetLow; + //dat50.forEach(function(x){console.log(x[0] + " - " + x[1])}); + $.plot( + "#percentile-chart", [{ + label: translate("Median"), + data: dat50, + id: 'c50', + color: "#000000", + points: { + show: false + }, + lines: { + show: true, + //fill: true + } + }, { + label: "25%/75% "+translate("percentile"), + data: dat25, + id: 'c25', + color: "#000055", + points: { + show: false + }, + lines: { + show: true, + fill: true + }, + fillBetween: 'c50' + }, { + data: dat75, + id: 'c75', + color: "#000055", + points: { + show: false + }, + lines: { + show: true, + fill: true + }, + fillBetween: 'c50' + }, { + label: "10%/90% "+translate("percentile"), + data: dat10, + id: 'c10', + color: "#a0a0FF", + points: { + show: false + }, + lines: { + show: true, + fill: true + }, + fillBetween: 'c25' + }, { + data: dat90, + id: 'c90', + color: "#a0a0FF", + points: { + show: false + }, + lines: { + show: true, + fill: true + }, + fillBetween: 'c75' + }, { + label: translate("High"), + data: [], + color: '#FFFF00', + }, { + label: translate("Low"), + data: [], + color: '#FF0000', + }], { + xaxis: { + mode: "time", + timezone: "browser", + timeformat: "%H:%M", + tickColor: "#555", + }, + yaxis: { + min: 0, + max: serverSettings.units == 'mmol' ? 22: 400, + tickColor: "#555", + }, + grid: { + markings: [{ + color: '#FF0000', + lineWidth: 2, + yaxis: { + from: low, + to: low + } + }, { + color: '#FFFF00', + lineWidth: 2, + yaxis: { + from: high, + to: high + } + }], + //hoverable: true + } + } + ); +} diff --git a/lib/report_plugins/success.js b/lib/report_plugins/success.js index 6e0d9ec037e..c9c50ff3dba 100644 --- a/lib/report_plugins/success.js +++ b/lib/report_plugins/success.js @@ -18,55 +18,55 @@ success.report = function report_success(datastorage,daystoshow,options) { var client = Nightscout.client; var translate = client.translate; - var low = parseInt(options.targetLow), - high = parseInt(options.targetHigh); - - var data = []; - var days = 0; - Object.keys(daystoshow).forEach(function (day) { - data = data.concat(datastorage[day].statsrecords); - days++; - }); - - var now = Date.now(); - var period = (7).days(); - var firstDataPoint = data.reduce(function(min, record) { - return Math.min(min, record.displayTime); - }, Number.MAX_VALUE); - if (firstDataPoint < 1390000000000) firstDataPoint = 1390000000000; - var quarters = Math.floor((Date.now() - firstDataPoint) / period); - - var grid = $("#success-grid"); - grid.empty(); - var table = $("
    '+translate('Time')+''+translate('Readings')+''+translate('Average')+''+translate('Min')+''+translate('Quartile')+' 25'+translate('Median')+''+translate('Quartile')+' 75'+translate('Max')+''+translate('Standard Deviation')+'
    " + display + "" + pivotedByHour[hour].length + " (" + Math.floor(100 * pivotedByHour[hour].length / data.length) + "%)" + avg + "" + Math.min.apply(Math, pivotedByHour[hour].map(function(r) { return r.sgv; })) + "" + ((tmp=ss.quantile(pivotedByHour[hour].map(function(r) { return r.sgv; }), 0.25)) ? tmp.toFixed(1) : 0 ) + "" + ((tmp=ss.quantile(pivotedByHour[hour].map(function(r) { return r.sgv; }), 0.5)) ? tmp.toFixed(1) : 0 ) + "" + ((tmp=ss.quantile(pivotedByHour[hour].map(function(r) { return r.sgv; }), 0.75)) ? tmp.toFixed(1) : 0 ) + "" + Math.max.apply(Math, pivotedByHour[hour].map(function(r) { return r.sgv; })) + "" + Math.floor(dev*10)/10 + "" + display + "" + pivotedByHour[hour].length + " (" + Math.floor(100 * pivotedByHour[hour].length / data.length) + "%)" + avg + "" + Math.min.apply(Math, pivotedByHour[hour].map(function(r) { return r.sgv; })) + "" + ((tmp=ss.quantile(pivotedByHour[hour].map(function(r) { return r.sgv; }), 0.25)) ? tmp.toFixed(1) : 0 ) + "" + ((tmp=ss.quantile(pivotedByHour[hour].map(function(r) { return r.sgv; }), 0.5)) ? tmp.toFixed(1) : 0 ) + "" + ((tmp=ss.quantile(pivotedByHour[hour].map(function(r) { return r.sgv; }), 0.75)) ? tmp.toFixed(1) : 0 ) + "" + Math.max.apply(Math, pivotedByHour[hour].map(function(r) { return r.sgv; })) + "" + Math.floor(dev*10)/10 + "
    "); - - if (quarters == 0) { - // insufficent data - grid.append("

    "+translate("There is not sufficient data to run this report. Select more days.")+"

    "); - return; - } - - var dim = function(n) { - var a = []; - for (var i = 0; i < n; i++) { - a[i]=0; - } - return a; - } - var sum = function(a) { - return a.reduce(function(sum,v) { - return sum+v; - }, 0); - } - var averages = { - percentLow: 0, - percentInRange: 0, - percentHigh: 0, - standardDeviation: 0, - lowerQuartile: 0, - upperQuartile: 0, - average: 0 - }; + var low = parseInt(options.targetLow), + high = parseInt(options.targetHigh); + + var data = []; + var days = 0; + Object.keys(daystoshow).forEach(function (day) { + data = data.concat(datastorage[day].statsrecords); + days++; + }); + + var now = Date.now(); + var period = (7).days(); + var firstDataPoint = data.reduce(function(min, record) { + return Math.min(min, record.displayTime); + }, Number.MAX_VALUE); + if (firstDataPoint < 1390000000000) firstDataPoint = 1390000000000; + var quarters = Math.floor((Date.now() - firstDataPoint) / period); + + var grid = $("#success-grid"); + grid.empty(); + var table = $("
    "); + + if (quarters == 0) { + // insufficent data + grid.append("

    "+translate("There is not sufficient data to run this report. Select more days.")+"

    "); + return; + } + + var dim = function(n) { + var a = []; + for (var i = 0; i < n; i++) { + a[i]=0; + } + return a; + } + var sum = function(a) { + return a.reduce(function(sum,v) { + return sum+v; + }, 0); + } + var averages = { + percentLow: 0, + percentInRange: 0, + percentHigh: 0, + standardDeviation: 0, + lowerQuartile: 0, + upperQuartile: 0, + average: 0 + }; quarters = dim(quarters).map(function(blank, n) { var starting = new Date(now - (n+1) * period), ending = new Date(now - n * period); @@ -74,7 +74,7 @@ success.report = function report_success(datastorage,daystoshow,options) { starting: starting, ending: ending, records: data.filter(function(record) { - return record.displayTime > starting && record.displayTime <= ending; + return record.displayTime > starting && record.displayTime <= ending; }) }; }).filter(function(quarter) { @@ -109,85 +109,85 @@ success.report = function report_success(datastorage,daystoshow,options) { return quarter; }); - var lowComparison = function(quarter, averages, field, invert) { - if (quarter[field] < averages[field] * 0.8) { - return (invert? "bad": "good"); - } else if (quarter[field] > averages[field] * 1.2) { - return (invert? "good": "bad"); - } else { - return ""; - } - } - - var lowQuartileEvaluation = function(quarter, averages) { - if (quarter.lowerQuartile < low) { - return "bad"; - } else { - return lowComparison(quarter, averages, "lowerQuartile"); - } - } - - var upperQuartileEvaluation = function(quarter, averages) { - if (quarter.upperQuartile > high) { - return "bad"; - } else { - return lowComparison(quarter, averages, "upperQuartile"); - } - } - - var averageEvaluation = function(quarter, averages) { - if (quarter.average > high) { - return "bad"; - } else if (quarter.average < low) { - return "bad"; - } else { - return lowComparison(quarter, averages, "average", true); - } - } - - table.append(""); - table.append("" + quarters.filter(function(quarter) { - return quarter.records.length > 0; - }).map(function(quarter) { - var INVERT = true; - return "" + [ - quarter.starting.format("M d Y") + " - " + quarter.ending.format("M d Y"), - { - klass: lowComparison(quarter, averages, "percentLow"), - text: Math.round(quarter.percentLow) + "%" - }, - { - klass: lowComparison(quarter, averages, "percentInRange", INVERT), - text: Math.round(quarter.percentInRange) + "%" - }, - { - klass: lowComparison(quarter, averages, "percentHigh"), - text: Math.round(quarter.percentHigh) + "%" - }, - { - klass: lowComparison(quarter, averages, "standardDeviation"), - text: (quarter.standardDeviation > 10? Math.round(quarter.standardDeviation): quarter.standardDeviation.toFixed(1)) - }, - { - klass: lowQuartileEvaluation(quarter, averages), - text: quarter.lowerQuartile - }, - { - klass: lowComparison(quarter, averages, "average"), - text: quarter.average.toFixed(1) - }, - { - klass: upperQuartileEvaluation(quarter, averages), - text: quarter.upperQuartile - } - ].map(function(v) { - if (typeof v == "object") { - return ""; - } else { - return ""; - } - }).join("") + ""; - }).join("") + ""); - table.appendTo(grid); + var lowComparison = function(quarter, averages, field, invert) { + if (quarter[field] < averages[field] * 0.8) { + return (invert? "bad": "good"); + } else if (quarter[field] > averages[field] * 1.2) { + return (invert? "good": "bad"); + } else { + return ""; + } + } + + var lowQuartileEvaluation = function(quarter, averages) { + if (quarter.lowerQuartile < low) { + return "bad"; + } else { + return lowComparison(quarter, averages, "lowerQuartile"); + } + } + + var upperQuartileEvaluation = function(quarter, averages) { + if (quarter.upperQuartile > high) { + return "bad"; + } else { + return lowComparison(quarter, averages, "upperQuartile"); + } + } + + var averageEvaluation = function(quarter, averages) { + if (quarter.average > high) { + return "bad"; + } else if (quarter.average < low) { + return "bad"; + } else { + return lowComparison(quarter, averages, "average", true); + } + } + + table.append(""); + table.append("" + quarters.filter(function(quarter) { + return quarter.records.length > 0; + }).map(function(quarter) { + var INVERT = true; + return "" + [ + quarter.starting.format("M d Y") + " - " + quarter.ending.format("M d Y"), + { + klass: lowComparison(quarter, averages, "percentLow"), + text: Math.round(quarter.percentLow) + "%" + }, + { + klass: lowComparison(quarter, averages, "percentInRange", INVERT), + text: Math.round(quarter.percentInRange) + "%" + }, + { + klass: lowComparison(quarter, averages, "percentHigh"), + text: Math.round(quarter.percentHigh) + "%" + }, + { + klass: lowComparison(quarter, averages, "standardDeviation"), + text: (quarter.standardDeviation > 10? Math.round(quarter.standardDeviation): quarter.standardDeviation.toFixed(1)) + }, + { + klass: lowQuartileEvaluation(quarter, averages), + text: quarter.lowerQuartile + }, + { + klass: lowComparison(quarter, averages, "average"), + text: quarter.average.toFixed(1) + }, + { + klass: upperQuartileEvaluation(quarter, averages), + text: quarter.upperQuartile + } + ].map(function(v) { + if (typeof v == "object") { + return ""; + } else { + return ""; + } + }).join("") + ""; + }).join("") + ""); + table.appendTo(grid); } \ No newline at end of file diff --git a/lib/report_plugins/treatments.js b/lib/report_plugins/treatments.js index dcef65abb22..f462a39286a 100644 --- a/lib/report_plugins/treatments.js +++ b/lib/report_plugins/treatments.js @@ -15,54 +15,54 @@ module.exports = init; treatments.report = function report_treatments(datastorage,daystoshow,options) { - function deleteTreatment(event) { - var data = JSON.parse($(this).attr('data')); - var day = $(this).attr('day'); - - var ok = window.confirm( - translate('Delete this treatment?')+'\n' + - '\n'+translate('Event Type')+': ' + data.eventType + - (data.glucose ? '\n'+translate('Blood Glucose')+': ' + data.glucose : '')+ - (data.glucoseType ? '\n'+translate('Method')+': ' + data.glucoseType : '')+ - (data.carbs ? '\n'+translate('Carbs Given')+': ' + data.carbs : '' )+ - (data.insulin ? '\n'+translate('Insulin Given')+': ' + data.insulin : '')+ - (data.preBolus ? '\n'+translate('Pre Bolus')+': ' + data.preBolus : '')+ - (data.notes ? '\n'+translate('Notes')+': ' + data.notes : '' )+ - (data.enteredBy ? '\n'+translate('Entered By')+': ' + data.enteredBy : '' )+ - ('\n'+translate('Event Time')+': ' + new Date(data.created_at).toLocaleString()) - ); - - if (ok) { - deleteTreatmentRecord(data._id); - delete datastorage[day]; - show(); - } - if (event) event.preventDefault(); - return false; - } - - function deleteTreatmentRecord(_id) { - if (!Nightscout.auth.isAuthenticated()) { - alert(translate('Your device is not authenticated yet')); - return false; - } - - var xhr = new XMLHttpRequest(); - xhr.open('DELETE', '/api/v1/treatments/'+_id, true); - xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); - xhr.setRequestHeader('api-secret', Nightscout.auth.hash()); - xhr.onload = function () { - if (xhr.statusText!='OK') { - alert(translate('Deleting record failed')); - } - } - xhr.send(null); - return true; - } - - function editTreatment(event) { - var data = JSON.parse($(this).attr('data')); - var day = $(this).attr('day'); + function deleteTreatment(event) { + var data = JSON.parse($(this).attr('data')); + var day = $(this).attr('day'); + + var ok = window.confirm( + translate('Delete this treatment?')+'\n' + + '\n'+translate('Event Type')+': ' + data.eventType + + (data.glucose ? '\n'+translate('Blood Glucose')+': ' + data.glucose : '')+ + (data.glucoseType ? '\n'+translate('Method')+': ' + data.glucoseType : '')+ + (data.carbs ? '\n'+translate('Carbs Given')+': ' + data.carbs : '' )+ + (data.insulin ? '\n'+translate('Insulin Given')+': ' + data.insulin : '')+ + (data.preBolus ? '\n'+translate('Pre Bolus')+': ' + data.preBolus : '')+ + (data.notes ? '\n'+translate('Notes')+': ' + data.notes : '' )+ + (data.enteredBy ? '\n'+translate('Entered By')+': ' + data.enteredBy : '' )+ + ('\n'+translate('Event Time')+': ' + new Date(data.created_at).toLocaleString()) + ); + + if (ok) { + deleteTreatmentRecord(data._id); + delete datastorage[day]; + show(); + } + if (event) event.preventDefault(); + return false; + } + + function deleteTreatmentRecord(_id) { + if (!Nightscout.auth.isAuthenticated()) { + alert(translate('Your device is not authenticated yet')); + return false; + } + + var xhr = new XMLHttpRequest(); + xhr.open('DELETE', '/api/v1/treatments/'+_id, true); + xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); + xhr.setRequestHeader('api-secret', Nightscout.auth.hash()); + xhr.onload = function () { + if (xhr.statusText!='OK') { + alert(translate('Deleting record failed')); + } + } + xhr.send(null); + return true; + } + + function editTreatment(event) { + var data = JSON.parse($(this).attr('data')); + var day = $(this).attr('day'); $( '#rp_edittreatmentdialog' ).dialog({ width: 350 @@ -88,7 +88,7 @@ treatments.report = function report_treatments(datastorage,daystoshow,options) { { text: translate('Cancel'), click: function () { $( this ).dialog( "close" ); } } - ] + ] , open : function(ev, ui) { $(this).parent().css('box-shadow', '20px 20px 20px 0px black'); $(this).parent().find('.ui-dialog-buttonset' ).css({'width':'100%','text-align':'right'}) @@ -110,30 +110,30 @@ treatments.report = function report_treatments(datastorage,daystoshow,options) { }); if (event) event.preventDefault(); - return false; - } - - function saveTreatmentRecord(data) { - if (!Nightscout.auth.isAuthenticated()) { - alert(translate('Your device is not authenticated yet')); - return false; - } - - - var dataJson = JSON.stringify(data, null, ' '); - - var xhr = new XMLHttpRequest(); - xhr.open('PUT', '/api/v1/treatments/', true); - xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); - xhr.setRequestHeader('api-secret', Nightscout.auth.hash()); - xhr.onload = function () { - if (xhr.statusText!='OK') { - alert(translate('Saving record failed')); - } - } - xhr.send(dataJson); - return true; - } + return false; + } + + function saveTreatmentRecord(data) { + if (!Nightscout.auth.isAuthenticated()) { + alert(translate('Your device is not authenticated yet')); + return false; + } + + + var dataJson = JSON.stringify(data, null, ' '); + + var xhr = new XMLHttpRequest(); + xhr.open('PUT', '/api/v1/treatments/', true); + xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); + xhr.setRequestHeader('api-secret', Nightscout.auth.hash()); + xhr.onload = function () { + if (xhr.statusText!='OK') { + alert(translate('Saving record failed')); + } + } + xhr.send(dataJson); + return true; + } var Nightscout = window.Nightscout; var client = Nightscout.client; @@ -172,4 +172,4 @@ treatments.report = function report_treatments(datastorage,daystoshow,options) { $('.deleteTreatment').click(deleteTreatment); $('.editTreatment').click(editTreatment); } - + From f61a7720098a73b056f731eb90231ae12b27b6bf Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Fri, 28 Aug 2015 22:10:10 +0200 Subject: [PATCH 718/937] doublequotes to quotes --- lib/report_plugins/calibrations.js | 12 ++--- lib/report_plugins/dailystats.js | 42 +++++++-------- lib/report_plugins/daytoday.js | 10 ++-- lib/report_plugins/glucosedistribution.js | 60 +++++++++++----------- lib/report_plugins/hourlystats.js | 40 +++++++-------- lib/report_plugins/percentile.js | 32 ++++++------ lib/report_plugins/success.js | 62 +++++++++++------------ lib/report_plugins/treatments.js | 4 +- lib/report_plugins/utils.js | 2 +- static/report/js/report.js | 4 +- 10 files changed, 134 insertions(+), 134 deletions(-) diff --git a/lib/report_plugins/calibrations.js b/lib/report_plugins/calibrations.js index bdb14f9c7a1..70bb9dd54e8 100644 --- a/lib/report_plugins/calibrations.js +++ b/lib/report_plugins/calibrations.js @@ -18,8 +18,8 @@ calibrations.report = function report_calibrations(datastorage,daystoshow,option var treatments = []; Object.keys(daystoshow).forEach(function (day) { treatments = treatments.concat(datastorage[day].treatments.filter(function (t) { - if (t.eventType == "Sensor Start") return true; - if (t.eventType == "Sensor Change") return true; + if (t.eventType == 'Sensor Start') return true; + if (t.eventType == 'Sensor Change') return true; return false; })); }); @@ -115,10 +115,10 @@ calibrations.report = function report_calibrations(datastorage,daystoshow,option $('#calibrations-chart').empty(); var charts = d3.select('#calibrations-chart').append('svg'); - charts.append("rect") - .attr("width", "100%") - .attr("height", "100%") - .attr("fill", "WhiteSmoke"); + charts.append('rect') + .attr('width', '100%') + .attr('height', '100%') + .attr('fill', 'WhiteSmoke'); calibration_context = charts.append('g'); diff --git a/lib/report_plugins/dailystats.js b/lib/report_plugins/dailystats.js index 58f9010e1b9..3511ae763e9 100644 --- a/lib/report_plugins/dailystats.js +++ b/lib/report_plugins/dailystats.js @@ -19,15 +19,15 @@ dailystats.report = function report_dailystats(datastorage,daystoshow,options) { var report_plugins = Nightscout.report_plugins; var todo = []; - var report = $("#dailystats-report"); + var report = $('#dailystats-report'); var minForDay, maxForDay; - var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec"]; + var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sept', 'Oct', 'Nov', 'Dec']; report.empty(); var table = $('
    "+translate("Period")+""+translate("Low")+""+translate("In Range")+""+translate("High")+""+translate("Standard Deviation")+""+translate("Low Quartile")+""+translate("Average")+""+translate("Upper Quartile")+"
    " + v.text + "" + v + "
    "+translate("Period")+""+translate("Low")+""+translate("In Range")+""+translate("High")+""+translate("Standard Deviation")+""+translate("Low Quartile")+""+translate("Average")+""+translate("Upper Quartile")+"
    " + v.text + "" + v + "
    '); report.append(table); - var thead = $(""); - $("").appendTo(thead); + var thead = $(''); + $('').appendTo(thead); $('').appendTo(thead); $('').appendTo(thead); $('').appendTo(thead); @@ -42,14 +42,14 @@ dailystats.report = function report_dailystats(datastorage,daystoshow,options) { thead.appendTo(table); Object.keys(daystoshow).forEach(function (day) { - var tr = $(""); + var tr = $(''); var dayInQuestion = new Date(day); var daysRecords = datastorage[day].statsrecords; if (daysRecords.length == 0) { - $("").appendTo(tr); + $('').appendTo(tr); $('').appendTo(tr); table.append(tr); return;; @@ -75,19 +75,19 @@ dailystats.report = function report_dailystats(datastorage,daystoshow,options) { highs: 0 }); var bgValues = daysRecords.map(function(r) { return r.sgv; }); - $("").appendTo(tr); + $('').appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); table.append(tr); var inrange = [ @@ -105,7 +105,7 @@ dailystats.report = function report_dailystats(datastorage,daystoshow,options) { } ]; $.plot( - "#dailystat-chart-" + day.toString(), + '#dailystat-chart-' + day.toString(), inrange, { series: { @@ -113,7 +113,7 @@ dailystats.report = function report_dailystats(datastorage,daystoshow,options) { show: true } }, - colors: ["#f88", "#8f8", "#ff8"] + colors: ['#f88', '#8f8', '#ff8'] } ); }); diff --git a/lib/report_plugins/daytoday.js b/lib/report_plugins/daytoday.js index 8f5e41f84f9..ef62099c52c 100644 --- a/lib/report_plugins/daytoday.js +++ b/lib/report_plugins/daytoday.js @@ -111,10 +111,10 @@ daytoday.report = function report_daytoday(datastorage,daystoshow,options) { '
    ' ).append('svg'); - charts.append("rect") - .attr("width", "100%") - .attr("height", "100%") - .attr("fill", "WhiteSmoke"); + charts.append('rect') + .attr('width', '100%') + .attr('height', '100%') + .attr('fill', 'WhiteSmoke'); context = charts.append('g'); @@ -237,7 +237,7 @@ daytoday.report = function report_daytoday(datastorage,daystoshow,options) { .attr('r', function(d) { if (d.type == 'mbg') { return 4; } else { return 2; }}); if (badData.length > 0) { - console.warn("Bad Data: isNaN(sgv)", badData); + console.warn('Bad Data: isNaN(sgv)', badData); } return sel; } diff --git a/lib/report_plugins/glucosedistribution.js b/lib/report_plugins/glucosedistribution.js index 5b0f278c5b1..332916fcf44 100644 --- a/lib/report_plugins/glucosedistribution.js +++ b/lib/report_plugins/glucosedistribution.js @@ -19,12 +19,12 @@ glucosedistribution.report = function report_glucosedistribution(datastorage,day var translate = client.translate; var Statician = ss; - var report = $("#glucosedistribution-report"); + var report = $('#glucosedistribution-report'); report.empty(); var minForDay, maxForDay; var stats = []; var table = $('
    '+translate('Date')+''+translate('Low')+''+translate('Normal')+'
    ").appendTo(tr); - $("" + report_plugins.utils.localeDate(dayInQuestion) + "').appendTo(tr); + $('' + report_plugins.utils.localeDate(dayInQuestion) + ''+translate('No data available')+'
    " + report_plugins.utils.localeDate(dayInQuestion) + "" + Math.floor((100 * stats.lows) / daysRecords.length) + "%" + Math.floor((100 * stats.normal) / daysRecords.length) + "%" + Math.floor((100 * stats.highs) / daysRecords.length) + "%" + daysRecords.length +"" + minForDay +"" + maxForDay +"" + Math.floor(ss.standard_deviation(bgValues)) + "" + ss.quantile(bgValues, 0.25) + "" + ss.quantile(bgValues, 0.5) + "" + ss.quantile(bgValues, 0.75) + "' + report_plugins.utils.localeDate(dayInQuestion) + '' + Math.floor((100 * stats.lows) / daysRecords.length) + '%' + Math.floor((100 * stats.normal) / daysRecords.length) + '%' + Math.floor((100 * stats.highs) / daysRecords.length) + '%' + daysRecords.length +'' + minForDay +'' + maxForDay +'' + Math.floor(ss.standard_deviation(bgValues)) + '' + ss.quantile(bgValues, 0.25) + '' + ss.quantile(bgValues, 0.5) + '' + ss.quantile(bgValues, 0.75) + '
    '); - var thead = $(""); + var thead = $(''); $('').appendTo(thead); $('').appendTo(thead); $('').appendTo(thead); @@ -44,11 +44,11 @@ glucosedistribution.report = function report_glucosedistribution(datastorage,day $('#glucosedistribution-days').text(days+' '+translate('days total')); ['Low', 'Normal', 'High'].forEach(function(range) { - var tr = $(""); + var tr = $(''); var rangeRecords = data.filter(function(r) { - if (range == "Low") { + if (range == 'Low') { return r.sgv > 0 && r.sgv < options.targetLow; - } else if (range == "Normal") { + } else if (range == 'Normal') { return r.sgv >= options.targetLow && r.sgv < options.targetHigh; } else { return r.sgv >= options.targetHigh; @@ -63,47 +63,47 @@ glucosedistribution.report = function report_glucosedistribution(datastorage,day var midpoint = Math.floor(rangeRecords.length / 2); //var statistics = ss.(new Statician(rangeRecords.map(function(r) { return r.sgv; }))).stats; - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); if (rangeRecords.length > 0) { - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); } else { - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); } table.append(tr); }); - var tr = $(""); - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); + var tr = $(''); + $('').appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); if (data.length > 0) { var localBgs = data.map(function(r) { return r.sgv; }).filter(function(bg) { return !!bg; }); var mgDlBgs = data.map(function(r) { return r.bgValue; }).filter(function(bg) { return !!bg; }); - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); } else { - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); } table.append(tr); report.append(table); setTimeout(function() { $.plot( - "#glucosedistribution-overviewchart", + '#glucosedistribution-overviewchart', stats, { series: { @@ -111,7 +111,7 @@ glucosedistribution.report = function report_glucosedistribution(datastorage,day show: true } }, - colors: ["#f88", "#8f8", "#ff8"] + colors: ['#f88', '#8f8', '#ff8'] } ); }); diff --git a/lib/report_plugins/hourlystats.js b/lib/report_plugins/hourlystats.js index ee3c536bd22..4fa8929aaaf 100644 --- a/lib/report_plugins/hourlystats.js +++ b/lib/report_plugins/hourlystats.js @@ -18,7 +18,7 @@ hourlystats.report = function report_hourlystats(datastorage,daystoshow,options) var client = Nightscout.client; var translate = client.translate; - var report = $("#hourlystats-report"); + var report = $('#hourlystats-report'); var stats = []; var pivotedByHour = {}; @@ -36,8 +36,8 @@ hourlystats.report = function report_hourlystats(datastorage,daystoshow,options) var d = new Date(record.displayTime); pivotedByHour[d.getHours()].push(record); }); - var table = $("
    '+translate('Range')+''+translate('% of Readings')+''+translate('# of Readings')+'
    " + translate(range) + ": " + Math.floor(100 * rangeRecords.length / data.length) + "%" + rangeRecords.length + "' + translate(range) + ': ' + Math.floor(100 * rangeRecords.length / data.length) + '%' + rangeRecords.length + '" + Math.floor(10*Statician.mean(localBgs))/10 + "" + rangeRecords[midpoint].sgv + "" + Math.floor(Statician.standard_deviation(localBgs)*10)/10 + " ' + Math.floor(10*Statician.mean(localBgs))/10 + '' + rangeRecords[midpoint].sgv + '' + Math.floor(Statician.standard_deviation(localBgs)*10)/10 + ' N/AN/AN/A N/AN/AN/A
    "+translate("Overall")+": " + data.length + "
    '+translate('Overall')+': ' + data.length + '" + Math.round(10*ss.mean(localBgs))/10 + "" + Math.round(10*ss.quantile(localBgs, 0.5))/10+ "" + Math.round(ss.standard_deviation(localBgs)*10)/10 + "
    " + Math.round(10*(ss.mean(mgDlBgs)+46.7)/28.7)/10 + "%DCCT | " +Math.round(((ss.mean(mgDlBgs)+46.7)/28.7 - 2.15)*10.929) + "IFCC
    ' + Math.round(10*ss.mean(localBgs))/10 + '' + Math.round(10*ss.quantile(localBgs, 0.5))/10+ '' + Math.round(ss.standard_deviation(localBgs)*10)/10 + '
    ' + Math.round(10*(ss.mean(mgDlBgs)+46.7)/28.7)/10 + '%DCCT | ' +Math.round(((ss.mean(mgDlBgs)+46.7)/28.7 - 2.15)*10.929) + 'IFCC
    N/AN/AN/AN/AN/AN/AN/AN/A
    "); - var thead = $(""); + var table = $('
    '); + var thead = $(''); $('').appendTo(thead); $('').appendTo(thead); $('').appendTo(thead); @@ -50,16 +50,16 @@ hourlystats.report = function report_hourlystats(datastorage,daystoshow,options) thead.appendTo(table); [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23].forEach(function(hour) { - var tr = $(""); + var tr = $(''); var display = hour % 12; if (hour === 0) { - display = "12"; + display = '12'; } - display += ":00 "; + display += ':00 '; if (hour >= 12) { - display += "PM"; + display += 'PM'; } else { - display += "AM"; + display += 'AM'; } var avg = Math.floor(pivotedByHour[hour].map(function(r) { return r.sgv; }).reduce(function(o,v){ return o+v; }, 0) / pivotedByHour[hour].length); @@ -74,15 +74,15 @@ hourlystats.report = function report_hourlystats(datastorage,daystoshow,options) avg + dev ]); var tmp; - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); - $("").appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); table.append(tr); }); @@ -90,7 +90,7 @@ hourlystats.report = function report_hourlystats(datastorage,daystoshow,options) report.append(table); $.plot( - "#hourlystats-overviewchart", + '#hourlystats-overviewchart', [{ data:stats, candle:true @@ -101,8 +101,8 @@ hourlystats.report = function report_hourlystats(datastorage,daystoshow,options) lines: false //Somehow it draws lines if you dont disable this. Should investigate and fix this ;) }, xaxis: { - mode: "time", - timeFormat: "%h:00", + mode: 'time', + timeFormat: '%h:00', min: 0, max: (24).hours()-(1).seconds() }, diff --git a/lib/report_plugins/percentile.js b/lib/report_plugins/percentile.js index ec71828116c..cd107433ceb 100644 --- a/lib/report_plugins/percentile.js +++ b/lib/report_plugins/percentile.js @@ -66,11 +66,11 @@ percentile.report = function report_percentile(datastorage,daystoshow,options) { var low = options.targetLow; //dat50.forEach(function(x){console.log(x[0] + " - " + x[1])}); $.plot( - "#percentile-chart", [{ - label: translate("Median"), + '#percentile-chart', [{ + label: translate('Median'), data: dat50, id: 'c50', - color: "#000000", + color: '#000000', points: { show: false }, @@ -79,10 +79,10 @@ percentile.report = function report_percentile(datastorage,daystoshow,options) { //fill: true } }, { - label: "25%/75% "+translate("percentile"), + label: '25%/75% '+translate('percentile'), data: dat25, id: 'c25', - color: "#000055", + color: '#000055', points: { show: false }, @@ -94,7 +94,7 @@ percentile.report = function report_percentile(datastorage,daystoshow,options) { }, { data: dat75, id: 'c75', - color: "#000055", + color: '#000055', points: { show: false }, @@ -104,10 +104,10 @@ percentile.report = function report_percentile(datastorage,daystoshow,options) { }, fillBetween: 'c50' }, { - label: "10%/90% "+translate("percentile"), + label: '10%/90% '+translate('percentile'), data: dat10, id: 'c10', - color: "#a0a0FF", + color: '#a0a0FF', points: { show: false }, @@ -119,7 +119,7 @@ percentile.report = function report_percentile(datastorage,daystoshow,options) { }, { data: dat90, id: 'c90', - color: "#a0a0FF", + color: '#a0a0FF', points: { show: false }, @@ -129,24 +129,24 @@ percentile.report = function report_percentile(datastorage,daystoshow,options) { }, fillBetween: 'c75' }, { - label: translate("High"), + label: translate('High'), data: [], color: '#FFFF00', }, { - label: translate("Low"), + label: translate('Low'), data: [], color: '#FF0000', }], { xaxis: { - mode: "time", - timezone: "browser", - timeformat: "%H:%M", - tickColor: "#555", + mode: 'time', + timezone: 'browser', + timeformat: '%H:%M', + tickColor: '#555', }, yaxis: { min: 0, max: serverSettings.units == 'mmol' ? 22: 400, - tickColor: "#555", + tickColor: '#555', }, grid: { markings: [{ diff --git a/lib/report_plugins/success.js b/lib/report_plugins/success.js index c9c50ff3dba..b693d7459e7 100644 --- a/lib/report_plugins/success.js +++ b/lib/report_plugins/success.js @@ -36,13 +36,13 @@ success.report = function report_success(datastorage,daystoshow,options) { if (firstDataPoint < 1390000000000) firstDataPoint = 1390000000000; var quarters = Math.floor((Date.now() - firstDataPoint) / period); - var grid = $("#success-grid"); + var grid = $('#success-grid'); grid.empty(); - var table = $("
    '+translate('Time')+''+translate('Readings')+''+translate('Average')+'
    " + display + "" + pivotedByHour[hour].length + " (" + Math.floor(100 * pivotedByHour[hour].length / data.length) + "%)" + avg + "" + Math.min.apply(Math, pivotedByHour[hour].map(function(r) { return r.sgv; })) + "" + ((tmp=ss.quantile(pivotedByHour[hour].map(function(r) { return r.sgv; }), 0.25)) ? tmp.toFixed(1) : 0 ) + "" + ((tmp=ss.quantile(pivotedByHour[hour].map(function(r) { return r.sgv; }), 0.5)) ? tmp.toFixed(1) : 0 ) + "" + ((tmp=ss.quantile(pivotedByHour[hour].map(function(r) { return r.sgv; }), 0.75)) ? tmp.toFixed(1) : 0 ) + "" + Math.max.apply(Math, pivotedByHour[hour].map(function(r) { return r.sgv; })) + "" + Math.floor(dev*10)/10 + "' + display + '' + pivotedByHour[hour].length + ' (' + Math.floor(100 * pivotedByHour[hour].length / data.length) + '%)' + avg + '' + Math.min.apply(Math, pivotedByHour[hour].map(function(r) { return r.sgv; })) + '' + ((tmp=ss.quantile(pivotedByHour[hour].map(function(r) { return r.sgv; }), 0.25)) ? tmp.toFixed(1) : 0 ) + '' + ((tmp=ss.quantile(pivotedByHour[hour].map(function(r) { return r.sgv; }), 0.5)) ? tmp.toFixed(1) : 0 ) + '' + ((tmp=ss.quantile(pivotedByHour[hour].map(function(r) { return r.sgv; }), 0.75)) ? tmp.toFixed(1) : 0 ) + '' + Math.max.apply(Math, pivotedByHour[hour].map(function(r) { return r.sgv; })) + '' + Math.floor(dev*10)/10 + '
    "); + var table = $('
    '); if (quarters == 0) { // insufficent data - grid.append("

    "+translate("There is not sufficient data to run this report. Select more days.")+"

    "); + grid.append('

    '+translate('There is not sufficient data to run this report. Select more days.')+'

    '); return; } @@ -84,7 +84,7 @@ success.report = function report_success(datastorage,daystoshow,options) { return record.sgv; }); quarter.standardDeviation = ss.standard_deviation(bgValues); - quarter.average = bgValues.length > 0? (sum(bgValues) / bgValues.length): "N/A"; + quarter.average = bgValues.length > 0? (sum(bgValues) / bgValues.length): 'N/A'; quarter.lowerQuartile = ss.quantile(bgValues, 0.25); quarter.upperQuartile = ss.quantile(bgValues, 0.75); quarter.numberLow = bgValues.filter(function(bg) { @@ -111,61 +111,61 @@ success.report = function report_success(datastorage,daystoshow,options) { var lowComparison = function(quarter, averages, field, invert) { if (quarter[field] < averages[field] * 0.8) { - return (invert? "bad": "good"); + return (invert? 'bad': 'good'); } else if (quarter[field] > averages[field] * 1.2) { - return (invert? "good": "bad"); + return (invert? 'good': 'bad'); } else { - return ""; + return ''; } } var lowQuartileEvaluation = function(quarter, averages) { if (quarter.lowerQuartile < low) { - return "bad"; + return 'bad'; } else { - return lowComparison(quarter, averages, "lowerQuartile"); + return lowComparison(quarter, averages, 'lowerQuartile'); } } var upperQuartileEvaluation = function(quarter, averages) { if (quarter.upperQuartile > high) { - return "bad"; + return 'bad'; } else { - return lowComparison(quarter, averages, "upperQuartile"); + return lowComparison(quarter, averages, 'upperQuartile'); } } var averageEvaluation = function(quarter, averages) { if (quarter.average > high) { - return "bad"; + return 'bad'; } else if (quarter.average < low) { - return "bad"; + return 'bad'; } else { - return lowComparison(quarter, averages, "average", true); + return lowComparison(quarter, averages, 'average', true); } } - table.append(""); - table.append("" + quarters.filter(function(quarter) { + table.append(''); + table.append('' + quarters.filter(function(quarter) { return quarter.records.length > 0; }).map(function(quarter) { var INVERT = true; - return "" + [ - quarter.starting.format("M d Y") + " - " + quarter.ending.format("M d Y"), + return '' + [ + quarter.starting.format('M d Y') + ' - ' + quarter.ending.format('M d Y'), { - klass: lowComparison(quarter, averages, "percentLow"), - text: Math.round(quarter.percentLow) + "%" + klass: lowComparison(quarter, averages, 'percentLow'), + text: Math.round(quarter.percentLow) + '%' }, { - klass: lowComparison(quarter, averages, "percentInRange", INVERT), - text: Math.round(quarter.percentInRange) + "%" + klass: lowComparison(quarter, averages, 'percentInRange', INVERT), + text: Math.round(quarter.percentInRange) + '%' }, { - klass: lowComparison(quarter, averages, "percentHigh"), - text: Math.round(quarter.percentHigh) + "%" + klass: lowComparison(quarter, averages, 'percentHigh'), + text: Math.round(quarter.percentHigh) + '%' }, { - klass: lowComparison(quarter, averages, "standardDeviation"), + klass: lowComparison(quarter, averages, 'standardDeviation'), text: (quarter.standardDeviation > 10? Math.round(quarter.standardDeviation): quarter.standardDeviation.toFixed(1)) }, { @@ -173,7 +173,7 @@ success.report = function report_success(datastorage,daystoshow,options) { text: quarter.lowerQuartile }, { - klass: lowComparison(quarter, averages, "average"), + klass: lowComparison(quarter, averages, 'average'), text: quarter.average.toFixed(1) }, { @@ -181,13 +181,13 @@ success.report = function report_success(datastorage,daystoshow,options) { text: quarter.upperQuartile } ].map(function(v) { - if (typeof v == "object") { - return ""; + if (typeof v == 'object') { + return ''; } else { - return ""; + return ''; } - }).join("") + ""; - }).join("") + ""); + }).join('') + ''; + }).join('') + ''); table.appendTo(grid); } \ No newline at end of file diff --git a/lib/report_plugins/treatments.js b/lib/report_plugins/treatments.js index f462a39286a..992955ad743 100644 --- a/lib/report_plugins/treatments.js +++ b/lib/report_plugins/treatments.js @@ -140,8 +140,8 @@ treatments.report = function report_treatments(datastorage,daystoshow,options) { var translate = client.translate; var report_plugins = Nightscout.report_plugins; - var icon_remove = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACrElEQVQ4T42Ty2sTQRzHv5tmk2yyjRNtpfZhL8V6s2KoUNC2XqwgaCsVQcGiFqpHi0c9iRdR/ANE9KR40FIQX4cueKoPaKFoLdSYNtE0abKT1+5s9iW7aUMiHtzTzO7v85md+c6PA4DrHbsPCKIgOWO1pA7dT6YXnXH949SE/F63pqwZtRrO+SCKgjQ5NUV+azpmHj2krMwaJC4c8Erj+/eRyloMMwWFKgbn1nC3ervlK1evkXBLGBZT8SOewotnTylTNLdgeg/pDgZDC2cPHSR8bB22DVC9hFe0SG/H0xFXcHlykjRHRDBWgJcZSCY38Xx2lhqMnRYE34Px/sN9vlQWeoHBAx2yXsRruVAVuFsIBaSJ8+eJGPaBqQV4NROJjTzez89jLBoFn6FgybQL54wS3uTyVDFQ3cL2IYpBv3RhdJSIIQ80tQyv7gEqJvS8AmUlBs7UXPhtjtZgh3UFNYngk86NHCfNAg9dMwHVBPu+CpsVkTXKeJeVG+AGgTOZ3tt6MSKKjy+NjEBjFrR4ElZmA4pdxstMFsyyJu6tZZ7Ux9vwB6EAL50ZGiRECEPPUOixVTRxHlicgSVWxEdZpuZWfNuS2hk48NjwMIkIYZglBnV5Cbqtws/5IaAJmsfCglrEl2y2QeKmEBJ80tixKmxrFpSVr0gV0viQoxho2YUuPohmeFD22PiklLC4ma5JuBvdrfLJI0dJd0s7bM0ES8aR/BXDXGaTskqlL+D3Lwy0tZEePoAd4EA5YF4tYymdonfjmQh3s6dTPjU4SHYGwjAKecSXFyGlM1TdytntE56T+ts7SC/vhw3gm6njc2Kd3vm5Ub1IwQAvnYhGiZpYw1wiWYPrIw7wnBTt7CLOOwdmut14kQQvqt24tfK/utGR6LaF+iRqMf4N/O/8D28HiiCRYqzAAAAAAElFTkSuQmCC"; - var icon_edit = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABEUlEQVQ4jZ3MMUsCYQDG8ee8IySQbNCLyyEKG/RLNAXicqvQcAeNLrcFLlE0+xHuNpt8wy04rrYm8Q4HQRE56BSC3lSqU1BwCoxM39dnffj9BWyxXvVeEzvtctBwHyRebNu2Nk2lzMlrgJB+qBEeTByiKYpihl+fIO8jTI9PDJEVF1+K2iw+M6PhDuyag4NkQi/c3FkCK5Z3ZbM76qLltpCbn+vXxq0FABsDy9hzPdBvqvtXvvXzrw1swmsDLPjfACteGeDBfwK8+FdgGwwAIgC0ncsjxGRSH/eiPBgAJADY2z8sJ4JBfNBsDqlADVYMANIzKalv/bHaefKsTH9iPFb8ISsGAJym0+Qinz3jQktbAHcxvx3559eSAAAAAElFTkSuQmCC"; + var icon_remove = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACrElEQVQ4T42Ty2sTQRzHv5tmk2yyjRNtpfZhL8V6s2KoUNC2XqwgaCsVQcGiFqpHi0c9iRdR/ANE9KR40FIQX4cueKoPaKFoLdSYNtE0abKT1+5s9iW7aUMiHtzTzO7v85md+c6PA4DrHbsPCKIgOWO1pA7dT6YXnXH949SE/F63pqwZtRrO+SCKgjQ5NUV+azpmHj2krMwaJC4c8Erj+/eRyloMMwWFKgbn1nC3ervlK1evkXBLGBZT8SOewotnTylTNLdgeg/pDgZDC2cPHSR8bB22DVC9hFe0SG/H0xFXcHlykjRHRDBWgJcZSCY38Xx2lhqMnRYE34Px/sN9vlQWeoHBAx2yXsRruVAVuFsIBaSJ8+eJGPaBqQV4NROJjTzez89jLBoFn6FgybQL54wS3uTyVDFQ3cL2IYpBv3RhdJSIIQ80tQyv7gEqJvS8AmUlBs7UXPhtjtZgh3UFNYngk86NHCfNAg9dMwHVBPu+CpsVkTXKeJeVG+AGgTOZ3tt6MSKKjy+NjEBjFrR4ElZmA4pdxstMFsyyJu6tZZ7Ux9vwB6EAL50ZGiRECEPPUOixVTRxHlicgSVWxEdZpuZWfNuS2hk48NjwMIkIYZglBnV5Cbqtws/5IaAJmsfCglrEl2y2QeKmEBJ80tixKmxrFpSVr0gV0viQoxho2YUuPohmeFD22PiklLC4ma5JuBvdrfLJI0dJd0s7bM0ES8aR/BXDXGaTskqlL+D3Lwy0tZEePoAd4EA5YF4tYymdonfjmQh3s6dTPjU4SHYGwjAKecSXFyGlM1TdytntE56T+ts7SC/vhw3gm6njc2Kd3vm5Ub1IwQAvnYhGiZpYw1wiWYPrIw7wnBTt7CLOOwdmut14kQQvqt24tfK/utGR6LaF+iRqMf4N/O/8D28HiiCRYqzAAAAAAElFTkSuQmCC'; + var icon_edit = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABEUlEQVQ4jZ3MMUsCYQDG8ee8IySQbNCLyyEKG/RLNAXicqvQcAeNLrcFLlE0+xHuNpt8wy04rrYm8Q4HQRE56BSC3lSqU1BwCoxM39dnffj9BWyxXvVeEzvtctBwHyRebNu2Nk2lzMlrgJB+qBEeTByiKYpihl+fIO8jTI9PDJEVF1+K2iw+M6PhDuyag4NkQi/c3FkCK5Z3ZbM76qLltpCbn+vXxq0FABsDy9hzPdBvqvtXvvXzrw1swmsDLPjfACteGeDBfwK8+FdgGwwAIgC0ncsjxGRSH/eiPBgAJADY2z8sJ4JBfNBsDqlADVYMANIzKalv/bHaefKsTH9iPFb8ISsGAJym0+Qinz3jQktbAHcxvx3559eSAAAAAElFTkSuQmCC'; var table = '
    "+translate("Period")+""+translate("Low")+""+translate("In Range")+""+translate("High")+""+translate("Standard Deviation")+""+translate("Low Quartile")+""+translate("Average")+""+translate("Upper Quartile")+"
    '+translate('Period')+''+translate('Low')+''+translate('In Range')+''+translate('High')+''+translate('Standard Deviation')+''+translate('Low Quartile')+''+translate('Average')+''+translate('Upper Quartile')+'
    " + v.text + "' + v.text + '" + v + "' + v + '
    '; table += ''; diff --git a/lib/report_plugins/utils.js b/lib/report_plugins/utils.js index b4235fcbce5..4cd5d5d68dd 100644 --- a/lib/report_plugins/utils.js +++ b/lib/report_plugins/utils.js @@ -11,7 +11,7 @@ module.exports = init; utils.localeDate = function localeDate(day) { var translate = Nightscout.client.translate; var ret = - [translate("Sunday"),translate("Monday"),translate("Tuesday"),translate("Wednesday"),translate("Thursday"),translate("Friday"),translate("Saturday")][new Date(day).getDay()]; + [translate('Sunday'),translate('Monday'),translate('Tuesday'),translate('Wednesday'),translate('Thursday'),translate('Friday'),translate('Saturday')][new Date(day).getDay()]; ret += ' '; ret += new Date(day).toLocaleDateString(); return ret; diff --git a/static/report/js/report.js b/static/report/js/report.js index fd4d41195ca..5726cffcb19 100644 --- a/static/report/js/report.js +++ b/static/report/js/report.js @@ -23,8 +23,8 @@ var daystoshow = {}; var targetBGdefault = { - "mg/dl": { low: 72, high: 180 }, - "mmol": { low: 4, high: 10 } + 'mg/dl': { low: 72, high: 180 }, + 'mmol': { low: 4, high: 10 } }; var From e3cb1dad810810105ef04783c01f0fe61124a07c Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Fri, 28 Aug 2015 22:26:33 +0200 Subject: [PATCH 719/937] === and !== --- lib/report_plugins/calibrations.js | 6 ++-- lib/report_plugins/dailystats.js | 2 +- lib/report_plugins/daytoday.js | 20 ++++++------- lib/report_plugins/glucosedistribution.js | 4 +-- lib/report_plugins/hourlystats.js | 4 +-- lib/report_plugins/percentile.js | 4 +-- lib/report_plugins/success.js | 4 +-- lib/report_plugins/treatments.js | 4 +-- lib/report_plugins/utils.js | 4 +-- static/report/js/report.js | 36 +++++++++++------------ 10 files changed, 44 insertions(+), 44 deletions(-) diff --git a/lib/report_plugins/calibrations.js b/lib/report_plugins/calibrations.js index 70bb9dd54e8..9dda3c1dc34 100644 --- a/lib/report_plugins/calibrations.js +++ b/lib/report_plugins/calibrations.js @@ -18,8 +18,8 @@ calibrations.report = function report_calibrations(datastorage,daystoshow,option var treatments = []; Object.keys(daystoshow).forEach(function (day) { treatments = treatments.concat(datastorage[day].treatments.filter(function (t) { - if (t.eventType == 'Sensor Start') return true; - if (t.eventType == 'Sensor Change') return true; + if (t.eventType === 'Sensor Start') return true; + if (t.eventType === 'Sensor Change') return true; return false; })); }); @@ -149,7 +149,7 @@ calibrations.report = function report_calibrations(datastorage,daystoshow,option //set the width and height of the SVG element charts.attr('width', width) - .attr('height', height) + .attr('height', height); // ranges are based on the width and height available so reset xScale2.range([0, chartWidth]); diff --git a/lib/report_plugins/dailystats.js b/lib/report_plugins/dailystats.js index 3511ae763e9..6367bebd6c2 100644 --- a/lib/report_plugins/dailystats.js +++ b/lib/report_plugins/dailystats.js @@ -47,7 +47,7 @@ dailystats.report = function report_dailystats(datastorage,daystoshow,options) { var daysRecords = datastorage[day].statsrecords; - if (daysRecords.length == 0) { + if (daysRecords.length === 0) { $('').appendTo(tr); $('').appendTo(tr); diff --git a/lib/report_plugins/daytoday.js b/lib/report_plugins/daytoday.js index ef62099c52c..bb2ff4bf03a 100644 --- a/lib/report_plugins/daytoday.js +++ b/lib/report_plugins/daytoday.js @@ -32,7 +32,7 @@ daytoday.report = function report_daytoday(datastorage,daystoshow,options) { function getTimeFormat() { var timeFormat = FORMAT_TIME_12; - if (Nightscout.client.settings.timeFormat == '24') { + if (Nightscout.client.settings.timeFormat === '24') { timeFormat = FORMAT_TIME_24; } return timeFormat; @@ -49,8 +49,8 @@ daytoday.report = function report_daytoday(datastorage,daystoshow,options) { , foodtexts = 0; // Tick Values - if (options.scale == report_plugins.getProperty('SCALE_LOG')) { - if (client.settings.units == 'mmol') { + if (options.scale === report_plugins.getProperty('SCALE_LOG')) { + if (client.settings.units === 'mmol') { tickValues = [ 2.0 , 3.0 @@ -74,7 +74,7 @@ daytoday.report = function report_daytoday(datastorage,daystoshow,options) { ]; } } else { - if (client.settings.units == 'mmol') { + if (client.settings.units === 'mmol') { tickValues = [ 2.0 , 4.0 @@ -122,7 +122,7 @@ daytoday.report = function report_daytoday(datastorage,daystoshow,options) { xScale2 = d3.time.scale() .domain(d3.extent(data.sgv, function (d) { return d.date; })); - if (options.scale == report_plugins.getProperty('SCALE_LOG')) { + if (options.scale === report_plugins.getProperty('SCALE_LOG')) { yScale2 = d3.scale.log() .domain([client.utils.scaleMgdl(36), client.utils.scaleMgdl(420)]); } else { @@ -226,15 +226,15 @@ daytoday.report = function report_daytoday(datastorage,daystoshow,options) { } }) .attr('fill', function (d) { - if (d.color == 'gray' && !options.raw) { + if (d.color === 'gray' && !options.raw) { return 'transparent'; } return d.color; }) .style('opacity', function (d) { 0.5 }) - .attr('stroke-width', function (d) {if (d.type == 'mbg') return 2; else return 0; }) + .attr('stroke-width', function (d) {if (d.type === 'mbg') return 2; else return 0; }) .attr('stroke', function (d) { return 'black'; }) - .attr('r', function(d) { if (d.type == 'mbg') { return 4; } else { return 2; }}); + .attr('r', function(d) { if (d.type === 'mbg') { return 4; } else { return 2; }}); if (badData.length > 0) { console.warn('Bad Data: isNaN(sgv)', badData); @@ -254,14 +254,14 @@ daytoday.report = function report_daytoday(datastorage,daystoshow,options) { for (var dt=from; dt < to; dt.add(5, 'minutes')) { if (options.iob) { var iob = Nightscout.plugins('iob').calcTotal(data.treatments,profile,dt.toDate()).iob; - if (dt!=from) { + if (dt!==from) { iobpolyline += ', '; } iobpolyline += (xScale2(dt) + padding.left) + ',' + (yInsulinScale(iob) + padding.top) + ' '; } if (options.cob) { var cob = Nightscout.plugins('cob').cobTotal(data.treatments,profile,dt.toDate()).cob; - if (dt!=from) { + if (dt!==from) { cobpolyline += ', '; } cobpolyline += (xScale2(dt.toDate()) + padding.left) + ',' + (yCarbsScale(cob) + padding.top) + ' '; diff --git a/lib/report_plugins/glucosedistribution.js b/lib/report_plugins/glucosedistribution.js index 332916fcf44..7511e1d1a8e 100644 --- a/lib/report_plugins/glucosedistribution.js +++ b/lib/report_plugins/glucosedistribution.js @@ -46,9 +46,9 @@ glucosedistribution.report = function report_glucosedistribution(datastorage,day ['Low', 'Normal', 'High'].forEach(function(range) { var tr = $(''); var rangeRecords = data.filter(function(r) { - if (range == 'Low') { + if (range === 'Low') { return r.sgv > 0 && r.sgv < options.targetLow; - } else if (range == 'Normal') { + } else if (range === 'Normal') { return r.sgv >= options.targetLow && r.sgv < options.targetHigh; } else { return r.sgv >= options.targetHigh; diff --git a/lib/report_plugins/hourlystats.js b/lib/report_plugins/hourlystats.js index 4fa8929aaaf..a4fff3b1d8e 100644 --- a/lib/report_plugins/hourlystats.js +++ b/lib/report_plugins/hourlystats.js @@ -13,7 +13,7 @@ function init() { module.exports = init; -hourlystats.report = function report_hourlystats(datastorage,daystoshow,options) { +hourlystats.report = function report_hourlystats(datastorage,daystoshow) { var Nightscout = window.Nightscout; var client = Nightscout.client; var translate = client.translate; @@ -108,7 +108,7 @@ hourlystats.report = function report_hourlystats(datastorage,daystoshow,options) }, yaxis: { min: 0, - max: serverSettings.units == 'mmol' ? 22: 400, + max: serverSettings.units === 'mmol' ? 22: 400, show: true }, grid: { diff --git a/lib/report_plugins/percentile.js b/lib/report_plugins/percentile.js index cd107433ceb..c405c762997 100644 --- a/lib/report_plugins/percentile.js +++ b/lib/report_plugins/percentile.js @@ -36,7 +36,7 @@ percentile.report = function report_percentile(datastorage,daystoshow,options) { date.setMinutes(minute); var readings = data.filter(function(record) { var recdate = new Date(record.displayTime); - return recdate.getHours() == hour && recdate.getMinutes() >= minute && + return recdate.getHours() === hour && recdate.getMinutes() >= minute && recdate.getMinutes() < minute + minutewindow; }); readings = readings.map(function(record) { @@ -145,7 +145,7 @@ percentile.report = function report_percentile(datastorage,daystoshow,options) { }, yaxis: { min: 0, - max: serverSettings.units == 'mmol' ? 22: 400, + max: serverSettings.units === 'mmol' ? 22: 400, tickColor: '#555', }, grid: { diff --git a/lib/report_plugins/success.js b/lib/report_plugins/success.js index b693d7459e7..1f20b0f0231 100644 --- a/lib/report_plugins/success.js +++ b/lib/report_plugins/success.js @@ -40,7 +40,7 @@ success.report = function report_success(datastorage,daystoshow,options) { grid.empty(); var table = $('
    '+translate('Time')+''+translate('Event Type')+''+translate('Blood Glucose')+''+translate('Insulin')+''+translate('Carbs')+''+translate('Entered By')+''+translate('Notes')+'
    ').appendTo(tr); $('' + report_plugins.utils.localeDate(dayInQuestion) + ''+translate('No data available')+'
    '); - if (quarters == 0) { + if (quarters === 0) { // insufficent data grid.append('

    '+translate('There is not sufficient data to run this report. Select more days.')+'

    '); return; @@ -181,7 +181,7 @@ success.report = function report_success(datastorage,daystoshow,options) { text: quarter.upperQuartile } ].map(function(v) { - if (typeof v == 'object') { + if (typeof v === 'object') { return ''; } else { return ''; diff --git a/lib/report_plugins/treatments.js b/lib/report_plugins/treatments.js index 992955ad743..c10b53dc048 100644 --- a/lib/report_plugins/treatments.js +++ b/lib/report_plugins/treatments.js @@ -52,7 +52,7 @@ treatments.report = function report_treatments(datastorage,daystoshow,options) { xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); xhr.setRequestHeader('api-secret', Nightscout.auth.hash()); xhr.onload = function () { - if (xhr.statusText!='OK') { + if (xhr.statusText!=='OK') { alert(translate('Deleting record failed')); } } @@ -127,7 +127,7 @@ treatments.report = function report_treatments(datastorage,daystoshow,options) { xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); xhr.setRequestHeader('api-secret', Nightscout.auth.hash()); xhr.onload = function () { - if (xhr.statusText!='OK') { + if (xhr.statusText!=='OK') { alert(translate('Saving record failed')); } } diff --git a/lib/report_plugins/utils.js b/lib/report_plugins/utils.js index 4cd5d5d68dd..1f240fbf220 100644 --- a/lib/report_plugins/utils.js +++ b/lib/report_plugins/utils.js @@ -50,9 +50,9 @@ utils.scaledTreatmentBG = function scaledTreatmentBG(treatment,data) { console.warn('found an invalid glucose value', treatment); } else { if (treatment.glucose && treatment.units && client.settings.units) { - if (treatment.units != client.settings.units) { + if (treatment.units !== client.settings.units) { console.info('found mismatched glucose units, converting ' + treatment.units + ' into ' + client.settings.units, treatment); - if (treatment.units == 'mmol') { + if (treatment.units === 'mmol') { //BG is in mmol and display in mg/dl treatmentGlucose = Math.round(treatment.glucose * 18) } else { diff --git a/static/report/js/report.js b/static/report/js/report.js index 5726cffcb19..1d9b9c61387 100644 --- a/static/report/js/report.js +++ b/static/report/js/report.js @@ -68,7 +68,7 @@ filter.category = $('#rp_category').val(); filter.subcategory = ''; $('#rp_subcategory').empty().append(new Option(translate('(none)'),'')); - if (filter.category != '') { + if (filter.category !== '') { for (var s in food_categories[filter.category]) { $('#rp_subcategory').append(new Option(s,s)); } @@ -84,9 +84,9 @@ } $('#rp_food').empty(); for (var i=0; i'+translate('Loading')+' ...'); $('#charts').html(''); for (var d in daystoshow) { - if (daystoshow[d]==matchesneeded) { + if (daystoshow[d]===matchesneeded) { if (displayeddays < maxdays) { $('#charts').append($('
    ')); loadData(d,options); @@ -365,7 +365,7 @@ delete daystoshow[d]; } } - if (displayeddays==0) { + if (displayeddays===0) { $('#charts').html(''+translate('Result is empty')+''); $('#info').empty(); } @@ -423,7 +423,7 @@ function loadData(day,options) { // check for loaded data - if (datastorage[day] && day != moment().format('YYYY-MM-DD')) { + if (datastorage[day] && day !== moment().format('YYYY-MM-DD')) { showreports(options); return; } @@ -465,7 +465,7 @@ console.log(xhr); , rssi: element.rssi , sgv: element.sgv }); - } else if (element.type == 'cal') { + } else if (element.type === 'cal') { calData.push({ x: element.date , d: element.dateString @@ -553,7 +553,7 @@ console.log(data.sgv); // for other reports data.statsrecords = data.sgv.filter(function(r) { - if (r.type) return r.type == 'sgv'; + if (r.type) return r.type === 'sgv'; else return true; }).map(function (r) { var ret = {}; From 1e16946399d87eec027b6669daae8cb53673c2c6 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Fri, 28 Aug 2015 22:42:05 +0200 Subject: [PATCH 720/937] maybePreventDefault --- static/report/js/report.js | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/static/report/js/report.js b/static/report/js/report.js index 1d9b9c61387..9ffbc6d4fde 100644 --- a/static/report/js/report.js +++ b/static/report/js/report.js @@ -1,3 +1,6 @@ +// TODO: +// - bypass nightmode in reports + (function () { 'use strict'; //for the tests window isn't the global object @@ -57,14 +60,10 @@ $('#rp_subcategory').change(doFoodFilter); $('#rp_name').on('input',doFoodFilter); - if (event) event.preventDefault(); - return false; + return maybePreventDefault(event); } function fillFoodSubcategories(event) { - if (event) { - event.preventDefault(); - } filter.category = $('#rp_category').val(); filter.subcategory = ''; $('#rp_subcategory').empty().append(new Option(translate('(none)'),'')); @@ -74,6 +73,7 @@ } } doFoodFilter(); + return maybePreventDefault(event); } function doFoodFilter(event) { @@ -95,7 +95,7 @@ $('#rp_food').append(new Option(o,food_list[i]._id)); } - if (event) event.preventDefault(); + return maybePreventDefault(event); } // ****** FOOD CODE END ****** @@ -149,12 +149,12 @@ $('#rp_show').click(show); $('#rp_food').change(function (event) { - event.preventDefault(); $('#rp_enablefood').prop('checked',true); + return maybePreventDefault(event); }); - $('#rp_notes').change(function (event) { - event.preventDefault(); + $('#rp_notes').change(function (event) { $('#rp_enablenotes').prop('checked',true); + return maybePreventDefault(event); }); $('#rp_targetlow').val(targetBGdefault[client.settings.units].low); @@ -169,12 +169,12 @@ $('#rp_show').click(show); $('#rp_food').change(function (event) { - event.preventDefault(); $('#rp_enablefood').prop('checked',true); + return maybePreventDefault(event); }); $('#rp_notes').change(function (event) { - event.preventDefault(); $('#rp_enablenotes').prop('checked',true); + return maybePreventDefault(event); }); $('#rp_targetlow').val(targetBGdefault[client.settings.units].low); @@ -374,7 +374,7 @@ $('#rp_show').css('display','none'); daystoshow = {}; datefilter(); - if (event) event.preventDefault(); + return maybePreventDefault(event); } function showreports(options) { @@ -406,8 +406,7 @@ function setDataRange(event,days) { $('#rp_to').val(moment().format('YYYY-MM-DD')); $('#rp_from').val(moment().add(-days+1, 'days').format('YYYY-MM-DD')); - - if (event) event.preventDefault(); + return maybePreventDefault(event); } function switchreport_handler(event) { @@ -418,7 +417,7 @@ $('.tabplaceholder').css('display','none'); $('#'+id+'-placeholder').css('display',''); - + return maybePreventDefault(event); } function loadData(day,options) { @@ -570,4 +569,10 @@ console.log(data.sgv); showreports(options); } + function maybePreventDefault(event) { + if (event) { + event.preventDefault(); + } + return false; + } })(); \ No newline at end of file From 643cf0032bbbd3a0661d4ad2c91f5b6684871c56 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Fri, 28 Aug 2015 22:56:56 +0200 Subject: [PATCH 721/937] if brackets --- lib/report_plugins/calibrations.js | 31 +++++++---- lib/report_plugins/dailystats.js | 8 ++- lib/report_plugins/success.js | 4 +- static/report/js/report.js | 87 +++++++++++++++++------------- 4 files changed, 82 insertions(+), 48 deletions(-) diff --git a/lib/report_plugins/calibrations.js b/lib/report_plugins/calibrations.js index 9dda3c1dc34..547c788a2f7 100644 --- a/lib/report_plugins/calibrations.js +++ b/lib/report_plugins/calibrations.js @@ -18,8 +18,12 @@ calibrations.report = function report_calibrations(datastorage,daystoshow,option var treatments = []; Object.keys(daystoshow).forEach(function (day) { treatments = treatments.concat(datastorage[day].treatments.filter(function (t) { - if (t.eventType === 'Sensor Start') return true; - if (t.eventType === 'Sensor Change') return true; + if (t.eventType === 'Sensor Start') { + return true; + } + if (t.eventType === 'Sensor Change') { + return true; + } return false; })); }); @@ -50,14 +54,18 @@ calibrations.report = function report_calibrations(datastorage,daystoshow,option for (var i=0; i
    '; }; @@ -80,7 +90,9 @@ calibrations.report = function report_calibrations(datastorage,daystoshow,option if (typeof events[i].device !== 'undefined') { events[i].checked = true; $('#calibrations-'+i).prop('checked',true); - if (--maxcals<1) break; + if (--maxcals<1) { + break; + } } } calibrations_drawelements(); @@ -245,8 +257,9 @@ calibrations.report = function report_calibrations(datastorage,daystoshow,option var last = null; var time = date.getTime(); for (var i=0; i time) + if (storage[i].x > time) { return last; + } last = storage[i]; } return last; diff --git a/lib/report_plugins/dailystats.js b/lib/report_plugins/dailystats.js index 6367bebd6c2..b998064cea1 100644 --- a/lib/report_plugins/dailystats.js +++ b/lib/report_plugins/dailystats.js @@ -66,8 +66,12 @@ dailystats.report = function report_dailystats(datastorage,daystoshow,options) { } else { out.highs++; } - if (minForDay > record.sgv) minForDay = record.sgv; - if (maxForDay < record.sgv) maxForDay = record.sgv; + if (minForDay > record.sgv) { + minForDay = record.sgv; + } + if (maxForDay < record.sgv) { + maxForDay = record.sgv; + } return out; }, { lows: 0, diff --git a/lib/report_plugins/success.js b/lib/report_plugins/success.js index 1f20b0f0231..18e7c03a3fb 100644 --- a/lib/report_plugins/success.js +++ b/lib/report_plugins/success.js @@ -33,7 +33,9 @@ success.report = function report_success(datastorage,daystoshow,options) { var firstDataPoint = data.reduce(function(min, record) { return Math.min(min, record.displayTime); }, Number.MAX_VALUE); - if (firstDataPoint < 1390000000000) firstDataPoint = 1390000000000; + if (firstDataPoint < 1390000000000) { + firstDataPoint = 1390000000000; + } var quarters = Math.floor((Date.now() - firstDataPoint) / period); var grid = $('#success-grid'); diff --git a/static/report/js/report.js b/static/report/js/report.js index 9ffbc6d4fde..c8b20da8086 100644 --- a/static/report/js/report.js +++ b/static/report/js/report.js @@ -84,9 +84,9 @@ } $('#rp_food').empty(); for (var i=0; i
    ' + v.text + '' + v + ''; e.bgcolor = colors[colorindex]; - if (e.eventType) + if (e.eventType) { html += ''+translate(e.eventType)+':
    '; - else if (typeof e.device !== 'undefined') { + } else if (typeof e.device !== 'undefined') { html += ' '; html += 'MBG: ' + e.y + ' Raw: '+e.raw+'
    '; lastmbg = e; @@ -66,7 +74,9 @@ calibrations.report = function report_calibrations(datastorage,daystoshow,option } else if (typeof e.scale !== 'undefined') { html += 'CAL: ' + ' Scale: ' + e.scale.toFixed(2) + ' Intercept: ' + e.intercept.toFixed(0) + ' Slope: ' + e.slope.toFixed(2) + '
    '; if (lastmbg) lastmbg.cals.push(e); - } else html += JSON.stringify(e); + } else { + html += JSON.stringify(e); + } html += '
    '; var lastmbg = null; for (var i=0; i0; i--) { + for (i=events.length-1; i>0; i--) { if (typeof events[i].device !== 'undefined') { events[i].checked = true; $('#calibrations-'+i).prop('checked',true); @@ -264,4 +264,4 @@ calibrations.report = function report_calibrations(datastorage,daystoshow,option } return last; } -} +}; diff --git a/lib/report_plugins/dailystats.js b/lib/report_plugins/dailystats.js index b998064cea1..24e118dfdc8 100644 --- a/lib/report_plugins/dailystats.js +++ b/lib/report_plugins/dailystats.js @@ -21,7 +21,6 @@ dailystats.report = function report_dailystats(datastorage,daystoshow,options) { var todo = []; var report = $('#dailystats-report'); var minForDay, maxForDay; - var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sept', 'Oct', 'Nov', 'Dec']; report.empty(); var table = $('
    '); @@ -127,4 +126,4 @@ dailystats.report = function report_dailystats(datastorage,daystoshow,options) { fn(); }); }, 50); -} +}; diff --git a/lib/report_plugins/daytoday.js b/lib/report_plugins/daytoday.js index bb2ff4bf03a..3c2503eca4f 100644 --- a/lib/report_plugins/daytoday.js +++ b/lib/report_plugins/daytoday.js @@ -16,7 +16,6 @@ module.exports = init; daytoday.report = function report_daytoday(datastorage,daystoshow,options) { var Nightscout = window.Nightscout; var client = Nightscout.client; - var translate = client.translate; var profile = client.sbx.data.profile; var report_plugins = Nightscout.report_plugins; var scaledTreatmentBG = report_plugins.utils.scaledTreatmentBG; @@ -231,9 +230,9 @@ daytoday.report = function report_daytoday(datastorage,daystoshow,options) { } return d.color; }) - .style('opacity', function (d) { 0.5 }) + .style('opacity', function () { return 0.5 }) .attr('stroke-width', function (d) {if (d.type === 'mbg') return 2; else return 0; }) - .attr('stroke', function (d) { return 'black'; }) + .attr('stroke', function () { return 'black'; }) .attr('r', function(d) { if (d.type === 'mbg') { return 4; } else { return 2; }}); if (badData.length > 0) { @@ -248,7 +247,7 @@ daytoday.report = function report_daytoday(datastorage,daystoshow,options) { contextCircles.exit() .remove(); - var to = moment(day).add(1, 'days') //.add(new Date().getTimezoneOffset(), 'minutes'); + var to = moment(day).add(1, 'days'); //.add(new Date().getTimezoneOffset(), 'minutes'); var from = moment(day); //.add(new Date().getTimezoneOffset(), 'minutes'); var iobpolyline = '', cobpolyline = ''; for (var dt=from; dt < to; dt.add(5, 'minutes')) { @@ -382,4 +381,4 @@ daytoday.report = function report_daytoday(datastorage,daystoshow,options) { } }); } -} +}; diff --git a/lib/report_plugins/glucosedistribution.js b/lib/report_plugins/glucosedistribution.js index 7511e1d1a8e..52806f08399 100644 --- a/lib/report_plugins/glucosedistribution.js +++ b/lib/report_plugins/glucosedistribution.js @@ -21,7 +21,6 @@ glucosedistribution.report = function report_glucosedistribution(datastorage,day var Statician = ss; var report = $('#glucosedistribution-report'); report.empty(); - var minForDay, maxForDay; var stats = []; var table = $('
    '); var thead = $(''); @@ -115,4 +114,4 @@ glucosedistribution.report = function report_glucosedistribution(datastorage,day } ); }); -} +}; diff --git a/lib/report_plugins/hourlystats.js b/lib/report_plugins/hourlystats.js index a4fff3b1d8e..69321abccff 100644 --- a/lib/report_plugins/hourlystats.js +++ b/lib/report_plugins/hourlystats.js @@ -116,4 +116,4 @@ hourlystats.report = function report_hourlystats(datastorage,daystoshow) { } } ); -} \ No newline at end of file +}; \ No newline at end of file diff --git a/lib/report_plugins/percentile.js b/lib/report_plugins/percentile.js index c405c762997..bf38d68f62d 100644 --- a/lib/report_plugins/percentile.js +++ b/lib/report_plugins/percentile.js @@ -18,7 +18,6 @@ percentile.report = function report_percentile(datastorage,daystoshow,options) { var client = Nightscout.client; var translate = client.translate; - var Statician = ss; var minutewindow = 30; //minute-window should be a divisor of 60 var data = []; @@ -168,4 +167,4 @@ percentile.report = function report_percentile(datastorage,daystoshow,options) { } } ); -} +}; diff --git a/lib/report_plugins/success.js b/lib/report_plugins/success.js index 18e7c03a3fb..b02afa48747 100644 --- a/lib/report_plugins/success.js +++ b/lib/report_plugins/success.js @@ -54,12 +54,12 @@ success.report = function report_success(datastorage,daystoshow,options) { a[i]=0; } return a; - } + }; var sum = function(a) { return a.reduce(function(sum,v) { return sum+v; }, 0); - } + }; var averages = { percentLow: 0, percentInRange: 0, @@ -119,7 +119,7 @@ success.report = function report_success(datastorage,daystoshow,options) { } else { return ''; } - } + }; var lowQuartileEvaluation = function(quarter, averages) { if (quarter.lowerQuartile < low) { @@ -127,7 +127,7 @@ success.report = function report_success(datastorage,daystoshow,options) { } else { return lowComparison(quarter, averages, 'lowerQuartile'); } - } + }; var upperQuartileEvaluation = function(quarter, averages) { if (quarter.upperQuartile > high) { @@ -135,17 +135,7 @@ success.report = function report_success(datastorage,daystoshow,options) { } else { return lowComparison(quarter, averages, 'upperQuartile'); } - } - - var averageEvaluation = function(quarter, averages) { - if (quarter.average > high) { - return 'bad'; - } else if (quarter.average < low) { - return 'bad'; - } else { - return lowComparison(quarter, averages, 'average', true); - } - } + }; table.append(''); table.append('' + quarters.filter(function(quarter) { diff --git a/lib/report_plugins/treatments.js b/lib/report_plugins/treatments.js index c10b53dc048..df911e4944d 100644 --- a/lib/report_plugins/treatments.js +++ b/lib/report_plugins/treatments.js @@ -13,7 +13,7 @@ function init() { module.exports = init; -treatments.report = function report_treatments(datastorage,daystoshow,options) { +treatments.report = function report_treatments(datastorage,daystoshow) { function deleteTreatment(event) { var data = JSON.parse($(this).attr('data')); @@ -55,7 +55,7 @@ treatments.report = function report_treatments(datastorage,daystoshow,options) { if (xhr.statusText!=='OK') { alert(translate('Deleting record failed')); } - } + }; xhr.send(null); return true; } @@ -86,12 +86,12 @@ treatments.report = function report_treatments(datastorage,daystoshow,options) { } }, { text: translate('Cancel'), - click: function () { $( this ).dialog( "close" ); } + click: function () { $( this ).dialog( 'close' ); } } ] - , open : function(ev, ui) { + , open : function() { $(this).parent().css('box-shadow', '20px 20px 20px 0px black'); - $(this).parent().find('.ui-dialog-buttonset' ).css({'width':'100%','text-align':'right'}) + $(this).parent().find('.ui-dialog-buttonset' ).css({'width':'100%','text-align':'right'}); $(this).parent().find('button:contains("'+translate('Save')+'")').css({'float':'left'}); $('#rp_eventType').val(translate(data.eventType)); $('#rp_glucoseValue').val(data.glucose ? data.glucose : '').attr('placeholder', translate('Value in') + ' ' + serverSettings.units); @@ -157,7 +157,7 @@ treatments.report = function report_treatments(datastorage,daystoshow,options) { table += ' '; table += ''; table += ''; - table += ''; + table += ''; table += ''; table += ''; table += ''; diff --git a/lib/report_plugins/utils.js b/lib/report_plugins/utils.js index 1f240fbf220..98fac2c7f57 100644 --- a/lib/report_plugins/utils.js +++ b/lib/report_plugins/utils.js @@ -15,12 +15,12 @@ utils.localeDate = function localeDate(day) { ret += ' '; ret += new Date(day).toLocaleDateString(); return ret; -} +}; utils.localeDateTime = function localeDateTime(day) { var ret = new Date(day).toLocaleDateString() + ' ' + new Date(day).toLocaleTimeString(); return ret; -} +}; utils.scaledTreatmentBG = function scaledTreatmentBG(treatment,data) { var client = Nightscout.client; @@ -70,4 +70,4 @@ utils.scaledTreatmentBG = function scaledTreatmentBG(treatment,data) { } return treatmentGlucose || client.utils.scaleMgdl(calcBGByTime(treatment.mills)); -} +}; From 91469499c39446bf0d77d1fd44f7f84b8bb55585 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Fri, 28 Aug 2015 23:29:57 +0200 Subject: [PATCH 723/937] codacy pass 2 --- lib/api/treatments/index.js | 5 +++-- lib/report_plugins/success.js | 2 +- lib/report_plugins/treatments.js | 4 +++- lib/treatments.js | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/api/treatments/index.js b/lib/api/treatments/index.js index cec3a375fab..30aaf48b76e 100644 --- a/lib/api/treatments/index.js +++ b/lib/api/treatments/index.js @@ -27,10 +27,11 @@ function configure (app, wares, ctx) { function post_response(req, res) { var treatment = req.body; ctx.treatments.create(treatment, function (err, created) { - if (err) + if (err) { res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', err); - else + } else { res.json(created); + } }); } if (app.treatments_auth) diff --git a/lib/report_plugins/success.js b/lib/report_plugins/success.js index b02afa48747..45f98cc8d69 100644 --- a/lib/report_plugins/success.js +++ b/lib/report_plugins/success.js @@ -182,4 +182,4 @@ success.report = function report_success(datastorage,daystoshow,options) { }).join('') + ''); table.appendTo(grid); -} \ No newline at end of file +}; \ No newline at end of file diff --git a/lib/report_plugins/treatments.js b/lib/report_plugins/treatments.js index df911e4944d..f367f7535f9 100644 --- a/lib/report_plugins/treatments.js +++ b/lib/report_plugins/treatments.js @@ -37,7 +37,9 @@ treatments.report = function report_treatments(datastorage,daystoshow) { delete datastorage[day]; show(); } - if (event) event.preventDefault(); + if (event) { + event.preventDefault(); + } return false; } diff --git a/lib/treatments.js b/lib/treatments.js index 94d7c3cba92..2e9e23f85ef 100644 --- a/lib/treatments.js +++ b/lib/treatments.js @@ -55,7 +55,7 @@ function storage (env, ctx) { } function remove (_id, fn) { - return api( ).remove({ "_id": new ObjectID(_id) }, fn); + return api( ).remove({ '_id': new ObjectID(_id) }, fn); } function save (obj, fn) { From 2fdb03bece3c936c60e01c6d2670b4331594b208 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Sat, 29 Aug 2015 00:20:16 +0200 Subject: [PATCH 724/937] codacy pass 3 --- lib/api/treatments/index.js | 6 +++--- lib/report_plugins/calibrations.js | 6 ++++-- lib/report_plugins/daytoday.js | 10 +++++----- lib/report_plugins/treatments.js | 8 +++++--- lib/report_plugins/utils.js | 2 +- 5 files changed, 18 insertions(+), 14 deletions(-) diff --git a/lib/api/treatments/index.js b/lib/api/treatments/index.js index 30aaf48b76e..c6fd36f4c8b 100644 --- a/lib/api/treatments/index.js +++ b/lib/api/treatments/index.js @@ -34,11 +34,11 @@ function configure (app, wares, ctx) { } }); } - if (app.treatments_auth) + if (app.treatments_auth) { api.post('/treatments/', wares.verifyAuthorization, post_response); - else + } else { api.post('/treatments/', post_response); - + } api.delete('/treatments/:_id', wares.verifyAuthorization, function(req, res) { ctx.treatments.remove(req.params._id, function ( ) { res.json({ }); diff --git a/lib/report_plugins/calibrations.js b/lib/report_plugins/calibrations.js index 4613cef5dac..49e8632c159 100644 --- a/lib/report_plugins/calibrations.js +++ b/lib/report_plugins/calibrations.js @@ -13,7 +13,7 @@ function init() { module.exports = init; -calibrations.report = function report_calibrations(datastorage,daystoshow,options) { +calibrations.report = function report_calibrations(datastorage,daystoshow) { var padding = { top: 15, right: 15, bottom: 30, left: 70 }; var treatments = []; Object.keys(daystoshow).forEach(function (day) { @@ -73,7 +73,9 @@ calibrations.report = function report_calibrations(datastorage,daystoshow,option e.checked = false; } else if (typeof e.scale !== 'undefined') { html += 'CAL: ' + ' Scale: ' + e.scale.toFixed(2) + ' Intercept: ' + e.intercept.toFixed(0) + ' Slope: ' + e.slope.toFixed(2) + '
    '; - if (lastmbg) lastmbg.cals.push(e); + if (lastmbg) { + lastmbg.cals.push(e); + } } else { html += JSON.stringify(e); } diff --git a/lib/report_plugins/daytoday.js b/lib/report_plugins/daytoday.js index 3c2503eca4f..9a8b52da7b4 100644 --- a/lib/report_plugins/daytoday.js +++ b/lib/report_plugins/daytoday.js @@ -25,9 +25,9 @@ daytoday.report = function report_daytoday(datastorage,daystoshow,options) { FORMAT_TIME_12 = '%I' , FORMAT_TIME_24 = '%H'; - for (var day in daystoshow) { + _.each(daystoshow, function (n, day) { drawChart(day,datastorage[day],options); - } + }); function getTimeFormat() { var timeFormat = FORMAT_TIME_12; @@ -196,7 +196,7 @@ daytoday.report = function report_daytoday(datastorage,daystoshow,options) { .style('fill', 'none') .call(xAxis2); - for (var li in tickValues) { + _.each(tickValues, function (n, li) { context.append('line') .attr('class', 'high-line') .attr('x1', xScale2(dataRange[0])+padding.left) @@ -205,7 +205,7 @@ daytoday.report = function report_daytoday(datastorage,daystoshow,options) { .attr('y2', yScale2(tickValues[li])+padding.top) .style('stroke-dasharray', ('3, 3')) .attr('stroke', 'grey'); - } + }); // bind up the context chart data to an array of circles var contextCircles = context.selectAll('circle') @@ -231,7 +231,7 @@ daytoday.report = function report_daytoday(datastorage,daystoshow,options) { return d.color; }) .style('opacity', function () { return 0.5 }) - .attr('stroke-width', function (d) {if (d.type === 'mbg') return 2; else return 0; }) + .attr('stroke-width', function (d) {if (d.type === 'mbg') { return 2; } else { return 0; }}) .attr('stroke', function () { return 'black'; }) .attr('r', function(d) { if (d.type === 'mbg') { return 4; } else { return 2; }}); diff --git a/lib/report_plugins/treatments.js b/lib/report_plugins/treatments.js index f367f7535f9..8e24f9a6ed2 100644 --- a/lib/report_plugins/treatments.js +++ b/lib/report_plugins/treatments.js @@ -111,7 +111,9 @@ treatments.report = function report_treatments(datastorage,daystoshow) { }); - if (event) event.preventDefault(); + if (event) { + event.preventDefault(); + } return false; } @@ -132,7 +134,7 @@ treatments.report = function report_treatments(datastorage,daystoshow) { if (xhr.statusText!=='OK') { alert(translate('Saving record failed')); } - } + }; xhr.send(dataJson); return true; } @@ -173,5 +175,5 @@ treatments.report = function report_treatments(datastorage,daystoshow) { $('#treatments-report').html(table); $('.deleteTreatment').click(deleteTreatment); $('.editTreatment').click(editTreatment); -} +}; diff --git a/lib/report_plugins/utils.js b/lib/report_plugins/utils.js index 98fac2c7f57..d5e00cc3f1d 100644 --- a/lib/report_plugins/utils.js +++ b/lib/report_plugins/utils.js @@ -54,7 +54,7 @@ utils.scaledTreatmentBG = function scaledTreatmentBG(treatment,data) { console.info('found mismatched glucose units, converting ' + treatment.units + ' into ' + client.settings.units, treatment); if (treatment.units === 'mmol') { //BG is in mmol and display in mg/dl - treatmentGlucose = Math.round(treatment.glucose * 18) + treatmentGlucose = Math.round(treatment.glucose * 18); } else { //BG is in mg/dl and display in mmol treatmentGlucose = client.utils.scaleMgdl(treatment.glucose); From 03e165293d959db65c7a27e2a4a76b446986ea5c Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Sat, 29 Aug 2015 22:11:44 +0200 Subject: [PATCH 725/937] make html plugin's code dymnamicaly injected on load --- lib/report_plugins/calibrations.js | 18 +- lib/report_plugins/dailystats.js | 8 +- lib/report_plugins/daytoday.js | 45 +++- lib/report_plugins/glucosedistribution.js | 16 +- lib/report_plugins/hourlystats.js | 6 + lib/report_plugins/index.js | 32 ++- lib/report_plugins/percentile.js | 9 +- lib/report_plugins/success.js | 7 +- lib/report_plugins/treatments.js | 7 +- static/report/index.html | 237 ++++++++-------------- static/report/js/report.js | 26 ++- 11 files changed, 217 insertions(+), 194 deletions(-) diff --git a/lib/report_plugins/calibrations.js b/lib/report_plugins/calibrations.js index 49e8632c159..8310b920d9a 100644 --- a/lib/report_plugins/calibrations.js +++ b/lib/report_plugins/calibrations.js @@ -1,4 +1,5 @@ 'use strict'; +var translate = require('../language')().translate; var calibrations = { name: 'calibrations' @@ -12,6 +13,11 @@ function init() { module.exports = init; +calibrations.html = + '

    ' + translate('Calibrations') + '

    ' + + '
    ' + + '
    ' + ; calibrations.report = function report_calibrations(datastorage,daystoshow) { var padding = { top: 15, right: 15, bottom: 30, left: 70 }; @@ -113,10 +119,8 @@ calibrations.report = function report_calibrations(datastorage,daystoshow) { for (var i=0; i' + translate('Daily stats report') + '' + + '
    ' + ; + dailystats.report = function report_dailystats(datastorage,daystoshow,options) { var Nightscout = window.Nightscout; var client = Nightscout.client; diff --git a/lib/report_plugins/daytoday.js b/lib/report_plugins/daytoday.js index 9a8b52da7b4..cb86dc38e30 100644 --- a/lib/report_plugins/daytoday.js +++ b/lib/report_plugins/daytoday.js @@ -1,8 +1,9 @@ 'use strict'; +var translate = require('../language')().translate; var daytoday = { name: 'daytoday' - , label: 'Daytoday' + , label: 'Day to day' , pluginType: 'report' }; @@ -12,6 +13,39 @@ function init() { module.exports = init; +daytoday.html = + translate('Display') + + ''+translate('Insulin')+'' + + ''+translate('Carbs')+'' + + ''+translate('Notes') + + ''+translate('Food') + + ''+translate('Raw')+'' + + ''+translate('IOB')+'' + + ''+translate('COB')+'' + + ' '+translate('Size') + + '' + + '
    ' + + translate('Scale') + + '' + + translate('Linear') + + '' + + translate('Logarithmic') + + '
    ' + + '
    ' + + '
    ' + ; + +daytoday.prepareHtml = function daytodayPrepareHtml(daystoshow) { + $('#daytodaycharts').empty(); + for (var d in daystoshow) { + $('#daytodaycharts').append($('
    ')); + } +}; daytoday.report = function report_daytoday(datastorage,daystoshow,options) { var Nightscout = window.Nightscout; @@ -24,7 +58,8 @@ daytoday.report = function report_daytoday(datastorage,daystoshow,options) { var FORMAT_TIME_12 = '%I' , FORMAT_TIME_24 = '%H'; - + + daytoday.prepareHtml(daystoshow) ; _.each(daystoshow, function (n, day) { drawChart(day,datastorage[day],options); }); @@ -48,7 +83,7 @@ daytoday.report = function report_daytoday(datastorage,daystoshow,options) { , foodtexts = 0; // Tick Values - if (options.scale === report_plugins.getProperty('SCALE_LOG')) { + if (options.scale === report_plugins.consts.SCALE_LOG) { if (client.settings.units === 'mmol') { tickValues = [ 2.0 @@ -104,7 +139,7 @@ daytoday.report = function report_daytoday(datastorage,daystoshow,options) { } // create svg and g to contain the chart contents - charts = d3.select('#'+report_plugins.getProperty('containerprefix')+day).html( + charts = d3.select('#daytodaychart-' + day).html( ''+ report_plugins.utils.localeDate(day)+ '
    ' @@ -121,7 +156,7 @@ daytoday.report = function report_daytoday(datastorage,daystoshow,options) { xScale2 = d3.time.scale() .domain(d3.extent(data.sgv, function (d) { return d.date; })); - if (options.scale === report_plugins.getProperty('SCALE_LOG')) { + if (options.scale === report_plugins.consts.SCALE_LOG) { yScale2 = d3.scale.log() .domain([client.utils.scaleMgdl(36), client.utils.scaleMgdl(420)]); } else { diff --git a/lib/report_plugins/glucosedistribution.js b/lib/report_plugins/glucosedistribution.js index 52806f08399..004cd17b3b4 100644 --- a/lib/report_plugins/glucosedistribution.js +++ b/lib/report_plugins/glucosedistribution.js @@ -1,8 +1,9 @@ 'use strict'; +var translate = require('../language')().translate; var glucosedistribution = { name: 'glucosedistribution' - , label: 'Glucose distribution' + , label: 'Distribution' , pluginType: 'report' }; @@ -12,6 +13,19 @@ function init() { module.exports = init; +glucosedistribution.html = + '

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

    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + + translate('* This is only a rough estimation that can be very inaccurate and does not replace actual blood testing. The formula used is taken from: Nathan, David M., et al. "Translating the A1C assay into estimated average glucose values." Diabetes care 31.8 (2008): 1473-1478.') + ; glucosedistribution.report = function report_glucosedistribution(datastorage,daystoshow,options) { var Nightscout = window.Nightscout; diff --git a/lib/report_plugins/hourlystats.js b/lib/report_plugins/hourlystats.js index 69321abccff..aa1c13c8190 100644 --- a/lib/report_plugins/hourlystats.js +++ b/lib/report_plugins/hourlystats.js @@ -1,4 +1,5 @@ 'use strict'; +var translate = require('../language')().translate; var hourlystats = { name: 'hourlystats' @@ -12,6 +13,11 @@ function init() { module.exports = init; +hourlystats.html = + '

    ' + translate('Hourly stats') + '

    ' + + '
    ' + + '
    ' + ; hourlystats.report = function report_hourlystats(datastorage,daystoshow) { var Nightscout = window.Nightscout; diff --git a/lib/report_plugins/index.js b/lib/report_plugins/index.js index f7ff151e9ab..98d04e8901a 100644 --- a/lib/report_plugins/index.js +++ b/lib/report_plugins/index.js @@ -1,10 +1,13 @@ 'use strict'; var _ = require('lodash'); +var translate = require('../language')().translate; function init() { - - var properties = {} + var consts = { + SCALE_LINEAR: 0 + , SCALE_LOG: 1 + } , allPlugins = [ require('./daytoday')() , require('./dailystats')() @@ -28,15 +31,26 @@ function init() { _.each(allPlugins, f); }; - plugins.setProperty = function setProperty(p, val) { - properties[p] = val; - }; - - plugins.getProperty = function getProperty(p) { - return properties[p]; - }; + plugins.consts = consts; plugins.utils = require('./utils')(); + + plugins.addHtmlFromPlugins = function addHtmlFromPlugins(client) { + plugins.eachPlugin(function addHtml(p) { + // add main plugin html + if (p.html && ! $('#' + p.name + '-placeholder').length) { + $('#pluginchartplaceholders').append($('
    ').attr('id',p.name + '-placeholder').addClass('tabplaceholder').css('display','none').append(p.html)); + } + // add menu item + if (p.html && ! $('#' + p.name).length) { + $('#tabnav').append($('
  • ').attr('id',p.name).addClass('menutab').append(client.translate(p.label))); + } + // select 1st tab + $('#tabnav > li:first').addClass('selected'); + // show 1st report + $('#pluginchartplaceholders > div:first').css('display',''); + }); + }; return plugins(); diff --git a/lib/report_plugins/percentile.js b/lib/report_plugins/percentile.js index bf38d68f62d..ecbcfda6b7e 100644 --- a/lib/report_plugins/percentile.js +++ b/lib/report_plugins/percentile.js @@ -1,8 +1,9 @@ 'use strict'; +var translate = require('../language')().translate; var percentile = { name: 'percentile' - , label: 'Percentile' + , label: 'Percentile Chart' , pluginType: 'report' }; @@ -12,6 +13,12 @@ function init() { module.exports = init; +percentile.html = + '

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

    ' + + '
    ' + + '
    ' + + '
    ' + ; percentile.report = function report_percentile(datastorage,daystoshow,options) { var Nightscout = window.Nightscout; diff --git a/lib/report_plugins/success.js b/lib/report_plugins/success.js index 45f98cc8d69..c6155eea27e 100644 --- a/lib/report_plugins/success.js +++ b/lib/report_plugins/success.js @@ -1,8 +1,9 @@ 'use strict'; +var translate = require('../language')().translate; var success = { name: 'success' - , label: 'Success' + , label: 'Weekly success' , pluginType: 'report' }; @@ -12,6 +13,10 @@ function init() { module.exports = init; +success.html = + '

    ' + translate('Weekly Success') + '

    ' + + '
    ' + ; success.report = function report_success(datastorage,daystoshow,options) { var Nightscout = window.Nightscout; diff --git a/lib/report_plugins/treatments.js b/lib/report_plugins/treatments.js index 8e24f9a6ed2..038bb414afd 100644 --- a/lib/report_plugins/treatments.js +++ b/lib/report_plugins/treatments.js @@ -1,4 +1,5 @@ 'use strict'; +var translate = require('../language')().translate; var treatments = { name: 'treatments' @@ -12,7 +13,11 @@ function init() { module.exports = init; - +treatments.html = + '

    ' + translate('Treatments') + '

    ' + + '
    ' + ; + treatments.report = function report_treatments(datastorage,daystoshow) { function deleteTreatment(event) { diff --git a/static/report/index.html b/static/report/index.html index 2ab71406553..eecabd778e8 100644 --- a/static/report/index.html +++ b/static/report/index.html @@ -7,158 +7,85 @@

    Nightscout reporting

    -
      - - - - - - - - -
    -
  • '+translate('Period')+''+translate('Low')+''+translate('In Range')+''+translate('High')+''+translate('Standard Deviation')+''+translate('Low Quartile')+''+translate('Average')+''+translate('Upper Quartile')+'
    '+(new Date(tr.created_at).toLocaleTimeString().replace(/([\d]+:[\d]{2})(:[\d]{2})(.*)/, "$1$3"))+''+(new Date(tr.created_at).toLocaleTimeString().replace(/([\d]+:[\d]{2})(:[\d]{2})(.*)/, '$1$3'))+''+(tr.eventType ? tr.eventType : '')+''+(tr.glucose ? tr.glucose + ' ('+translate(tr.glucoseType)+')' : '')+''+(tr.insulin ? tr.insulin : '')+'
    - - - - - - - +
      +
    +
    - From: - To: - Today - Last 2 days - Last 3 days - Last week - Last 2 weeks - Last month - Last 3 months -
    - Category: - Subcategory: - Name: -
    + + + + + + + - - - - + + + + - - - - - - - - - - - - - - - - - - -
    + From: + To: + Today + Last 2 days + Last 3 days + Last week + Last 2 weeks + Last month + Last 3 months +
    + Category: + Subcategory: + Name: +
    - - - Food: -
    + + + Food: +
    - Notes contain: -
    - Event type contains: -
    - Mo - Tu - We - Th - Fr - Sa - Su -
    - Target bg range bottom: - - top: - -
    - -
    -
    -
    - Display: - Insulin - Carbs - Notes - Food - Raw - IOB - COB -  Size: - -
    - Scale: - - Linear - - Logarithmic -
    -
    -
    -
    - - - - - - - + + + + + Notes contain: + + + + + + + Event type contains: + + + + + Mo + Tu + We + Th + Fr + Sa + Su + + + + + + Target bg range bottom: + + top: + + + + + + +
    +
    +
    - - - +
    + + @@ -224,10 +151,10 @@

    Calibrations

    - + - - + + diff --git a/static/report/js/report.js b/static/report/js/report.js index c8b20da8086..bafacb31e90 100644 --- a/static/report/js/report.js +++ b/static/report/js/report.js @@ -16,8 +16,12 @@ } else { client.init(serverSettings, Nightscout.plugins); } + + // init HTML code + report_plugins.addHtmlFromPlugins( client ); var translate = client.translate; + //language.set(client.settings.language); var maxInsulinValue = 0 ,maxCarbsValue = 0; @@ -34,10 +38,6 @@ ONE_MIN_IN_MS = 60000 , SIX_MINS_IN_MS = 360000; - report_plugins.setProperty('SCALE_LINEAR', 0); - report_plugins.setProperty('SCALE_LOG', 1); - report_plugins.setProperty('containerprefix', 'chart-'); - // ****** FOOD CODE START ****** var food_categories = []; var food_list = []; @@ -198,7 +198,7 @@ carbs: true, iob : true, cob : true, - scale: report_plugins.getProperty('SCALE_LINEAR') + scale: report_plugins.consts.SCALE_LINEAR }; options.targetLow = parseFloat($('#rp_targetlow').val().replace(',','.')); @@ -210,7 +210,7 @@ options.food = $('#rp_optionsfood').is(':checked'); options.insulin = $('#rp_optionsinsulin').is(':checked'); options.carbs = $('#rp_optionscarbs').is(':checked'); - options.scale = $('#rp_linear').is(':checked') ? report_plugins.getProperty('SCALE_LINEAR') : report_plugins.getProperty('SCALE_LOG'); + options.scale = ( $('#rp_linear').is(':checked') ? report_plugins.consts.SCALE_LINEAR : report_plugins.consts.SCALE_LOG ); options.width = parseInt($('#rp_size :selected').attr('x')); options.height = parseInt($('#rp_size :selected').attr('y')); @@ -363,23 +363,21 @@ console.log('Total: ',daystoshow,'Needed: ',matchesneeded); var displayeddays = 0; $('#info').html(''+translate('Loading')+' ...'); - $('#charts').html(''); for (var d in daystoshow) { if (daystoshow[d]===matchesneeded) { if (displayeddays < maxdays) { - $('#charts').append($('
    ')); + $('#info').append($('
    ')); loadData(d,options); displayeddays++; } else { - $('#charts').append($('
    '+d+' '+translate('not displayed')+'.
    ')); + $('#info').append($('
    '+d+' '+translate('not displayed')+'.
    ')); } } else { delete daystoshow[d]; } } if (displayeddays===0) { - $('#charts').html(''+translate('Result is empty')+''); - $('#info').empty(); + $('#info').html(''+translate('Result is empty')+''); } } @@ -444,7 +442,7 @@ var to = from + 1000 * 60 * 60 * 24; var query = '?find[date][$gte]='+from+'&find[date][$lt]='+to+'&count=10000'; - $('#'+report_plugins.getProperty('containerprefix')+day).html(''+translate('Loading CGM data of')+' '+day+' ...'); + $('#info-' + day).html(''+translate('Loading CGM data of')+' '+day+' ...'); console.log('/api/v1/entries.json'+query); $.ajax('/api/v1/entries.json'+query, { success: function (xhr) { @@ -497,7 +495,7 @@ console.log(data.sgv); data.cal.sort(function(a, b) { return a.x - b.x; }); } }).done(function () { - $('#'+report_plugins.getProperty('containerprefix')+day).html(''+translate('Loading treatments data of')+' '+day+' ...'); + $('#info-' + day).html(''+translate('Loading treatments data of')+' '+day+' ...'); var tquery = '?find[created_at][$gte]='+new Date(from).toISOString()+'&find[created_at][$lt]='+new Date(to).toISOString(); $.ajax('/api/v1/treatments.json'+tquery, { success: function (xhr) { @@ -510,7 +508,7 @@ console.log(data.sgv); data.treatments.sort(function(a, b) { return a.mills - b.mills; }); } }).done(function () { - $('#'+report_plugins.getProperty('containerprefix')+day).html(''+translate('Processing data of')+' '+day+' ...'); + $('#info-' + day).html(''+translate('Processing data of')+' '+day+' ...'); processData(data,day,options); }); From 4129a2934ab841d5cfa6cf2a15b60db9e7ee6914 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Sat, 29 Aug 2015 22:45:42 +0200 Subject: [PATCH 726/937] codacy pass 4 --- lib/report_plugins/daytoday.js | 2 +- lib/report_plugins/index.js | 9 ++++----- static/report/js/report.js | 14 +++++++++----- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/lib/report_plugins/daytoday.js b/lib/report_plugins/daytoday.js index cb86dc38e30..58839df741d 100644 --- a/lib/report_plugins/daytoday.js +++ b/lib/report_plugins/daytoday.js @@ -23,7 +23,7 @@ daytoday.html = + ''+translate('IOB')+'' + ''+translate('COB')+'' + ' '+translate('Size') - + '' + ' ' + ' ' + ' ' diff --git a/lib/report_plugins/index.js b/lib/report_plugins/index.js index 98d04e8901a..48b9bd08ae0 100644 --- a/lib/report_plugins/index.js +++ b/lib/report_plugins/index.js @@ -1,7 +1,6 @@ 'use strict'; var _ = require('lodash'); -var translate = require('../language')().translate; function init() { var consts = { @@ -45,11 +44,11 @@ function init() { if (p.html && ! $('#' + p.name).length) { $('#tabnav').append($('
  • ').attr('id',p.name).addClass('menutab').append(client.translate(p.label))); } - // select 1st tab - $('#tabnav > li:first').addClass('selected'); - // show 1st report - $('#pluginchartplaceholders > div:first').css('display',''); }); + // select 1st tab + $('#tabnav > li:first').addClass('selected'); + // show 1st report + $('#pluginchartplaceholders > div:first').css('display',''); }; return plugins(); diff --git a/static/report/js/report.js b/static/report/js/report.js index bafacb31e90..72d9ef57434 100644 --- a/static/report/js/report.js +++ b/static/report/js/report.js @@ -1,5 +1,13 @@ // TODO: // - bypass nightmode in reports +// - optimize .done() on food load +// - hiding food html +// - make axis on daytoday better working with thresholds +// - get rid of /static/report/js/time.js +// - load css dynamic + optimize +// - move rp_edittreatmentdialog html to plugin +// - check if everything is translated +// - add tests (function () { 'use strict'; @@ -21,7 +29,6 @@ report_plugins.addHtmlFromPlugins( client ); var translate = client.translate; - //language.set(client.settings.language); var maxInsulinValue = 0 ,maxCarbsValue = 0; @@ -34,9 +41,7 @@ 'mmol': { low: 4, high: 10 } }; - var - ONE_MIN_IN_MS = 60000 - , SIX_MINS_IN_MS = 360000; + var ONE_MIN_IN_MS = 60000; // ****** FOOD CODE START ****** var food_categories = []; @@ -530,7 +535,6 @@ console.log(data.sgv); var temp1 = [ ]; if (cal) { temp1 = data.sgv.map(function (entry) { - var noise = entry.noise || 0; var rawBg = rawIsigToRawBg(entry, cal); return { x: entry.x, date: new Date(entry.x - 2 * 1000), y: rawBg, sgv: client.utils.scaleMgdl(rawBg), color: 'gray', type: 'rawbg', filtered: entry.filtered, unfiltered: entry.unfiltered } }).filter(function(entry) { return entry.y > 0}); From bd4b72e94b135a9fa1ffc192ef7722f7f39cd318 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Sat, 29 Aug 2015 23:10:02 +0200 Subject: [PATCH 727/937] restore replaced new code in treatments.js --- lib/treatments.js | 7 ++++++- static/report/js/report.js | 3 +++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/treatments.js b/lib/treatments.js index 2e9e23f85ef..d9d5309b437 100644 --- a/lib/treatments.js +++ b/lib/treatments.js @@ -84,6 +84,11 @@ function prepareData(obj) { , preBolusCarbs: '' }; + obj.glucose = Number(obj.glucose); + obj.carbs = Number(obj.carbs); + obj.insulin = Number(obj.insulin); + obj.preBolus = Number(obj.preBolus); + var eventTime; if (obj.eventTime) { eventTime = new Date(obj.eventTime); @@ -114,7 +119,7 @@ function prepareData(obj) { deleteIfEmpty('notes'); deleteIfEmpty('preBolus'); - if (!obj.glucose || obj.glucose === 0) { + if (obj.glucose === 0 || isNaN(obj.glucose)) { delete obj.glucose; delete obj.glucoseType; delete obj.units; diff --git a/static/report/js/report.js b/static/report/js/report.js index 72d9ef57434..37c752ec245 100644 --- a/static/report/js/report.js +++ b/static/report/js/report.js @@ -8,6 +8,9 @@ // - move rp_edittreatmentdialog html to plugin // - check if everything is translated // - add tests +// - optimize merging data inside every plugin +// - auto check checkbox to enable filter when data changed + (function () { 'use strict'; From 56ae37cda2d99c7a15933b5511d71b84adf5da29 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Sun, 30 Aug 2015 09:35:20 +0200 Subject: [PATCH 728/937] rebase changes --- lib/treatments.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/treatments.js b/lib/treatments.js index d9d5309b437..c7b08c4e818 100644 --- a/lib/treatments.js +++ b/lib/treatments.js @@ -133,7 +133,8 @@ storage.queryOpts = { insulin: parseInt , carbs: parseInt , glucose: parseInt - , notes: find_options.parseRegEx + , notes: find_options.parseRegEx + , eventType: find_options.parseRegEx } , dateField: 'created_at' }; From 2a24bc4041815ec8ee2a7a0ff6740b5a1bcc85a9 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Sun, 30 Aug 2015 09:56:10 +0200 Subject: [PATCH 729/937] allow regex in query --- lib/query.js | 6 +++++- lib/treatments.js | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/query.js b/lib/query.js index c322919cb57..989a0faa832 100644 --- a/lib/query.js +++ b/lib/query.js @@ -174,7 +174,11 @@ function walk_prop (prop, typer) { } function parseRegEx (str) { - return new RegExp(str); + var regtest = /\/(.*)\/(.*)/.exec(str); + if (regtest) { + return new RegExp(regtest[1],regtest[2]); + } + return str; } // attach helpers and utilities to main function for testing diff --git a/lib/treatments.js b/lib/treatments.js index c7b08c4e818..ed8eb77ac01 100644 --- a/lib/treatments.js +++ b/lib/treatments.js @@ -70,7 +70,7 @@ function storage (env, ctx) { api.list = list; api.create = create; - api.indexedFields = ['created_at', 'eventType', 'insulin', 'carbs', 'glucose', 'boluscalc.foods._id', 'notes']; + api.indexedFields = ['created_at', 'eventType', 'insulin', 'carbs', 'glucose', 'enteredBy', 'boluscalc.foods._id', 'notes']; api.remove = remove; api.save = save; return api; @@ -135,6 +135,7 @@ storage.queryOpts = { , glucose: parseInt , notes: find_options.parseRegEx , eventType: find_options.parseRegEx + , enteredBy: find_options.parseRegEx } , dateField: 'created_at' }; From 11283bee1dc259e55b34315aff5d565eb9859e99 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Sun, 30 Aug 2015 11:16:27 +0200 Subject: [PATCH 730/937] .x -> .mills everywhere to make calibration.js work --- lib/report_plugins/calibrations.js | 19 ++++++++++-------- lib/report_plugins/treatments.js | 2 +- static/report/js/report.js | 31 +++++++++++++----------------- 3 files changed, 25 insertions(+), 27 deletions(-) diff --git a/lib/report_plugins/calibrations.js b/lib/report_plugins/calibrations.js index 8310b920d9a..919669d7825 100644 --- a/lib/report_plugins/calibrations.js +++ b/lib/report_plugins/calibrations.js @@ -20,6 +20,9 @@ calibrations.html = ; calibrations.report = function report_calibrations(datastorage,daystoshow) { + var Nightscout = window.Nightscout; + var report_plugins = Nightscout.report_plugins; + var padding = { top: 15, right: 15, bottom: 30, left: 70 }; var treatments = []; Object.keys(daystoshow).forEach(function (day) { @@ -51,7 +54,7 @@ calibrations.report = function report_calibrations(datastorage,daystoshow) { mbgs.forEach(function (mbg) { calibrations_calcmbg(mbg); }); - var events = treatments.concat(cals).concat(mbgs).sort(function(a, b) { return a.x - b.x; }); + var events = treatments.concat(cals).concat(mbgs).sort(function(a, b) { return a.mills - b.mills; }); var colors = ['Aqua','Blue','Brown','Chartreuse','Coral','CornflowerBlue','DarkCyan','DarkMagenta','DarkOrange','Fuchsia','Green','Yellow']; var colorindex = 0; @@ -67,7 +70,7 @@ calibrations.report = function report_calibrations(datastorage,daystoshow) { currentcolor = colors[colorindex]; } html += ''; - html += '' + localeDateTime(new Date(e.x)) + ''; + html += '' + report_plugins.utils.localeDateTime(new Date(e.mills)) + ''; e.bgcolor = colors[colorindex]; if (e.eventType) { html += ''+translate(e.eventType)+':
    '; @@ -219,7 +222,7 @@ calibrations.report = function report_calibrations(datastorage,daystoshow) { } function calibrations_drawcal(cal) { - var color = cal.color; + var color = cal.bgcolor; var y1 = 50000; var x1 = cal.scale * (y1 - cal.intercept) / cal.slope; var y2 = 400000; @@ -234,11 +237,11 @@ calibrations.report = function report_calibrations(datastorage,daystoshow) { } function calibrations_calcmbg(mbg) { - var lastsgv = calibrations_findlatest(new Date(mbg.x),sgvs); + var lastsgv = calibrations_findlatest(new Date(mbg.mills),sgvs); if (lastsgv) { - if (mbg.x-lastsgv.x>5*60*1000) { - console.log('Last SGV too old for MBG. Time diff: '+((mbg.x-lastsgv.x)/1000/60).toFixed(1)+' min',mbg); + if (mbg.mills-lastsgv.mills>5*60*1000) { + console.log('Last SGV too old for MBG. Time diff: '+((mbg.mills-lastsgv.mills)/1000/60).toFixed(1)+' min',mbg); } else { mbg.raw = lastsgv.filtered || lastsgv.unfiltered; } @@ -248,7 +251,7 @@ calibrations.report = function report_calibrations(datastorage,daystoshow) { } function calibrations_drawmbg(mbg) { - var color = mbg.color; + var color = mbg.bgcolor; if (mbg.raw) { calibration_context.append('circle') .attr('cx', xScale2(mbg.y) + padding.left) @@ -265,7 +268,7 @@ calibrations.report = function report_calibrations(datastorage,daystoshow) { var last = null; var time = date.getTime(); for (var i=0; i time) { + if (storage[i].mills > time) { return last; } last = storage[i]; diff --git a/lib/report_plugins/treatments.js b/lib/report_plugins/treatments.js index 038bb414afd..22516ebdae8 100644 --- a/lib/report_plugins/treatments.js +++ b/lib/report_plugins/treatments.js @@ -167,7 +167,7 @@ treatments.report = function report_treatments(datastorage,daystoshow) { table += ''; table += ''; table += ''+(new Date(tr.created_at).toLocaleTimeString().replace(/([\d]+:[\d]{2})(:[\d]{2})(.*)/, '$1$3'))+''; - table += ''+(tr.eventType ? tr.eventType : '')+''; + table += ''+(tr.eventType ? translate(tr.eventType) : '')+''; table += ''+(tr.glucose ? tr.glucose + ' ('+translate(tr.glucoseType)+')' : '')+''; table += ''+(tr.insulin ? tr.insulin : '')+''; table += ''+(tr.carbs ? tr.carbs : '')+''; diff --git a/static/report/js/report.js b/static/report/js/report.js index 37c752ec245..219ba3ef415 100644 --- a/static/report/js/report.js +++ b/static/report/js/report.js @@ -6,10 +6,11 @@ // - get rid of /static/report/js/time.js // - load css dynamic + optimize // - move rp_edittreatmentdialog html to plugin -// - check if everything is translated +// - check everything is translated // - add tests // - optimize merging data inside every plugin // - auto check checkbox to enable filter when data changed +// - Insuling Change vs Insulin Cartridge Change in translations (function () { @@ -451,23 +452,21 @@ var query = '?find[date][$gte]='+from+'&find[date][$lt]='+to+'&count=10000'; $('#info-' + day).html(''+translate('Loading CGM data of')+' '+day+' ...'); -console.log('/api/v1/entries.json'+query); $.ajax('/api/v1/entries.json'+query, { success: function (xhr) { -console.log(xhr); xhr.forEach(function (element) { if (element) { if (element.mbg) { mbgData.push({ y: element.mbg - , x: element.date + , mills: element.date , d: element.dateString , device: element.device }); } else if (element.sgv) { cgmData.push({ y: element.sgv - , x: element.date + , mills: element.date , d: element.dateString , device: element.device , filtered: element.filtered @@ -478,7 +477,7 @@ console.log(xhr); }); } else if (element.type === 'cal') { calData.push({ - x: element.date + mills: element.date , d: element.dateString , scale: element.scale , intercept: element.intercept @@ -489,18 +488,17 @@ console.log(xhr); }); // sometimes cgm contains duplicates. uniq it. data.sgv = cgmData.slice(); -console.log(data.sgv); - data.sgv.sort(function(a, b) { return a.x - b.x; }); + data.sgv.sort(function(a, b) { return a.mills - b.mills; }); var lastDate = 0; data.sgv = data.sgv.filter(function(d) { - var ok = (lastDate + ONE_MIN_IN_MS) < d.x; - lastDate = d.x; + var ok = (lastDate + ONE_MIN_IN_MS) < d.mills; + lastDate = d.mills; return ok; }); data.mbg = mbgData.slice(); - data.mbg.sort(function(a, b) { return a.x - b.x; }); + data.mbg.sort(function(a, b) { return a.mills - b.mills; }); data.cal = calData.slice(); - data.cal.sort(function(a, b) { return a.x - b.x; }); + data.cal.sort(function(a, b) { return a.mills - b.mills; }); } }).done(function () { $('#info-' + day).html(''+translate('Loading treatments data of')+' '+day+' ...'); @@ -539,16 +537,16 @@ console.log(data.sgv); if (cal) { temp1 = data.sgv.map(function (entry) { var rawBg = rawIsigToRawBg(entry, cal); - return { x: entry.x, date: new Date(entry.x - 2 * 1000), y: rawBg, sgv: client.utils.scaleMgdl(rawBg), color: 'gray', type: 'rawbg', filtered: entry.filtered, unfiltered: entry.unfiltered } + return { mills: entry.mills, date: new Date(entry.mills - 2 * 1000), y: rawBg, sgv: client.utils.scaleMgdl(rawBg), color: 'gray', type: 'rawbg', filtered: entry.filtered, unfiltered: entry.unfiltered } }).filter(function(entry) { return entry.y > 0}); } var temp2 = data.sgv.map(function (obj) { - return { x: obj.x, date: new Date(obj.x), y: obj.y, sgv: client.utils.scaleMgdl(obj.y), color: sgvToColor(client.utils.scaleMgdl(obj.y),options), type: 'sgv', noise: obj.noise, filtered: obj.filtered, unfiltered: obj.unfiltered} + return { mills: obj.mills, date: new Date(obj.mills), y: obj.y, sgv: client.utils.scaleMgdl(obj.y), color: sgvToColor(client.utils.scaleMgdl(obj.y),options), type: 'sgv', noise: obj.noise, filtered: obj.filtered, unfiltered: obj.unfiltered} }); data.sgv = [].concat(temp1, temp2); //Add MBG's also, pretend they are SGV's - data.sgv = data.sgv.concat(data.mbg.map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: client.utils.scaleMgdl(obj.y), color: 'red', type: 'mbg', device: obj.device } })); + data.sgv = data.sgv.concat(data.mbg.map(function (obj) { return { date: new Date(obj.mills), y: obj.y, sgv: client.utils.scaleMgdl(obj.y), color: 'red', type: 'mbg', device: obj.device } })); // make sure data range will be exactly 24h var from = new Date(new Date(day).getTime() + (new Date().getTimezoneOffset()*60*1000)); @@ -564,9 +562,6 @@ console.log(data.sgv); return true; }); - //delete data.cal; - //delete data.mbg; - // for other reports data.statsrecords = data.sgv.filter(function(r) { if (r.type) { From b1cc6b1a8b029417beb75b232099a9b20fb5d97d Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Sun, 30 Aug 2015 11:37:52 +0200 Subject: [PATCH 731/937] units passed with options --- lib/report_plugins/hourlystats.js | 4 ++-- lib/report_plugins/percentile.js | 2 +- static/report/js/report.js | 25 +++++++++++++------------ 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/lib/report_plugins/hourlystats.js b/lib/report_plugins/hourlystats.js index aa1c13c8190..6587169e835 100644 --- a/lib/report_plugins/hourlystats.js +++ b/lib/report_plugins/hourlystats.js @@ -19,7 +19,7 @@ hourlystats.html = + '
    ' ; -hourlystats.report = function report_hourlystats(datastorage,daystoshow) { +hourlystats.report = function report_hourlystats(datastorage, daystoshow, options) { var Nightscout = window.Nightscout; var client = Nightscout.client; var translate = client.translate; @@ -114,7 +114,7 @@ hourlystats.report = function report_hourlystats(datastorage,daystoshow) { }, yaxis: { min: 0, - max: serverSettings.units === 'mmol' ? 22: 400, + max: options.units === 'mmol' ? 22: 400, show: true }, grid: { diff --git a/lib/report_plugins/percentile.js b/lib/report_plugins/percentile.js index ecbcfda6b7e..cdce8e9c736 100644 --- a/lib/report_plugins/percentile.js +++ b/lib/report_plugins/percentile.js @@ -151,7 +151,7 @@ percentile.report = function report_percentile(datastorage,daystoshow,options) { }, yaxis: { min: 0, - max: serverSettings.units === 'mmol' ? 22: 400, + max: options.units === 'mmol' ? 22: 400, tickColor: '#555', }, grid: { diff --git a/static/report/js/report.js b/static/report/js/report.js index 219ba3ef415..fa24934dd61 100644 --- a/static/report/js/report.js +++ b/static/report/js/report.js @@ -196,18 +196,19 @@ function show(event) { var options = { - width: 1000, - height: 300, - targetLow: 3.5, - targetHigh: 10, - raw: true, - notes: true, - food: true, - insulin: true, - carbs: true, - iob : true, - cob : true, - scale: report_plugins.consts.SCALE_LINEAR + width: 1000 + , height: 300 + , targetLow: 3.5 + , targetHigh: 10 + , raw: true + , notes: true + , food: true + , insulin: true + , carbs: true + , iob : true + , cob : true + , scale: report_plugins.consts.SCALE_LINEAR + , units: client.settings.units }; options.targetLow = parseFloat($('#rp_targetlow').val().replace(',','.')); From c78e9b05eca749c1253e20448510d5fce07b3ad6 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Sun, 30 Aug 2015 12:09:18 +0200 Subject: [PATCH 732/937] plugin.html must be a function(client) to be correctly translated with initialized language module --- lib/report_plugins/calibrations.js | 6 ++- lib/report_plugins/dailystats.js | 6 ++- lib/report_plugins/daytoday.js | 56 ++++++++++++----------- lib/report_plugins/glucosedistribution.js | 6 ++- lib/report_plugins/hourlystats.js | 6 ++- lib/report_plugins/index.js | 2 +- lib/report_plugins/percentile.js | 6 ++- lib/report_plugins/success.js | 6 ++- lib/report_plugins/treatments.js | 6 ++- 9 files changed, 66 insertions(+), 34 deletions(-) diff --git a/lib/report_plugins/calibrations.js b/lib/report_plugins/calibrations.js index 919669d7825..b283f59b3e3 100644 --- a/lib/report_plugins/calibrations.js +++ b/lib/report_plugins/calibrations.js @@ -13,11 +13,15 @@ function init() { module.exports = init; -calibrations.html = +calibrations.html = function html(client) { + var translate = client.translate; + var ret = '

    ' + translate('Calibrations') + '

    ' + '
    ' + '
    ' ; + return ret; +} calibrations.report = function report_calibrations(datastorage,daystoshow) { var Nightscout = window.Nightscout; diff --git a/lib/report_plugins/dailystats.js b/lib/report_plugins/dailystats.js index 6f38c01f6e6..6e16cd25827 100644 --- a/lib/report_plugins/dailystats.js +++ b/lib/report_plugins/dailystats.js @@ -13,10 +13,14 @@ function init() { module.exports = init; -dailystats.html = +dailystats.html = function html(client) { + var translate = client.translate; + var ret = '

    ' + translate('Daily stats report') + '

    ' + '
    ' ; + return ret; +} dailystats.report = function report_dailystats(datastorage,daystoshow,options) { var Nightscout = window.Nightscout; diff --git a/lib/report_plugins/daytoday.js b/lib/report_plugins/daytoday.js index 58839df741d..76ae4b61dea 100644 --- a/lib/report_plugins/daytoday.js +++ b/lib/report_plugins/daytoday.js @@ -13,32 +13,36 @@ function init() { module.exports = init; -daytoday.html = - translate('Display') - + ''+translate('Insulin')+'' - + ''+translate('Carbs')+'' - + ''+translate('Notes') - + ''+translate('Food') - + ''+translate('Raw')+'' - + ''+translate('IOB')+'' - + ''+translate('COB')+'' - + ' '+translate('Size') - + ' ' - + '
    ' - + translate('Scale') - + '' - + translate('Linear') - + '' - + translate('Logarithmic') - + '
    ' - + '
    ' - + '
    ' - ; +daytoday.html = function html(client) { + var translate = client.translate; + var ret = + translate('Display') + ': ' + + ''+translate('Insulin')+'' + + ''+translate('Carbs')+'' + + ''+translate('Notes') + + ''+translate('Food') + + ''+translate('Raw')+'' + + ''+translate('IOB')+'' + + ''+translate('COB')+'' + + ' '+translate('Size') + + ' ' + + '
    ' + + translate('Scale') + + '' + + translate('Linear') + + '' + + translate('Logarithmic') + + '
    ' + + '
    ' + + '
    ' + ; + return ret; +} daytoday.prepareHtml = function daytodayPrepareHtml(daystoshow) { $('#daytodaycharts').empty(); diff --git a/lib/report_plugins/glucosedistribution.js b/lib/report_plugins/glucosedistribution.js index 004cd17b3b4..aef141ceacb 100644 --- a/lib/report_plugins/glucosedistribution.js +++ b/lib/report_plugins/glucosedistribution.js @@ -13,7 +13,9 @@ function init() { module.exports = init; -glucosedistribution.html = +glucosedistribution.html = function html(client) { + var translate = client.translate; + var ret = '

    ' + translate('Glucose distribution') + ' (' @@ -26,6 +28,8 @@ glucosedistribution.html = + '
    ' + translate('* This is only a rough estimation that can be very inaccurate and does not replace actual blood testing. The formula used is taken from: Nathan, David M., et al. "Translating the A1C assay into estimated average glucose values." Diabetes care 31.8 (2008): 1473-1478.') ; + return ret; +} glucosedistribution.report = function report_glucosedistribution(datastorage,daystoshow,options) { var Nightscout = window.Nightscout; diff --git a/lib/report_plugins/hourlystats.js b/lib/report_plugins/hourlystats.js index 6587169e835..c43470b79c2 100644 --- a/lib/report_plugins/hourlystats.js +++ b/lib/report_plugins/hourlystats.js @@ -13,11 +13,15 @@ function init() { module.exports = init; -hourlystats.html = +hourlystats.html = function html(client) { + var translate = client.translate; + var ret = '

    ' + translate('Hourly stats') + '

    ' + '
    ' + '
    ' ; + return ret; +} hourlystats.report = function report_hourlystats(datastorage, daystoshow, options) { var Nightscout = window.Nightscout; diff --git a/lib/report_plugins/index.js b/lib/report_plugins/index.js index 48b9bd08ae0..f8470a4b6be 100644 --- a/lib/report_plugins/index.js +++ b/lib/report_plugins/index.js @@ -38,7 +38,7 @@ function init() { plugins.eachPlugin(function addHtml(p) { // add main plugin html if (p.html && ! $('#' + p.name + '-placeholder').length) { - $('#pluginchartplaceholders').append($('
    ').attr('id',p.name + '-placeholder').addClass('tabplaceholder').css('display','none').append(p.html)); + $('#pluginchartplaceholders').append($('
    ').attr('id',p.name + '-placeholder').addClass('tabplaceholder').css('display','none').append(p.html(client))); } // add menu item if (p.html && ! $('#' + p.name).length) { diff --git a/lib/report_plugins/percentile.js b/lib/report_plugins/percentile.js index cdce8e9c736..715379ddf8f 100644 --- a/lib/report_plugins/percentile.js +++ b/lib/report_plugins/percentile.js @@ -13,12 +13,16 @@ function init() { module.exports = init; -percentile.html = +percentile.html = function html(client) { + var translate = client.translate; + var ret = '

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

    ' + '
    ' + '
    ' + '
    ' ; + return ret; +} percentile.report = function report_percentile(datastorage,daystoshow,options) { var Nightscout = window.Nightscout; diff --git a/lib/report_plugins/success.js b/lib/report_plugins/success.js index c6155eea27e..87990b5ce3d 100644 --- a/lib/report_plugins/success.js +++ b/lib/report_plugins/success.js @@ -13,10 +13,14 @@ function init() { module.exports = init; -success.html = +success.html = function html(client) { + var translate = client.translate; + var ret = '

    ' + translate('Weekly Success') + '

    ' + '
    ' ; + return ret; +} success.report = function report_success(datastorage,daystoshow,options) { var Nightscout = window.Nightscout; diff --git a/lib/report_plugins/treatments.js b/lib/report_plugins/treatments.js index 22516ebdae8..a87e74a0676 100644 --- a/lib/report_plugins/treatments.js +++ b/lib/report_plugins/treatments.js @@ -13,10 +13,14 @@ function init() { module.exports = init; -treatments.html = +treatments.html = function html(client) { + var translate = client.translate; + var ret = '

    ' + translate('Treatments') + '

    ' + '
    ' ; + return ret; +} treatments.report = function report_treatments(datastorage,daystoshow) { From f8079d4175475c0193521750a9c5b3da10bbb622 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Sun, 30 Aug 2015 12:17:29 +0200 Subject: [PATCH 733/937] require not needed anymore --- lib/report_plugins/dailystats.js | 1 - lib/report_plugins/daytoday.js | 1 - lib/report_plugins/glucosedistribution.js | 1 - lib/report_plugins/hourlystats.js | 1 - lib/report_plugins/percentile.js | 1 - lib/report_plugins/success.js | 1 - 6 files changed, 6 deletions(-) diff --git a/lib/report_plugins/dailystats.js b/lib/report_plugins/dailystats.js index 6e16cd25827..ae56fdced00 100644 --- a/lib/report_plugins/dailystats.js +++ b/lib/report_plugins/dailystats.js @@ -1,5 +1,4 @@ 'use strict'; -var translate = require('../language')().translate; var dailystats = { name: 'dailystats' diff --git a/lib/report_plugins/daytoday.js b/lib/report_plugins/daytoday.js index 76ae4b61dea..5d8bc40c5b8 100644 --- a/lib/report_plugins/daytoday.js +++ b/lib/report_plugins/daytoday.js @@ -1,5 +1,4 @@ 'use strict'; -var translate = require('../language')().translate; var daytoday = { name: 'daytoday' diff --git a/lib/report_plugins/glucosedistribution.js b/lib/report_plugins/glucosedistribution.js index aef141ceacb..c674661330a 100644 --- a/lib/report_plugins/glucosedistribution.js +++ b/lib/report_plugins/glucosedistribution.js @@ -1,5 +1,4 @@ 'use strict'; -var translate = require('../language')().translate; var glucosedistribution = { name: 'glucosedistribution' diff --git a/lib/report_plugins/hourlystats.js b/lib/report_plugins/hourlystats.js index c43470b79c2..6e3125c7609 100644 --- a/lib/report_plugins/hourlystats.js +++ b/lib/report_plugins/hourlystats.js @@ -1,5 +1,4 @@ 'use strict'; -var translate = require('../language')().translate; var hourlystats = { name: 'hourlystats' diff --git a/lib/report_plugins/percentile.js b/lib/report_plugins/percentile.js index 715379ddf8f..9658b6ed20b 100644 --- a/lib/report_plugins/percentile.js +++ b/lib/report_plugins/percentile.js @@ -1,5 +1,4 @@ 'use strict'; -var translate = require('../language')().translate; var percentile = { name: 'percentile' diff --git a/lib/report_plugins/success.js b/lib/report_plugins/success.js index 87990b5ce3d..60d259608c3 100644 --- a/lib/report_plugins/success.js +++ b/lib/report_plugins/success.js @@ -1,5 +1,4 @@ 'use strict'; -var translate = require('../language')().translate; var success = { name: 'success' From 549933aacc65322fef6968965c99a0644ae63809 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Sun, 30 Aug 2015 13:56:58 +0200 Subject: [PATCH 734/937] codacy pass 5 --- lib/report_plugins/calibrations.js | 2 +- lib/report_plugins/dailystats.js | 2 +- lib/report_plugins/daytoday.js | 2 +- lib/report_plugins/glucosedistribution.js | 2 +- lib/report_plugins/hourlystats.js | 2 +- lib/report_plugins/percentile.js | 2 +- lib/report_plugins/success.js | 2 +- lib/report_plugins/treatments.js | 6 +++--- lib/treatments.js | 12 ------------ static/report/js/report.js | 5 ++--- 10 files changed, 12 insertions(+), 25 deletions(-) diff --git a/lib/report_plugins/calibrations.js b/lib/report_plugins/calibrations.js index b283f59b3e3..cb57903cbf5 100644 --- a/lib/report_plugins/calibrations.js +++ b/lib/report_plugins/calibrations.js @@ -21,7 +21,7 @@ calibrations.html = function html(client) { + '
    ' ; return ret; -} +}; calibrations.report = function report_calibrations(datastorage,daystoshow) { var Nightscout = window.Nightscout; diff --git a/lib/report_plugins/dailystats.js b/lib/report_plugins/dailystats.js index ae56fdced00..838fed41692 100644 --- a/lib/report_plugins/dailystats.js +++ b/lib/report_plugins/dailystats.js @@ -19,7 +19,7 @@ dailystats.html = function html(client) { + '
    ' ; return ret; -} +}; dailystats.report = function report_dailystats(datastorage,daystoshow,options) { var Nightscout = window.Nightscout; diff --git a/lib/report_plugins/daytoday.js b/lib/report_plugins/daytoday.js index 5d8bc40c5b8..91f30659919 100644 --- a/lib/report_plugins/daytoday.js +++ b/lib/report_plugins/daytoday.js @@ -41,7 +41,7 @@ daytoday.html = function html(client) { + '
    ' ; return ret; -} +}; daytoday.prepareHtml = function daytodayPrepareHtml(daystoshow) { $('#daytodaycharts').empty(); diff --git a/lib/report_plugins/glucosedistribution.js b/lib/report_plugins/glucosedistribution.js index c674661330a..68c6bd173d7 100644 --- a/lib/report_plugins/glucosedistribution.js +++ b/lib/report_plugins/glucosedistribution.js @@ -28,7 +28,7 @@ glucosedistribution.html = function html(client) { + translate('* This is only a rough estimation that can be very inaccurate and does not replace actual blood testing. The formula used is taken from: Nathan, David M., et al. "Translating the A1C assay into estimated average glucose values." Diabetes care 31.8 (2008): 1473-1478.') ; return ret; -} +}; glucosedistribution.report = function report_glucosedistribution(datastorage,daystoshow,options) { var Nightscout = window.Nightscout; diff --git a/lib/report_plugins/hourlystats.js b/lib/report_plugins/hourlystats.js index 6e3125c7609..353a0ba4ec9 100644 --- a/lib/report_plugins/hourlystats.js +++ b/lib/report_plugins/hourlystats.js @@ -20,7 +20,7 @@ hourlystats.html = function html(client) { + '
    ' ; return ret; -} +}; hourlystats.report = function report_hourlystats(datastorage, daystoshow, options) { var Nightscout = window.Nightscout; diff --git a/lib/report_plugins/percentile.js b/lib/report_plugins/percentile.js index 9658b6ed20b..fa52cefcffc 100644 --- a/lib/report_plugins/percentile.js +++ b/lib/report_plugins/percentile.js @@ -21,7 +21,7 @@ percentile.html = function html(client) { + '
    ' ; return ret; -} +}; percentile.report = function report_percentile(datastorage,daystoshow,options) { var Nightscout = window.Nightscout; diff --git a/lib/report_plugins/success.js b/lib/report_plugins/success.js index 60d259608c3..bdd079600bc 100644 --- a/lib/report_plugins/success.js +++ b/lib/report_plugins/success.js @@ -19,7 +19,7 @@ success.html = function html(client) { + '
    ' ; return ret; -} +}; success.report = function report_success(datastorage,daystoshow,options) { var Nightscout = window.Nightscout; diff --git a/lib/report_plugins/treatments.js b/lib/report_plugins/treatments.js index a87e74a0676..54c6910f97c 100644 --- a/lib/report_plugins/treatments.js +++ b/lib/report_plugins/treatments.js @@ -20,7 +20,7 @@ treatments.html = function html(client) { + '
    ' ; return ret; -} +}; treatments.report = function report_treatments(datastorage,daystoshow) { @@ -90,14 +90,14 @@ treatments.report = function report_treatments(datastorage,daystoshow) { data.notes = $('#rp_adnotes').val(); data.enteredBy = $('#rp_enteredBy').val(); data.created_at = Nightscout.utils.mergeInputTime($('#rp_eventTimeValue').val(), $('#rp_eventDateValue').val()); - $( this ).dialog( "close" ); + $( this ).dialog('close'); saveTreatmentRecord(data); delete datastorage[day]; show(); } }, { text: translate('Cancel'), - click: function () { $( this ).dialog( 'close' ); } + click: function () { $( this ).dialog('close'); } } ] , open : function() { diff --git a/lib/treatments.js b/lib/treatments.js index ed8eb77ac01..000645ad361 100644 --- a/lib/treatments.js +++ b/lib/treatments.js @@ -5,18 +5,6 @@ var find_options = require('./query'); function storage (env, ctx) { var ObjectID = require('mongodb').ObjectID; - // allow regexp search - // /api/v1/treatments.json?find[notes]=/sometext/i - function find_query (opts) { - var reg; - ['notes','eventType'].forEach(function(d) { - if (opts && opts.find && opts.find[d] && (reg=/\/(.*)\/(.*)/.exec(opts.find[d]))) { - opts.find[d] = new RegExp(reg[1],reg[2]); - } - }); - return opts; - } - function create (obj, fn) { var results = prepareData(obj); diff --git a/static/report/js/report.js b/static/report/js/report.js index fa24934dd61..15399ad8493 100644 --- a/static/report/js/report.js +++ b/static/report/js/report.js @@ -17,7 +17,6 @@ 'use strict'; //for the tests window isn't the global object var $ = window.$; - var _ = window._; var moment = window.moment; var Nightscout = window.Nightscout; var client = Nightscout.client; @@ -538,11 +537,11 @@ if (cal) { temp1 = data.sgv.map(function (entry) { var rawBg = rawIsigToRawBg(entry, cal); - return { mills: entry.mills, date: new Date(entry.mills - 2 * 1000), y: rawBg, sgv: client.utils.scaleMgdl(rawBg), color: 'gray', type: 'rawbg', filtered: entry.filtered, unfiltered: entry.unfiltered } + return { mills: entry.mills, date: new Date(entry.mills - 2 * 1000), y: rawBg, sgv: client.utils.scaleMgdl(rawBg), color: 'gray', type: 'rawbg', filtered: entry.filtered, unfiltered: entry.unfiltered }; }).filter(function(entry) { return entry.y > 0}); } var temp2 = data.sgv.map(function (obj) { - return { mills: obj.mills, date: new Date(obj.mills), y: obj.y, sgv: client.utils.scaleMgdl(obj.y), color: sgvToColor(client.utils.scaleMgdl(obj.y),options), type: 'sgv', noise: obj.noise, filtered: obj.filtered, unfiltered: obj.unfiltered} + return { mills: obj.mills, date: new Date(obj.mills), y: obj.y, sgv: client.utils.scaleMgdl(obj.y), color: sgvToColor(client.utils.scaleMgdl(obj.y),options), type: 'sgv', noise: obj.noise, filtered: obj.filtered, unfiltered: obj.unfiltered}; }); data.sgv = [].concat(temp1, temp2); From 89f184a70aa91de7ca12530d5e5c56d8af29b9b8 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Sun, 30 Aug 2015 14:24:36 +0200 Subject: [PATCH 735/937] gui improvements --- lib/report_plugins/daytoday.js | 2 +- static/report/index.html | 5 +- static/report/js/report.js | 116 +++++++++++++++++---------------- 3 files changed, 64 insertions(+), 59 deletions(-) diff --git a/lib/report_plugins/daytoday.js b/lib/report_plugins/daytoday.js index 91f30659919..214c85436c0 100644 --- a/lib/report_plugins/daytoday.js +++ b/lib/report_plugins/daytoday.js @@ -31,7 +31,7 @@ daytoday.html = function html(client) { + ' ' + '' + '
    ' - + translate('Scale') + + translate('Scale') + ': ' + '' + translate('Linear') + '' diff --git a/static/report/index.html b/static/report/index.html index eecabd778e8..bc34df627df 100644 --- a/static/report/index.html +++ b/static/report/index.html @@ -33,15 +33,14 @@

    Nightscout reporting

    - + Food: - - + diff --git a/static/report/js/report.js b/static/report/js/report.js index 15399ad8493..be99d5ad7ed 100644 --- a/static/report/js/report.js +++ b/static/report/js/report.js @@ -1,7 +1,5 @@ // TODO: // - bypass nightmode in reports -// - optimize .done() on food load -// - hiding food html // - make axis on daytoday better working with thresholds // - get rid of /static/report/js/time.js // - load css dynamic + optimize @@ -9,7 +7,6 @@ // - check everything is translated // - add tests // - optimize merging data inside every plugin -// - auto check checkbox to enable filter when data changed // - Insuling Change vs Insulin Cartridge Change in translations @@ -45,6 +42,8 @@ }; var ONE_MIN_IN_MS = 60000; + + prepareGUI(); // ****** FOOD CODE START ****** var food_categories = []; @@ -106,9 +105,68 @@ return maybePreventDefault(event); } + $('#info').html(''+translate('Loading food database')+' ...'); + $.ajax('/api/v1/food/regular.json', { + success: function foodLoadSuccess(records) { + records.forEach(function (r) { + food_list.push(r); + if (r.category && !food_categories[r.category]) { food_categories[r.category] = {}; } + if (r.category && r.subcategory) { food_categories[r.category][r.subcategory] = true; } + }); + fillFoodForm(); + } + }).done(function() { + if (food_list.length) { + enableFoodGUI(); + } else { + disableFoodGUI(); + } + }).fail(function() { + disableFoodGUI(); + }); + + function enableFoodGUI( ) { + $('#info').html(''); + + $('#rp_foodgui').css('display',''); + $('#rp_food').change(function (event) { + $('#rp_enablefood').prop('checked',true); + return maybePreventDefault(event); + }); + } + + function disableFoodGUI(){ + $('#info').html(''); + $('#rp_foodgui').css('display','none'); + } + // ****** FOOD CODE END ****** + function prepareGUI() { + $('.presetdates').click(function(event) { + var days = $(this).attr('days'); + $('#rp_enabledate').prop('checked',true); + return setDataRange(event,days); + }); + $('#rp_show').click(show); + $('#rp_notes').bind('input', function (event) { + $('#rp_enablenotes').prop('checked',true); + return maybePreventDefault(event); + }); + $('#rp_eventtype').bind('input', function (event) { + $('#rp_enableeventtype').prop('checked',true); + return maybePreventDefault(event); + }); + + $('#rp_targetlow').val(targetBGdefault[client.settings.units].low); + $('#rp_targethigh').val(targetBGdefault[client.settings.units].high); + + $('.menutab').click(switchreport_handler); + + setDataRange(null,7); + } + function rawIsigToRawBg(entry, cal) { var raw = 0 , unfiltered = parseInt(entry.unfiltered) || 0 @@ -141,58 +199,6 @@ return color; } - $('#info').html(''+translate('Loading food database')+' ...'); - $.ajax('/api/v1/food/regular.json', { - success: function foodLoadSuccess(records) { - records.forEach(function (r) { - food_list.push(r); - if (r.category && !food_categories[r.category]) { food_categories[r.category] = {}; } - if (r.category && r.subcategory) { food_categories[r.category][r.subcategory] = true; } - }); - fillFoodForm(); - } - }).done(function() { - $('#info').html(''); - $('.presetdates').click(function(e) { var days = $(this).attr('days'); setDataRange(e,days); }); - - $('#rp_show').click(show); - $('#rp_food').change(function (event) { - $('#rp_enablefood').prop('checked',true); - return maybePreventDefault(event); - }); - $('#rp_notes').change(function (event) { - $('#rp_enablenotes').prop('checked',true); - return maybePreventDefault(event); - }); - - $('#rp_targetlow').val(targetBGdefault[client.settings.units].low); - $('#rp_targethigh').val(targetBGdefault[client.settings.units].high); - - $('.menutab').click(switchreport_handler); - - setDataRange(null,7); - }).fail(function() { - $('#info').html(''); - $('.presetdates').click(function(e) { var days = $(this).attr('days'); setDataRange(e,days); }); - - $('#rp_show').click(show); - $('#rp_food').change(function (event) { - $('#rp_enablefood').prop('checked',true); - return maybePreventDefault(event); - }); - $('#rp_notes').change(function (event) { - $('#rp_enablenotes').prop('checked',true); - return maybePreventDefault(event); - }); - - $('#rp_targetlow').val(targetBGdefault[client.settings.units].low); - $('#rp_targethigh').val(targetBGdefault[client.settings.units].high); - - $('.menutab').click(switchreport_handler); - - setDataRange(null,7); - }); - function show(event) { var options = { width: 1000 From e1c5977460790c569a80dff08a05b2c7876c3300 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Sun, 30 Aug 2015 14:47:24 +0200 Subject: [PATCH 736/937] make careportal events dynamic --- lib/client/careportal.js | 26 ++++++++++++++++++++++++++ static/index.html | 17 +---------------- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/lib/client/careportal.js b/lib/client/careportal.js index 9c055d83e84..872f0ead697 100644 --- a/lib/client/careportal.js +++ b/lib/client/careportal.js @@ -8,6 +8,23 @@ function init (client, $) { var translate = client.translate; var storage = $.localStorage; + careportal.events = [ + { val: "BG Check", name: 'BG Check' } + , { val: "Snack Bolus", name: 'Snack Bolus' } + , { val: "Meal Bolus", name: 'Meal Bolus' } + , { val: "Correction Bolus", name: 'Correction Bolus' } + , { val: "Carb Correction", name: 'Carb Correction' } + , { val: "Announcement", name: 'Announcement' } + , { val: "Note", name: 'Note' } + , { val: "Question", name: 'Question' } + , { val: "Exercise", name: 'Exercise' } + , { val: "Site Change", name: 'Pump Site Change' } + , { val: "Sensor Start", name: 'Dexcom Sensor Start' } + , { val: "Sensor Change", name: 'Dexcom Sensor Change' } + , { val: "Insulin Change", name: 'Insulin Cartridge Change' } + , { val: "D.A.D. Alert", name: 'D.A.D. Alert' } + ]; + var eventTime = $('#eventTimeValue'); var eventDate = $('#eventDateValue'); @@ -32,7 +49,16 @@ function init (client, $) { } } + careportal.prepareEvents = function prepareEvents ( ) { + $('#eventType').empty(); + _.each(careportal.events, function eachEvent(event) { + $('#eventType').append(''); + }); + + }; + careportal.prepare = function prepare ( ) { + careportal.prepareEvents(); $('#eventType').val('BG Check'); $('#glucoseValue').val('').attr('placeholder', translate('Value in') + ' ' + client.settings.units); $('#meter').prop('checked', true); diff --git a/static/index.html b/static/index.html index 4574917292d..ab6d97b6c89 100644 --- a/static/index.html +++ b/static/index.html @@ -176,22 +176,7 @@ Log a Treatment
    From db784e520b18ca993b9e456e78164570937561c4 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Sun, 30 Aug 2015 15:22:27 +0200 Subject: [PATCH 737/937] careportal test fix --- lib/client/careportal.js | 1 + tests/careportal.test.js | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/client/careportal.js b/lib/client/careportal.js index 872f0ead697..a9522c1a9c0 100644 --- a/lib/client/careportal.js +++ b/lib/client/careportal.js @@ -1,6 +1,7 @@ 'use strict'; var moment = require('moment-timezone'); +var _ = require('lodash'); function init (client, $) { var careportal = { }; diff --git a/tests/careportal.test.js b/tests/careportal.test.js index 2f877503d68..e71251ae9da 100644 --- a/tests/careportal.test.js +++ b/tests/careportal.test.js @@ -72,6 +72,7 @@ describe('client', function ( ) { client.init(serverSettings, plugins); client.dataUpdate(nowData); + client.careportal.prepareEvents(); client.careportal.toggleDrawer(); $('#eventType').val('Snack Bolus'); From 69c01214a7f45770cbf3f1f83cff32c5a64fb6b5 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Sun, 30 Aug 2015 15:43:20 +0200 Subject: [PATCH 738/937] careportal codacy fix --- lib/client/careportal.js | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/client/careportal.js b/lib/client/careportal.js index a9522c1a9c0..3ac738dd3f0 100644 --- a/lib/client/careportal.js +++ b/lib/client/careportal.js @@ -10,20 +10,20 @@ function init (client, $) { var storage = $.localStorage; careportal.events = [ - { val: "BG Check", name: 'BG Check' } - , { val: "Snack Bolus", name: 'Snack Bolus' } - , { val: "Meal Bolus", name: 'Meal Bolus' } - , { val: "Correction Bolus", name: 'Correction Bolus' } - , { val: "Carb Correction", name: 'Carb Correction' } - , { val: "Announcement", name: 'Announcement' } - , { val: "Note", name: 'Note' } - , { val: "Question", name: 'Question' } - , { val: "Exercise", name: 'Exercise' } - , { val: "Site Change", name: 'Pump Site Change' } - , { val: "Sensor Start", name: 'Dexcom Sensor Start' } - , { val: "Sensor Change", name: 'Dexcom Sensor Change' } - , { val: "Insulin Change", name: 'Insulin Cartridge Change' } - , { val: "D.A.D. Alert", name: 'D.A.D. Alert' } + { val: 'BG Check', name: 'BG Check' } + , { val: 'Snack Bolus', name: 'Snack Bolus' } + , { val: 'Meal Bolus', name: 'Meal Bolus' } + , { val: 'Correction Bolus', name: 'Correction Bolus' } + , { val: 'Carb Correction', name: 'Carb Correction' } + , { val: 'Announcement', name: 'Announcement' } + , { val: 'Note', name: 'Note' } + , { val: 'Question', name: 'Question' } + , { val: 'Exercise', name: 'Exercise' } + , { val: 'Site Change', name: 'Pump Site Change' } + , { val: 'Sensor Start', name: 'Dexcom Sensor Start' } + , { val: 'Sensor Change', name: 'Dexcom Sensor Change' } + , { val: 'Insulin Change', name: 'Insulin Cartridge Change' } + , { val: 'D.A.D. Alert', name: 'D.A.D. Alert' } ]; var eventTime = $('#eventTimeValue'); From 3ac1c71c773f1c3fd9774357ddd96a634df0dc85 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Sun, 30 Aug 2015 16:01:53 +0200 Subject: [PATCH 739/937] reuse careportal's event list in reports --- lib/client/careportal.js | 1 - lib/language.js | 17 +++-------------- static/report/index.html | 2 +- static/report/js/report.js | 8 ++++++++ 4 files changed, 12 insertions(+), 16 deletions(-) diff --git a/lib/client/careportal.js b/lib/client/careportal.js index 3ac738dd3f0..fa766497590 100644 --- a/lib/client/careportal.js +++ b/lib/client/careportal.js @@ -55,7 +55,6 @@ function init (client, $) { _.each(careportal.events, function eachEvent(event) { $('#eventType').append(''); }); - }; careportal.prepare = function prepare ( ) { diff --git a/lib/language.js b/lib/language.js index 4fc95d7cbd7..0c348155a85 100644 --- a/lib/language.js +++ b/lib/language.js @@ -473,20 +473,6 @@ function init() { ,dk: 'Noter indeholder' ,nb: 'Notater inneholder' } - ,'Event type contains' : { - cs: 'Typ události obsahuje' - ,de: 'Ereignis-Typ beinhaltet' - ,es: 'Contenido del tipo de evento' - ,fr: 'Type d\'événement contient' - ,pt: 'Tipo de evento contém' - ,ro: 'Conținut tip de eveniment' - ,bg: 'Типа събитие включва' - ,hr: 'Sadržaj vrste događaja' - ,sv: 'Händelsen innehåller' - ,it: 'Contiene evento' - ,dk: 'Hændelsen indeholder' - ,nb: 'Hendelsen inneholder' - } ,'Target bg range bottom' : { cs: 'Cílová glykémie spodní' ,de: 'Untergrenze des Blutzuckerzielbereiches' @@ -3659,6 +3645,9 @@ function init() { ,'Update' : { // Update button cs: 'Aktualizovat' } + ,'All sensor events' : { // 'Sensor Start' + 'Sensor Change' events + cs: 'Všechny události senzoru' + } }; diff --git a/static/report/index.html b/static/report/index.html index bc34df627df..111092ed4e7 100644 --- a/static/report/index.html +++ b/static/report/index.html @@ -51,7 +51,7 @@

    Nightscout reporting

    - Event type contains: + Event Type: diff --git a/static/report/js/report.js b/static/report/js/report.js index be99d5ad7ed..cee00c5ab11 100644 --- a/static/report/js/report.js +++ b/static/report/js/report.js @@ -159,6 +159,13 @@ return maybePreventDefault(event); }); + // fill careportal events + $('#rp_eventtype').empty(); + _.each(client.careportal.events, function eachEvent(event) { + $('#rp_eventtype').append(''); + }); + $('#rp_eventtype').append(''); + $('#rp_targetlow').val(targetBGdefault[client.settings.units].low); $('#rp_targethigh').val(targetBGdefault[client.settings.units].high); @@ -393,6 +400,7 @@ } if (displayeddays===0) { $('#info').html(''+translate('Result is empty')+''); + $('#rp_show').css('display',''); } } From 907f22db8e9fcaa750e749c24448e8e234e2785d Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Sun, 30 Aug 2015 16:50:23 +0200 Subject: [PATCH 740/937] fixed bug in processing request response --- lib/report_plugins/daytoday.js | 6 +++--- static/report/js/report.js | 23 ++++++++++++++--------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/lib/report_plugins/daytoday.js b/lib/report_plugins/daytoday.js index 214c85436c0..797e141c7a0 100644 --- a/lib/report_plugins/daytoday.js +++ b/lib/report_plugins/daytoday.js @@ -44,7 +44,7 @@ daytoday.html = function html(client) { }; daytoday.prepareHtml = function daytodayPrepareHtml(daystoshow) { - $('#daytodaycharts').empty(); + $('#daytodaycharts').html(''); for (var d in daystoshow) { $('#daytodaycharts').append($('
    ')); } @@ -140,8 +140,8 @@ daytoday.report = function report_daytoday(datastorage,daystoshow,options) { ]; } } - - // create svg and g to contain the chart contents + + // create svg and g to contain the chart contents charts = d3.select('#daytodaychart-' + day).html( ''+ report_plugins.utils.localeDate(day)+ diff --git a/static/report/js/report.js b/static/report/js/report.js index cee00c5ab11..554431576c6 100644 --- a/static/report/js/report.js +++ b/static/report/js/report.js @@ -8,6 +8,7 @@ // - add tests // - optimize merging data inside every plugin // - Insuling Change vs Insulin Cartridge Change in translations +// - pressing Show 2nd time generates d3 errors, previous graphs are not removed (function () { @@ -222,6 +223,9 @@ , scale: report_plugins.consts.SCALE_LINEAR , units: client.settings.units }; + + // default time range if no time range specified in GUI + var timerange = '&find[created_at][$gte]='+new Date('1970-01-01').toISOString(); options.targetLow = parseFloat($('#rp_targetlow').val().replace(',','.')); options.targetHigh = parseFloat($('#rp_targethigh').val().replace(',','.')); @@ -244,7 +248,7 @@ matchesneeded++; var from = moment($('#rp_from').val()); var to = moment($('#rp_to').val()); - + timerange = '&find[created_at][$gte]='+new Date(from).toISOString()+'&find[created_at][$lt]='+new Date(to).toISOString(); while (from <= to) { if (daystoshow[from.format('YYYY-MM-DD')]) { daystoshow[from.format('YYYY-MM-DD')]++; @@ -265,11 +269,11 @@ var _id = $('#rp_food').val(); if (_id) { var treatmentData; - var tquery = '?find[boluscalc.foods._id]='+_id; + var tquery = '?find[boluscalc.foods._id]=' + _id + timerange; $.ajax('/api/v1/treatments.json'+tquery, { success: function (xhr) { treatmentData = xhr.map(function (treatment) { - return moment(treatment.mills).format('YYYY-MM-DD'); + return moment(treatment.created_at).format('YYYY-MM-DD'); }); // unique it treatmentData = $.grep(treatmentData, function(v, k){ @@ -301,11 +305,11 @@ var notes = $('#rp_notes').val(); if (notes) { var treatmentData; - var tquery = '?find[notes]=/'+notes+'/i'; - $.ajax('/api/v1/treatments.json'+tquery, { + var tquery = '?find[notes]=/' + notes + '/i'; + $.ajax('/api/v1/treatments.json' + tquery + timerange, { success: function (xhr) { treatmentData = xhr.map(function (treatment) { - return moment(treatment.mills).format('YYYY-MM-DD'); + return moment(treatment.created_at).format('YYYY-MM-DD'); }); // unique it treatmentData = $.grep(treatmentData, function(v, k){ @@ -337,11 +341,11 @@ var eventtype = $('#rp_eventtype').val(); if (eventtype) { var treatmentData; - var tquery = '?find[eventType]=/'+eventtype+'/i'; - $.ajax('/api/v1/treatments.json'+tquery, { + var tquery = '?find[eventType]=/' + eventtype + '/i'; + $.ajax('/api/v1/treatments.json' + tquery + timerange, { success: function (xhr) { treatmentData = xhr.map(function (treatment) { - return moment(treatment.mills).format('YYYY-MM-DD'); + return moment(treatment.created_at).format('YYYY-MM-DD'); }); // unique it treatmentData = $.grep(treatmentData, function(v, k){ @@ -406,6 +410,7 @@ $('#rp_show').css('display','none'); daystoshow = {}; + datefilter(); return maybePreventDefault(event); } From 555fdca8120b55a79c1082c7ec51194bd4169303 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Sun, 30 Aug 2015 17:21:00 +0200 Subject: [PATCH 741/937] resolved translation issue between eventType value and text in dropdownbox --- lib/report_plugins/treatments.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/report_plugins/treatments.js b/lib/report_plugins/treatments.js index 54c6910f97c..36dadd02d01 100644 --- a/lib/report_plugins/treatments.js +++ b/lib/report_plugins/treatments.js @@ -131,8 +131,7 @@ treatments.report = function report_treatments(datastorage,daystoshow) { alert(translate('Your device is not authenticated yet')); return false; } - - + var dataJson = JSON.stringify(data, null, ' '); var xhr = new XMLHttpRequest(); @@ -148,6 +147,15 @@ treatments.report = function report_treatments(datastorage,daystoshow) { return true; } + function resolveEventName(value) { + _.each(Nightscout.client.careportal.events, function eachEvent(e) { + if (e.val === value) { + value = e.name; + } + }); + return value; + } + var Nightscout = window.Nightscout; var client = Nightscout.client; var translate = client.translate; @@ -171,7 +179,7 @@ treatments.report = function report_treatments(datastorage,daystoshow) { table += ''; table += ''; table += ''+(new Date(tr.created_at).toLocaleTimeString().replace(/([\d]+:[\d]{2})(:[\d]{2})(.*)/, '$1$3'))+''; - table += ''+(tr.eventType ? translate(tr.eventType) : '')+''; + table += ''+(tr.eventType ? translate(resolveEventName(tr.eventType)) : '')+''; table += ''+(tr.glucose ? tr.glucose + ' ('+translate(tr.glucoseType)+')' : '')+''; table += ''+(tr.insulin ? tr.insulin : '')+''; table += ''+(tr.carbs ? tr.carbs : '')+''; From d995465cabc65a5aacb83b82d6103c7c385c933e Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Sun, 30 Aug 2015 17:22:29 +0200 Subject: [PATCH 742/937] ... and removed from todo --- static/report/js/report.js | 1 - 1 file changed, 1 deletion(-) diff --git a/static/report/js/report.js b/static/report/js/report.js index 554431576c6..7b018866684 100644 --- a/static/report/js/report.js +++ b/static/report/js/report.js @@ -7,7 +7,6 @@ // - check everything is translated // - add tests // - optimize merging data inside every plugin -// - Insuling Change vs Insulin Cartridge Change in translations // - pressing Show 2nd time generates d3 errors, previous graphs are not removed From a765c042ee2292be72ba3b53f2ecb67a2c5ea342 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Sun, 30 Aug 2015 18:24:02 +0200 Subject: [PATCH 743/937] updated fontello icons --- static/glyphs/config.json | 24 +++++++ static/glyphs/css/fontello-codes.css | 6 +- static/glyphs/css/fontello-embedded.css | 18 +++-- static/glyphs/css/fontello-ie7-codes.css | 6 +- static/glyphs/css/fontello-ie7.css | 6 +- static/glyphs/css/fontello.css | 24 ++++--- static/glyphs/demo.html | 82 ++++++++++++++++++----- static/glyphs/font/fontello.eot | Bin 7304 -> 8364 bytes static/glyphs/font/fontello.svg | 4 ++ static/glyphs/font/fontello.ttf | Bin 7136 -> 8196 bytes static/glyphs/font/fontello.woff | Bin 4064 -> 4780 bytes 11 files changed, 137 insertions(+), 33 deletions(-) diff --git a/static/glyphs/config.json b/static/glyphs/config.json index e72e5aff933..d8524e5c26b 100644 --- a/static/glyphs/config.json +++ b/static/glyphs/config.json @@ -18,6 +18,24 @@ "code": 59394, "src": "fontawesome" }, + { + "uid": "83458acd9f38d03ec0226ce82a83c0f4", + "css": "tint", + "code": 59406, + "src": "fontawesome" + }, + { + "uid": "ea2d9a8c51ca42b38ef0d2a07f16d9a7", + "css": "chart-line", + "code": 59407, + "src": "fontawesome" + }, + { + "uid": "1ee2aeb352153a403df4b441a8bc9bda", + "css": "calc", + "code": 59405, + "src": "fontawesome" + }, { "uid": "c759418c008e9562944080fee617fc76", "css": "cancel-circled", @@ -60,6 +78,12 @@ "code": 59400, "src": "typicons" }, + { + "uid": "2d220a22480861b15878d1632e31ab47", + "css": "sort-numeric", + "code": 59408, + "src": "typicons" + }, { "uid": "3e02a8849305ac80a0e36302f461f265", "css": "help-circled", diff --git a/static/glyphs/css/fontello-codes.css b/static/glyphs/css/fontello-codes.css index d06e8a87fa7..014c5ddd4fd 100644 --- a/static/glyphs/css/fontello-codes.css +++ b/static/glyphs/css/fontello-codes.css @@ -11,4 +11,8 @@ .icon-cancel-circled:before { content: '\e809'; } /* '' */ .icon-volume:before { content: '\e80a'; } /* '' */ .icon-plus:before { content: '\e80b'; } /* '' */ -.icon-hourglass:before { content: '\e80c'; } /* '' */ \ No newline at end of file +.icon-hourglass:before { content: '\e80c'; } /* '' */ +.icon-calc:before { content: '\e80d'; } /* '' */ +.icon-tint:before { content: '\e80e'; } /* '' */ +.icon-chart-line:before { content: '\e80f'; } /* '' */ +.icon-sort-numeric:before { content: '\e810'; } /* '' */ \ No newline at end of file diff --git a/static/glyphs/css/fontello-embedded.css b/static/glyphs/css/fontello-embedded.css index 2f21144d71d..a41d4de3298 100644 --- a/static/glyphs/css/fontello-embedded.css +++ b/static/glyphs/css/fontello-embedded.css @@ -1,15 +1,15 @@ @font-face { font-family: 'fontello'; - src: url('../font/fontello.eot?94033511'); - src: url('../font/fontello.eot?94033511#iefix') format('embedded-opentype'), - url('../font/fontello.svg?94033511#fontello') format('svg'); + src: url('../font/fontello.eot?87658047'); + src: url('../font/fontello.eot?87658047#iefix') format('embedded-opentype'), + url('../font/fontello.svg?87658047#fontello') format('svg'); font-weight: normal; font-style: normal; } @font-face { font-family: 'fontello'; - src: url('data:application/octet-stream;base64,d09GRgABAAAAAA/gAA4AAAAAG+AAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABPUy8yAAABRAAAAEQAAABWPilJGWNtYXAAAAGIAAAAOgAAAUrQHRm3Y3Z0IAAAAcQAAAAKAAAACgAAAABmcGdtAAAB0AAABZQAAAtwiJCQWWdhc3AAAAdkAAAACAAAAAgAAAAQZ2x5ZgAAB2wAAAVGAAAI4MiMbSloZWFkAAAMtAAAADUAAAA2BVrhJmhoZWEAAAzsAAAAHgAAACQHlwNfaG10eAAADQwAAAAlAAAAODDnAABsb2NhAAANNAAAAB4AAAAeES4Odm1heHAAAA1UAAAAIAAAACAAqgvlbmFtZQAADXQAAAF3AAACzcydGx1wb3N0AAAO7AAAAIkAAADKSDwwcXByZXAAAA94AAAAZQAAAHvdawOFeJxjYGSuY5zAwMrAwVTFtIeBgaEHQjM+YDBkZGJgYGJgZWbACgLSXFMYHF4wvOBhDvqfxRDFHMQwHSjMCJIDAOtVC8B4nGNgYGBmgGAZBkYGEHAB8hjBfBYGDSDNBqQZGZgYGF7w/P8PUvCCAURLMELVAwEjG8OIBwBxIwa6AAAAAAAAAAAAAAAAAAB4nK1WaXMTRxCd1WHLNj6CDxI2gVnGcox2VpjLCBDG7EoW4BzylexCjl1Ldu6LT/wG/ZpekVSRb/y0vB4d2GAnVVQoSv2m9+1M9+ueXpPQksReWI+k3HwpprY2aWTnSUg3bFqO4kPZ2QspU0z+LoiCaLXUvu04JCISgap1hSWC2PfI0iTjQ48yWrYlvWpSbulJd9kaD+qt+vbT0FGO3QklNZuhQ+uRLanCqBJFMu2RkjYtw9VfSVrh5yvMfNUMJYLoJJLGm2EMj+Rn44xWGa3GdhxFkU2WG0WKRDM8iCKPslpin1wxQUD5oBlSXvk0onyEH5EVe5TTCnHJdprf9yU/6R3OvyTieouyJQf+QHZkB3unK/ki0toK46adbEehivB0fSfEI5uT6p/sUV7TaOB2RaYnzQiWyleQWPkJZfYPyWrhfMqXPBrVkoOcCFovc2Jf8g60HkdMiWsmyILujk6IoO6XnKHYY/q4+OO9XSwXIQTIOJb1jkq4EEYpYbOaJG0EOYiSskWV1HpHTJzyOi3iLWG/Tu3oS2e0Sag7MZ6th46tnKjkeDSp00ymTu2k5tGUBlFKOhM85tcBlB/RJK+2sZrEyqNpbDNjJJFQoIVzaSqIZSeWNAXRPJrRm7thmmvXokWaPFDPPXpPb26Fmzs9p+3AP2v8Z3UqpoO9MJ2eDshKfJp2uUnRun56hn8m8UPWAiqRLTbDlMVDtn4H5eVjS47CawNs957zK+h99kTIpIH4G/AeL9UpBUyFmFVQC9201rUsy9RqVotUZOq7IU0rX9ZpAk05Dn1jX8Y4/q+ZGUtMCd/vxOnZEZeeufYlyDSH3GZdj+Z1arFdgM5sz+k0y/Z9nebYfqDTPNvzOh1ha+t0lO2HOi2w/UinY2wvaEGT7jsEchGBXMAGEoGwdRAI20sIhK1CIGwXEQjbIgJhu4RA2H6MQNguIxC2l7Wsmn4qaRw7E8sARYgDoznuyGVuKldTyaUSrotGpzbkKXKrpKJ4Vv0rA/3ikTesgbVAukTW/IpJrnxUleOPrmh508S5Ao5Vf3tzXJ8TD2W/WPhT8L/amqqkV6x5ZHIVeSPQk+NE1yYVj67p8rmqR9f/i4oOa4F+A6UQC0VZlg2+mZDwUafTUA1c5RAzGzMP1/W6Zc3P4fybGCEL6H78NxQaC9yDTllJWe1gr9XXj2W5twflsCdYkmK+zOtb4YuMzEr7RWYpez7yecAVMCqVYasNXK3gzXsS85DpTfJMELcVZYOkjceZILGBYx4wb76TICRMXbWB2imcsIG8YMwp2O+EQ1RvlOVwe6F9Ho2Uf2tX7MgZFU0Q+G32Rtjrs1DyW6yBhCe/1NdAVSFNxbipgEsj5YZq8GFcrdtGMk6gr6jYDcuyig8fR9x3So5lIPlIEatHRz+tvUKd1Ln9yihu3zv9CIJBaWL+9r6Z4qCUd7WSZVZtA1O3GpVT15rDxasO3c2j7nvH2Sdy1jTddE/c9L6mVbeDg7lZEO3bHJSlTC6o68MOG6jLzaXQ6mVckt52DzAsMKDfoRUb/1f3cfg8V6oKo+NIvZ2oH6PPYgzyDzh/R/UF6OcxTLmGlOd7lxOfbtzD2TJdxV2sn+LfwKy15mbpGnBD0w2Yh6xaHbrKDXynBjo90tyO9BDwse4K8QBgE8Bi8InuWsbzKYDxfMYcH+Bz5jBoMofBFnMYbDNnDWCHOQx2mcNgjzkMvmDOOsCXzGEQModBxBwGT5gTADxlDoOvmMPga+Yw+IY59wG+ZQ6DmDkMEuYw2Nd0ayhzixd0F6htUBXowPQTFvewONRUGbK/44Vhf28Qs38wiKk/aro9pP7EC0P92SCm/mIQU3/VdGdI/Y0Xhvq7QUz9wyCmPtMvxnKZwV9GvkuFA8ouNp/z98T7B8IaQLYAAQAB//8AD3icrVVNbBtFFJ43uztr7I1jJ+vd/DgbZ92si+04qb3eDeC4SVraJoSgQhoFa5WmaX7aQBGHNgm0qipUCXGgLRISqQVtiMoFVZEAIQQHqBqhckCqKiGBSMQR9cAR5dQ4vHXa0qIIlFLbevvmW8/M99588x7h19fX3+E+5TIkSJrILtJHKjq9z+3p2qEpFTzfkgiaeaqmNern9BS1ZY3mOdtMge4HW7XSIZlVAv780ACZrGnZedgJrrVNI6pzspK2UpT1nr566ODiqT5A5xo+p9uH37pwZsSC4YVvrzg/N7Yc4oEJtF2RTjAOZLFaYtUyDHS+FIzvilNjz3YYvjf9u8XTvb2nFw/uPes8Ta2RMz0HF4aHF/aPN6i8BwQeGqS6muY6jlHw8D6RBbdHd+wuvRvR9U4dthH80LIZoodJiCSI58tYXZCnLYlmGePQW0F0jWHaYKVVULKbonSIhQU/W1kWAkI9Y8DjAHi2KTgtIiwsLwtCmLmAIJTusJtlcGXlHsgYgg9wO36Xm14nudwwI8gihgQwt+lGsF2D3DZDkVvpjhDgcUFWj4svr+AgzDYF4XXR3dgl4P+b0E0XFISHQUI4Qu7qRCI1ZBvxftVQG6zwCRzmLq2gDqJNumFaKqQVmcXKvl32EU+hJLjueD6XXDOSuXyc/prMbfi5pOvn42tGIge5ZCHXEs/nB/MAr+YH8pAbzAEcyx9AKAekzAHNb/QD5KCTNHJIxTRFYsjB1o0OMK200gAyw31VZBTbIBILmlamKa1wQdPAMVODsoJj61YoEolrGj2fzEE+4XSuLXUVwOmkHV2FSU1ZWwpFQFNoRyhyXVNuhTQthBPgqVziSDIPXQ4UuqG9y3FKb0O7okEkVPoB/0MI/wDHWtJKdrr36RkzEa1FVeN9atYNO+sSLTPcOms45vJKhkH77FH4byUUV4/rR7nb3H4SI22ox1Rc87p6DIlMVMUY3vtozIiJMdtohaxt2TEsCTshoyqqrYqKKpoGlgksAdztr72+2YnLcqN2vj6kfDz+hs9z44YozY7PV0bC58NK3aWJmQpxYXB2gA4dL8An5+rq1Y/Gpr2+69+Lvpmx+VBt+Fw4VHN5bMYjLS2J0omxD+tDh3MDA7MDA8hTeCjnBslhFavslPZ1tbcZETfvwr/l/f6b/3EIEC0D6Aw+4mls5WDYP+LNkB7yMgl2+g/0P9uRadmImf1HzLH7McceRwKuuQTR+fwxJmIrOXG1+g23j6PEQ2TUatDHE1eraSXAIkbAAhuFiqpFadpwZXF1tXRldRX44mBxbqhYHJorDnJ0A3NWS05xcG4O37h2o+6sH8N78AIqzU8UrDtKtd/Lc1h3ssEMBKHZMmO2osqiagXQCYh/wGjpEsz39Ow9Sl9pPXmyr1g8B/PQdEeDpmjvj329U++/N946AyMne4ulviLugfVtfQr3eJH4CPviCQ7ctZvuf2ES6mCydLH0+617TukiTG5wm6JtOK+ebCejyO35tBGp8XJu3/CDrIF7quWzzwM27g0NhASRqUombVsx7NPV2OOxiWRt7Ceq3MgphhKV/bSSMoNlWQPkadbEVq+BmjVbKV72FNC2kdHRkau/XHUfiz8tlrQq3uOcOjvk10amZi94wlJtg0PH/hzjCy3VwaCkSdTLPFJlJdULnuO7ze43mZOoqhAkkaevJY8M55yJ1LTe369Ppyac3PCR5HSkvz/C/B5mpr2yVeqt0rb5a8KOlrasdKSQqA6FaJu3qsrvrQwEqA6FSDdvtOmaprcZfLfmPFnt98iSQv4C8vjDpQAAeJxjYGRgYADig+qzBeL5bb4ycDO/AIowXNRKvg2hLVf///8/k/kFcxCQy8HABBIFAFh/DPMAAAB4nGNgZGBgDvqfxRDF/IKB4f8/IAkUQQF8AJFABfwAAHicY37BwMC8koGBqQmCmVcB8T0ofoHE9oDygZjJgIEBACsoCg8AAAAAAAAAAHgAxgEUAVYBqAIOAmIC2gNkA5IDxAPgBHAAAAABAAAADgBjAAYAAAAAAAIAAAAQAHMAAAAiC3AAAAAAeJx1kc1Kw0AURr9pa9UWVBTceldSEdMf6EYQCpW60U2RbiWNaZKSZspkWuhr+A4+jC/hs/g1nYq0mJDMuWfu3LmZADjHNxQ2V5fPhhWOGG24hEM8OC7TPzqukJ8dH6COV8dV+jfHNdwiclzHBT5YQVWOGU3x6VjhTJ06LuFEXTku0985rpAfHB/gUr04rtIHjmsYqdxxHdfqq6/nK5NEsZVG/0Y6rXZXxivRVEnmp+IvbKxNLj2Z6MyGaaq9QM+2PAyjReqbbbgdR6HJE51J22tt1VOYhca34fu6er6MOtZOZGL0TAYuQ+ZGT8PAerG18/tm8+9+6ENjjhUMEh5VDAtBg/aGYwcttPkjBGNmCDM3WQky+EhpfCy4Ii5mcsY9PhNGGW3IjJTsIeB7tueHpIjrU1Yxe7O78Yi03iMpvLAvj93tZj2RsiLTL+z7b+85ltytQ2u5at2lKboSDHZqCM9jPTelCei94lQs7T2avP/5vh/gZIRNAHicbY3bCoJAFEXPVrO8ZPQhAxZI3zOOBw2OM+I4RX9fEApB62Wv9bQpoi85/aciQoQYCXZIsccBGXIUKHFEVQ4skzL32Qh3J217YdW50H4mTOef7tzTJiPbkLd6WXh+qWuzaVPHxvVb3ppi1UtdV0Zbw7LepA8nYeRkkuCzwYW5F+090RvUNDECAAAAeJxj8N7BcCIoYiMjY1/kBsadHAwcDMkFGxlYnTYyMGhBaA4UeicDAwMnMouZwWWjCmNHYMQGh46IjcwpLhvVQLxdHA0MjCwOHckhESAlkUCwkYFHawfj/9YNLL0bmRhcAAfTIrgAAAA=') format('woff'), - url('data:application/octet-stream;base64,AAEAAAAOAIAAAwBgT1MvMj4pSRkAAADsAAAAVmNtYXDQHRm3AAABRAAAAUpjdnQgAAAAAAAAD+gAAAAKZnBnbYiQkFkAAA/0AAALcGdhc3AAAAAQAAAP4AAAAAhnbHlmyIxtKQAAApAAAAjgaGVhZAVa4SYAAAtwAAAANmhoZWEHlwNfAAALqAAAACRobXR4MOcAAAAAC8wAAAA4bG9jYREuDnYAAAwEAAAAHm1heHAAqgvlAAAMJAAAACBuYW1lzJ0bHQAADEQAAALNcG9zdEg8MHEAAA8UAAAAynByZXDdawOFAAAbZAAAAHsAAQN+AZAABQAIAnoCvAAAAIwCegK8AAAB4AAxAQIAAAIABQMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUGZFZABA6ADoDANS/2oAWgNSAJcAAAABAAAAAAAAAAAAAwAAAAMAAAAcAAEAAAAAAEQAAwABAAAAHAAEACgAAAAGAAQAAQACAADoDP//AAAAAOgA//8AABgBAAEAAAAAAAAAAAEGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABP///4kDqgMzABEAIQBDAEwADUAKS0ZBMR4WDQQELSsRND4CFzIeAg4DIi4CNxQeAj4DNzQuASIOATcXNjIVFAYPAQYPAQ4BHQEzNTQ2Nz4BPwE2Nz4BNzQmIyIDFBYyNi4CBkp+rGFfrnxMAUp+rMCufEx2OF6CkIBgNgFeor6kXNcfLWEEAQYFAjgWDHUGAwEUBxMMBhMUAVRAUxEqQyoCJkYoAV5frnxMAUp+rL+ufkpKfq5fR4RcOgI2YIBJX6JeXqJRZR0XBAgBBQQBHQwaGCUaAwYCAQgECwcGESgjMUT+jSAiIkAiASQAAAAAAgAAAAACWAJjABUAKwAItScaEQQCLSslFA8BBiIvAQcGIi8BJjQ3ATYyFwEWNRQPAQYiLwEHBiIvASY0NwE2MhcBFgJYBhwFDgbc2wUQBRsGBgEEBQ4GAQQGBhwFDgbc2wUQBRsGBgEEBQ4GAQQGdgcGHAUF29sFBRwGDgYBBAUF/vwGzwcGHAUF3NwFBRwGDgYBBAYG/vwGAAAAAgAAAAACWAJ0ABUAKwAItSIaDAQCLSsBFAcBBiInASY0PwE2Mh8BNzYyHwEWNRQHAQYiJwEmND8BNjIfATc2Mh8BFgJYBv78BRAE/vwGBhsGDgbb3AUQBBwGBv78BRAE/vwGBhsGDgbb3AUQBBwGAXAHBv78BgYBBAYOBhwFBdzcBQUcBs8HBv78BQUBBAYOBhwFBdzcBQUcBgAAAwAA/4kDqgMzAAwAGAAkAAq3HRkRDQsFAy0rJTIWFRQGIyEiJjQ2FwEyFhQGJyEiJjQ2NwEyFhQGIyEiLgE2NwNCKj48LP0mLDw+KgLaLDw8LP0mLDw8LALaLDw+Kv0mKzwBPCxaPC0qPj5WPgEBbD5UPgE8VjwBAW0+VT4+VjwBAAAAAwAAAAAD3gKXAAwAIgAyAAq3LiceFgwGAy0rNyImPQE0NjIWHQEUBgEyFhcVFAYnFAYnISImJxE0NjMhMhYDETQmJyEiBhcRFBYzITI20RUgICoeHgKPLDwBPitcQP3DQVoBXEACPUFaZx4W/cMVIAEeFgI9FSDCHhbRFR4eFdEVIAE5PCtoLD4BQVwBWkIBOEFcXP6HATgWHgEgFf7IFR4eAAAEAAAAAAPeApcADAAZAC8APwANQAo7NCsjGRMMBgQtKyUiJjc1NDYyFhcVFAYnIiY9ATQ2MhYdARQGATIWFxUUBicUBichIiYnETQ2MyEyFgMRNCYnISIGFxEUFjMhMjYBbRUgAR4sHAEesRUgICoeHgKPLDwBPitcQP3DQVoBXEACPUFaZx4W/cMVIAEeFgI9FSDCHhbRFR4eFdEVIAEeFtEVHh4V0RUgATk8K2gsPgFBXAFaQgE4QVxc/ocBOBYeASAV/sgVHh4AAAACAAD/aQPoA1EAJwAwAAi1LioeCgItKwEVBwYHFwcnBg8BIycmJwcnNyYvATU3NjcnNxc2PwEzFxYXNxcHFhcHNCYiDgEWMjYD6LkKC3hmnxQfHo8bFRahZXkLCMfHBwx4ZaAPIByPHBYanmZ3DQeiVnhUAlh0WgGljhobF51kdgoLwsUHC3dkoBUZHI4cFRifZHcIDMPDBwx1ZJwbFWM8VFR4VFQAAAAFAAAAAAPeApcADAAZACYAPABMAA9ADEhBODAmIBkTDAYFLSslIiY3NTQ2MhYXFRQGJyImPQE0NjIWHQEUBiUiJjc1NDYyFh0BFAYBMhYXFRQGJxQGJyEiJicRNDYzITIWAxE0JichIgYXERQWMyEyNgFtFSABHiwcAR6xFSAgKh4eASMVIAEeLB4eAVYsPAE+K1xA/cNBWgFcQAI9QVpnHhb9wxUgAR4WAj0VIMIeFtEVHh4V0RUgAR4W0RUeHhXRFSABHhbRFR4eFdEVIAE5PCtoLD4BQVwBWkIBOEFcXP6HATgWHgEgFf7IFR4eAAAGAAAAAAPeApcADAAZACYAMwBJAFkAEUAOVU5FPTMtJiAZEwwGBi0rJSImNzU0NjIWFxUUBiciJj0BNDYyFh0BFAYlIiYnNTQ2MhYdARQGJyImNzU0NjIWHQEUBgEyFhcVFAYnFAYnISImJxE0NjMhMhYDETQmJyEiBhcRFBYzITI2AW0VIAEeLBwBHrEVICAqHh4BwBYeASAqHh6yFSABHiweHgFWLDwBPitcQP3DQVoBXEACPUFaZx4W/cMVIAEeFgI9FSDCHhbRFR4eFdEVIAEeFtEVHh4V0RUgAR4W0RUeHhXRFSABHhbRFR4eFdEVIAE5PCtoLD4BQVwBWkIBOEFcXP6HATgWHgEgFf7IFR4eAAACAAD/ugNIAwIACAAUAAi1EQsEAAItKwEyFhAGICYQNgE3JwcnBxcHFzcXNwGkrvb2/qT29gEEmlaamFiamliYmlYDAvb+pPb2AVz2/lyaVpiYVpqYVpiYVgAAAAMAAP9tA+gDTwAFAA4AFgAKtxYTDgoEAwMtKzURMwERASU2NCc3FhcUBxc2ECc3FhAH7AFi/p4BoElJR2kCay97e0yamo4BoAEh/B4BISNKzExKapSRZS93AWB7Spr+TJoAAAABAAD/agPoA1IACwAGswkDAS0rNREhESERIREhESERAWcBGgFn/pn+5tEBGgFn/pn+5v6ZAWcAAAMAAP9qAjADUgAbACgAYgAKt00yJiAYCgMtKwEUDgEUHgEdARQGIiY9ATQ+ATQuAT0BNDYyFhUFBwYXFjMyNzYnJiMiEzQ+Aj8BNjU3BiInFxQfAxYmFiMUDgIPAgYmBjUGHQE+AjU0MhUUHgEXNTQvAiYvAS4BAjBgYmJgrNisYGJiYK7Urv4eEgQIXHyEWA4eYGp4kAgcDBkdXAJk9GQEWi0TEREMHgwCCgYIDA8PAiJaCHRENEJ6BlwrEg0FDAcEAm4saF48XGYudiJOTiJ2LmZcPF5oLHYgTk4gBg4IBjQyChQ2/koSHiQOGBxcHjI2NjIgWisTFRUCMAoSEg4KDxAQAiIBWiBCBCYwIh4eIjAmBEIeXCkTDggUDBYAAAEAAAABAADBJ5sQXw889QALA+gAAAAA0Spj2wAAAADRKjmr////aQPoA1IAAAAIAAIAAAAAAAAAAQAAA1L/agBaA+gAAP/+A+gAAQAAAAAAAAAAAAAAAAAAAA4D6AAAA6kAAAKCAAACggAAA6oAAAPeAAAD3gAAA+gAAAPeAAAD3gAAA0gAAAPoAAAD6AAAAjAAAAAAAAAAeADGARQBVgGoAg4CYgLaA2QDkgPEA+AEcAAAAAEAAAAOAGMABgAAAAAAAgAAABAAcwAAACILcAAAAAAAAAASAN4AAQAAAAAAAAA1AAAAAQAAAAAAAQAIADUAAQAAAAAAAgAHAD0AAQAAAAAAAwAIAEQAAQAAAAAABAAIAEwAAQAAAAAABQALAFQAAQAAAAAABgAIAF8AAQAAAAAACgArAGcAAQAAAAAACwATAJIAAwABBAkAAABqAKUAAwABBAkAAQAQAQ8AAwABBAkAAgAOAR8AAwABBAkAAwAQAS0AAwABBAkABAAQAT0AAwABBAkABQAWAU0AAwABBAkABgAQAWMAAwABBAkACgBWAXMAAwABBAkACwAmAclDb3B5cmlnaHQgKEMpIDIwMTUgYnkgb3JpZ2luYWwgYXV0aG9ycyBAIGZvbnRlbGxvLmNvbWZvbnRlbGxvUmVndWxhcmZvbnRlbGxvZm9udGVsbG9WZXJzaW9uIDEuMGZvbnRlbGxvR2VuZXJhdGVkIGJ5IHN2ZzJ0dGYgZnJvbSBGb250ZWxsbyBwcm9qZWN0Lmh0dHA6Ly9mb250ZWxsby5jb20AQwBvAHAAeQByAGkAZwBoAHQAIAAoAEMAKQAgADIAMAAxADUAIABiAHkAIABvAHIAaQBnAGkAbgBhAGwAIABhAHUAdABoAG8AcgBzACAAQAAgAGYAbwBuAHQAZQBsAGwAbwAuAGMAbwBtAGYAbwBuAHQAZQBsAGwAbwBSAGUAZwB1AGwAYQByAGYAbwBuAHQAZQBsAGwAbwBmAG8AbgB0AGUAbABsAG8AVgBlAHIAcwBpAG8AbgAgADEALgAwAGYAbwBuAHQAZQBsAGwAbwBHAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAHMAdgBnADIAdAB0AGYAIABmAHIAbwBtACAARgBvAG4AdABlAGwAbABvACAAcAByAG8AagBlAGMAdAAuAGgAdAB0AHAAOgAvAC8AZgBvAG4AdABlAGwAbABvAC4AYwBvAG0AAAAAAgAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAABAgEDAQQBBQEGAQcBCAEJAQoBCwEMAQ0BDgxoZWxwLWNpcmNsZWQPYW5nbGUtZG91YmxlLXVwEWFuZ2xlLWRvdWJsZS1kb3duBG1lbnUKYmF0dGVyeS0yNQpiYXR0ZXJ5LTUwA2NvZwpiYXR0ZXJ5LTc1C2JhdHRlcnktMTAwDmNhbmNlbC1jaXJjbGVkBnZvbHVtZQRwbHVzCWhvdXJnbGFzcwAAAAAAAQAB//8ADwAAAAAAAAAAAAAAALAALCCwAFVYRVkgIEu4AA5RS7AGU1pYsDQbsChZYGYgilVYsAIlYbkIAAgAY2MjYhshIbAAWbAAQyNEsgABAENgQi2wASywIGBmLbACLCBkILDAULAEJlqyKAEKQ0VjRVJbWCEjIRuKWCCwUFBYIbBAWRsgsDhQWCGwOFlZILEBCkNFY0VhZLAoUFghsQEKQ0VjRSCwMFBYIbAwWRsgsMBQWCBmIIqKYSCwClBYYBsgsCBQWCGwCmAbILA2UFghsDZgG2BZWVkbsAErWVkjsABQWGVZWS2wAywgRSCwBCVhZCCwBUNQWLAFI0KwBiNCGyEhWbABYC2wBCwjISMhIGSxBWJCILAGI0KxAQpDRWOxAQpDsABgRWOwAyohILAGQyCKIIqwASuxMAUlsAQmUVhgUBthUllYI1khILBAU1iwASsbIbBAWSOwAFBYZVktsAUssAdDK7IAAgBDYEItsAYssAcjQiMgsAAjQmGwAmJmsAFjsAFgsAUqLbAHLCAgRSCwC0NjuAQAYiCwAFBYsEBgWWawAWNgRLABYC2wCCyyBwsAQ0VCKiGyAAEAQ2BCLbAJLLAAQyNEsgABAENgQi2wCiwgIEUgsAErI7AAQ7AEJWAgRYojYSBkILAgUFghsAAbsDBQWLAgG7BAWVkjsABQWGVZsAMlI2FERLABYC2wCywgIEUgsAErI7AAQ7AEJWAgRYojYSBksCRQWLAAG7BAWSOwAFBYZVmwAyUjYUREsAFgLbAMLCCwACNCsgsKA0VYIRsjIVkqIS2wDSyxAgJFsGRhRC2wDiywAWAgILAMQ0qwAFBYILAMI0JZsA1DSrAAUlggsA0jQlktsA8sILAQYmawAWMguAQAY4ojYbAOQ2AgimAgsA4jQiMtsBAsS1RYsQRkRFkksA1lI3gtsBEsS1FYS1NYsQRkRFkbIVkksBNlI3gtsBIssQAPQ1VYsQ8PQ7ABYUKwDytZsABDsAIlQrEMAiVCsQ0CJUKwARYjILADJVBYsQEAQ2CwBCVCioogiiNhsA4qISOwAWEgiiNhsA4qIRuxAQBDYLACJUKwAiVhsA4qIVmwDENHsA1DR2CwAmIgsABQWLBAYFlmsAFjILALQ2O4BABiILAAUFiwQGBZZrABY2CxAAATI0SwAUOwAD6yAQEBQ2BCLbATLACxAAJFVFiwDyNCIEWwCyNCsAojsABgQiBgsAFhtRAQAQAOAEJCimCxEgYrsHIrGyJZLbAULLEAEystsBUssQETKy2wFiyxAhMrLbAXLLEDEystsBgssQQTKy2wGSyxBRMrLbAaLLEGEystsBsssQcTKy2wHCyxCBMrLbAdLLEJEystsB4sALANK7EAAkVUWLAPI0IgRbALI0KwCiOwAGBCIGCwAWG1EBABAA4AQkKKYLESBiuwcisbIlktsB8ssQAeKy2wICyxAR4rLbAhLLECHistsCIssQMeKy2wIyyxBB4rLbAkLLEFHistsCUssQYeKy2wJiyxBx4rLbAnLLEIHistsCgssQkeKy2wKSwgPLABYC2wKiwgYLAQYCBDI7ABYEOwAiVhsAFgsCkqIS2wKyywKiuwKiotsCwsICBHICCwC0NjuAQAYiCwAFBYsEBgWWawAWNgI2E4IyCKVVggRyAgsAtDY7gEAGIgsABQWLBAYFlmsAFjYCNhOBshWS2wLSwAsQACRVRYsAEWsCwqsAEVMBsiWS2wLiwAsA0rsQACRVRYsAEWsCwqsAEVMBsiWS2wLywgNbABYC2wMCwAsAFFY7gEAGIgsABQWLBAYFlmsAFjsAErsAtDY7gEAGIgsABQWLBAYFlmsAFjsAErsAAWtAAAAAAARD4jOLEvARUqLbAxLCA8IEcgsAtDY7gEAGIgsABQWLBAYFlmsAFjYLAAQ2E4LbAyLC4XPC2wMywgPCBHILALQ2O4BABiILAAUFiwQGBZZrABY2CwAENhsAFDYzgtsDQssQIAFiUgLiBHsAAjQrACJUmKikcjRyNhIFhiGyFZsAEjQrIzAQEVFCotsDUssAAWsAQlsAQlRyNHI2GwCUMrZYouIyAgPIo4LbA2LLAAFrAEJbAEJSAuRyNHI2EgsAQjQrAJQysgsGBQWCCwQFFYswIgAyAbswImAxpZQkIjILAIQyCKI0cjRyNhI0ZgsARDsAJiILAAUFiwQGBZZrABY2AgsAErIIqKYSCwAkNgZCOwA0NhZFBYsAJDYRuwA0NgWbADJbACYiCwAFBYsEBgWWawAWNhIyAgsAQmI0ZhOBsjsAhDRrACJbAIQ0cjRyNhYCCwBEOwAmIgsABQWLBAYFlmsAFjYCMgsAErI7AEQ2CwASuwBSVhsAUlsAJiILAAUFiwQGBZZrABY7AEJmEgsAQlYGQjsAMlYGRQWCEbIyFZIyAgsAQmI0ZhOFktsDcssAAWICAgsAUmIC5HI0cjYSM8OC2wOCywABYgsAgjQiAgIEYjR7ABKyNhOC2wOSywABawAyWwAiVHI0cjYbAAVFguIDwjIRuwAiWwAiVHI0cjYSCwBSWwBCVHI0cjYbAGJbAFJUmwAiVhuQgACABjYyMgWGIbIVljuAQAYiCwAFBYsEBgWWawAWNgIy4jICA8ijgjIVktsDossAAWILAIQyAuRyNHI2EgYLAgYGawAmIgsABQWLBAYFlmsAFjIyAgPIo4LbA7LCMgLkawAiVGUlggPFkusSsBFCstsDwsIyAuRrACJUZQWCA8WS6xKwEUKy2wPSwjIC5GsAIlRlJYIDxZIyAuRrACJUZQWCA8WS6xKwEUKy2wPiywNSsjIC5GsAIlRlJYIDxZLrErARQrLbA/LLA2K4ogIDywBCNCijgjIC5GsAIlRlJYIDxZLrErARQrsARDLrArKy2wQCywABawBCWwBCYgLkcjRyNhsAlDKyMgPCAuIzixKwEUKy2wQSyxCAQlQrAAFrAEJbAEJSAuRyNHI2EgsAQjQrAJQysgsGBQWCCwQFFYswIgAyAbswImAxpZQkIjIEewBEOwAmIgsABQWLBAYFlmsAFjYCCwASsgiophILACQ2BkI7ADQ2FkUFiwAkNhG7ADQ2BZsAMlsAJiILAAUFiwQGBZZrABY2GwAiVGYTgjIDwjOBshICBGI0ewASsjYTghWbErARQrLbBCLLA1Ky6xKwEUKy2wQyywNishIyAgPLAEI0IjOLErARQrsARDLrArKy2wRCywABUgR7AAI0KyAAEBFRQTLrAxKi2wRSywABUgR7AAI0KyAAEBFRQTLrAxKi2wRiyxAAEUE7AyKi2wRyywNCotsEgssAAWRSMgLiBGiiNhOLErARQrLbBJLLAII0KwSCstsEossgAAQSstsEsssgABQSstsEwssgEAQSstsE0ssgEBQSstsE4ssgAAQistsE8ssgABQistsFAssgEAQistsFEssgEBQistsFIssgAAPistsFMssgABPistsFQssgEAPistsFUssgEBPistsFYssgAAQCstsFcssgABQCstsFgssgEAQCstsFkssgEBQCstsFossgAAQystsFsssgABQystsFwssgEAQystsF0ssgEBQystsF4ssgAAPystsF8ssgABPystsGAssgEAPystsGEssgEBPystsGIssDcrLrErARQrLbBjLLA3K7A7Ky2wZCywNyuwPCstsGUssAAWsDcrsD0rLbBmLLA4Ky6xKwEUKy2wZyywOCuwOystsGgssDgrsDwrLbBpLLA4K7A9Ky2waiywOSsusSsBFCstsGsssDkrsDsrLbBsLLA5K7A8Ky2wbSywOSuwPSstsG4ssDorLrErARQrLbBvLLA6K7A7Ky2wcCywOiuwPCstsHEssDorsD0rLbByLLMJBAIDRVghGyMhWUIrsAhlsAMkUHiwARUwLQBLuADIUlixAQGOWbABuQgACABjcLEABUKxAAAqsQAFQrEACCqxAAVCsQAIKrEABUK5AAAACSqxAAVCuQAAAAkqsQMARLEkAYhRWLBAiFixA2REsSYBiFFYugiAAAEEQIhjVFixAwBEWVlZWbEADCq4Af+FsASNsQIARAA=') format('truetype'); + src: url('data:application/octet-stream;base64,d09GRgABAAAAABKsAA4AAAAAIAQAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABPUy8yAAABRAAAAEQAAABWPilJKmNtYXAAAAGIAAAAOgAAAUrQIRm3Y3Z0IAAAAcQAAAAKAAAACgAAAABmcGdtAAAB0AAABZQAAAtwiJCQWWdhc3AAAAdkAAAACAAAAAgAAAAQZ2x5ZgAAB2wAAAfoAAAMxIkINSdoZWFkAAAPVAAAADYAAAA2BjgrqGhoZWEAAA+MAAAAIAAAACQIJgPvaG10eAAAD6wAAAAuAAAASD/HAABsb2NhAAAP3AAAACYAAAAmHSwZmG1heHAAABAEAAAAIAAAACAAwwwIbmFtZQAAECQAAAF3AAACzcydGx1wb3N0AAARnAAAAKUAAAD0bFz0+nByZXAAABJEAAAAZQAAAHvdawOFeJxjYGTuZpzAwMrAwVTFtIeBgaEHQjM+YDBkZGJgYGJgZWbACgLSXFMYHF4wvBBgDvqfxRDFHMQwHSjMCJIDAO/YC9F4nGNgYGBmgGAZBkYGEHAB8hjBfBYGDSDNBqQZGZgYGF4I/P8PUvCCAURLMELVAwEjG8OIBwB1lwa+AAAAAAAAAAAAAAAAAAB4nK1WaXMTRxCd1WHLNj6CDxI2gVnGcox2VpjLCBDG7EoW4BzylexCjl1Ldu6LT/wG/ZpekVSRb/y0vB4d2GAnVVQoSv2m9+1M9+ueXpPQksReWI+k3HwpprY2aWTnSUg3bFqO4kPZ2QspU0z+LoiCaLXUvu04JCISgap1hSWC2PfI0iTjQ48yWrYlvWpSbulJd9kaD+qt+vbT0FGO3QklNZuhQ+uRLanCqBJFMu2RkjYtw9VfSVrh5yvMfNUMJYLoJJLGm2EMj+Rn44xWGa3GdhxFkU2WG0WKRDM8iCKPslpin1wxQUD5oBlSXvk0onyEH5EVe5TTCnHJdprf9yU/6R3OvyTieouyJQf+QHZkB3unK/ki0toK46adbEehivB0fSfEI5uT6p/sUV7TaOB2RaYnzQiWyleQWPkJZfYPyWrhfMqXPBrVkoOcCFovc2Jf8g60HkdMiWsmyILujk6IoO6XnKHYY/q4+OO9XSwXIQTIOJb1jkq4EEYpYbOaJG0EOYiSskWV1HpHTJzyOi3iLWG/Tu3oS2e0Sag7MZ6th46tnKjkeDSp00ymTu2k5tGUBlFKOhM85tcBlB/RJK+2sZrEyqNpbDNjJJFQoIVzaSqIZSeWNAXRPJrRm7thmmvXokWaPFDPPXpPb26Fmzs9p+3AP2v8Z3UqpoO9MJ2eDshKfJp2uUnRun56hn8m8UPWAiqRLTbDlMVDtn4H5eVjS47CawNs957zK+h99kTIpIH4G/AeL9UpBUyFmFVQC9201rUsy9RqVotUZOq7IU0rX9ZpAk05Dn1jX8Y4/q+ZGUtMCd/vxOnZEZeeufYlyDSH3GZdj+Z1arFdgM5sz+k0y/Z9nebYfqDTPNvzOh1ha+t0lO2HOi2w/UinY2wvaEGT7jsEchGBXMAGEoGwdRAI20sIhK1CIGwXEQjbIgJhu4RA2H6MQNguIxC2l7Wsmn4qaRw7E8sARYgDoznuyGVuKldTyaUSrotGpzbkKXKrpKJ4Vv0rA/3ikTesgbVAukTW/IpJrnxUleOPrmh508S5Ao5Vf3tzXJ8TD2W/WPhT8L/amqqkV6x5ZHIVeSPQk+NE1yYVj67p8rmqR9f/i4oOa4F+A6UQC0VZlg2+mZDwUafTUA1c5RAzGzMP1/W6Zc3P4fybGCEL6H78NxQaC9yDTllJWe1gr9XXj2W5twflsCdYkmK+zOtb4YuMzEr7RWYpez7yecAVMCqVYasNXK3gzXsS85DpTfJMELcVZYOkjceZILGBYx4wb76TICRMXbWB2imcsIG8YMwp2O+EQ1RvlOVwe6F9Ho2Uf2tX7MgZFU0Q+G32Rtjrs1DyW6yBhCe/1NdAVSFNxbipgEsj5YZq8GFcrdtGMk6gr6jYDcuyig8fR9x3So5lIPlIEatHRz+tvUKd1Ln9yihu3zv9CIJBaWL+9r6Z4qCUd7WSZVZtA1O3GpVT15rDxasO3c2j7nvH2Sdy1jTddE/c9L6mVbeDg7lZEO3bHJSlTC6o68MOG6jLzaXQ6mVckt52DzAsMKDfoRUb/1f3cfg8V6oKo+NIvZ2oH6PPYgzyDzh/R/UF6OcxTLmGlOd7lxOfbtzD2TJdxV2sn+LfwKy15mbpGnBD0w2Yh6xaHbrKDXynBjo90tyO9BDwse4K8QBgE8Bi8InuWsbzKYDxfMYcH+Bz5jBoMofBFnMYbDNnDWCHOQx2mcNgjzkMvmDOOsCXzGEQModBxBwGT5gTADxlDoOvmMPga+Yw+IY59wG+ZQ6DmDkMEuYw2Nd0ayhzixd0F6htUBXowPQTFvewONRUGbK/44Vhf28Qs38wiKk/aro9pP7EC0P92SCm/mIQU3/VdGdI/Y0Xhvq7QUz9wyCmPtMvxnKZwV9GvkuFA8ouNp/z98T7B8IaQLYAAQAB//8AD3icrVZtbFPXGT7vuV+OfXNtx/eDOPaNc0PuTW3HSe3rezMwlwQYgSzLRAlR6lkhpUn4KGz9AUkKFepQJ9RJI2yaNBqthSLKD4QyddO0rT82VDQxTZMQUte1A+3nxKSJH92GJi04e69tGCC0iq5xdO57nvP1nPe8X4RdXV19g7nEFEiUdJDNZIQ0DwS/snXwWV1tZtmeTNT2qJbXqcQYOerKOvUY186BIYGrOXlF5sOA/xIkoVC0HdeDjeC3rm12Goys5p0c5YePX35h9/KrI4DCFfzO9U+eOP3alAOT5391ofLH9p4XWOA52q+KR3gGZCEm8jEZxgZ2RtOb09Tc2g2T95f/evn48PDx5d1Dr1fWUWfqte27z09Ont8xk9TYAHAsJMX4mq44w1MIsCGBj3Z3Prul+t2UYQwYsJbgH601E/RFopAMCfzMikdZ2pPpkvEeRi8IfmPaLjh5DdTiE1E6wSc4ib91k4twbTwPLHaA5Z8IzgkIczdvclyC9wGOq67w12vgrVv3QZ5H8CFuhxvcjLjoc0ONIAsLCaBu8+3g+g1yexKK3KorXITFDfk23PzmLewk+CeC8LLgH+wTkP5L6LoPctyjICEMIQ07EckaspYEf55sjTaHOAZ1l1fRDjo7DNN2NMirMm/VZLcmI55Dk2A2pb1S9p6ZLXlp+qdsqS6Xsr7spe+ZmRKUsuVST9rzxj2Ag96YB6XxEsAhbxdCJSA1Dtj8mf4QORgkjxxylq6KPHJwDXMD2E5eTYLM47kaMrLqRKyo7RQ68ioTtU3s81pUVrHv3FBSqbSu08VsCbxMZeDe1cEyVAbohsHyXl29d1VJga7SDUrqA129oei6ggvgS6XMvqwHgxUob4L+wUqlehL6VR1SSvW3OIcQ9iGOraSXbPT9ab2d6WxFq0Z/6jJMt+gTrTF8etZwyOeVTYD+3ufh/zRX8e1xdT9zm9lBLNKH9phL60HfHhWBFzTBQr/vtExLsFyzF4qu41oYEjZCQVM1VxNUTbBNDBMYApjbvwyGFmbPyu36YpuivjPzSihw7ZogLsycC6cSiwk1/vbsfLNwfnxhjE4cLsO7p+Jt2lvTc8HQB78RQvPT55TWxKmEsubs9HxAvHpVEI9M/6hNebE0NrYwNoY8uUd0bpISRrHwgLhtsL/PTPl65/6X3h+M/B+PAJ01AIXxz/kaT/Mw/GP3LZDt5HkSHZB2jX55Q6Gnfmf+M+5sPbiz9UUo4IpPEIWffIGKeBqd+Lb6PrONoSRAZLTVaIglvq3m1QifMiMOuGioaLVomi5cWL57t3rh7l1gl8aXzkwsLU2cWRpnaB2r3K1WlsbPnMERv63HndVD6AdfQ0uTiIpxR41JQZbBuFOMFiAKXY5tuaomC5oTQSEi/A32VN+Gc9u3D+2nL/UeOzaytHQKzkHHig4dncO/Hxk+8IPvzfTOw9Sx4aXqyBKegfFt9QCe8RwJEf6nTQz4e3c8+MFeiMPe6pvVv9y4L1TfhL11bgdoH65rI91kD3L7at5MrQkyft6QQNbBf9Xa23uAibtuAwon8JpayLuOhXk6hjkek0jRxXyiye2MaqqdskTDlDf5Ip8EjxZtTPU6aEW7l6Kz54D2Te3ZM3X548v+Z/nD5arewgYqr74+IelTBxZOBxJia7JCp/8xzZZ7YtGoqIs0yAfEcJga5cDhLfamo3wl09LMiQJLv5HdN1mqzObmjNFRYy43WylN7svOpUZHU7wU4O18UHaqwy36WmlNoqLnHSefKmdiikL7gi0tUjAciVADyqlNrNln6LrRZ7Kb9MozMSkgiyohzTW9voP6CWCFEyedJEuKxCNDZCepkIPkCPk2aR9IfOuVw9/cP/X18edGtg5u6Ld7M2ZHslUOh3ja3JPRMJDxOtSqGj+ieWDVEOpwDwMS6OA8OtIAagIUUf285uvRYe6D6HgmZkhUsqxq6FhCY3ksWs+oxYYDfpRel6XdnvlJutRNs/25Txp9+HED+PizBuASjgB2/tAAPpQj9xxJVSX6u7AKjWXD2cTKnWQ6nWRakmkBBR9ub3z/qadra99/rP/pffyxfvXlj5JpmmurtXCtgV5f9A9dDMugvtuA4MTKxdqhZWzrvvwvup7pRV/z844Zl5v8Gs22DN/2LNMQ0DqBV/z6sgut3LRsFzMPPpCHStV0RlMV0EMQkPgIS2VgI2KgKeTfP3HxzkXoYdv7W70wl9DjfKRkl7ie63JMVJpSYlNTsy6HxJiMPGbmL9JLR3Zs43Jj5tFCVKaRWOHoyCQztLNRR66+x84zIYwJ7cixLcoAcuSUjmghChhBe6FeOlq16gxJO+sxGLHz/35jiLktB7dUz2KBdeL2S7DV/wKzuSnQ/HchKGxjyit36C8+FYNN/hz+xF8P4pT6DDVImNUVLDKG6AG05l5SRn/fVexqEUm9BnKjAo/VFjqziqGafwYME2XfphQ/CBRRTViThaGQVyUALNtQd64fEARflbYp1Oq2vIcaRR2jrl2n4A++hSE4KadbJHlvog9ScKW60qG33pC716X1dMrOf3+msHBSSdEEnHSnvzMLqW4DTMc02zA7KBGVxqXWYDC9NhJKj68fg+dTCoyEg1J3OlCQjHh1SkkN4Gbru612VdfVbYX8zOnCAm4MXYXtW/fQ2S3+Nm2m6XT7SYeqhkzVFkntjo2Or8uR/wDYZ3UuAAEAAAABAAC/H7FUXw889QALA+gAAAAA0VMJHAAAAADRUt7s//z/aQR3A1IAAAAIAAIAAAAAAAB4nGNgZGBgDvqfxRDFUsbA8P8PSzkDUAQFCAEAfcMFHXicY37BwMC8koGBqQmCmVcB8T0ofoHE9oDygZjJAEpbMzCwlAGxOwMDAODAC/wAAAAAAAAAeADGARQBVgGoAg4CYgLaA2QDkgPEA+AEcAVKBZwF2AZiAAAAAQAAABIAhgANAAAAAAACAAAAEABzAAAAMAtwAAAAAHicdZHNSsNAFEa/aWvVFlQU3HpXUhHTH+hGEAqVutFNkW4ljWmSkmbKZFroa/gOPowv4bP4NZ2KtJiQzLln7ty5mQA4xzcUNleXz4YVjhhtuIRDPDgu0z86rpCfHR+gjlfHVfo3xzXcInJcxwU+WEFVjhlN8elY4UydOi7hRF05LtPfOa6QHxwf4FK9OK7SB45rGKnccR3X6quv5yuTRLGVRv9GOq12V8Yr0VRJ5qfiL2ysTS49mejMhmmqvUDPtjwMo0Xqm224HUehyROdSdtrbdVTmIXGt+H7unq+jDrWTmRi9EwGLkPmRk/DwHqxtfP7ZvPvfuhDY44VDBIeVQwLQYP2hmMHLbT5IwRjZggzN1kJMvhIaXwsuCIuZnLGPT4TRhltyIyU7CHge7bnh6SI61NWMXuzu/GItN4jKbywL4/d7WY9kbIi0y/s+2/vOZbcrUNruWrdpSm6Egx2agjPYz03pQnoveJULO09mrz/+b4f4GSETQB4nG2NWwrCMBREMxpfabXqPgJVKK4nvQ1t4TYpaaK4ewuiIDg/cw4MjFiId5T4n7MQWGAJiRXW2GCLHRQy5NjjgAJHnHDOO8ujpj4Q26YwrmWrG5/qudJ4+vHGP5wcrEuqNjHa8NTX6otVuSTffvVWZR+8lOWBjCPLn5v13XMarBw5TbvOp9CymSZJhknG3kVFnQlRc+9sPvmZ3LwOPQnxAnKBPYsAAAB4nGPw3sFwIihiIyNjX+QGxp0cDBwMyQUbGVidNjIwaEFoDhR6JwMDAycyi5nBZaMKY0dgxAaHjoiNzCkuG9VAvF0cDQyMLA4dySERICWRQLCRgUdrB+P/1g0svRuZGFwAB9MiuAAAAA==') format('woff'), + url('data:application/octet-stream;base64,AAEAAAAOAIAAAwBgT1MvMj4pSSoAAADsAAAAVmNtYXDQIRm3AAABRAAAAUpjdnQgAAAAAAAAFAwAAAAKZnBnbYiQkFkAABQYAAALcGdhc3AAAAAQAAAUBAAAAAhnbHlmiQg1JwAAApAAAAzEaGVhZAY4K6gAAA9UAAAANmhoZWEIJgPvAAAPjAAAACRobXR4P8cAAAAAD7AAAABIbG9jYR0sGZgAAA/4AAAAJm1heHAAwwwIAAAQIAAAACBuYW1lzJ0bHQAAEEAAAALNcG9zdGxc9PoAABMQAAAA9HByZXDdawOFAAAfiAAAAHsAAQOLAZAABQAIAnoCvAAAAIwCegK8AAAB4AAxAQIAAAIABQMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUGZFZABA6ADoEANS/2oAWgNSAJcAAAABAAAAAAAAAAAAAwAAAAMAAAAcAAEAAAAAAEQAAwABAAAAHAAEACgAAAAGAAQAAQACAADoEP//AAAAAOgA//8AABgBAAEAAAAAAAAAAAEGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABP///4kDqgMzABEAIQBDAEwADUAKS0ZBMR4WDQQELSsRND4CFzIeAg4DIi4CNxQeAj4DNzQuASIOATcXNjIVFAYPAQYPAQ4BHQEzNTQ2Nz4BPwE2Nz4BNzQmIyIDFBYyNi4CBkp+rGFfrnxMAUp+rMCufEx2OF6CkIBgNgFeor6kXNcfLWEEAQYFAjgWDHUGAwEUBxMMBhMUAVRAUxEqQyoCJkYoAV5frnxMAUp+rL+ufkpKfq5fR4RcOgI2YIBJX6JeXqJRZR0XBAgBBQQBHQwaGCUaAwYCAQgECwcGESgjMUT+jSAiIkAiASQAAAAAAgAAAAACWAJjABUAKwAItScaEQQCLSslFA8BBiIvAQcGIi8BJjQ3ATYyFwEWNRQPAQYiLwEHBiIvASY0NwE2MhcBFgJYBhwFDgbc2wUQBRsGBgEEBQ4GAQQGBhwFDgbc2wUQBRsGBgEEBQ4GAQQGdgcGHAUF29sFBRwGDgYBBAUF/vwGzwcGHAUF3NwFBRwGDgYBBAYG/vwGAAAAAgAAAAACWAJ0ABUAKwAItSIaDAQCLSsBFAcBBiInASY0PwE2Mh8BNzYyHwEWNRQHAQYiJwEmND8BNjIfATc2Mh8BFgJYBv78BRAE/vwGBhsGDgbb3AUQBBwGBv78BRAE/vwGBhsGDgbb3AUQBBwGAXAHBv78BgYBBAYOBhwFBdzcBQUcBs8HBv78BQUBBAYOBhwFBdzcBQUcBgAAAwAA/4kDqgMzAAwAGAAkAAq3HRkRDQsFAy0rJTIWFRQGIyEiJjQ2FwEyFhQGJyEiJjQ2NwEyFhQGIyEiLgE2NwNCKj48LP0mLDw+KgLaLDw8LP0mLDw8LALaLDw+Kv0mKzwBPCxaPC0qPj5WPgEBbD5UPgE8VjwBAW0+VT4+VjwBAAAAAwAAAAAD3gKXAAwAIgAyAAq3LiceFgwGAy0rNyImPQE0NjIWHQEUBgEyFhcVFAYnFAYnISImJxE0NjMhMhYDETQmJyEiBhcRFBYzITI20RUgICoeHgKPLDwBPitcQP3DQVoBXEACPUFaZx4W/cMVIAEeFgI9FSDCHhbRFR4eFdEVIAE5PCtoLD4BQVwBWkIBOEFcXP6HATgWHgEgFf7IFR4eAAAEAAAAAAPeApcADAAZAC8APwANQAo7NCsjGRMMBgQtKyUiJjc1NDYyFhcVFAYnIiY9ATQ2MhYdARQGATIWFxUUBicUBichIiYnETQ2MyEyFgMRNCYnISIGFxEUFjMhMjYBbRUgAR4sHAEesRUgICoeHgKPLDwBPitcQP3DQVoBXEACPUFaZx4W/cMVIAEeFgI9FSDCHhbRFR4eFdEVIAEeFtEVHh4V0RUgATk8K2gsPgFBXAFaQgE4QVxc/ocBOBYeASAV/sgVHh4AAAACAAD/aQPoA1EAJwAwAAi1LioeCgItKwEVBwYHFwcnBg8BIycmJwcnNyYvATU3NjcnNxc2PwEzFxYXNxcHFhcHNCYiDgEWMjYD6LkKC3hmnxQfHo8bFRahZXkLCMfHBwx4ZaAPIByPHBYanmZ3DQeiVnhUAlh0WgGljhobF51kdgoLwsUHC3dkoBUZHI4cFRifZHcIDMPDBwx1ZJwbFWM8VFR4VFQAAAAFAAAAAAPeApcADAAZACYAPABMAA9ADEhBODAmIBkTDAYFLSslIiY3NTQ2MhYXFRQGJyImPQE0NjIWHQEUBiUiJjc1NDYyFh0BFAYBMhYXFRQGJxQGJyEiJicRNDYzITIWAxE0JichIgYXERQWMyEyNgFtFSABHiwcAR6xFSAgKh4eASMVIAEeLB4eAVYsPAE+K1xA/cNBWgFcQAI9QVpnHhb9wxUgAR4WAj0VIMIeFtEVHh4V0RUgAR4W0RUeHhXRFSABHhbRFR4eFdEVIAE5PCtoLD4BQVwBWkIBOEFcXP6HATgWHgEgFf7IFR4eAAAGAAAAAAPeApcADAAZACYAMwBJAFkAEUAOVU5FPTMtJiAZEwwGBi0rJSImNzU0NjIWFxUUBiciJj0BNDYyFh0BFAYlIiYnNTQ2MhYdARQGJyImNzU0NjIWHQEUBgEyFhcVFAYnFAYnISImJxE0NjMhMhYDETQmJyEiBhcRFBYzITI2AW0VIAEeLBwBHrEVICAqHh4BwBYeASAqHh6yFSABHiweHgFWLDwBPitcQP3DQVoBXEACPUFaZx4W/cMVIAEeFgI9FSDCHhbRFR4eFdEVIAEeFtEVHh4V0RUgAR4W0RUeHhXRFSABHhbRFR4eFdEVIAE5PCtoLD4BQVwBWkIBOEFcXP6HATgWHgEgFf7IFR4eAAACAAD/ugNIAwIACAAUAAi1EQsEAAItKwEyFhAGICYQNgE3JwcnBxcHFzcXNwGkrvb2/qT29gEEmlaamFiamliYmlYDAvb+pPb2AVz2/lyaVpiYVpqYVpiYVgAAAAMAAP9tA+gDTwAFAA4AFgAKtxYTDgoEAwMtKzURMwERASU2NCc3FhcUBxc2ECc3FhAH7AFi/p4BoElJR2kCay97e0yamo4BoAEh/B4BISNKzExKapSRZS93AWB7Spr+TJoAAAABAAD/agPoA1IACwAGswkDAS0rNREhESERIREhESERAWcBGgFn/pn+5tEBGgFn/pn+5v6ZAWcAAAMAAP9qAjADUgAbACgAYgAKt00yJiAYCgMtKwEUDgEUHgEdARQGIiY9ATQ+ATQuAT0BNDYyFhUFBwYXFjMyNzYnJiMiEzQ+Aj8BNjU3BiInFxQfAxYmFiMUDgIPAgYmBjUGHQE+AjU0MhUUHgEXNTQvAiYvAS4BAjBgYmJgrNisYGJiYK7Urv4eEgQIXHyEWA4eYGp4kAgcDBkdXAJk9GQEWi0TEREMHgwCCgYIDA8PAiJaCHRENEJ6BlwrEg0FDAcEAm4saF48XGYudiJOTiJ2LmZcPF5oLHYgTk4gBg4IBjQyChQ2/koSHiQOGBxcHjI2NjIgWisTFRUCMAoSEg4KDxAQAiIBWiBCBCYwIh4eIjAmBEIeXCkTDggUDBYAAA0AAP9qA6EDUgAIABEAGgAjACwANQA+AEcAUwBcAGwAdQCFAB9AHIF5dG9pYFtWUkxGQT04NC8rJiEdGRQPCwYCDS0rFzQmIgYeAT4BNzQmIg4BFj4BJzQmIgYeAjYFNCYiDgEWPgEnNCYiDgEeATYnNCYiBh4CNgU0JiIOAR4BNic0JiIOAR4BNgE1NC4BBhcVFB4BNgM0JiIOAR4BNjc1NCYjISIGHQEUFhchMjYHNCYiBh4CNhMRFAYjISImNRE0NjMhMhbWKjosAig+JtkqPCgCLDgu2So6LAIoPiYBryo8KAIsOC7YKjwoAiw4LtkqOiwCKD4mAa8qPCgCLDgu2Co8KAIsOC4Bqio6LAEqPCjVKjwoAiw4LtQUEP02DhYWDgLKDxYBKjosAig+JkosHPzuHSoqHQMSHSoHHSoqOiwCKB8dKio6LAIo9R4qKjwoAiy6HSoqOiwCKPUeKio8KAIs8h4qKjwoAiy6HioqPCgCLPIeKio8KAIs/nDWHSoCLhvWHSoCLgHHHioqPCgCLM+PDhYWDo8PFAEWpR4qKjwoAiwBgvymHSoqHQNaHSoqAAACAAD/+AI7Ay8AFgAwAAi1JhoUCQItKyU0JyIvAS4BJyYiBw4CDwEGFRQWMjYlFA4BJic0NzY/AT4BNz4BHgEXHgMXFhUBHgsBCA4GEAQCFAEEEAwICQsqOiwBHKbupgEtBB84GT4PBRweGgYQPDQ8BS3PFBMMFQkgDAkJDR4UCwwTFB0qKmV3pgKqdVFIBS5UJnozERQCEBMzekxeA0dTAAAAAAIAAP+xBHcDCwAFAB8ACLUbEQMBAi0rBRUhETMRARUUBi8BAQYiLwEHJwE2Mh8BAScmNjsBMhYEd/uJRwPoFApE/p8GDgaC6GsBRgYOBoIBA0MJCA3zBwoHSANa/O4CuPIMCglE/p8GBoLpbAFGBgaCAQNDCRYKAAP//AAABEcCagARAC8AWgAKt1U1JRIMAAMtKzciJjcRBwYuATY/ATYWFxEUBikBIiY/ATY0JiIGFRQGIiY1NDc2MhYUDwEzMhYOAQEWFRQOASY3NDYyFgcUFjI2NCYHIiY0NjcyPgEmJyIHDgEuATc2MzIWBxSdFSABHRQqEg4UZxwwASABwP78IR4Z0RQoOioeKiA0MpJlM3iHFSACHAGHN2SKZgEgKCIBJjYmJhsVICAVEBYCGg4ZCgoqJBALKlY7VAFZIBUBTA8KDigqCDMOIhr+YBUgQBnRFDsoJx8WHh4WSDMyZZAzeB4qIAElM0lGYgJmRBUgIBUbJiY2KAEeLBwCFiIUAhYSDhYoE05WOi4AAAEAAAABAAC/H7FUXw889QALA+gAAAAA0VMJHAAAAADRUt7s//z/aQR3A1IAAAAIAAIAAAAAAAAAAQAAA1L/agBaBHYAAP/8BHcAAQAAAAAAAAAAAAAAAAAAABID6AAAA6kAAAKCAAACggAAA6oAAAPeAAAD3gAAA+gAAAPeAAAD3gAAA0gAAAPoAAAD6AAAAjAAAAPoAAACOwAABHYAAARHAAAAAAAAAHgAxgEUAVYBqAIOAmIC2gNkA5IDxAPgBHAFSgWcBdgGYgAAAAEAAAASAIYADQAAAAAAAgAAABAAcwAAADALcAAAAAAAAAASAN4AAQAAAAAAAAA1AAAAAQAAAAAAAQAIADUAAQAAAAAAAgAHAD0AAQAAAAAAAwAIAEQAAQAAAAAABAAIAEwAAQAAAAAABQALAFQAAQAAAAAABgAIAF8AAQAAAAAACgArAGcAAQAAAAAACwATAJIAAwABBAkAAABqAKUAAwABBAkAAQAQAQ8AAwABBAkAAgAOAR8AAwABBAkAAwAQAS0AAwABBAkABAAQAT0AAwABBAkABQAWAU0AAwABBAkABgAQAWMAAwABBAkACgBWAXMAAwABBAkACwAmAclDb3B5cmlnaHQgKEMpIDIwMTUgYnkgb3JpZ2luYWwgYXV0aG9ycyBAIGZvbnRlbGxvLmNvbWZvbnRlbGxvUmVndWxhcmZvbnRlbGxvZm9udGVsbG9WZXJzaW9uIDEuMGZvbnRlbGxvR2VuZXJhdGVkIGJ5IHN2ZzJ0dGYgZnJvbSBGb250ZWxsbyBwcm9qZWN0Lmh0dHA6Ly9mb250ZWxsby5jb20AQwBvAHAAeQByAGkAZwBoAHQAIAAoAEMAKQAgADIAMAAxADUAIABiAHkAIABvAHIAaQBnAGkAbgBhAGwAIABhAHUAdABoAG8AcgBzACAAQAAgAGYAbwBuAHQAZQBsAGwAbwAuAGMAbwBtAGYAbwBuAHQAZQBsAGwAbwBSAGUAZwB1AGwAYQByAGYAbwBuAHQAZQBsAGwAbwBmAG8AbgB0AGUAbABsAG8AVgBlAHIAcwBpAG8AbgAgADEALgAwAGYAbwBuAHQAZQBsAGwAbwBHAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAHMAdgBnADIAdAB0AGYAIABmAHIAbwBtACAARgBvAG4AdABlAGwAbABvACAAcAByAG8AagBlAGMAdAAuAGgAdAB0AHAAOgAvAC8AZgBvAG4AdABlAGwAbABvAC4AYwBvAG0AAAAAAgAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASAAABAgEDAQQBBQEGAQcBCAEJAQoBCwEMAQ0BDgEPARABEQESDGhlbHAtY2lyY2xlZA9hbmdsZS1kb3VibGUtdXARYW5nbGUtZG91YmxlLWRvd24EbWVudQpiYXR0ZXJ5LTI1CmJhdHRlcnktNTADY29nCmJhdHRlcnktNzULYmF0dGVyeS0xMDAOY2FuY2VsLWNpcmNsZWQGdm9sdW1lBHBsdXMJaG91cmdsYXNzBGNhbGMEdGludApjaGFydC1saW5lDHNvcnQtbnVtZXJpYwAAAAEAAf//AA8AAAAAAAAAAAAAAACwACwgsABVWEVZICBLuAAOUUuwBlNaWLA0G7AoWWBmIIpVWLACJWG5CAAIAGNjI2IbISGwAFmwAEMjRLIAAQBDYEItsAEssCBgZi2wAiwgZCCwwFCwBCZasigBCkNFY0VSW1ghIyEbilggsFBQWCGwQFkbILA4UFghsDhZWSCxAQpDRWNFYWSwKFBYIbEBCkNFY0UgsDBQWCGwMFkbILDAUFggZiCKimEgsApQWGAbILAgUFghsApgGyCwNlBYIbA2YBtgWVlZG7ABK1lZI7AAUFhlWVktsAMsIEUgsAQlYWQgsAVDUFiwBSNCsAYjQhshIVmwAWAtsAQsIyEjISBksQViQiCwBiNCsQEKQ0VjsQEKQ7AAYEVjsAMqISCwBkMgiiCKsAErsTAFJbAEJlFYYFAbYVJZWCNZISCwQFNYsAErGyGwQFkjsABQWGVZLbAFLLAHQyuyAAIAQ2BCLbAGLLAHI0IjILAAI0JhsAJiZrABY7ABYLAFKi2wBywgIEUgsAtDY7gEAGIgsABQWLBAYFlmsAFjYESwAWAtsAgssgcLAENFQiohsgABAENgQi2wCSywAEMjRLIAAQBDYEItsAosICBFILABKyOwAEOwBCVgIEWKI2EgZCCwIFBYIbAAG7AwUFiwIBuwQFlZI7AAUFhlWbADJSNhRESwAWAtsAssICBFILABKyOwAEOwBCVgIEWKI2EgZLAkUFiwABuwQFkjsABQWGVZsAMlI2FERLABYC2wDCwgsAAjQrILCgNFWCEbIyFZKiEtsA0ssQICRbBkYUQtsA4ssAFgICCwDENKsABQWCCwDCNCWbANQ0qwAFJYILANI0JZLbAPLCCwEGJmsAFjILgEAGOKI2GwDkNgIIpgILAOI0IjLbAQLEtUWLEEZERZJLANZSN4LbARLEtRWEtTWLEEZERZGyFZJLATZSN4LbASLLEAD0NVWLEPD0OwAWFCsA8rWbAAQ7ACJUKxDAIlQrENAiVCsAEWIyCwAyVQWLEBAENgsAQlQoqKIIojYbAOKiEjsAFhIIojYbAOKiEbsQEAQ2CwAiVCsAIlYbAOKiFZsAxDR7ANQ0dgsAJiILAAUFiwQGBZZrABYyCwC0NjuAQAYiCwAFBYsEBgWWawAWNgsQAAEyNEsAFDsAA+sgEBAUNgQi2wEywAsQACRVRYsA8jQiBFsAsjQrAKI7AAYEIgYLABYbUQEAEADgBCQopgsRIGK7ByKxsiWS2wFCyxABMrLbAVLLEBEystsBYssQITKy2wFyyxAxMrLbAYLLEEEystsBkssQUTKy2wGiyxBhMrLbAbLLEHEystsBwssQgTKy2wHSyxCRMrLbAeLACwDSuxAAJFVFiwDyNCIEWwCyNCsAojsABgQiBgsAFhtRAQAQAOAEJCimCxEgYrsHIrGyJZLbAfLLEAHistsCAssQEeKy2wISyxAh4rLbAiLLEDHistsCMssQQeKy2wJCyxBR4rLbAlLLEGHistsCYssQceKy2wJyyxCB4rLbAoLLEJHistsCksIDywAWAtsCosIGCwEGAgQyOwAWBDsAIlYbABYLApKiEtsCsssCorsCoqLbAsLCAgRyAgsAtDY7gEAGIgsABQWLBAYFlmsAFjYCNhOCMgilVYIEcgILALQ2O4BABiILAAUFiwQGBZZrABY2AjYTgbIVktsC0sALEAAkVUWLABFrAsKrABFTAbIlktsC4sALANK7EAAkVUWLABFrAsKrABFTAbIlktsC8sIDWwAWAtsDAsALABRWO4BABiILAAUFiwQGBZZrABY7ABK7ALQ2O4BABiILAAUFiwQGBZZrABY7ABK7AAFrQAAAAAAEQ+IzixLwEVKi2wMSwgPCBHILALQ2O4BABiILAAUFiwQGBZZrABY2CwAENhOC2wMiwuFzwtsDMsIDwgRyCwC0NjuAQAYiCwAFBYsEBgWWawAWNgsABDYbABQ2M4LbA0LLECABYlIC4gR7AAI0KwAiVJiopHI0cjYSBYYhshWbABI0KyMwEBFRQqLbA1LLAAFrAEJbAEJUcjRyNhsAlDK2WKLiMgIDyKOC2wNiywABawBCWwBCUgLkcjRyNhILAEI0KwCUMrILBgUFggsEBRWLMCIAMgG7MCJgMaWUJCIyCwCEMgiiNHI0cjYSNGYLAEQ7ACYiCwAFBYsEBgWWawAWNgILABKyCKimEgsAJDYGQjsANDYWRQWLACQ2EbsANDYFmwAyWwAmIgsABQWLBAYFlmsAFjYSMgILAEJiNGYTgbI7AIQ0awAiWwCENHI0cjYWAgsARDsAJiILAAUFiwQGBZZrABY2AjILABKyOwBENgsAErsAUlYbAFJbACYiCwAFBYsEBgWWawAWOwBCZhILAEJWBkI7ADJWBkUFghGyMhWSMgILAEJiNGYThZLbA3LLAAFiAgILAFJiAuRyNHI2EjPDgtsDgssAAWILAII0IgICBGI0ewASsjYTgtsDkssAAWsAMlsAIlRyNHI2GwAFRYLiA8IyEbsAIlsAIlRyNHI2EgsAUlsAQlRyNHI2GwBiWwBSVJsAIlYbkIAAgAY2MjIFhiGyFZY7gEAGIgsABQWLBAYFlmsAFjYCMuIyAgPIo4IyFZLbA6LLAAFiCwCEMgLkcjRyNhIGCwIGBmsAJiILAAUFiwQGBZZrABYyMgIDyKOC2wOywjIC5GsAIlRlJYIDxZLrErARQrLbA8LCMgLkawAiVGUFggPFkusSsBFCstsD0sIyAuRrACJUZSWCA8WSMgLkawAiVGUFggPFkusSsBFCstsD4ssDUrIyAuRrACJUZSWCA8WS6xKwEUKy2wPyywNiuKICA8sAQjQoo4IyAuRrACJUZSWCA8WS6xKwEUK7AEQy6wKystsEAssAAWsAQlsAQmIC5HI0cjYbAJQysjIDwgLiM4sSsBFCstsEEssQgEJUKwABawBCWwBCUgLkcjRyNhILAEI0KwCUMrILBgUFggsEBRWLMCIAMgG7MCJgMaWUJCIyBHsARDsAJiILAAUFiwQGBZZrABY2AgsAErIIqKYSCwAkNgZCOwA0NhZFBYsAJDYRuwA0NgWbADJbACYiCwAFBYsEBgWWawAWNhsAIlRmE4IyA8IzgbISAgRiNHsAErI2E4IVmxKwEUKy2wQiywNSsusSsBFCstsEMssDYrISMgIDywBCNCIzixKwEUK7AEQy6wKystsEQssAAVIEewACNCsgABARUUEy6wMSotsEUssAAVIEewACNCsgABARUUEy6wMSotsEYssQABFBOwMiotsEcssDQqLbBILLAAFkUjIC4gRoojYTixKwEUKy2wSSywCCNCsEgrLbBKLLIAAEErLbBLLLIAAUErLbBMLLIBAEErLbBNLLIBAUErLbBOLLIAAEIrLbBPLLIAAUIrLbBQLLIBAEIrLbBRLLIBAUIrLbBSLLIAAD4rLbBTLLIAAT4rLbBULLIBAD4rLbBVLLIBAT4rLbBWLLIAAEArLbBXLLIAAUArLbBYLLIBAEArLbBZLLIBAUArLbBaLLIAAEMrLbBbLLIAAUMrLbBcLLIBAEMrLbBdLLIBAUMrLbBeLLIAAD8rLbBfLLIAAT8rLbBgLLIBAD8rLbBhLLIBAT8rLbBiLLA3Ky6xKwEUKy2wYyywNyuwOystsGQssDcrsDwrLbBlLLAAFrA3K7A9Ky2wZiywOCsusSsBFCstsGcssDgrsDsrLbBoLLA4K7A8Ky2waSywOCuwPSstsGossDkrLrErARQrLbBrLLA5K7A7Ky2wbCywOSuwPCstsG0ssDkrsD0rLbBuLLA6Ky6xKwEUKy2wbyywOiuwOystsHAssDorsDwrLbBxLLA6K7A9Ky2wciyzCQQCA0VYIRsjIVlCK7AIZbADJFB4sAEVMC0AS7gAyFJYsQEBjlmwAbkIAAgAY3CxAAVCsQAAKrEABUKxAAgqsQAFQrEACCqxAAVCuQAAAAkqsQAFQrkAAAAJKrEDAESxJAGIUViwQIhYsQNkRLEmAYhRWLoIgAABBECIY1RYsQMARFlZWVmxAAwquAH/hbAEjbECAEQA') format('truetype'); } /* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */ /* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */ @@ -17,7 +17,7 @@ @media screen and (-webkit-min-device-pixel-ratio:0) { @font-face { font-family: 'fontello'; - src: url('../font/fontello.svg?94033511#fontello') format('svg'); + src: url('../font/fontello.svg?87658047#fontello') format('svg'); } } */ @@ -64,4 +64,8 @@ .icon-cancel-circled:before { content: '\e809'; } /* '' */ .icon-volume:before { content: '\e80a'; } /* '' */ .icon-plus:before { content: '\e80b'; } /* '' */ -.icon-hourglass:before { content: '\e80c'; } /* '' */ \ No newline at end of file +.icon-hourglass:before { content: '\e80c'; } /* '' */ +.icon-calc:before { content: '\e80d'; } /* '' */ +.icon-tint:before { content: '\e80e'; } /* '' */ +.icon-chart-line:before { content: '\e80f'; } /* '' */ +.icon-sort-numeric:before { content: '\e810'; } /* '' */ \ No newline at end of file diff --git a/static/glyphs/css/fontello-ie7-codes.css b/static/glyphs/css/fontello-ie7-codes.css index f12e517b8fe..6cc26c1385a 100644 --- a/static/glyphs/css/fontello-ie7-codes.css +++ b/static/glyphs/css/fontello-ie7-codes.css @@ -11,4 +11,8 @@ .icon-cancel-circled { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } .icon-volume { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } .icon-plus { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-hourglass { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } \ No newline at end of file +.icon-hourglass { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } +.icon-calc { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } +.icon-tint { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } +.icon-chart-line { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } +.icon-sort-numeric { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } \ No newline at end of file diff --git a/static/glyphs/css/fontello-ie7.css b/static/glyphs/css/fontello-ie7.css index f57862786bf..4770e782110 100644 --- a/static/glyphs/css/fontello-ie7.css +++ b/static/glyphs/css/fontello-ie7.css @@ -22,4 +22,8 @@ .icon-cancel-circled { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } .icon-volume { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } .icon-plus { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-hourglass { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } \ No newline at end of file +.icon-hourglass { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } +.icon-calc { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } +.icon-tint { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } +.icon-chart-line { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } +.icon-sort-numeric { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } \ No newline at end of file diff --git a/static/glyphs/css/fontello.css b/static/glyphs/css/fontello.css index dcef60596fe..1bdd8aedcc4 100644 --- a/static/glyphs/css/fontello.css +++ b/static/glyphs/css/fontello.css @@ -1,10 +1,10 @@ @font-face { font-family: 'fontello'; - src: url('../font/fontello.eot?46427953'); - src: url('../font/fontello.eot?46427953#iefix') format('embedded-opentype'), - url('../font/fontello.woff?46427953') format('woff'), - url('../font/fontello.ttf?46427953') format('truetype'), - url('../font/fontello.svg?46427953#fontello') format('svg'); + src: url('../font/fontello.eot?87142642'); + src: url('../font/fontello.eot?87142642#iefix') format('embedded-opentype'), + url('../font/fontello.woff?87142642') format('woff'), + url('../font/fontello.ttf?87142642') format('truetype'), + url('../font/fontello.svg?87142642#fontello') format('svg'); font-weight: normal; font-style: normal; } @@ -14,7 +14,7 @@ @media screen and (-webkit-min-device-pixel-ratio:0) { @font-face { font-family: 'fontello'; - src: url('../font/fontello.svg?46427953#fontello') format('svg'); + src: url('../font/fontello.svg?87142642#fontello') format('svg'); } } */ @@ -35,7 +35,7 @@ /* For safety - reset parent styles, that can break glyph codes*/ font-variant: normal; text-transform: none; - + /* fix buttons height, for twitter bootstrap */ line-height: 1em; @@ -46,6 +46,10 @@ /* you can be more comfortable with increased icons size */ /* font-size: 120%; */ + /* Font smoothing. That was taken from TWBS */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + /* Uncomment for 3D effect */ /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */ } @@ -62,4 +66,8 @@ .icon-cancel-circled:before { content: '\e809'; } /* '' */ .icon-volume:before { content: '\e80a'; } /* '' */ .icon-plus:before { content: '\e80b'; } /* '' */ -.icon-hourglass:before { content: '\e80c'; } /* '' */ \ No newline at end of file +.icon-hourglass:before { content: '\e80c'; } /* '' */ +.icon-calc:before { content: '\e80d'; } /* '' */ +.icon-tint:before { content: '\e80e'; } /* '' */ +.icon-chart-line:before { content: '\e80f'; } /* '' */ +.icon-sort-numeric:before { content: '\e810'; } /* '' */ \ No newline at end of file diff --git a/static/glyphs/demo.html b/static/glyphs/demo.html index e8078fcc615..3ba8dbc7164 100644 --- a/static/glyphs/demo.html +++ b/static/glyphs/demo.html @@ -227,8 +227,54 @@ .i-code { display: none; } - - +@font-face { + font-family: 'fontello'; + src: url('./font/fontello.eot?65044999'); + src: url('./font/fontello.eot?65044999#iefix') format('embedded-opentype'), + url('./font/fontello.woff?65044999') format('woff'), + url('./font/fontello.ttf?65044999') format('truetype'), + url('./font/fontello.svg?65044999#fontello') format('svg'); + font-weight: normal; + font-style: normal; + } + + + .demo-icon + { + font-family: "fontello"; + font-style: normal; + font-weight: normal; + speak: none; + + display: inline-block; + text-decoration: inherit; + width: 1em; + margin-right: .2em; + text-align: center; + /* opacity: .8; */ + + /* For safety - reset parent styles, that can break glyph codes*/ + font-variant: normal; + text-transform: none; + + /* fix buttons height, for twitter bootstrap */ + line-height: 1em; + + /* Animation center compensation - margins should be symmetric */ + /* remove if not needed */ + margin-left: .2em; + + /* You can be more comfortable with increased icons size */ + /* font-size: 120%; */ + + /* Font smoothing. That was taken from TWBS */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + + /* Uncomment for 3D effect */ + /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */ + } + diff --git a/static/report/js/report.js b/static/report/js/report.js index 7b018866684..9008380972c 100644 --- a/static/report/js/report.js +++ b/static/report/js/report.js @@ -3,11 +3,12 @@ // - make axis on daytoday better working with thresholds // - get rid of /static/report/js/time.js // - load css dynamic + optimize -// - move rp_edittreatmentdialog html to plugin // - check everything is translated // - add tests // - optimize merging data inside every plugin // - pressing Show 2nd time generates d3 errors, previous graphs are not removed +// - XMLHttpRequest - > $.ajax in treatments.js +// - finish TREATMENT_AUTH in careportal (function () { @@ -27,6 +28,8 @@ // init HTML code report_plugins.addHtmlFromPlugins( client ); + // make show() accessible outside for treatments.js + report_plugins.show = show; var translate = client.translate; From 1c59b3360604dc27b842ab4bd13f2b469785b984 Mon Sep 17 00:00:00 2001 From: Ben West Date: Sun, 30 Aug 2015 12:06:24 -0700 Subject: [PATCH 746/937] add endpoint to explain how queries work Echo endpoint to debug queries. --- lib/api/entries/index.js | 24 ++++++++++++++++++++++++ lib/entries.js | 5 +++++ 2 files changed, 29 insertions(+) diff --git a/lib/api/entries/index.js b/lib/api/entries/index.js index afe856bf264..c9f05bcd8c3 100644 --- a/lib/api/entries/index.js +++ b/lib/api/entries/index.js @@ -173,6 +173,17 @@ function configure (app, wares, ctx) { api.get('/entries', query_models, format_entries); + function echo_query (req, res, next) { + var query = req.query; + var input = JSON.parse(JSON.stringify(query)); + + // If "?count=" is present, use that number to decided how many to return. + if (!query.count) { + query.count = 10; + } + + res.json({ query: ctx.entries.query_for(query), input: input, params: req.params}); + } function query_models (req, res, next) { var query = req.query; @@ -215,6 +226,19 @@ function configure (app, wares, ctx) { }); } + api.param('spec', function (req, res, next, spec) { + if (isId(spec)) { + prepReqModel(req, req.params.model); + req.query = {find: {_id: req.params.spec}}; + } else { + req.params.model = req.params.spec; + prepReqModel(req, req.params.model); + } + next( ); + }); + api.get('/echo/:spec', echo_query); + api.get('/echo', echo_query); + // Allow previewing your post content, just echos everything you // posted back out. api.post('/entries/preview', function (req, res, next) { diff --git a/lib/entries.js b/lib/entries.js index 5f2a85a5c7d..399d35363a3 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -123,6 +123,10 @@ function storage(env, ctx) { }); } + function query_for (opts) { + return find_options(opts, storage.queryOpts); + } + // closure to represent the API function api ( ) { // obtain handle usable for querying the collection associated @@ -137,6 +141,7 @@ function storage(env, ctx) { api.create = create; api.remove = remove; api.persist = persist; + api.query_for = query_for; api.getEntry = getEntry; api.indexedFields = [ 'date', 'type', 'sgv', 'sysTime', 'dateString' ]; return api; From 45ca876ef2b88dd98bb4c9cbd95005efdbb5c73f Mon Sep 17 00:00:00 2001 From: Ben West Date: Sun, 30 Aug 2015 12:10:54 -0700 Subject: [PATCH 747/937] add delete //.../entries to swagger --- swagger.yaml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/swagger.yaml b/swagger.yaml index aa240697244..67777d5e0b4 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -97,6 +97,26 @@ paths: description: Invalid input "200": description: Rejected list of entries. Empty list is success. + delete: + tags: + - Entries + summary: Delete entries matching query. + description: "Remove entries, same search syntax as GET." + operationId: remove + parameters: + - name: find + in: query + description: The query used to find entries, support nested query syntax, for example `find[dateString][$gte]=2015-08-27`. All find parameters are interpreted as strings. + required: false + type: string + - name: count + in: query + description: Number of entries to return. + required: false + type: number + responses: + "200": + description: Empty list is success. /treatments: get: From 5741758517020cb94376a98271d9b0457f6509ac Mon Sep 17 00:00:00 2001 From: Ben West Date: Sun, 30 Aug 2015 12:14:32 -0700 Subject: [PATCH 748/937] tweak jsdocs --- lib/query.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/query.js b/lib/query.js index 817e1f3a1be..dd69960a249 100644 --- a/lib/query.js +++ b/lib/query.js @@ -14,11 +14,13 @@ var TWO_DAYS = 172800000; * Options for query. * Interpret and return the options to use for building our query. * - * @returns Object Options for create, below. + * @returns {Object} Options for create, below. +``` * { deltaAgo: // ms ago to constrain queries missing any query body , dateField: "date" // name of field to ensure there is a valid query body , walker: // a mapping of names to types } +``` */ function default_options (opts) { opts = opts || { }; From aca4c6ea74a6e83c84b39db799f92067e3e925ae Mon Sep 17 00:00:00 2001 From: Ben West Date: Sun, 30 Aug 2015 12:52:38 -0700 Subject: [PATCH 749/937] re-use even more code --- lib/api/entries/index.js | 7 +++---- lib/entries.js | 4 ++-- lib/treatments.js | 8 +++++++- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/lib/api/entries/index.js b/lib/api/entries/index.js index c9f05bcd8c3..7227d2598e2 100644 --- a/lib/api/entries/index.js +++ b/lib/api/entries/index.js @@ -181,8 +181,9 @@ function configure (app, wares, ctx) { if (!query.count) { query.count = 10; } + var model = req.params.model || 'entries'; - res.json({ query: ctx.entries.query_for(query), input: input, params: req.params}); + res.json({ query: ctx[model].query_for(query), input: input, params: req.params, model: model}); } function query_models (req, res, next) { var query = req.query; @@ -231,13 +232,11 @@ function configure (app, wares, ctx) { prepReqModel(req, req.params.model); req.query = {find: {_id: req.params.spec}}; } else { - req.params.model = req.params.spec; prepReqModel(req, req.params.model); } next( ); }); - api.get('/echo/:spec', echo_query); - api.get('/echo', echo_query); + api.get('/echo/:model?/:spec?', echo_query); // Allow previewing your post content, just echos everything you // posted back out. diff --git a/lib/entries.js b/lib/entries.js index 43583004488..2d436252c5f 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -43,7 +43,7 @@ function storage(env, ctx) { // now just stitch them all together limit.call(collection - .find(find_options(opts, storage.queryOpts)) + .find(query_for(opts)) .sort(sort( )) ).toArray(toArray); }); @@ -51,7 +51,7 @@ function storage(env, ctx) { function remove (opts, fn) { with_collection(function (err, collection) { - collection.remove(find_options(opts, storage.queryOpts), fn); + collection.remove(query_for(opts), fn); }); } diff --git a/lib/treatments.js b/lib/treatments.js index 3adf50fbb87..11650c2867a 100644 --- a/lib/treatments.js +++ b/lib/treatments.js @@ -36,17 +36,23 @@ function storage (env, ctx) { function list (opts, fn) { return ctx.store.limit.call(api() - .find(find_options(opts, storage.queryOpts)) + .find(query_for(opts)) .sort(opts && opts.sort || {created_at: -1}), opts) .toArray(fn); } + function query_for (opts) { + return find_options(opts, storage.queryOpts); + } + + function api ( ) { return ctx.store.db.collection(env.treatments_collection); } api.list = list; api.create = create; + api.query_for = query_for; api.indexedFields = ['created_at', 'eventType', 'insulin', 'carbs', 'glucose']; return api; } From fc93b7362d1e0bd1ffbc0075a808f1d5011ef869 Mon Sep 17 00:00:00 2001 From: Ben West Date: Sun, 30 Aug 2015 13:10:29 -0700 Subject: [PATCH 750/937] improve echo api --- lib/api/entries/index.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/api/entries/index.js b/lib/api/entries/index.js index 7227d2598e2..1a89e5230cb 100644 --- a/lib/api/entries/index.js +++ b/lib/api/entries/index.js @@ -181,9 +181,9 @@ function configure (app, wares, ctx) { if (!query.count) { query.count = 10; } - var model = req.params.model || 'entries'; + var storage = req.params.echo || 'entries'; - res.json({ query: ctx[model].query_for(query), input: input, params: req.params, model: model}); + res.json({ query: ctx[storage].query_for(query), input: input, params: req.params, storage: storage}); } function query_models (req, res, next) { var query = req.query; @@ -236,7 +236,14 @@ function configure (app, wares, ctx) { } next( ); }); - api.get('/echo/:model?/:spec?', echo_query); + api.param('echo', function (req, res, next, echo) { + console.log('echo', echo); + if (!echo) { + req.params.echo = 'entries'; + } + next( ); + }); + api.get('/echo/:echo/:model?/:spec?', echo_query); // Allow previewing your post content, just echos everything you // posted back out. From 24db2153ad609c7e1786ee6e3e3fd9fda1f066cf Mon Sep 17 00:00:00 2001 From: Ben West Date: Sun, 30 Aug 2015 13:35:30 -0700 Subject: [PATCH 751/937] fix mqtt notifications on shared/hosted mqtt --- lib/mqtt.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/mqtt.js b/lib/mqtt.js index 7876cffc912..7994248c7a6 100644 --- a/lib/mqtt.js +++ b/lib/mqtt.js @@ -16,6 +16,7 @@ function init (env, ctx) { var username = info.auth.split(':').slice(0, -1).join(''); var shared_topic = '/downloads/' + username + '/#'; var alias_topic = '/downloads/' + username + '/protobuf'; + var notification_topic = '/notifications/' + username + '/json'; env.mqtt_shared_topic = shared_topic; mqtt.client = connect(env); @@ -58,10 +59,12 @@ function init (env, ctx) { mqtt.emitNotification = function emitNotification(notify) { console.info('Publishing notification to mqtt: ', notify); - mqtt.client.publish('/notifications/json', JSON.stringify(notify), function mqttCallback (err) { - if (err) { - console.error('Unable to publish notification to MQTT', err); - } + [notification_topic, '/notifications/json'].forEach(function iter_notify (topic) { + mqtt.client.publish(topic, JSON.stringify(notify), function mqttCallback (err) { + if (err) { + console.error('Unable to publish notification to MQTT', err); + } + }); }); }; From 00bb1509d1f776b834b5fcb84af493c5a663e4bd Mon Sep 17 00:00:00 2001 From: Ben West Date: Sun, 30 Aug 2015 14:33:32 -0700 Subject: [PATCH 752/937] test more of mqtt code path --- lib/mqtt.js | 3 ++- tests/mqtt.test.js | 41 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/lib/mqtt.js b/lib/mqtt.js index 7994248c7a6..a4f2e74ec2e 100644 --- a/lib/mqtt.js +++ b/lib/mqtt.js @@ -20,7 +20,7 @@ function init (env, ctx) { env.mqtt_shared_topic = shared_topic; mqtt.client = connect(env); - var downloads = downloader(); + var downloads = mqtt.downloads = downloader(); if (mqtt.client) { listenForMessages(ctx); @@ -309,4 +309,5 @@ function iter_mqtt_record_stream (packet, prop, sync) { return stream.pipe(es.map(map)); } +init.downloadProtobuf = downloadProtobuf; module.exports = init; diff --git a/tests/mqtt.test.js b/tests/mqtt.test.js index 44ffc8a4281..d6978745501 100644 --- a/tests/mqtt.test.js +++ b/tests/mqtt.test.js @@ -13,7 +13,15 @@ describe('mqtt', function ( ) { process.env.MONGO='mongodb://localhost/test_db'; process.env.MONGO_COLLECTION='test_sgvs'; self.env = require('../env')(); - self.mqtt = require('../lib/mqtt')(self.env, {}); + self.es = require('event-stream'); + self.results = self.es.through(function (ch) { this.queue(ch); }); + function outputs (fn) { + return self.es.writeArray(function (err, results) { + fn(err, results); + self.results.write(err || results); + }); + } + self.mqtt = require('../lib/mqtt')(self.env, {entries: { persist: outputs, create: self.results.write }, devicestatus: { create: self.results.write } }); }); after(function () { @@ -73,6 +81,37 @@ describe('mqtt', function ( ) { }); + it('downloadProtobuf should dispatch', function (done) { + + var payload = new Buffer('0a1108b70110d6d1fa6318f08df963200428011a1d323031352d30382d32335432323a35333a35352e3634392d30373a303020d7d1fa6328004a1508e0920b10c0850b18b20120d5d1fa6328ef8df963620a534d34313837393135306a053638393250', 'hex'); + + // var payload = self.mqtt.downloads.format(packet); + console.log('yaploda', '/downloads/protobuf', payload); + var l = [ ]; + self.results.on('data', function (chunk) { + console.log('test data', l.length, chunk.length, chunk); + l.push(chunk); + switch (l.length) { + case 0: // sgv + chunk.length.should.equal(1); + chunk[0].sgv.should.be.ok; + chunk[0].noise.should.be.ok; + chunk[0].date.should.be.ok; + chunk[0].dateString.should.be.ok; + chunk[0].type.should.equal('sgv'); + break; + case 1: // cal + break; + case 2: // meter + done( ); + break; + default: + break; + } + }); + self.mqtt.client.emit('message', '/downloads/protobuf', payload); + }); + it('merge sgvs and sensor records that match up, and get the sgvs that don\'t match', function (done) { var packet = { sgv: [ From 02f09a1953a1c77b9adff0a605ed4a313bc01515 Mon Sep 17 00:00:00 2001 From: Ben West Date: Sun, 30 Aug 2015 14:40:17 -0700 Subject: [PATCH 753/937] slightly more coverage? --- tests/mqtt.test.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/mqtt.test.js b/tests/mqtt.test.js index d6978745501..8694812d0eb 100644 --- a/tests/mqtt.test.js +++ b/tests/mqtt.test.js @@ -21,7 +21,11 @@ describe('mqtt', function ( ) { self.results.write(err || results); }); } - self.mqtt = require('../lib/mqtt')(self.env, {entries: { persist: outputs, create: self.results.write }, devicestatus: { create: self.results.write } }); + function written (data, fn) { + self.results.write(data); + fn( ); + } + self.mqtt = require('../lib/mqtt')(self.env, {entries: { persist: outputs, create: written }, devicestatus: { create: written } }); }); after(function () { From b2cfa9eb3687745870a3fb4896bec594b4a65c71 Mon Sep 17 00:00:00 2001 From: Ben West Date: Sun, 30 Aug 2015 14:45:55 -0700 Subject: [PATCH 754/937] use setTimeout to skip tick --- tests/mqtt.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/mqtt.test.js b/tests/mqtt.test.js index 8694812d0eb..2012e9367b2 100644 --- a/tests/mqtt.test.js +++ b/tests/mqtt.test.js @@ -23,7 +23,7 @@ describe('mqtt', function ( ) { } function written (data, fn) { self.results.write(data); - fn( ); + setTimeout(fn, 5); } self.mqtt = require('../lib/mqtt')(self.env, {entries: { persist: outputs, create: written }, devicestatus: { create: written } }); }); From afeabcecadd96f3a4216c1a037cbc02b1beb49e9 Mon Sep 17 00:00:00 2001 From: Ben West Date: Sun, 30 Aug 2015 15:00:43 -0700 Subject: [PATCH 755/937] better test for mqtt --- tests/mqtt.test.js | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/tests/mqtt.test.js b/tests/mqtt.test.js index 2012e9367b2..f2999742c0f 100644 --- a/tests/mqtt.test.js +++ b/tests/mqtt.test.js @@ -14,7 +14,7 @@ describe('mqtt', function ( ) { process.env.MONGO_COLLECTION='test_sgvs'; self.env = require('../env')(); self.es = require('event-stream'); - self.results = self.es.through(function (ch) { this.queue(ch); }); + self.results = self.es.through(function (ch) { this.push(ch); }); function outputs (fn) { return self.es.writeArray(function (err, results) { fn(err, results); @@ -93,25 +93,36 @@ describe('mqtt', function ( ) { console.log('yaploda', '/downloads/protobuf', payload); var l = [ ]; self.results.on('data', function (chunk) { - console.log('test data', l.length, chunk.length, chunk); l.push(chunk); + console.log('test data', l.length, chunk.length, chunk); switch (l.length) { - case 0: // sgv + case 0: // devicestatus + break; + case 2: // sgv + break; + case 3: // sgv chunk.length.should.equal(1); - chunk[0].sgv.should.be.ok; - chunk[0].noise.should.be.ok; - chunk[0].date.should.be.ok; - chunk[0].dateString.should.be.ok; - chunk[0].type.should.equal('sgv'); + var first = chunk[0]; + console.log("FIRST", first); + first.sgv.should.be.ok; + first.noise.should.be.ok; + first.date.should.be.ok; + first.dateString.should.be.ok; + first.type.should.equal('sgv'); break; - case 1: // cal + case 4: // cal break; - case 2: // meter - done( ); + case 1: // meter break; default: break; } + if (l.length >= 5) { + self.results.end( ); + } + }); + self.results.on('end', function (chunk) { + done( ); }); self.mqtt.client.emit('message', '/downloads/protobuf', payload); }); From 92f067bb2f348414e83eb5f85d8ee232f331e13d Mon Sep 17 00:00:00 2001 From: Ben West Date: Sun, 30 Aug 2015 15:56:15 -0700 Subject: [PATCH 756/937] few tweaks for codacy --- lib/api/entries/index.js | 2 +- lib/mqtt.js | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/lib/api/entries/index.js b/lib/api/entries/index.js index 1a89e5230cb..611f3a03045 100644 --- a/lib/api/entries/index.js +++ b/lib/api/entries/index.js @@ -173,7 +173,7 @@ function configure (app, wares, ctx) { api.get('/entries', query_models, format_entries); - function echo_query (req, res, next) { + function echo_query (req, res) { var query = req.query; var input = JSON.parse(JSON.stringify(query)); diff --git a/lib/mqtt.js b/lib/mqtt.js index a4f2e74ec2e..bfc001da9b6 100644 --- a/lib/mqtt.js +++ b/lib/mqtt.js @@ -130,8 +130,9 @@ function downloader ( ) { function downloadProtobuf (msg, topic, downloads, ctx) { var b = new Buffer(msg, 'binary'); console.log('BINARY', b.length, b.toString('hex')); + var packet; try { - var packet = downloads.parse(b); + packet = downloads.parse(b); if (!packet.type) { packet.type = topic; @@ -216,18 +217,18 @@ function toTimestamp (proto, receiver_time, download_time) { return obj; } +function timestamp (item) { + return toTimestamp(item, receiver_time, download_time.clone( )); +} + +function timeSort (a, b) { + return a.date - b.date; +} + function sgvSensorMerge (packet) { var receiver_time = packet.receiver_system_time_sec; var download_time = moment(packet.download_timestamp); - function timestamp (item) { - return toTimestamp(item, receiver_time, download_time.clone( )); - } - - function timeSort (a, b) { - return a.date - b.date; - } - var sgvs = (packet['sgv'] || []).map(function(sgv) { var timestamped = timestamp(sgv); return toSGV(sgv, timestamped); From e7662e4c09b4e819c2d7f6b83f4429a9fc3a7f96 Mon Sep 17 00:00:00 2001 From: Ben West Date: Sun, 30 Aug 2015 16:01:30 -0700 Subject: [PATCH 757/937] fix broken mqtt sgvSensorMerge Should fix tests. --- lib/mqtt.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/mqtt.js b/lib/mqtt.js index bfc001da9b6..0a4058e1a8e 100644 --- a/lib/mqtt.js +++ b/lib/mqtt.js @@ -217,8 +217,13 @@ function toTimestamp (proto, receiver_time, download_time) { return obj; } -function timestamp (item) { - return toTimestamp(item, receiver_time, download_time.clone( )); +function timestampFactory (packet) { + var receiver_time = packet.receiver_system_time_sec; + var download_time = moment(packet.download_timestamp); + function timestamp (item) { + return toTimestamp(item, receiver_time, download_time.clone( )); + } + return timestamp; } function timeSort (a, b) { @@ -226,9 +231,7 @@ function timeSort (a, b) { } function sgvSensorMerge (packet) { - var receiver_time = packet.receiver_system_time_sec; - var download_time = moment(packet.download_timestamp); - + var timestamp = timestampFactory(packet); var sgvs = (packet['sgv'] || []).map(function(sgv) { var timestamped = timestamp(sgv); return toSGV(sgv, timestamped); From 6cd5e72a0f601d3913763b1c994d27d2c5dfca1f Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 30 Aug 2015 16:44:03 -0700 Subject: [PATCH 758/937] beta3 bump --- bower.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index 09f505e7f2e..01d4f08d2ed 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "nightscout", - "version": "0.8.0-beta2", + "version": "0.8.0-beta3", "dependencies": { "angularjs": "1.3.0-beta.19", "bootstrap": "~3.2.0", diff --git a/package.json b/package.json index 21198271cd3..5c56ec6b0d8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Nightscout", - "version": "0.8.0-beta2", + "version": "0.8.0-beta3", "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": "AGPL3", "author": "Nightscout Team", From e143acddbffd68d72d00a9f1d9ba692063a68a6a Mon Sep 17 00:00:00 2001 From: Ben West Date: Sun, 30 Aug 2015 18:35:55 -0700 Subject: [PATCH 759/937] teach API to slice modal times of days Expand glob patterns to regexps, optimally, the fast prefix kind. For @jasoncalabrese, @stavior, @kenstack and friends. :-) --- lib/api/entries/index.js | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/lib/api/entries/index.js b/lib/api/entries/index.js index 611f3a03045..1922cc7aeb7 100644 --- a/lib/api/entries/index.js +++ b/lib/api/entries/index.js @@ -3,6 +3,7 @@ var consts = require('../../constants'); var es = require('event-stream'); var sgvdata = require('sgvdata'); +var expand = require('expand-braces'); var ID_PATTERN = /^[a-f\d]{24}$/; function isId (value) { @@ -245,6 +246,41 @@ function configure (app, wares, ctx) { }); api.get('/echo/:echo/:model?/:spec?', echo_query); + function prep_patterns (req, res, next) { + var pattern = [ ]; + if (req.params.prefix) { + pattern.push('^' + req.params.prefix); + } + if (req.params.regex) { + pattern.push('.*' + req.params.regex); + } + pattern = expand(pattern.join('')); + req.pattern = pattern; + var matches = pattern.map(function iter (pat) { + return new RegExp(pat); + }); + var query = { + dateString: { + $in: matches + } + }; + + if (req.query.find) { + if (req.query.find.dateString) { + req.query.find.dateString.$in = query.dateString.$in; + } else { + req.query.find.dateString = query.dateString; + } + } else { + req.query.find = query; + } + next( ); + } + api.get('/times/echo/:prefix?/:regex?', prep_patterns, function (req, res, next) { + res.json({ req: { params: req.params, query: req.query}, pattern: req.pattern}); + }); + api.get('/times/:prefix?/:regex?', prep_patterns, query_models, format_entries); + // Allow previewing your post content, just echos everything you // posted back out. api.post('/entries/preview', function (req, res, next) { From 7c11847033cc3f7acf2bef855dc5809cabf06aa2 Mon Sep 17 00:00:00 2001 From: Ben West Date: Sun, 30 Aug 2015 18:40:19 -0700 Subject: [PATCH 760/937] remember to include expand-braces in deps' --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 5c56ec6b0d8..904443549c7 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "d3": "^3.5.6", "errorhandler": "^1.1.1", "event-stream": "~3.1.5", + "expand-braces": "^0.1.1", "express": "^4.6.1", "express-extension-to-accept": "0.0.2", "forever": "~0.13.0", From 05eff3f604358c0f3a6eb56c6e22a1790273b365 Mon Sep 17 00:00:00 2001 From: Ben West Date: Sun, 30 Aug 2015 19:34:51 -0700 Subject: [PATCH 761/937] add more ways to slice time, update swagger to match --- lib/api/entries/index.js | 41 ++++++++++--- swagger.yaml | 129 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 161 insertions(+), 9 deletions(-) diff --git a/lib/api/entries/index.js b/lib/api/entries/index.js index 1922cc7aeb7..e91750feee2 100644 --- a/lib/api/entries/index.js +++ b/lib/api/entries/index.js @@ -194,7 +194,8 @@ function configure (app, wares, ctx) { query.count = 10; } - ctx.entries.list(query, function(err, entries) { + var storage = req.storage || ctx.entries; + storage.list(query, function(err, entries) { res.entries = entries; res.entries_err = err; return next( ); @@ -259,27 +260,49 @@ function configure (app, wares, ctx) { var matches = pattern.map(function iter (pat) { return new RegExp(pat); }); - var query = { - dateString: { + var field = req.patternField; + var query = { }; + query[field] = { $in: matches - } }; if (req.query.find) { - if (req.query.find.dateString) { - req.query.find.dateString.$in = query.dateString.$in; + if (req.query.find[field]) { + req.query.find[field].$in = query[field].$in; } else { - req.query.find.dateString = query.dateString; + req.query.find[field] = query[field]; } } else { req.query.find = query; } + if (req.params.type) { + req.query.find.type = req.params.type; + } + next( ); + } + + function prep_pattern_field (req, res, next) { + if (req.params.field) { + req.patternField = req.params.field; + } else { + req.patternField = 'dateString'; + } + next( ); + } + function prep_storage (req, res, next) { + if (req.params.storage) { + req.storage = ctx[req.params.storage]; + } else { + req.storage = ctx.entries; + } next( ); } - api.get('/times/echo/:prefix?/:regex?', prep_patterns, function (req, res, next) { + + api.get('/times/echo/:prefix?/:regex?', prep_storage, prep_pattern_field, prep_patterns, prep_patterns, function (req, res, next) { res.json({ req: { params: req.params, query: req.query}, pattern: req.pattern}); }); - api.get('/times/:prefix?/:regex?', prep_patterns, query_models, format_entries); + api.get('/times/:prefix?/:regex?', prep_storage, prep_pattern_field, prep_patterns, prep_patterns, query_models, format_entries); + api.get('/slice/:storage/:field/:type?/:prefix?/:regex?', prep_storage, prep_pattern_field, prep_patterns, query_models, format_entries); // Allow previewing your post content, just echos everything you // posted back out. diff --git a/swagger.yaml b/swagger.yaml index 67777d5e0b4..345c8382cd8 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -46,6 +46,135 @@ paths: schema: $ref: '#/definitions/Error' + /slice/{storage}/{field}/{type}/{prefix}/{regex}: + get: + summary: All Entries matching query + description: The Entries endpoint returns information about the Nightscout entries. + parameters: + - name: storage + in: path + type: string + description: Prefix to use in constructing a prefix-based regex, default is `entries`. + required: true + - name: field + in: path + type: string + description: Name of the field to use Regex against in query object, default is `dateString`. + required: true + - name: type + in: path + type: string + description: The type field to search against, default is sgv. + required: true + - name: prefix + in: path + type: string + description: Prefix to use in constructing a prefix-based regex. + required: false + - name: regex + in: path + type: string + description: Tail part of regexp to use in expanding/construccting a query object. + required: false + - name: find + in: query + description: The query used to find entries, support nested query syntax, for example `find[dateString][$gte]=2015-08-27`. All find paramertes are interpreted as strings. + required: false + type: string + - name: count + in: query + description: Number of entries to return. + required: false + type: number + tags: + - Entries + responses: + "200": + description: An array of entries + schema: + $ref: '#/definitions/Entries' + default: + description: Unexpected error + schema: + $ref: '#/definitions/Error' + + + /times/echo/{prefix}/{regex}: + get: + summary: Echo the query object to be used. + description: Echo debug information about the query object constructed. + parameters: + - name: prefix + in: path + type: string + description: Prefix to use in constructing a prefix-based regex. + required: false + - name: regex + in: path + type: string + description: Tail part of regexp to use in expanding/construccting a query object. + required: false + - name: find + in: query + description: The query used to find entries, support nested query syntax, for example `find[dateString][$gte]=2015-08-27`. All find paramertes are interpreted as strings. + required: false + type: string + - name: count + in: query + description: Number of entries to return. + required: false + type: number + tags: + - Entries + responses: + "200": + description: An array of entries + schema: + $ref: '#/definitions/Entries' + default: + description: Unexpected error + schema: + $ref: '#/definitions/Error' + + + /times/{prefix}/{regex}: + get: + summary: All Entries matching query + description: The Entries endpoint returns information about the Nightscout entries. + parameters: + - name: prefix + in: path + type: string + description: Prefix to use in constructing a prefix-based regex. + required: false + - name: regex + in: path + type: string + description: Tail part of regexp to use in expanding/construccting a query object. + required: false + - name: find + in: query + description: The query used to find entries, support nested query syntax, for example `find[dateString][$gte]=2015-08-27`. All find paramertes are interpreted as strings. + required: false + type: string + - name: count + in: query + description: Number of entries to return. + required: false + type: number + tags: + - Entries + responses: + "200": + description: An array of entries + schema: + $ref: '#/definitions/Entries' + default: + description: Unexpected error + schema: + $ref: '#/definitions/Error' + + /entries: get: summary: All Entries matching query From 8415a53da3f4dca336e30bf8d858d0fb3f0737d8 Mon Sep 17 00:00:00 2001 From: Ben West Date: Sun, 30 Aug 2015 19:39:40 -0700 Subject: [PATCH 762/937] add some sane defaults --- swagger.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/swagger.yaml b/swagger.yaml index 345c8382cd8..e1bf4cbe88d 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -56,16 +56,19 @@ paths: type: string description: Prefix to use in constructing a prefix-based regex, default is `entries`. required: true + default: entries - name: field in: path type: string description: Name of the field to use Regex against in query object, default is `dateString`. + default: dateString required: true - name: type in: path type: string description: The type field to search against, default is sgv. required: true + default: sgv - name: prefix in: path type: string From b6b83445d2cf7c8a2fc47a2daf246d2ac1539628 Mon Sep 17 00:00:00 2001 From: Ben West Date: Sun, 30 Aug 2015 19:53:59 -0700 Subject: [PATCH 763/937] fix typo --- swagger.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/swagger.yaml b/swagger.yaml index e1bf4cbe88d..3a409be3f1f 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -26,7 +26,7 @@ paths: required: true - name: find in: query - description: The query used to find entries, support nested query syntax, for example `find[dateString][$gte]=2015-08-27`. All find paramertes are interpreted as strings. + description: The query used to find entries, support nested query syntax, for example `find[dateString][$gte]=2015-08-27`. All find parameters are interpreted as strings. required: false type: string - name: count @@ -81,7 +81,7 @@ paths: required: false - name: find in: query - description: The query used to find entries, support nested query syntax, for example `find[dateString][$gte]=2015-08-27`. All find paramertes are interpreted as strings. + description: The query used to find entries, support nested query syntax, for example `find[dateString][$gte]=2015-08-27`. All find parameters are interpreted as strings. required: false type: string - name: count @@ -119,7 +119,7 @@ paths: required: false - name: find in: query - description: The query used to find entries, support nested query syntax, for example `find[dateString][$gte]=2015-08-27`. All find paramertes are interpreted as strings. + description: The query used to find entries, support nested query syntax, for example `find[dateString][$gte]=2015-08-27`. All find parameters are interpreted as strings. required: false type: string - name: count @@ -157,7 +157,7 @@ paths: required: false - name: find in: query - description: The query used to find entries, support nested query syntax, for example `find[dateString][$gte]=2015-08-27`. All find paramertes are interpreted as strings. + description: The query used to find entries, support nested query syntax, for example `find[dateString][$gte]=2015-08-27`. All find parameters are interpreted as strings. required: false type: string - name: count @@ -185,7 +185,7 @@ paths: parameters: - name: find in: query - description: The query used to find entries, support nested query syntax, for example `find[dateString][$gte]=2015-08-27`. All find paramertes are interpreted as strings. + description: The query used to find entries, support nested query syntax, for example `find[dateString][$gte]=2015-08-27`. All find parameters are interpreted as strings. required: false type: string - name: count @@ -264,7 +264,7 @@ paths: `find[insulin][$gte]=3` `find[carb][$gte]=100` `find[eventType]=Correction+Bolus` - All find paramertes are interpreted as strings. + All find parameters are interpreted as strings. required: false type: string - name: count From 3702e551be2b04f74d26cbf764acf100fea9a044 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Mon, 31 Aug 2015 08:15:23 +0200 Subject: [PATCH 764/937] food html code not hidden properly fix --- static/report/index.html | 4 ++-- static/report/js/report.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/static/report/index.html b/static/report/index.html index cfd3a330438..eb7ba373ba9 100644 --- a/static/report/index.html +++ b/static/report/index.html @@ -24,7 +24,7 @@

    Nightscout reporting

    Last 3 months - + Category: @@ -33,7 +33,7 @@

    Nightscout reporting

    - + diff --git a/static/report/js/report.js b/static/report/js/report.js index 9008380972c..4bbe43258ce 100644 --- a/static/report/js/report.js +++ b/static/report/js/report.js @@ -131,7 +131,7 @@ function enableFoodGUI( ) { $('#info').html(''); - $('#rp_foodgui').css('display',''); + $('.rp_foodgui').css('display',''); $('#rp_food').change(function (event) { $('#rp_enablefood').prop('checked',true); return maybePreventDefault(event); @@ -140,7 +140,7 @@ function disableFoodGUI(){ $('#info').html(''); - $('#rp_foodgui').css('display','none'); + $('.rp_foodgui').css('display','none'); } // ****** FOOD CODE END ****** From 72d66e576149d2cc9e2a8476c6dfff6d1f3939e3 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Mon, 31 Aug 2015 11:07:24 +0200 Subject: [PATCH 765/937] Fixed bug drawing was called multiple times --- static/report/js/report.js | 45 ++++++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/static/report/js/report.js b/static/report/js/report.js index 4bbe43258ce..6e0face8541 100644 --- a/static/report/js/report.js +++ b/static/report/js/report.js @@ -6,7 +6,6 @@ // - check everything is translated // - add tests // - optimize merging data inside every plugin -// - pressing Show 2nd time generates d3 errors, previous graphs are not removed // - XMLHttpRequest - > $.ajax in treatments.js // - finish TREATMENT_AUTH in careportal @@ -384,19 +383,19 @@ if (day===5 && $('#rp_fr').is(':checked')) { daystoshow[d]++; } if (day===6 && $('#rp_sa').is(':checked')) { daystoshow[d]++; } } + countDays(); display(); } function display() { - console.log('Total: ',daystoshow,'Needed: ',matchesneeded); - var displayeddays = 0; + var count = 0; $('#info').html(''+translate('Loading')+' ...'); for (var d in daystoshow) { if (daystoshow[d]===matchesneeded) { - if (displayeddays < maxdays) { + if (count < maxdays) { $('#info').append($('
    ')); - loadData(d,options); - displayeddays++; + count++; + loadData(d, options, dataLoadedCallback); } else { $('#info').append($('
    '+d+' '+translate('not displayed')+'.
    ')); } @@ -404,12 +403,33 @@ delete daystoshow[d]; } } - if (displayeddays===0) { + if (count===0) { $('#info').html(''+translate('Result is empty')+''); $('#rp_show').css('display',''); } } + var dayscount = 0; + var loadeddays = 0; + + function countDays() { + for (var d in daystoshow) { + if (daystoshow[d]===matchesneeded) { + if (dayscount < maxdays) { + dayscount++; + } + } + } + console.log('Total: ', daystoshow, 'Matches needed: ', matchesneeded, 'Will be loaded: ', dayscount); + } + + function dataLoadedCallback () { + loadeddays++; + if (loadeddays === dayscount) { + showreports(options); + } + } + $('#rp_show').css('display','none'); daystoshow = {}; @@ -427,6 +447,7 @@ report_plugins.eachPlugin(function (plugin) { // jquery plot doesn't draw to hidden div $('#'+plugin.name+'-placeholder').css('display',''); + //console.log('Drawing ',plugin.name); plugin.report(datastorage,daystoshow,options); if (!$('#'+plugin.name).hasClass('selected')) { $('#'+plugin.name+'-placeholder').css('display','none'); @@ -454,10 +475,10 @@ return maybePreventDefault(event); } - function loadData(day,options) { + function loadData(day, options, callback) { // check for loaded data if (datastorage[day] && day !== moment().format('YYYY-MM-DD')) { - showreports(options); + callback(); return; } // patientData = [actual, predicted, mbg, treatment, cal, devicestatusData]; @@ -536,13 +557,13 @@ } }).done(function () { $('#info-' + day).html(''+translate('Processing data of')+' '+day+' ...'); - processData(data,day,options); + processData(data, day, options, callback); }); }); } - function processData(data,day,options) { + function processData(data, day, options, callback) { // treatments data.treatments.forEach(function (d) { if (parseFloat(d.insulin) > maxInsulinValue) { @@ -602,7 +623,7 @@ datastorage[day] = data; options.maxInsulinValue = maxInsulinValue; options.maxCarbsValue = maxCarbsValue; - showreports(options); + callback(); } function maybePreventDefault(event) { From 55e8d92ad95b2d12a34d02b245f13c2b45c589e7 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Mon, 31 Aug 2015 11:31:03 +0200 Subject: [PATCH 766/937] translation fix in dailystats --- lib/report_plugins/dailystats.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/report_plugins/dailystats.js b/lib/report_plugins/dailystats.js index 838fed41692..7f72e0997e9 100644 --- a/lib/report_plugins/dailystats.js +++ b/lib/report_plugins/dailystats.js @@ -108,7 +108,7 @@ dailystats.report = function report_dailystats(datastorage,daystoshow,options) { data: Math.floor(stats.lows * 1000 / daysRecords.length) / 10 }, { - label: translate('In range'), + label: translate('In Range'), data: Math.floor(stats.normal * 1000 / daysRecords.length) / 10 }, { From f2e047f73b6da1a18e0158ed581875cefa66f305 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Mon, 31 Aug 2015 13:36:12 +0200 Subject: [PATCH 767/937] optimized merging data in reports --- lib/report_plugins/glucosedistribution.js | 8 ++------ lib/report_plugins/hourlystats.js | 8 ++------ lib/report_plugins/percentile.js | 8 ++------ lib/report_plugins/success.js | 8 ++------ static/report/js/report.js | 15 ++++++++------- 5 files changed, 16 insertions(+), 31 deletions(-) diff --git a/lib/report_plugins/glucosedistribution.js b/lib/report_plugins/glucosedistribution.js index 68c6bd173d7..27a226c27bb 100644 --- a/lib/report_plugins/glucosedistribution.js +++ b/lib/report_plugins/glucosedistribution.js @@ -50,12 +50,8 @@ glucosedistribution.report = function report_glucosedistribution(datastorage,day $(''+translate('A1c estimation*')+'').appendTo(thead); thead.appendTo(table); - var data = []; - var days = 0; - Object.keys(daystoshow).forEach(function (day) { - data = data.concat(datastorage[day].statsrecords); - days++; - }); + var data = datastorage.allstatsrecords; + var days = datastorage.alldays; $('#glucosedistribution-days').text(days+' '+translate('days total')); diff --git a/lib/report_plugins/hourlystats.js b/lib/report_plugins/hourlystats.js index 353a0ba4ec9..0fbe1d2b655 100644 --- a/lib/report_plugins/hourlystats.js +++ b/lib/report_plugins/hourlystats.js @@ -31,12 +31,8 @@ hourlystats.report = function report_hourlystats(datastorage, daystoshow, option var stats = []; var pivotedByHour = {}; - var data = []; - var days = 0; - Object.keys(daystoshow).forEach(function (day) { - data = data.concat(datastorage[day].statsrecords); - days++; - }); + var data = datastorage.allstatsrecords; + var days = datastorage.alldays; for (var i = 0; i < 24; i++) { pivotedByHour[i] = []; diff --git a/lib/report_plugins/percentile.js b/lib/report_plugins/percentile.js index fa52cefcffc..5fb748a0eb8 100644 --- a/lib/report_plugins/percentile.js +++ b/lib/report_plugins/percentile.js @@ -30,12 +30,8 @@ percentile.report = function report_percentile(datastorage,daystoshow,options) { var minutewindow = 30; //minute-window should be a divisor of 60 - var data = []; - var days = 0; - Object.keys(daystoshow).forEach(function (day) { - data = data.concat(datastorage[day].statsrecords); - days++; - }); + var data = datastorage.allstatsrecords; + var days = datastorage.alldays; var bins = []; for (var hour = 0; hour < 24; hour++) { diff --git a/lib/report_plugins/success.js b/lib/report_plugins/success.js index bdd079600bc..748898906b7 100644 --- a/lib/report_plugins/success.js +++ b/lib/report_plugins/success.js @@ -29,12 +29,8 @@ success.report = function report_success(datastorage,daystoshow,options) { var low = parseInt(options.targetLow), high = parseInt(options.targetHigh); - var data = []; - var days = 0; - Object.keys(daystoshow).forEach(function (day) { - data = data.concat(datastorage[day].statsrecords); - days++; - }); + var data = datastorage.allstatsrecords; + var days = datastorage.alldays; var now = Date.now(); var period = (7).days(); diff --git a/static/report/js/report.js b/static/report/js/report.js index 6e0face8541..a117a14ba2a 100644 --- a/static/report/js/report.js +++ b/static/report/js/report.js @@ -5,7 +5,6 @@ // - load css dynamic + optimize // - check everything is translated // - add tests -// - optimize merging data inside every plugin // - XMLHttpRequest - > $.ajax in treatments.js // - finish TREATMENT_AUTH in careportal @@ -438,12 +437,14 @@ } function showreports(options) { - // wait for all loads - for (var d in daystoshow) { - if (!datastorage[d]) { - return; // all data not loaded yet - } - } + // prepare some data used in more reports + datastorage.allstatsrecords = []; + datastorage.alldays = 0; + Object.keys(daystoshow).forEach(function (day) { + datastorage.allstatsrecords = datastorage.allstatsrecords.concat(datastorage[day].statsrecords); + datastorage.alldays++; + }); + report_plugins.eachPlugin(function (plugin) { // jquery plot doesn't draw to hidden div $('#'+plugin.name+'-placeholder').css('display',''); From 0cbdf57aa9e92d8d37df909b9823926e960241f2 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Mon, 31 Aug 2015 13:45:38 +0200 Subject: [PATCH 768/937] added missing translation --- lib/language.js | 3 +++ lib/report_plugins/glucosedistribution.js | 2 +- static/report/js/report.js | 1 - 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/language.js b/lib/language.js index 157e3575c57..05d713c2bc5 100644 --- a/lib/language.js +++ b/lib/language.js @@ -3654,6 +3654,9 @@ function init() { ,'Edit treatment' : { cs: 'Upravit ošetření' } + ,'This is only a rough estimation that can be very inaccurate and does not replace actual blood testing. The formula used is taken from: Nathan, David M., et al. "Translating the A1C assay into estimated average glucose values." Diabetes care 31.8 (2008): 1473-1478.' : { + cs: 'Toto je pouze hrubý odhad, který může být velmi nepřesný a nenahrazuje měření z krve. Vzorec je převzatý z: Nathan, David M., et al. "Translating the A1C assay into estimated average glucose values." Diabetes care 31.8 (2008): 1473-1478.' + } }; diff --git a/lib/report_plugins/glucosedistribution.js b/lib/report_plugins/glucosedistribution.js index 27a226c27bb..ce03b12d791 100644 --- a/lib/report_plugins/glucosedistribution.js +++ b/lib/report_plugins/glucosedistribution.js @@ -25,7 +25,7 @@ glucosedistribution.html = function html(client) { + '
    ' + '
    ' + '
    ' - + translate('* This is only a rough estimation that can be very inaccurate and does not replace actual blood testing. The formula used is taken from: Nathan, David M., et al. "Translating the A1C assay into estimated average glucose values." Diabetes care 31.8 (2008): 1473-1478.') + + '* ' + translate('This is only a rough estimation that can be very inaccurate and does not replace actual blood testing. The formula used is taken from: Nathan, David M., et al. "Translating the A1C assay into estimated average glucose values." Diabetes care 31.8 (2008): 1473-1478.') ; return ret; }; diff --git a/static/report/js/report.js b/static/report/js/report.js index a117a14ba2a..25d26c79137 100644 --- a/static/report/js/report.js +++ b/static/report/js/report.js @@ -3,7 +3,6 @@ // - make axis on daytoday better working with thresholds // - get rid of /static/report/js/time.js // - load css dynamic + optimize -// - check everything is translated // - add tests // - XMLHttpRequest - > $.ajax in treatments.js // - finish TREATMENT_AUTH in careportal From 57fe7dc9c3ef7b98ddfd675159f1653677b1a269 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Mon, 31 Aug 2015 15:13:58 +0200 Subject: [PATCH 769/937] TREATMENT_AUTH handling, added authentication status to careportal --- lib/api/treatments/index.js | 4 +++- lib/client/careportal.js | 10 ++++++++-- lib/report_plugins/treatments.js | 12 ++++++------ static/index.html | 3 +++ static/report/js/report.js | 2 -- 5 files changed, 20 insertions(+), 11 deletions(-) diff --git a/lib/api/treatments/index.js b/lib/api/treatments/index.js index c6fd36f4c8b..69a6087b368 100644 --- a/lib/api/treatments/index.js +++ b/lib/api/treatments/index.js @@ -28,13 +28,15 @@ function configure (app, wares, ctx) { var treatment = req.body; ctx.treatments.create(treatment, function (err, created) { if (err) { + console.log('Error adding treatment'); res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', err); } else { + console.log('Treatment created'); res.json(created); } }); } - if (app.treatments_auth) { + if (app.settings.treatments_auth) { api.post('/treatments/', wares.verifyAuthorization, post_response); } else { api.post('/treatments/', post_response); diff --git a/lib/client/careportal.js b/lib/client/careportal.js index fa766497590..29a32fc6dad 100644 --- a/lib/client/careportal.js +++ b/lib/client/careportal.js @@ -126,10 +126,16 @@ function init (client, $) { if (window.confirm(buildConfirmText(data))) { $.ajax({ method: 'POST', - url: '/api/v1/treatments/', - data: data + url: '/api/v1/treatments/' + , headers: { + 'api-secret': client.hashauth.hash() + } + , data: data }).done(function treatmentSaved (response) { console.info('treatment saved', response); + }).fail(function treatmentSaveFail (response) { + console.info('treatment saved', response); + alert(translate('Entering record failed') + '. ' + translate('Status') + ': ' + response.status); }); storage.set('enteredBy', data.enteredBy); diff --git a/lib/report_plugins/treatments.js b/lib/report_plugins/treatments.js index 9f058a01fc1..a583d63cb4e 100644 --- a/lib/report_plugins/treatments.js +++ b/lib/report_plugins/treatments.js @@ -69,12 +69,17 @@ treatments.report = function report_treatments(datastorage, daystoshow, options) var report_plugins = Nightscout.report_plugins; function deleteTreatment(event) { + if (!client.hashauth.isAuthenticated()) { + alert(translate('Your device is not authenticated yet')); + return false; + } + var data = JSON.parse($(this).attr('data')); var day = $(this).attr('day'); var ok = window.confirm( translate('Delete this treatment?')+'\n' + - '\n'+translate('Event Type')+': ' + data.eventType + + '\n'+translate('Event Type')+': ' + translate(resolveEventName(data.eventType)) + (data.glucose ? '\n'+translate('Blood Glucose')+': ' + data.glucose : '')+ (data.glucoseType ? '\n'+translate('Method')+': ' + data.glucoseType : '')+ (data.carbs ? '\n'+translate('Carbs Given')+': ' + data.carbs : '' )+ @@ -97,11 +102,6 @@ treatments.report = function report_treatments(datastorage, daystoshow, options) } function deleteTreatmentRecord(_id) { - if (!client.hashauth.isAuthenticated()) { - alert(translate('Your device is not authenticated yet')); - return false; - } - var xhr = new XMLHttpRequest(); xhr.open('DELETE', '/api/v1/treatments/'+_id, true); xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); diff --git a/static/index.html b/static/index.html index ab6d97b6c89..3b607b054dc 100644 --- a/static/index.html +++ b/static/index.html @@ -243,6 +243,9 @@
    View all treatments +
    + Authentication status:
    +
  • diff --git a/static/report/js/report.js b/static/report/js/report.js index 25d26c79137..2d3d4a774c1 100644 --- a/static/report/js/report.js +++ b/static/report/js/report.js @@ -5,8 +5,6 @@ // - load css dynamic + optimize // - add tests // - XMLHttpRequest - > $.ajax in treatments.js -// - finish TREATMENT_AUTH in careportal - (function () { 'use strict'; From 2d5e8f3ba8a3285b6403757d3222fc798508d5c2 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Mon, 31 Aug 2015 21:38:21 +0200 Subject: [PATCH 770/937] fixed mock func in test --- tests/careportal.test.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/careportal.test.js b/tests/careportal.test.js index e71251ae9da..34539eb2aec 100644 --- a/tests/careportal.test.js +++ b/tests/careportal.test.js @@ -57,6 +57,10 @@ describe('client', function ( ) { done: function mockDone (fn) { fn(); done(); + return self.$.ajax(); + } + , fail: function mockFail (fn) { + return self.$.ajax(); } }; }; From 8728f0bba60b6fcc614490543841a9296374cd85 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Mon, 31 Aug 2015 23:20:51 +0200 Subject: [PATCH 771/937] treatments edit/save needed changes, XMLHttp -> $.ajax --- lib/api/treatments/index.js | 2 +- lib/language.js | 3 ++ lib/report_plugins/treatments.js | 50 ++++++++++++++++++-------------- lib/treatments.js | 9 ++++-- static/report/js/report.js | 3 +- 5 files changed, 40 insertions(+), 27 deletions(-) diff --git a/lib/api/treatments/index.js b/lib/api/treatments/index.js index 69a6087b368..65a7cccdba6 100644 --- a/lib/api/treatments/index.js +++ b/lib/api/treatments/index.js @@ -57,7 +57,7 @@ function configure (app, wares, ctx) { console.log(err); } else { res.json(created); - console.log('Treatment saved'); + console.log('Treatment saved', data); } }); }); diff --git a/lib/language.js b/lib/language.js index 05d713c2bc5..57b59a11898 100644 --- a/lib/language.js +++ b/lib/language.js @@ -3657,6 +3657,9 @@ function init() { ,'This is only a rough estimation that can be very inaccurate and does not replace actual blood testing. The formula used is taken from: Nathan, David M., et al. "Translating the A1C assay into estimated average glucose values." Diabetes care 31.8 (2008): 1473-1478.' : { cs: 'Toto je pouze hrubý odhad, který může být velmi nepřesný a nenahrazuje měření z krve. Vzorec je převzatý z: Nathan, David M., et al. "Translating the A1C assay into estimated average glucose values." Diabetes care 31.8 (2008): 1473-1478.' } + ,'Saving record failed' : { + cs: 'Uložení záznamu selhalo' + } }; diff --git a/lib/report_plugins/treatments.js b/lib/report_plugins/treatments.js index a583d63cb4e..c1b3e8fcf06 100644 --- a/lib/report_plugins/treatments.js +++ b/lib/report_plugins/treatments.js @@ -102,16 +102,18 @@ treatments.report = function report_treatments(datastorage, daystoshow, options) } function deleteTreatmentRecord(_id) { - var xhr = new XMLHttpRequest(); - xhr.open('DELETE', '/api/v1/treatments/'+_id, true); - xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); - xhr.setRequestHeader('api-secret', client.hashauth.hash()); - xhr.onload = function () { - if (xhr.statusText!=='OK') { - alert(translate('Deleting record failed')); + $.ajax({ + method: 'DELETE' + , url: '/api/v1/treatments/' + _id + , headers: { + 'api-secret': client.hashauth.hash() } - }; - xhr.send(null); + }).done(function treatmentDeleted (response) { + console.info('treatment deleted', response); + }).fail(function treatmentDeleteFail (response) { + console.info('treatment delete failed', response); + alert(translate('Deleting record failed') + '. ' + translate('Status') + ': ' + response.status); + }); return true; } @@ -139,8 +141,10 @@ treatments.report = function report_treatments(datastorage, daystoshow, options) data.insulin = $('#rped_insulinGiven').val(); data.notes = $('#rped_adnotes').val(); data.enteredBy = $('#rped_enteredBy').val(); - data.created_at = client.utils.mergeInputTime($('#rped_eventTimeValue').val(), $('#rped_eventDateValue').val()); + data.eventTime = new Date(client.utils.mergeInputTime($('#rped_eventTimeValue').val(), $('#rped_eventDateValue').val())).toISOString(); data.units = options.units; + delete data.mills; + delete data.created_at; $( this ).dialog('close'); saveTreatmentRecord(data); delete datastorage[day]; @@ -183,18 +187,20 @@ treatments.report = function report_treatments(datastorage, daystoshow, options) return false; } - var dataJson = JSON.stringify(data, null, ' '); - - var xhr = new XMLHttpRequest(); - xhr.open('PUT', '/api/v1/treatments/', true); - xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); - xhr.setRequestHeader('api-secret', client.hashauth.hash()); - xhr.onload = function () { - if (xhr.statusText!=='OK') { - alert(translate('Saving record failed')); - } - }; - xhr.send(dataJson); + $.ajax({ + method: 'PUT' + , url: '/api/v1/treatments/' + , headers: { + 'api-secret': client.hashauth.hash() + } + , data: data + }).done(function treatmentSaved (response) { + console.info('treatment saved', response); + }).fail(function treatmentSaveFail (response) { + console.info('treatment save failed', response); + alert(translate('Saving record failed') + '. ' + translate('Status') + ': ' + response.status); + }); + return true; } diff --git a/lib/treatments.js b/lib/treatments.js index 000645ad361..623deae33e4 100644 --- a/lib/treatments.js +++ b/lib/treatments.js @@ -43,12 +43,17 @@ function storage (env, ctx) { } function remove (_id, fn) { - return api( ).remove({ '_id': new ObjectID(_id) }, fn); + api( ).remove({ '_id': new ObjectID(_id) }, fn); + + ctx.bus.emit('data-received'); } function save (obj, fn) { obj._id = new ObjectID(obj._id); + prepareData(obj); api().save(obj, fn); + + ctx.bus.emit('data-received'); } @@ -84,7 +89,7 @@ function prepareData(obj) { } obj.created_at = results.created_at.toISOString(); - if (obj.preBolus !== 0 && obj.carbs) { + if (obj.preBolus && obj.preBolus !== 0 && obj.carbs) { results.preBolusCarbs = obj.carbs; delete obj.carbs; } diff --git a/static/report/js/report.js b/static/report/js/report.js index 2d3d4a774c1..c8db0cedf94 100644 --- a/static/report/js/report.js +++ b/static/report/js/report.js @@ -1,10 +1,9 @@ // TODO: // - bypass nightmode in reports -// - make axis on daytoday better working with thresholds // - get rid of /static/report/js/time.js // - load css dynamic + optimize // - add tests -// - XMLHttpRequest - > $.ajax in treatments.js +// - on save/delete treatment ctx.bus.emit('data-received'); is not enough. we must add something like 'data-updated' (function () { 'use strict'; From 73eacf0f788cae1fc3519490497247d73dce4192 Mon Sep 17 00:00:00 2001 From: Ben West Date: Mon, 31 Aug 2015 17:02:37 -0700 Subject: [PATCH 772/937] attempt fixing swagger? --- swagger.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/swagger.yaml b/swagger.yaml index 3a409be3f1f..c504ceaf200 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -74,11 +74,13 @@ paths: type: string description: Prefix to use in constructing a prefix-based regex. required: false + default: 2015 - name: regex in: path type: string description: Tail part of regexp to use in expanding/construccting a query object. required: false + default: .* - name: find in: query description: The query used to find entries, support nested query syntax, for example `find[dateString][$gte]=2015-08-27`. All find parameters are interpreted as strings. From 093712b860477633d34e89a1f726aa63b47a5678 Mon Sep 17 00:00:00 2001 From: Ben West Date: Mon, 31 Aug 2015 17:35:44 -0700 Subject: [PATCH 773/937] tweak swagger yaml --- swagger.yaml | 60 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 16 deletions(-) diff --git a/swagger.yaml b/swagger.yaml index c504ceaf200..4b14217f8d2 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -17,16 +17,26 @@ paths: /entries/{spec}: get: summary: All Entries matching query - description: The Entries endpoint returns information about the Nightscout entries. + description: | + The Entries endpoint returns information about the + Nightscout entries. + parameters: - name: spec in: path type: string - description: entry id, such as '55cf81bc436037528ec75fa5' or a type filter such as 'sgv' + description: | + entry id, such as `55cf81bc436037528ec75fa5` or a type filter such + as `sgv`, `mbg`, etc. + + default: sgv required: true - name: find in: query - description: The query used to find entries, support nested query syntax, for example `find[dateString][$gte]=2015-08-27`. All find parameters are interpreted as strings. + description: | + The query used to find entries, support nested query syntax, for + example `find[dateString][$gte]=2015-08-27`. All find parameters + are interpreted as strings. required: false type: string - name: count @@ -42,9 +52,9 @@ paths: schema: $ref: '#/definitions/Entries' default: - description: Unexpected error + description: Entries schema: - $ref: '#/definitions/Error' + $ref: '#/definitions/Entries' /slice/{storage}/{field}/{type}/{prefix}/{regex}: get: @@ -73,17 +83,25 @@ paths: in: path type: string description: Prefix to use in constructing a prefix-based regex. - required: false - default: 2015 + required: true + default: '2015' - name: regex in: path type: string - description: Tail part of regexp to use in expanding/construccting a query object. - required: false + description: | + Tail part of regexp to use in expanding/construccting a query object. + Regexp also has bash-style brace and glob expansion applied to it, + creating ways to search for modal times of day, perhaps using + something like this syntax: `T{15..17}:.*`, this would search for + all records from 3pm to 5pm. + required: true default: .* - name: find in: query - description: The query used to find entries, support nested query syntax, for example `find[dateString][$gte]=2015-08-27`. All find parameters are interpreted as strings. + description: | + The query used to find entries, support nested query syntax, for + example `find[dateString][$gte]=2015-08-27`. All find parameters + are interpreted as strings. required: false type: string - name: count @@ -113,12 +131,17 @@ paths: in: path type: string description: Prefix to use in constructing a prefix-based regex. - required: false + required: true - name: regex in: path type: string - description: Tail part of regexp to use in expanding/construccting a query object. - required: false + description: | + Tail part of regexp to use in expanding/construccting a query object. + Regexp also has bash-style brace and glob expansion applied to it, + creating ways to search for modal times of day, perhaps using + something like this syntax: `T{15..17}:.*`, this would search for + all records from 3pm to 5pm. + required: true - name: find in: query description: The query used to find entries, support nested query syntax, for example `find[dateString][$gte]=2015-08-27`. All find parameters are interpreted as strings. @@ -151,12 +174,17 @@ paths: in: path type: string description: Prefix to use in constructing a prefix-based regex. - required: false + required: true - name: regex in: path type: string - description: Tail part of regexp to use in expanding/construccting a query object. - required: false + description: | + Tail part of regexp to use in expanding/construccting a query object. + Regexp also has bash-style brace and glob expansion applied to it, + creating ways to search for modal times of day, perhaps using + something like this syntax: `T{15..17}:.*`, this would search for + all records from 3pm to 5pm. + required: true - name: find in: query description: The query used to find entries, support nested query syntax, for example `find[dateString][$gte]=2015-08-27`. All find parameters are interpreted as strings. From 77a1a4241cc9bb7061de31d157437fd73b40c4e2 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 31 Aug 2015 19:59:19 -0700 Subject: [PATCH 774/937] check if value from local storage is defined instead or being truthy --- lib/client/browser-settings.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/client/browser-settings.js b/lib/client/browser-settings.js index 4b1b6d47d93..1d58d9adbd6 100644 --- a/lib/client/browser-settings.js +++ b/lib/client/browser-settings.js @@ -141,7 +141,8 @@ function init (client, plugins, serverSettings, $) { try { settings.eachSetting(function setEach (name) { - return storage.get(name) || serverSettings.settings[name]; + var stored = storage.get(name); + return stored !== undefined ? storage.get(name) : serverSettings.settings[name]; }); } catch(err) { console.error(err); From 53f9c99fb376d31007a2e82b295c1842c4abd8ea Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Tue, 1 Sep 2015 19:40:04 +0300 Subject: [PATCH 775/937] Finnish localization --- lib/language.js | 263 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 263 insertions(+) diff --git a/lib/language.js b/lib/language.js index 4fc95d7cbd7..fa061b0e786 100644 --- a/lib/language.js +++ b/lib/language.js @@ -21,6 +21,7 @@ function init() { , { code: 'pt', language: 'Português (Brasil)' } , { code: 'ro', language: 'Română' } , { code: 'sv', language: 'Svenska' } + , { code: 'fi', language: 'Suomi' } ]; var translations = { @@ -36,6 +37,7 @@ function init() { ,hr: 'Slušanje na portu' ,it: 'Porta in ascolto' ,dk: 'Lytter på port' + ,fi: 'Kuuntelen porttia' ,nb: 'Lytter på port' } // Client @@ -51,6 +53,7 @@ function init() { ,hr: 'Pon' ,it: 'Lun' ,dk: 'Man' + ,fi: 'Ma' ,nb: 'Man' } ,'Tu' : { @@ -65,6 +68,7 @@ function init() { ,hr: 'Ut' ,it: 'Mar' ,dk: 'Tir' + ,fi: 'Ti' ,nb: 'Tir' }, ',We' : { @@ -79,6 +83,7 @@ function init() { ,hr: 'Sri' ,it: 'Mer' ,dk: 'Ons' + ,fi: 'Ke' ,nb: 'Ons' } ,'Th' : { @@ -93,6 +98,7 @@ function init() { ,hr: 'Čet' ,it: 'Gio' ,dk: 'Tor' + ,fi: 'To' ,no: 'Tor' } ,'Fr' : { @@ -107,6 +113,7 @@ function init() { ,hr: 'Pet' ,it: 'Ven' ,dk: 'Fre' + ,fi: 'Pe' ,nb: 'Fre' } ,'Sa' : { @@ -121,6 +128,7 @@ function init() { ,hr: 'Sub' ,it: 'Sab' ,dk: 'Lør' + ,fi: 'La' ,nb: 'Lør' } ,'Su' : { @@ -135,6 +143,7 @@ function init() { ,hr: 'Ned' ,it: 'Dom' ,dk: 'Søn' + ,fi: 'Su' ,nb: 'Søn' } ,'Monday' : { @@ -149,6 +158,7 @@ function init() { ,hr: 'Ponedjeljak' ,it: 'Lunedì' ,dk: 'Mandag' + ,fi: 'Maanantai' ,nb: 'Mandag' } ,'Tuesday' : { @@ -163,6 +173,7 @@ function init() { ,sv: 'Tisdag' ,it: 'Martedì' ,dk: 'Tirsdag' + ,fi: 'Tiistai' ,nb: 'Tirsdag' } ,'Wednesday' : { @@ -177,6 +188,7 @@ function init() { ,hr: 'Srijeda' ,it: 'Mercoledì' ,dk: 'Onsdag' + ,fi: 'Keskiviikko' ,nb: 'Onsdag' } ,'Thursday' : { @@ -191,6 +203,7 @@ function init() { ,hr: 'Četvrtak' ,it: 'Giovedì' ,dk: 'Torsdag' + ,fi: 'Torstai' ,nb: 'Torsdag' } ,'Friday' : { @@ -205,6 +218,7 @@ function init() { ,hr: 'Petak' ,it: 'Venerdì' ,dk: 'Fredag' + ,fi: 'Perjantai' ,nb: 'Fredag' } ,'Saturday' : { @@ -219,6 +233,7 @@ function init() { ,sv: 'Lördag' ,it: 'Sabato' ,dk: 'Lørdag' + ,fi: 'Lauantai' ,nb: 'Lørdag' } ,'Sunday' : { @@ -233,6 +248,7 @@ function init() { ,sv: 'Söndag' ,it: 'Domenica' ,dk: 'Søndag' + ,fi: 'Sunnuntai' ,nb: 'Søndag' } ,'Category' : { @@ -247,6 +263,7 @@ function init() { ,hr: 'Kategorija' ,it:'Categoria' ,dk: 'Kategori' + ,fi: 'Luokka' ,nb: 'Kategori' } ,'Subcategory' : { @@ -261,6 +278,7 @@ function init() { ,hr: 'Podkategorija' ,it: 'Sottocategoria' ,dk: 'Underkategori' + ,fi: 'Alaluokka' ,nb: 'Underkategori' } ,'Name' : { @@ -275,6 +293,7 @@ function init() { ,hr: 'Ime' ,it: 'Nome' ,dk: 'Navn' + ,fi: 'Nimi' ,nb: 'Navn' } ,'Today' : { @@ -289,6 +308,7 @@ function init() { ,sv: 'Idag' ,it: 'Oggi' ,dk: 'Idag' + ,fi: 'Tänään' ,nb: 'Idag' } ,'Last 2 days' : { @@ -303,6 +323,7 @@ function init() { ,sv: 'Senaste 2 dagarna' ,it: 'Ultimi 2 giorni' ,dk: 'Sidste 2 dage' + ,fi: 'Edelliset 2 päivää' ,nb: 'Siste 2 dager' } ,'Last 3 days' : { @@ -317,6 +338,7 @@ function init() { ,hr: 'Posljednja 3 dana' ,it: 'Ultimi 3 giorni' ,dk: 'Sidste 3 dage' + ,fi: 'Edelliset 3 päivää' ,nb: 'Siste 3 dager' } ,'Last week' : { @@ -331,6 +353,7 @@ function init() { ,sv: 'Senaste veckan' ,it: 'Settimana scorsa' ,dk: 'Sidste uge' + ,fi: 'Viime viikko' ,nb: 'Siste uke' } ,'Last 2 weeks' : { @@ -345,6 +368,7 @@ function init() { ,sv: 'Senaste 2 veckorna' ,it: 'Ultime 2 settimane' ,dk: 'Sidste 2 uger' + ,fi: 'Viimeiset 2 viikkoa' ,nb: 'Siste 2 uker' } ,'Last month' : { @@ -359,6 +383,7 @@ function init() { ,sv: 'Senaste månaden' ,it: 'Mese scorso' ,dk: 'Sidste måned' + ,fi: 'Viime kuu' ,nb: 'Siste måned' } ,'Last 3 months' : { @@ -373,6 +398,7 @@ function init() { ,sv: 'Senaste 3 månaderna' ,it: 'Ultimi 3 mesi' ,dk: 'Sidste 3 måneder' + ,fi: 'Viimeiset 3 kuukautta' ,nb: 'Siste 3 måneder' } ,'From' : { @@ -387,6 +413,7 @@ function init() { ,hr: 'Od' ,it: 'Da' ,dk: 'Fra' + ,fi: 'Alkaen' ,nb: 'Fra' } ,'To' : { @@ -401,6 +428,7 @@ function init() { ,sv: 'Till' ,it: 'A' ,dk: 'Til' + ,fi: 'Asti' ,nb: 'Til' } ,'Notes' : { @@ -415,6 +443,7 @@ function init() { ,hr: 'Bilješke' ,it: 'Note' ,dk: 'Noter' + ,fi: 'Merkinnät' ,nb: 'Notater' } ,'Food' : { @@ -429,6 +458,7 @@ function init() { ,hr: 'Hrana' ,it: 'Cibo' ,dk: 'Mad' + ,fi: 'Ruoka' ,nb: 'Mat' } ,'Insulin' : { @@ -443,6 +473,7 @@ function init() { ,sv: 'Insulin' ,it: 'Insulina' ,dk: 'Insulin' + ,fi: 'Insuliini' ,nb: 'Insulin' } ,'Carbs' : { @@ -457,6 +488,7 @@ function init() { ,sv: 'Kolhydrater' ,it: 'Carboidrati' ,dk: 'Kulhydrater' + ,fi: 'Hiilihydraatit' ,nb: 'Karbohydrater' } ,'Notes contain' : { @@ -471,6 +503,7 @@ function init() { ,sv: 'Notering innehåller' ,it: 'Contiene note' ,dk: 'Noter indeholder' + ,fi: 'Merkinnät sisältävät' ,nb: 'Notater inneholder' } ,'Event type contains' : { @@ -485,6 +518,7 @@ function init() { ,sv: 'Händelsen innehåller' ,it: 'Contiene evento' ,dk: 'Hændelsen indeholder' + ,fi: 'Tapahtuman tyyppi sisältää' ,nb: 'Hendelsen inneholder' } ,'Target bg range bottom' : { @@ -499,6 +533,7 @@ function init() { ,sv: 'Gräns för nedre blodsockervärde' ,it: 'Limite inferiore della glicemia' ,dk: 'Nedre grænse for blodsukkerværdier' + ,fi: 'Tavoitealueen alaraja' ,nb: 'Nedre grense for blodsukkerverdier' } ,'top' : { @@ -513,6 +548,7 @@ function init() { ,sv: 'Toppen' ,it: 'Superiore' ,dk: 'Top' + ,fi: 'yläraja' ,nb: 'Topp' } ,'Show' : { @@ -527,6 +563,7 @@ function init() { ,hr: 'Prikaži' ,it: 'Mostra' ,dk: 'Vis' + ,fi: 'Näytä' ,nb: 'Vis' } ,'Display' : { @@ -541,6 +578,7 @@ function init() { ,sv: 'Visa' ,it: 'Schermo' ,dk: 'Vis' + ,fi: 'Näyttö' ,nb: 'Vis' } ,'Loading' : { @@ -555,6 +593,7 @@ function init() { ,sv: 'Laddar' ,it: 'Sto Caricando' ,dk: 'Indlæser' + ,fi: 'Lataan' ,nb: 'Laster' } ,'Loading profile' : { @@ -569,6 +608,7 @@ function init() { ,hr: 'Učitavanje profila' ,it: 'Sto Caricando il profilo' ,dk: 'Indlæser profil' + ,fi: 'Lataan profiilia' ,nb: 'Leser profil' } ,'Loading status' : { @@ -583,6 +623,7 @@ function init() { ,hr: 'Učitavanje statusa' ,it: 'Stato di caricamento' ,dk: 'Indlæsnings status' + ,fi: 'Lataan tilaa' ,nb: 'Leser status' } ,'Loading food database' : { @@ -597,6 +638,7 @@ function init() { ,hr: 'Učitavanje baze podataka o hrani' ,it: 'Carico dati alimenti' ,dk: 'Indlæser mad database' + ,fi: 'Lataan ruokatietokantaa' ,nb: 'Leser matdatabase' } ,'not displayed' : { @@ -611,6 +653,7 @@ function init() { ,sv: 'Visas ej' ,it: 'Non visualizzato' ,dk: 'Vises ikke' + ,fi: 'ei näytetä' ,nb: 'Vises ikke' } ,'Loading CGM data of' : { @@ -625,6 +668,7 @@ function init() { ,hr: 'Učitavanja podataka CGM-a' ,it: 'Carico dati CGM' ,dk: 'Indlæser CGM-data for' + ,fi: 'Lataan sensoritietoja: ' ,nb: 'Leser CGM-data for' } ,'Loading treatments data of' : { @@ -639,6 +683,7 @@ function init() { ,hr: 'Učitavanje podataka o tretmanu' ,it: 'Carico trattamenti dei dati di' ,dk: 'Indlæser data for' + ,fi: 'Lataan toimenpidetietoja: ' ,nb: 'Leser behandlingsdata for' } ,'Processing data of' : { @@ -653,6 +698,7 @@ function init() { ,hr: 'Obrada podataka' ,it: 'Elaborazione dei dati di' ,dk: 'Behandler data for' + ,fi: 'Käsittelen tietoja: ' ,nb: 'Behandler data for' } ,'Portion' : { @@ -667,6 +713,7 @@ function init() { ,sv: 'Portion' ,it: 'Porzione' ,dk: 'Portion' + ,fi: 'Annos' ,nb: 'Porsjon' } ,'Size' : { @@ -681,6 +728,7 @@ function init() { ,hr: 'Veličina' ,it: 'Formato' ,dk: 'Størrelse' + ,fi: 'Koko' ,nb: 'Størrelse' } ,'(none)' : { @@ -695,6 +743,7 @@ function init() { ,hr: '(Prazno)' ,it: '(Nessuno)' ,dk: '(ingen)' + ,fi: '(tyhjä)' ,nb: '(ingen)' } ,'Result is empty' : { @@ -709,6 +758,7 @@ function init() { ,hr: 'Prazan rezultat' ,it: 'Risultato vuoto' ,dk: 'Tomt resultat' + ,fi: 'Ei tuloksia' ,nb: 'Tomt resultat' } // ported reporting @@ -724,6 +774,7 @@ function init() { ,hr: 'Svakodnevno' ,it: 'Giorno per giorno' ,dk: 'Dag til dag' + ,fi: 'Päivittäinen' ,nb: 'Dag til dag' } ,'Daily Stats' : { @@ -738,6 +789,7 @@ function init() { ,hr: 'Dnevna statistika' ,it: 'Statistiche giornaliere' ,dk: 'Daglig statistik' + ,fi: 'Päivittäiset tilastot' ,nb: 'Daglig statistikk' } ,'Percentile Chart' : { @@ -752,6 +804,7 @@ function init() { ,sv: 'Procentgraf' ,it: 'Grafico percentile' ,dk: 'Procentgraf' + ,fi: 'Suhteellinen kuvaaja' ,nb: 'Prosentgraf' } ,'Distribution' : { @@ -766,6 +819,7 @@ function init() { ,sv: 'Distribution' ,it: 'Distribuzione' ,dk: 'Distribution' + ,fi: 'Jakauma' ,nb: 'Distribusjon' } ,'Hourly stats' : { @@ -780,6 +834,7 @@ function init() { ,hr: 'Statistika po satu' ,it: 'Statistiche per ore' ,dk: 'Timestatistik' + ,fi: 'Tunneittainen tilasto' ,nb: 'Timestatistikk' } ,'Weekly success' : { @@ -794,6 +849,7 @@ function init() { ,sv: 'Veckoresultat' ,it: 'Statistiche settimanali' ,dk: 'Uge resultat' + ,fi: 'Viikottainen saavutus' ,nb: 'Ukeresultat' } ,'No data available' : { @@ -808,6 +864,7 @@ function init() { ,sv: 'Data saknas' ,it: 'Dati non disponibili' ,dk: 'Mangler data' + ,fi: 'Tietoja ei saatavilla' ,nb: 'Mangler data' } ,'Low' : { @@ -822,6 +879,7 @@ function init() { ,hr: 'Nizak' ,it: 'Basso' ,dk: 'Lav' + ,fi: 'Matala' ,nb: 'Lav' } ,'In Range' : { @@ -836,6 +894,7 @@ function init() { ,hr: 'U rasponu' ,it: 'In intervallo' ,dk: 'Indenfor intervallet' + ,fi: 'Tavoitealueella' ,nb: 'Innenfor intervallet' } ,'Period' : { @@ -850,6 +909,7 @@ function init() { ,hr: 'Period' ,it: 'Periodo' ,dk: 'Period' + ,fi: 'Aikaväli' ,nb: 'Periode' } ,'High' : { @@ -864,6 +924,7 @@ function init() { ,hr: 'Visok' ,it: 'Alto' ,dk: 'Høj' + ,fi: 'Korkea' ,nb: 'Høy' } ,'Average' : { @@ -878,6 +939,7 @@ function init() { ,hr: 'Prosjek' ,it: 'Media' ,dk: 'Gennemsnit' + ,fi: 'Keskiarvo' ,nb: 'Gjennomsnitt' } ,'Low Quartile' : { @@ -892,6 +954,7 @@ function init() { ,sv: 'Nedre kvadranten' ,it: 'Quartile basso' ,dk: 'Nedre kvartil' + ,fi: 'Alin neljäsosa' ,nb: 'Nedre kvartil' } ,'Upper Quartile' : { @@ -906,6 +969,7 @@ function init() { ,sv: 'Övre kvadranten' ,it: 'Quartile alto' ,dk: 'Øvre kvartil' + ,fi: 'Ylin neljäsosa' ,nb: 'Øvre kvartil' } ,'Quartile' : { @@ -920,6 +984,7 @@ function init() { ,sv: 'Kvadrant' ,it: 'Quartile' ,dk: 'Kvartil' + ,fi: 'Neljäsosa' ,nb: 'Kvartil' } ,'Date' : { @@ -934,6 +999,7 @@ function init() { ,hr: 'Datum' ,it: 'Data' ,dk: 'Dato' + ,fi: 'Päivämäärä' ,nb: 'Dato' } ,'Normal' : { @@ -948,6 +1014,7 @@ function init() { ,hr: 'Normalno' ,it: 'Normale' ,dk: 'Normal' + ,fi: 'Normaali' ,nb: 'Normal' } ,'Median' : { @@ -962,6 +1029,7 @@ function init() { ,sv: 'Median' ,it: 'Mediana' ,dk: 'Median' + ,fi: 'Mediaani' ,nb: 'Median' } ,'Readings' : { @@ -976,6 +1044,7 @@ function init() { ,hr: 'Vrijednosti' ,it: 'Valori' ,dk: 'Aflæsning' + ,fi: 'Lukemia' ,nb: 'Avlesning' } ,'StDev' : { @@ -990,6 +1059,7 @@ function init() { ,hr: 'Standardna devijacija' ,it: 'Deviazione standard' ,dk: 'Standard afvigelse' + ,fi: 'Keskijakauma' ,nb: 'Standardavvik' } ,'Daily stats report' : { @@ -1004,6 +1074,7 @@ function init() { ,sv: 'Dygnsstatistik' ,it: 'Statistiche giornaliere' ,dk: 'Daglig statistik rapport' + ,fi: 'Päivittäinen tilasto' ,nb: 'Daglig statistikkrapport' } ,'Glucose Percentile report' : { @@ -1018,6 +1089,7 @@ function init() { ,hr: 'Izvješće o postotku GUK-a' ,it: 'Percentuale Glicemie' ,dk: 'Glukoserapport i procent' + ,fi: 'Glukoosin suhteeellinen tilasto' ,nb: 'Glukoserapport i prosent' } ,'Glucose distribution' : { @@ -1032,6 +1104,7 @@ function init() { ,sv: 'Glukosdistribution' ,it: 'Distribuzione glicemie' ,dk: 'Glukosefordeling' + ,fi: 'Glukoosijakauma' ,nb: 'Glukosefordeling' } ,'days total' : { @@ -1046,6 +1119,7 @@ function init() { ,hr: 'ukupno dana' ,it: 'Giorni totali' ,dk: 'antal dage' + ,fi: 'päivä yhteensä' ,nb: 'antall dager' } ,'Overall' : { @@ -1060,6 +1134,7 @@ function init() { ,hr: 'Ukupno' ,it: 'Generale' ,dk: 'Overall' + ,fi: 'Kaiken kaikkiaan' ,nb: 'Generelt' } ,'Range' : { @@ -1074,6 +1149,7 @@ function init() { ,hr: 'Raspon' ,it: 'Intervallo' ,dk: 'Interval' + ,fi: 'Alue' ,nb: 'Intervall' } ,'% of Readings' : { @@ -1088,6 +1164,7 @@ function init() { ,hr: '% očitanja' ,it: '% dei valori' ,dk: '% af aflæsningerne' + ,fi: '% lukemista' ,nb: '% af avlesningene' } ,'# of Readings' : { @@ -1102,6 +1179,7 @@ function init() { ,hr: 'broj očitanja' ,it: '# di valori' ,dk: 'Antal af aflæsninger' + ,fi: 'Lukemien määrä' ,nb: 'Antall avlesninger' } ,'Mean' : { @@ -1116,6 +1194,7 @@ function init() { ,hr: 'Prosjek' ,it: 'Medio' ,dk: 'Gennemsnit' + ,fi: 'Keskiarvo' ,nb: 'Gjennomsnitt' } ,'Standard Deviation' : { @@ -1130,6 +1209,7 @@ function init() { ,sv: 'Standardavvikelse' ,it: 'Deviazione Standard' ,dk: 'Standardafgivelse' + ,fi: 'Keskijakauma' ,nb: 'Standardavvik' } ,'Max' : { @@ -1144,6 +1224,7 @@ function init() { ,hr: 'Max' ,it: 'Max' ,dk: 'Max' + ,fi: 'Maks' ,nb: 'Max' } ,'Min' : { @@ -1158,6 +1239,7 @@ function init() { ,hr: 'Min' ,it: 'Min' ,dk: 'Min' + ,fi: 'Min' ,nb: 'Min' } ,'A1c estimation*' : { @@ -1172,6 +1254,7 @@ function init() { ,sv: 'Beräknat A1c-värde ' ,it: 'stima A1c' ,dk: 'Beregnet A1c-værdi ' + ,fi: 'A1c arvio*' ,nb: 'Beregnet HbA1c' } ,'Weekly Success' : { @@ -1186,6 +1269,7 @@ function init() { ,sv: 'Veckoresultat' ,it: 'Risultati settimanali' ,dk: 'Uge resultat' + ,fi: 'Viikottainen tulos' ,nb: 'Ukeresultat' } ,'There is not sufficient data to run this report. Select more days.' : { @@ -1200,6 +1284,7 @@ function init() { ,sv: 'Data saknas för att köra rapport. Välj fler dagar.' ,it: 'Non ci sono dati sufficienti per eseguire questo rapporto. Selezionare più giorni.' ,dk: 'Der er utilstrækkeligt data til at generere rapporten. Vælg flere dage.' + ,fi: 'Raporttia ei voida luoda liian vähäisen tiedon vuoksi. Valitse useampia päiviä.' ,nb: 'Der er ikke nok data til å lage rapporten. Velg flere dager.' } // food editor @@ -1215,6 +1300,7 @@ function init() { ,sv: 'Använd hemlig API-nyckel' ,it: 'Stai utilizzando API hash segreta' ,dk: 'Anvender gemt API-nøgle' + ,fi: 'Tallennettu salainen API-tarkiste käytössä' ,nb: 'Bruker lagret API nøkkel' } ,'No API secret hash stored yet. You need to enter API secret.' : { @@ -1229,6 +1315,7 @@ function init() { ,sv: 'Hemlig api-nyckel saknas. Du måste ange API hemlighet' ,it: 'No API hash segreto ancora memorizzato. È necessario inserire API segreto.' ,dk: 'Mangler API-nøgle. Du skal indtaste API hemmelighed' + ,fi: 'Salainen API-tarkiste puuttuu. Syötä API tarkiste.' ,nb: 'Mangler API nøkkel. Du må skrive inn API hemmelighet.' } ,'Database loaded' : { @@ -1243,6 +1330,7 @@ function init() { ,sv: 'Databas laddad' ,it: 'Database caricato' ,dk: 'Database indlæst' + ,fi: 'Tietokanta ladattu' ,nb: 'Database lest' } ,'Error: Database failed to load' : { @@ -1257,6 +1345,7 @@ function init() { ,sv: 'Error: Databas kan ej laddas' ,it: 'Errore: database non è stato caricato' ,dk: 'Fejl: Database kan ikke indlæses' + ,fi: 'Virhe: Tietokannan lataaminen epäonnistui' ,nb: 'Feil: Database kan ikke leses' } ,'Create new record' : { @@ -1271,6 +1360,7 @@ function init() { ,sv: 'Skapa ny post' ,it: 'Crea nuovo registro' ,dk: 'Danner ny post' + ,fi: 'Luo uusi tallenne' ,nb: 'Lager ny registrering' } ,'Save record' : { @@ -1285,6 +1375,7 @@ function init() { ,sv: 'Spara post' ,it: 'Salva Registro' ,dk: 'Gemmer post' + ,fi: 'Tallenna' ,nb: 'Lagrer registrering' } ,'Portions' : { @@ -1299,6 +1390,7 @@ function init() { ,sv: 'Portion' ,it: 'Porzioni' ,dk: 'Portioner' + ,fi: 'Annokset' ,nb: 'Porsjoner' } ,'Unit' : { @@ -1313,6 +1405,7 @@ function init() { ,sv: 'Enhet' ,it: 'Unità' ,dk: 'Enheder' + ,fi: 'Yksikkö' ,nb: 'Enhet' } ,'GI' : { @@ -1327,6 +1420,7 @@ function init() { ,hr: 'GI' ,it: 'GI' ,dk: 'GI' + ,fi: 'GI' ,nb: 'GI' } ,'Edit record' : { @@ -1341,6 +1435,7 @@ function init() { ,sv: 'Editera post' ,it: 'Modifica registro' ,dk: 'Editere post' + ,fi: 'Muokkaa tallennetta' ,nb: 'Editere registrering' } ,'Delete record' : { @@ -1355,6 +1450,7 @@ function init() { ,sv: 'Radera post' ,it: 'Cancella registro' ,dk: 'Slet post' + ,fi: 'Tuhoa tallenne' ,nb: 'Slette registrering' } ,'Move to the top' : { @@ -1369,6 +1465,7 @@ function init() { ,hr: 'Premjesti na vrh' ,it: 'Spostare verso l\'alto' ,dk: 'Gå til toppen' + ,fi: 'Siirrä ylimmäksi' ,nb: 'Gå til toppen' } ,'Hidden' : { @@ -1383,6 +1480,7 @@ function init() { ,hr: 'Skriveno' ,it: 'Nascosto' ,dk: 'Skjult' + ,fi: 'Piilotettu' ,nb: 'Skjult' } ,'Hide after use' : { @@ -1397,6 +1495,7 @@ function init() { ,sv: 'Dölj efter användning' ,it: 'Nascondi dopo l\'uso' ,dk: 'Skjul efter brug' + ,fi: 'Piilota käytön jälkeen' ,nb: 'Skjul etter bruk' } ,'Your API secret must be at least 12 characters long' : { @@ -1411,6 +1510,7 @@ function init() { ,sv: 'Hemlig API-nyckel måsta innehålla 12 tecken' ,it: 'il vostro API secreto deve essere lungo almeno 12 caratteri' ,dk: 'Din API nøgle skal være mindst 12 tegn lang' + ,fi: 'API-avaimen tulee olla ainakin 12 merkin mittainen' ,nb: 'Din API nøkkel må være minst 12 tegn lang' } ,'Bad API secret' : { @@ -1425,6 +1525,7 @@ function init() { ,sv: 'Felaktig API-nyckel' ,it: 'API secreto incorretto' ,dk: 'Forkert API-nøgle' + ,fi: 'Väärä API-avain' ,nb: 'Ugyldig API nøkkel' } ,'API secret hash stored' : { @@ -1439,6 +1540,7 @@ function init() { ,sv: 'Lagrad hemlig API-hash' ,it: 'Hash API secreto memorizzato' ,dk: 'Hemmelig API-hash gemt' + ,fi: '' ,nb: 'API nøkkel lagret' } ,'Status' : { @@ -1453,6 +1555,7 @@ function init() { ,hr: 'Status' ,it: 'Stato' ,dk: 'Status' + ,fi: 'Tila' ,nb: 'Status' } ,'Not loaded' : { @@ -1467,6 +1570,7 @@ function init() { ,sv: 'Ej laddad' ,it: 'Non caricato' ,dk: 'Ikke indlæst' + ,fi: 'Ei ladattu' ,nb: 'Ikke lest' } ,'Food editor' : { @@ -1481,6 +1585,7 @@ function init() { ,sv: 'Födoämneseditor' ,it: 'Modifica alimenti' ,dk: 'Mad editor' + ,fi: 'Muokkaa ruokia' ,nb: 'Mat editor' } ,'Your database' : { @@ -1495,6 +1600,7 @@ function init() { ,hr: 'Vaša baza podataka' ,it: 'Vostro database' ,dk: 'Din database' + ,fi: 'Tietokantasi' ,nb: 'Din database' } ,'Filter' : { @@ -1509,6 +1615,7 @@ function init() { ,hr: 'Filter' ,it: 'Filtro' ,dk: 'Filter' + ,fi: 'Suodatin' ,nb: 'Filter' } ,'Save' : { @@ -1523,6 +1630,7 @@ function init() { ,sv: 'Spara' ,it: 'Salva' ,dk: 'Gem' + ,fi: 'Tallenna' ,nb: 'Lagre' } ,'Clear' : { @@ -1537,6 +1645,7 @@ function init() { ,sv: 'Rensa' ,it: 'Pulisci' ,dk: 'Rense' + ,fi: 'Tyhjennä' ,nb: 'Tøm' } ,'Record' : { @@ -1551,6 +1660,7 @@ function init() { ,hr: 'Zapis' ,it: 'Registro' ,dk: 'Post' + ,fi: 'Tietue' ,nb: 'Registrering' } ,'Quick picks' : { @@ -1565,6 +1675,7 @@ function init() { ,sv: 'Snabbval' ,it: 'Scelta rapida' ,dk: 'Hurtig valg' + ,fi: 'Nopeat valinnat' ,nb: 'Hurtigvalg' } ,'Show hidden' : { @@ -1579,6 +1690,7 @@ function init() { ,sv: 'Visa dolda' ,it: 'Mostra nascosto' ,dk: 'Vis skjulte' + ,fi: 'Näytä piilotettu' ,nb: 'Vis skjulte' } ,'Your API secret' : { @@ -1593,6 +1705,7 @@ function init() { ,hr: 'Vaš tajni API' ,it: 'Il tuo API secreto' ,dk: 'Din API-nøgle' + ,fi: 'Sinun API-avaimesi' ,nb: 'Din API nøkkel' } ,'Store hash on this computer (Use only on private computers)' : { @@ -1607,6 +1720,7 @@ function init() { ,sv: 'Lagra hashvärde på denna dator (använd endast på privat dator)' ,it: 'Conservare hash su questo computer (utilizzare solo su computer privati)' ,dk: 'Gemme hash på denne computer (brug kun på privat computer)' + ,fi: 'Tallenna avain tälle tietokoneelle (käytä vain omalla tietokoneellasi)' ,nb: 'Lagre hash på denne pc (bruk kun på privat pc)' } ,'Treatments' : { @@ -1621,6 +1735,7 @@ function init() { ,hr: 'Tretmani' ,it: 'Somministrazione' ,dk: 'Behandling' + ,fi: 'Hoitotoimenpiteet' ,nb: 'Behandlinger' } ,'Time' : { @@ -1635,6 +1750,7 @@ function init() { ,hr: 'Vrijeme' ,it: 'Tempo' ,dk: 'Tid' + ,fi: 'Aika' ,nb: 'Tid' } ,'Event Type' : { @@ -1649,6 +1765,7 @@ function init() { ,hr: 'Vrsta događaja' ,it: 'Tipo di evento' ,dk: 'Hændelsestype' + ,fi: 'Tapahtumatyyppi' ,nb: 'Type' } ,'Blood Glucose' : { @@ -1663,6 +1780,7 @@ function init() { ,hr: 'GUK' ,it: 'Glicemia' ,dk: 'Glukoseværdi' + ,fi: 'Verensokeri' ,nb: 'Blodsukker' } ,'Entered By' : { @@ -1677,6 +1795,7 @@ function init() { ,hr: 'Unos izvršio' ,it: 'inserito da' ,dk: 'Indtastet af' + ,fi: 'Tiedot syötti' ,nb: 'Lagt inn av' } ,'Delete this treatment?' : { @@ -1691,6 +1810,7 @@ function init() { ,sv: 'Ta bort händelse?' ,it: 'Eliminare questa somministrazione?' ,dk: 'Slet denne hændelse?' + ,fi: 'Tuhoa tämä hoitotoimenpide?' ,nb: 'Slett denne hendelsen?' } ,'Carbs Given' : { @@ -1705,6 +1825,7 @@ function init() { ,sv: 'Antal kolhydrater' ,it: 'Carboidrati' ,dk: 'Antal kulhydrater' + ,fi: 'Hiilihydraatit' ,nb: 'Karbo' } ,'Inzulin Given' : { @@ -1719,6 +1840,7 @@ function init() { ,sv: 'Insulin' ,it: 'Insulina' ,dk: 'Insulin' + ,fi: 'Insuliiniannos' ,nb: 'Insulin' } ,'Event Time' : { @@ -1733,6 +1855,7 @@ function init() { ,hr: 'Vrijeme događaja' ,it: 'Ora Evento' ,dk: 'Tidspunkt for hændelsen' + ,fi: 'Aika' ,nb: 'Tidspunkt for hendelsen' } ,'Please verify that the data entered is correct' : { @@ -1747,6 +1870,7 @@ function init() { ,sv: 'Vänligen verifiera att inlagd data är korrekt' ,it: 'Si prega di verificare che i dati inseriti sono corretti' ,dk: 'Venligst verificer at indtastet data er korrekt' + ,fi: 'Varmista, että tiedot ovat oikein' ,nb: 'Vennligst verifiser at inntastet data er korrekt' } ,'BG' : { @@ -1761,6 +1885,7 @@ function init() { ,hr: 'GUK' ,it: 'Glicemia' ,dk: 'BS' + ,fi: 'VS' ,nb: 'BS' } ,'Use BG correction in calculation' : { @@ -1775,6 +1900,7 @@ function init() { ,sv: 'Använd BS-korrektion för beräkning' ,it: 'Utilizzare la correzione Glicemia nei calcoli' ,dk: 'Anvend BS-korrektion før beregning' + ,fi: 'Käytä korjausannosta laskentaan' ,nb: 'Bruk blodsukkerkorrigering i beregning' } ,'BG from CGM (autoupdated)' : { @@ -1789,6 +1915,7 @@ function init() { ,hr: 'GUK sa CGM-a (ažuriran automatski)' ,it: 'Glicemie da CGM (aggiornamento automatico)' ,dk: 'BS fra CGM (automatisk)' + ,fi: 'VS sensorilta (päivitetty automaattisesti)' ,nb: 'BS fra CGM (automatisk)' } ,'BG from meter' : { @@ -1803,6 +1930,7 @@ function init() { ,hr: 'GUK s glukometra' ,it: 'Glicemie da glucometro' ,dk: 'BS fra blodsukkerapperat' + ,fi: 'VS mittarilta' ,nb: 'BS fra blodsukkerapparat' } ,'Manual BG' : { @@ -1817,6 +1945,7 @@ function init() { ,sv: 'Manuellt BS' ,it: 'Inserisci Glicemia' ,dk: 'Manuelt BS' + ,fi: 'Käsin syötetty VS' ,nb: 'Manuelt BS' } ,'Quickpick' : { @@ -1831,6 +1960,7 @@ function init() { ,sv: 'Snabbval' ,it: 'Scelta rapida' ,dk: 'Hurtig snack' + ,fi: 'Pikavalinta' ,nb: 'Hurtigvalg' } ,'or' : { @@ -1845,6 +1975,7 @@ function init() { ,hr: 'ili' ,it: 'o' ,dk: 'eller' + ,fi: 'tai' ,nb: 'eller' } ,'Add from database' : { @@ -1859,6 +1990,7 @@ function init() { ,sv: 'Lägg till från databas' ,it: 'Aggiungi dal database' ,dk: 'Tilføj fra database' + ,fi: 'Lisää tietokannasta' ,nb: 'Legg til fra database' } ,'Use carbs correction in calculation' : { @@ -1873,6 +2005,7 @@ function init() { ,sv: 'Använd kolhydratkorrektion för beräkning' ,it: 'Utilizzare la correzione con carboidrati nel calcolo' ,dk: 'Benyt kulhydratkorrektion i beregning' + ,fi: 'Käytä hiilihydraattikorjausta laskennassa' ,nb: 'Bruk karbohydratkorrigering i beregning' } ,'Use COB correction in calculation' : { @@ -1887,6 +2020,7 @@ function init() { ,sv: 'Använd aktiva kolhydrater för beräkning' ,it: 'Utilizzare la correzione COB nel calcolo' ,dk: 'Benyt aktive kulhydrater i beregning' + ,fi: 'Käytä aktiivisia hiilihydraatteja laskennassa' ,nb: 'Benytt aktive karbohydrater i beregning' } ,'Use IOB in calculation' : { @@ -1901,6 +2035,7 @@ function init() { ,sv: 'Använd aktivt insulin för beräkning' ,it: 'Utilizzare la correzione IOB nel calcolo' ,dk: 'Benyt aktivt insulin i beregningen' + ,fi: 'Käytä aktiviivista insuliinia laskennassa' ,nb: 'Bruk aktivt insulin i beregningen' } ,'Other correction' : { @@ -1915,6 +2050,7 @@ function init() { ,sv: 'Övrig korrektion' ,it: 'Altre correzioni' ,dk: 'Øvrig korrektion' + ,fi: 'Muu korjaus' ,nb: 'Annen korrigering' } ,'Rounding' : { @@ -1929,6 +2065,7 @@ function init() { ,hr: 'Zaokruživanje' ,it: 'Arrotondamento' ,dk: 'Afrunding' + ,fi: 'Pyöristys' ,nb: 'Avrunding' } ,'Enter insulin correction in treatment' : { @@ -1943,6 +2080,7 @@ function init() { ,sv: 'Ange insulinkorrektion för händelse' ,it: 'Inserisci correzione insulina nella somministrazione' ,dk: 'Indtast insulionkorrektion' + ,fi: 'Syötä insuliinikorjaus' ,nb: 'Task inn insulinkorrigering' } ,'Insulin needed' : { @@ -1957,6 +2095,7 @@ function init() { ,sv: 'Beräknad insulinmängd' ,it: 'Insulina necessaria' ,dk: 'Insulin påkrævet' + ,fi: 'Insuliinitarve' ,nb: 'Insulin nødvendig' } ,'Carbs needed' : { @@ -1971,6 +2110,7 @@ function init() { ,sv: 'Beräknad kolhydratmängd' ,it: 'Carboidrati necessari' ,dk: 'Kulhydrater påkrævet' + ,fi: 'Hiilihydraattitarve' ,nb: 'Karbohydrater nødvendig' } ,'Carbs needed if Insulin total is negative value' : { @@ -1985,6 +2125,7 @@ function init() { ,sv: 'Nödvändig kolhydratmängd för angiven insulinmängd' ,it: 'Carboidrati necessari se l\'insulina totale è un valore negativo' ,dk: 'Kulhydrater er nødvendige når total insulin mængde er negativ' + ,fi: 'Hiilihydraattitarve, jos yhteenlaskettu insuliini on negatiivinen' ,nb: 'Karbohydrater er nødvendige når total insulinmengde er negativ' } ,'Basal rate' : { @@ -1999,6 +2140,7 @@ function init() { ,sv: 'Basaldos' ,it: 'Velocità basale' ,dk: 'Basal rate' + ,fi: 'Perusannos' ,nb: 'Basal' } ,'60 minutes earlier' : { @@ -2013,6 +2155,7 @@ function init() { ,hr: 'Prije 60 minuta' ,it: '60 minuti prima' ,dk: '60 min tidligere' + ,fi: '60 minuuttia aiemmin' ,nb: '60 min tidligere' } ,'45 minutes earlier' : { @@ -2027,6 +2170,7 @@ function init() { ,hr: 'Prije 45 minuta' ,it: '45 minuti prima' ,dk: '45 min tidligere' + ,fi: '45 minuuttia aiemmin' ,nb: '45 min tidligere' } ,'30 minutes earlier' : { @@ -2041,6 +2185,7 @@ function init() { ,hr: 'Prije 30 minuta' ,it: '30 minuti prima' ,dk: '30 min tidigere' + ,fi: '30 minuuttia aiemmin' ,nb: '30 min tidigere' } ,'20 minutes earlier' : { @@ -2055,6 +2200,7 @@ function init() { ,hr: 'Prije 20 minuta' ,it: '20 minuti prima' ,dk: '20 min tidligere' + ,fi: '20 minuuttia aiemmin' ,nb: '20 min tidligere' } ,'15 minutes earlier' : { @@ -2069,6 +2215,7 @@ function init() { ,hr: 'Prije 15 minuta' ,it: '15 minuti prima' ,dk: '15 min tidligere' + ,fi: '15 minuuttia aiemmin' ,nb: '15 min tidligere' } ,'Time in minutes' : { @@ -2083,6 +2230,7 @@ function init() { ,hr: 'Vrijeme u minutama' ,it: 'Tempo in minuti' ,dk: 'Tid i minutter' + ,fi: 'Aika minuuteissa' ,nb: 'Tid i minutter' } ,'15 minutes later' : { @@ -2096,6 +2244,7 @@ function init() { ,sv: '15 min senare' ,it: '15 minuti più tardi' ,dk: '15 min senere' + ,fi: '15 minuuttia myöhemmin' ,nb: '15 min senere' } ,'20 minutes later' : { @@ -2110,6 +2259,7 @@ function init() { ,sv: '20 min senare' ,it: '20 minuti più tardi' ,dk: '20 min senere' + ,fi: '20 minuuttia myöhemmin' ,nb: '20 min senere' } ,'30 minutes later' : { @@ -2124,6 +2274,7 @@ function init() { ,sv: '30 min senare' ,it: '30 minuti più tardi' ,dk: '30 min senere' + ,fi: '30 minuuttia myöhemmin' ,nb: '30 min senere' } ,'45 minutes later' : { @@ -2138,6 +2289,7 @@ function init() { ,sv: '45 min senare' ,it: '45 minuti più tardi' ,dk: '45 min senere' + ,fi: '45 minuuttia myöhemmin' ,nb: '45 min senere' } ,'60 minutes later' : { @@ -2152,6 +2304,7 @@ function init() { ,sv: '60 min senare' ,it: '60 minuti più tardi' ,dk: '60 min senere' + ,fi: '60 minuuttia myöhemmin' ,nb: '60 min senere' } ,'Additional Notes, Comments' : { @@ -2166,6 +2319,7 @@ function init() { ,sv: 'Notering, övrigt' ,it: 'Note aggiuntive, commenti' ,dk: 'Ekstra noter, kommentarer' + ,fi: 'Lisähuomiot, kommentit' ,nb: 'Ekstra notater, kommentarer' } ,'RETRO MODE' : { @@ -2180,6 +2334,7 @@ function init() { ,hr: 'Retrospektivni način' ,it: 'Modalità retrospettiva' ,dk: 'Retro mode' + ,fi: 'VANHENTUNEET TIEDOT' ,nb: 'Retro mode' } ,'Now' : { @@ -2194,6 +2349,7 @@ function init() { ,hr: 'Sad' ,it: 'Adesso' ,dk: 'Nu' + ,fi: 'Nyt' ,nb: 'Nå' } ,'Other' : { @@ -2208,6 +2364,7 @@ function init() { ,hr: 'Drugo' ,it: 'Altro' ,dk: 'Øvrige' + ,fi: 'Muu' ,nb: 'Annet' } ,'Submit Form' : { @@ -2222,6 +2379,7 @@ function init() { ,hr: 'Predaj obrazac' ,it: 'Inviare il modulo' ,dk: 'Gem hændelsen' + ,fi: 'Lähetä tiedot' ,nb: 'Lagre' } ,'Profile editor' : { @@ -2236,6 +2394,7 @@ function init() { ,hr: 'Editor profila' ,it: 'Editor dei profili' ,dk: 'Profil editor' + ,fi: 'Profiilin muokkaus' ,nb: 'Profileditor' } ,'Reporting tool' : { @@ -2250,6 +2409,7 @@ function init() { ,hr: 'Alat za prijavu' ,it: 'Strumento di reporting' ,dk: 'Rapporteringsværktøj' + ,fi: 'Raportointityökalu' ,nb: 'Rapporteringsverktøy' } ,'Add food from your database' : { @@ -2264,6 +2424,7 @@ function init() { ,sv: 'Lägg till livsmedel från databas' ,it: 'Aggiungere cibo al database' ,dk: 'Tilføj mad fra din database' + ,fi: 'Lisää ruoka tietokannasta' ,nb: 'Legg til mat fra din database' } ,'Reload database' : { @@ -2278,6 +2439,7 @@ function init() { ,sv: 'Ladda om databas' ,it: 'Ricarica database' ,dk: 'Genindlæs databasen' + ,fi: 'Lataa tietokanta uudelleen' ,nb: 'Last inn databasen på nytt' } ,'Add' : { @@ -2292,6 +2454,7 @@ function init() { ,sv: 'Lägg till' ,it: 'Aggiungere' ,dk: 'Tilføj' + ,fi: 'Lisää' ,nb: 'Legg til' } ,'Unauthorized' : { @@ -2306,6 +2469,7 @@ function init() { ,hr: 'Neautorizirano' ,it: 'non autorizzato' ,dk: 'Uautoriseret' + ,fi: 'Kielletty' ,nb: 'Uautorisert' } ,'Entering record failed' : { @@ -2320,6 +2484,7 @@ function init() { ,sv: 'Lägga till post nekas' ,it: 'Voce del Registro fallita' ,dk: 'Tilføjelse af post fejlede' + ,fi: 'Tiedon tallentaminen epäonnistui' ,nb: 'Lagring feilet' } ,'Device authenticated' : { @@ -2334,6 +2499,7 @@ function init() { ,hr: 'Uređaj autenticiran' ,it: 'Dispositivo autenticato' ,dk: 'Enhed godkendt' + ,fi: 'Laite autentikoitu' ,nb: 'Enhet godkjent' } ,'Device not authenticated' : { @@ -2348,6 +2514,7 @@ function init() { ,hr: 'Uređaj nije autenticiran' ,it: 'Dispositivo non autenticato' ,dk: 'Enhed ikke godkendt' + ,fi: 'Laite ei ole autentikoitu' ,nb: 'Enhet ikke godkjent' } ,'Authentication status' : { @@ -2362,6 +2529,7 @@ function init() { ,sv: 'Autentiseringsstatus' ,it: 'Stato di autenticazione' ,dk: 'Autentifikationsstatus' + ,fi: 'Autentikoinnin tila' ,nb: 'Autentiseringsstatus' } ,'Authenticate' : { @@ -2376,6 +2544,7 @@ function init() { ,hr: 'Autenticirati' ,it: 'Autenticare' ,dk: 'Godkende' + ,fi: 'Autentikoi' ,nb: 'Autentiser' } ,'Remove' : { @@ -2390,6 +2559,7 @@ function init() { ,sv: 'Ta bort' ,it: 'Rimuovere' ,dk: 'Fjern' + ,fi: 'Poista' ,nb: 'Slett' } ,'Your device is not authenticated yet' : { @@ -2404,6 +2574,7 @@ function init() { ,sv: 'Din enhet är ej autentiserad' ,it: 'Il tuo dispositivo non è ancora stato autenticato' ,dk: 'Din enhed er ikke godkendt endnu' + ,fi: 'Laitettasi ei ole vielä autentikoitu' ,nb: 'Din enhet er ikke godkjent enda' } ,'Sensor' : { @@ -2418,6 +2589,7 @@ function init() { ,hr: 'Senzor' ,it: 'Sensore' ,dk: 'Sensor' + ,fi: 'Sensori' ,nb: 'Sensor' } ,'Finger' : { @@ -2432,6 +2604,7 @@ function init() { ,hr: 'Prst' ,it: 'Dito' ,dk: 'Finger' + ,fi: 'Sormi' ,nb: 'Finger' } ,'Manual' : { @@ -2446,6 +2619,7 @@ function init() { ,hr: 'Ručno' ,it: 'Manuale' ,dk: 'Manuel' + ,fi: 'Manuaalinen' ,nb: 'Manuell' } ,'Scale' : { @@ -2460,6 +2634,7 @@ function init() { ,sv: 'Skala' ,it: 'Scala' ,dk: 'Skala' + ,fi: 'Skaala' ,nb: 'Skala' } ,'Linear' : { @@ -2474,6 +2649,7 @@ function init() { ,hr: 'Linearno' ,it: 'Lineare' ,dk: 'Lineær' + ,fi: 'Lineaarinen' ,nb: 'Lineær' } ,'Logarithmic' : { @@ -2488,6 +2664,7 @@ function init() { ,hr: 'Logaritamski' ,it: 'Logaritmica' ,dk: 'Logaritmisk' + ,fi: 'Logaritminen' ,nb: 'Logaritmisk' } ,'Silence for 30 minutes' : { @@ -2502,6 +2679,7 @@ function init() { ,sv: 'Tyst i 30 min' ,it: 'Silenzio per 30 minuti' ,dk: 'Stilhed i 30 min' + ,fi: 'Hiljennä 30 minuutiksi' ,nb: 'Stille i 30 min' } ,'Silence for 60 minutes' : { @@ -2516,6 +2694,7 @@ function init() { ,sv: 'Tyst i 60 min' ,it: 'Silenzio per 60 minuti' ,dk: 'Stilhed i 60 min' + ,fi: 'Hiljennä tunniksi' ,nb: 'Stille i 60 min' } ,'Silence for 90 minutes' : { @@ -2530,6 +2709,7 @@ function init() { ,sv: 'Tyst i 90 min' ,it: 'Silenzio per 90 minuti' ,dk: 'Stilhed i 90 min' + ,fi: 'Hiljennä 1.5 tunniksi' ,nb: 'Stille i 90 min' } ,'Silence for 120 minutes' : { @@ -2544,6 +2724,7 @@ function init() { ,sv: 'Tyst i 120 min' ,it: 'Silenzio per 120 minuti' ,dk: 'Stilhed i 120 min' + ,fi: 'Hiljennä 2 tunniksi' ,nb: 'Stile i 120 min' } ,'3HR' : { @@ -2558,6 +2739,7 @@ function init() { ,hr: '3h' ,it: '3h' ,dk: '3tim' + ,fi: '3h' ,nb: '3t' } ,'6HR' : { @@ -2572,6 +2754,7 @@ function init() { ,hr: '6h' ,it: '6h' ,dk: '6tim' + ,fi: '6h' ,nb: '6t' } ,'12HR' : { @@ -2586,6 +2769,7 @@ function init() { ,hr: '12h' ,it: '12h' ,dk: '12tim' + ,fi: '12h' ,nb: '12t' } ,'24HR' : { @@ -2600,6 +2784,7 @@ function init() { ,hr: '24h' ,it: '24h' ,dk: '24tim' + ,fi: '24h' ,nb: '24t' } ,'Settings' : { @@ -2614,6 +2799,7 @@ function init() { ,hr: 'Postavke' ,it: 'Impostazioni' ,dk: 'Opsætning' + ,fi: 'Asetukset' ,nb: 'Innstillinger' } ,'Units' : { @@ -2628,6 +2814,7 @@ function init() { ,sv: 'Enheter' ,it: 'Unità' ,dk: 'Enheder' + ,fi: 'Yksikköä' ,nb: 'Enheter' } ,'Date format' : { @@ -2642,6 +2829,7 @@ function init() { ,hr: 'Format datuma' ,it: 'Formato data' ,dk: 'dato format' + ,fi: 'Aikamuoto' ,nb: 'datoformat' } ,'12 hours' : { @@ -2656,6 +2844,7 @@ function init() { ,hr: '12 sati' ,it: '12 ore' ,dk: '12 timer' + ,fi: '12 tuntia' ,nb: '12 timer' } ,'24 hours' : { @@ -2670,6 +2859,7 @@ function init() { ,hr: '24 sata' ,it: '24 ore' ,dk: '24 timer' + ,fi: '24 tuntia' ,nb: '24 timer' } ,'Log a Treatment' : { @@ -2684,6 +2874,7 @@ function init() { ,sv: 'Ange händelse' ,it: 'Somministrazione' ,dk: 'Log en hændelse' + ,fi: 'Tallenna tapahtuma' ,nb: 'Logg en hendelse' } ,'BG Check' : { @@ -2698,6 +2889,7 @@ function init() { ,hr: 'Kontrola GUK-a' ,it: 'Controllo Glicemia' ,dk: 'BS kontrol' + ,fi: 'Verensokerin tarkistus' ,nb: 'Blodsukkerkontroll' } ,'Meal Bolus' : { @@ -2712,6 +2904,7 @@ function init() { ,sv: 'Måltidsbolus' ,it: 'bolo pasto' ,dk: 'Måltidsbolus' + ,fi: 'Ruokailubolus' ,nb: 'Måltidsbolus' } ,'Snack Bolus' : { @@ -2726,6 +2919,7 @@ function init() { ,hr: 'Bolus za užinu' ,it: 'Bolo merenda' ,dk: 'Mellemmåltidsbolus' + ,fi: 'Ruokakorjaus' ,nb: 'Mellommåltidsbolus' } ,'Correction Bolus' : { @@ -2740,6 +2934,7 @@ function init() { ,sv: 'Korrektionsbolus' ,it: 'Correzione bolo' ,dk: 'Korrektionsbolus' + ,fi: 'Korjausbolus' ,nb: 'Korreksjonsbolus' } ,'Carb Correction' : { @@ -2754,6 +2949,7 @@ function init() { ,sv: 'Kolhydratskorrektion' ,it: 'Correzione carboidrati' ,dk: 'Kulhydratskorrektion' + ,fi: 'Hiilihydraattikorjaus' ,nb: 'Karbohydratkorrigering' } ,'Note' : { @@ -2768,6 +2964,7 @@ function init() { ,hr: 'Bilješka' ,it: 'Nota' ,dk: 'Note' + ,fi: 'Merkintä' ,nb: 'Notat' } ,'Question' : { @@ -2782,10 +2979,12 @@ function init() { ,hr: 'Pitanje' ,it: 'Domanda' ,dk: 'Spørgsmål' + ,fi: 'Kysymys' ,nb: 'Spørsmål' } ,'Announcement' : { bg: 'Известяване' + , fi: 'Tiedoitus' } ,'Exercise' : { cs: 'Cvičení' @@ -2799,6 +2998,7 @@ function init() { ,hr: 'Aktivnost' ,it: 'Esercizio' ,dk: 'Træning' + ,fi: 'Fyysinen harjoitus' ,nb: 'Trening' } ,'Pump Site Change' : { @@ -2813,6 +3013,7 @@ function init() { ,hr: 'Promjena seta' ,it: 'Cambio Ago' ,dk: 'Skift insulin infusionssted' + ,fi: 'Pumpun kanyylin vaihto' ,nb: 'Pumpebytte' } ,'Sensor Start' : { @@ -2827,6 +3028,7 @@ function init() { ,hr: 'Start senzora' ,it: 'Avvio sensore' ,dk: 'Sensorstart' + ,fi: 'Sensorin aloitus' ,nb: 'Sensorstart' } ,'Sensor Change' : { @@ -2841,6 +3043,7 @@ function init() { ,hr: 'Promjena senzora' ,it: 'Cambio sensore' ,dk: 'Sensor ombytning' + ,fi: 'Sensorin vaihto' ,nb: 'Sensorbytte' } ,'Dexcom Sensor Start' : { @@ -2855,6 +3058,7 @@ function init() { ,hr: 'Start Dexcom senzora' ,it: 'Avvio sensore Dexcom' ,dk: 'Dexcom sensor start' + ,fi: 'Sensorin aloitus' ,nb: 'Dexcom sensor start' } ,'Dexcom Sensor Change' : { @@ -2869,6 +3073,7 @@ function init() { ,hr: 'Promjena Dexcom senzora' ,it: 'Cambio sensore Dexcom' ,dk: 'Dexcom sensor ombytning' + ,fi: 'Sensorin vaihto' ,nb: 'Dexcom sensor bytte' } ,'Insulin Cartridge Change' : { @@ -2883,6 +3088,7 @@ function init() { ,hr: 'Promjena spremnika inzulina' ,it: 'Cambio cartuccia insulina' ,dk: 'Skift insulin beholder' + ,fi: 'Insuliinisäiliön vaihto' ,nb: 'Skifte insulin beholder' } ,'D.A.D. Alert' : { @@ -2897,6 +3103,7 @@ function init() { ,hr: 'Obavijest dijabetičkog psa' ,it: 'Allarme D.A.D.' ,dk: 'Vuf Vuf! (Diabeteshundealarm!)' + ,fi: 'Diabeteskoirahälytys' ,nb: 'Diabeteshundalarm' } ,'Glucose Reading' : { @@ -2911,6 +3118,7 @@ function init() { ,hr: 'Vrijednost GUK-a' ,it: 'Lettura glicemie' ,dk: 'Glukose aflæsning' + ,fi: 'Verensokeri' ,nb: 'Blodsukkermåling' } ,'Measurement Method' : { @@ -2925,6 +3133,7 @@ function init() { ,hr: 'Metoda mjerenja' ,it: 'Metodo di misurazione' ,dk: 'Målemetode' + ,fi: 'Mittaustapa' ,nb: 'Målemetode' } ,'Meter' : { @@ -2939,6 +3148,7 @@ function init() { ,hr: 'Glukometar' ,it: 'Glucometro' ,dk: 'Glukosemeter' + ,fi: 'Sokerimittari' ,nb: 'Måleapparat' } ,'Insulin Given' : { @@ -2953,6 +3163,7 @@ function init() { ,hr: 'Količina iznulina' ,it: 'Insulina' ,dk: 'Insulin dosis' + ,fi: 'Insuliiniannos' ,nb: 'Insulin' } ,'Amount in grams' : { @@ -2967,6 +3178,7 @@ function init() { ,hr: 'Količina u gramima' ,it: 'Quantità in grammi' ,dk: 'Antal gram' + ,fi: 'Määrä grammoissa' ,nb: 'Antall gram' } ,'Amount in units' : { @@ -2981,6 +3193,7 @@ function init() { ,sv: 'Antal enheter' ,it: 'Quantità in unità' ,dk: 'Antal enheder' + ,fi: 'Annos yksiköissä' ,nb: 'Antall enheter' } ,'View all treatments' : { @@ -2995,6 +3208,7 @@ function init() { ,hr: 'Prikaži sve tretmane' ,it: 'Visualizza tutti le somministrazioni' ,dk: 'Vis behandlinger' + ,fi: 'Katso kaikki hoitotoimenpiteet' ,nb: 'Vis behandlinger' } ,'Enable Alarms' : { @@ -3009,6 +3223,7 @@ function init() { ,hr: 'Aktiviraj alarme' ,it: 'Attiva Allarme' ,dk: 'Aktivere alarmer' + ,fi: 'Aktivoi hälytykset' ,nb: 'Aktiver alarmer' } ,'When enabled an alarm may sound.' : { @@ -3023,6 +3238,7 @@ function init() { ,hr: 'Kad je aktiviran, alarm se može oglasiti' ,it: 'Quando si attiva un allarme acustico.' ,dk: 'Når aktiveret kan alarm lyde' + ,fi: 'Aktivointi mahdollistaa äänihälytykset' ,nb: 'Når aktivert er alarmer aktive' } ,'Urgent High Alarm' : { @@ -3037,6 +3253,7 @@ function init() { ,hr: 'Hitni alarm za hiper' ,it: 'Allarme Urgente: Glicemia Molto Alta' ,dk: 'Høj grænse overskredet' + ,fi: 'Kriittinen korkea' ,nb: 'Kritisk høy alarm' } ,'High Alarm' : { @@ -3051,6 +3268,7 @@ function init() { ,hr: 'Alarm za hiper' ,it: 'Allarme: Glicemia Alta' ,dk: 'Høj grænse overskredet' + ,fi: 'Korkea verensokeri' ,nb: 'Høy alarm' } ,'Low Alarm' : { @@ -3065,6 +3283,7 @@ function init() { ,hr: 'Alarm za hipo' ,it: 'Allarme Glicemia bassa' ,dk: 'Lav grænse overskredet' + ,fi: 'Matala verensokeri' ,nb: 'Lav alarm' } ,'Urgent Low Alarm' : { @@ -3079,6 +3298,7 @@ function init() { ,hr: 'Hitni alarm za hipo' ,it: 'Allarme Urgente. Glicemia Molto Bassa' ,dk: 'Advarsel: Lav' + ,fi: 'Kriittinen matala' ,nb: 'Kritisk lav alarm' } ,'Stale Data: Warn' : { @@ -3093,6 +3313,7 @@ function init() { ,hr: 'Pažnja: Stari podaci' ,it: 'Dati obsoleti: Notifica' ,dk: 'Advarsel: Gamle data' + ,fi: 'Vanhat tiedot: varoitus' ,nb: 'Advarsel: Gamle data' } ,'Stale Data: Urgent' : { @@ -3107,6 +3328,7 @@ function init() { ,hr: 'Hitno: Stari podaci' ,it: 'Dati obsoleti: Urgente' ,dk: 'Advarsel: Gamle data' + ,fi: 'Vanhat tiedot: hälytys' ,nb: 'Advarsel: Veldig gamle data' } ,'mins' : { @@ -3121,6 +3343,7 @@ function init() { ,hr: 'min' ,it: 'min' ,dk: 'min' + ,fi: 'minuuttia' ,nb: 'min' } ,'Night Mode' : { @@ -3135,6 +3358,7 @@ function init() { ,hr: 'Noćni način' ,it: 'Modalità notte' ,dk: 'Nat mode' + ,fi: 'Yömoodi' ,nb: 'Nattmodus' } ,'When enabled the page will be dimmed from 10pm - 6am.' : { @@ -3149,6 +3373,7 @@ function init() { ,hr: 'Kad je uključen, stranica će biti zatamnjena od 22-06' ,it: 'Attivandola, la pagina sarà oscurato da 10:00-06:00.' ,dk: 'Når aktiveret vil denne side nedtones fra 22:00-6:00' + ,fi: 'Aktivoituna sivu himmenee kello 22 ja 06 välillä' ,nb: 'Når aktivert vil denne siden nedtones fra 22:00-06:00' } ,'Enable' : { @@ -3163,6 +3388,7 @@ function init() { ,hr: 'Aktiviraj' ,it: 'Permettere' ,dk: 'Aktivere' + ,fi: 'Aktivoi' ,nb: 'Aktiver' } ,'Show Raw BG Data' : { @@ -3177,6 +3403,7 @@ function init() { ,hr: 'Prikazuj sirove podatke o GUK-u' ,it: 'Mostra dati Raw BG' ,dk: 'Vis rå data' + ,fi: 'Näytä raaka VS tieto' ,nb: 'Vis rådata' } ,'Never' : { @@ -3191,6 +3418,7 @@ function init() { ,hr: 'Nikad' ,it: 'Mai' ,dk: 'Aldrig' + ,fi: 'Ei koskaan' ,nb: 'Aldri' } ,'Always' : { @@ -3205,6 +3433,7 @@ function init() { ,hr: 'Uvijek' ,it: 'Sempre' ,dk: 'Altid' + ,fi: 'Aina' ,nb: 'Alltid' } ,'When there is noise' : { @@ -3219,6 +3448,7 @@ function init() { ,hr: 'Kad postoji šum' ,it: 'Quando vi è rumore' ,dk: 'Når der er støj' + ,fi: 'Signaalihäiriöiden yhteydessä' ,nb: 'Når det er støy' } ,'When enabled small white dots will be displayed for raw BG data' : { @@ -3233,6 +3463,7 @@ function init() { ,hr: 'Kad je omogućeno, male bijele točkice će prikazivati sirove podatke o GUK-u.' ,it: 'Quando lo abiliti, visualizzerai piccoli puntini bianchi (raw BG data)' ,dk: 'Ved aktivering vil små hvide prikker blive vist for rå BG tal' + ,fi: 'Aktivoituna raaka VS tieto piirtyy aikajanalle valkoisina pisteinä' ,nb: 'Ved aktivering vil små hvite prikker bli vist for rå BG tall' } ,'Custom Title' : { @@ -3247,6 +3478,7 @@ function init() { ,hr: 'Vlastiti naziv' ,it: 'Titolo personalizzato' ,dk: 'Egen titel' + ,fi: 'Omavalintainen otsikko' ,nb: 'Egen tittel' } ,'Theme' : { @@ -3261,6 +3493,7 @@ function init() { ,sv: 'Tema' ,it: 'Tema' ,dk: 'Tema' + ,fi: 'Teema' ,nb: 'Tema' } ,'Default' : { @@ -3275,6 +3508,7 @@ function init() { ,hr: 'Default' ,it: 'Predefinito' ,dk: 'Standard' + ,fi: 'Oletus' ,nb: 'Standard' } ,'Colors' : { @@ -3289,6 +3523,7 @@ function init() { ,hr: 'Boje' ,it: 'Colori' ,dk: 'Farver' + ,fi: 'Värit' ,nb: 'Farger' } ,'Reset, and use defaults' : { @@ -3303,6 +3538,7 @@ function init() { ,hr: 'Resetiraj i koristi defaultne vrijednosti' ,it: 'Resetta e utilizza le impostazioni predefinite' ,dk: 'Returner til standardopsætning' + ,fi: 'Palauta oletusasetukset' ,nb: 'Gjenopprett standardinnstillinger' } ,'Calibrations' : { @@ -3317,6 +3553,7 @@ function init() { ,hr: 'Kalibriranje' ,it: 'Calibrazioni' ,dk: 'Kalibrering' + ,fi: 'Kalibraatiot' ,nb: 'Kalibreringer' } ,'Alarm Test / Smartphone Enable' : { @@ -3331,6 +3568,7 @@ function init() { ,hr: 'Alarm test / Aktiviraj smartphone' ,it: 'Test Allarme / Abilita Smartphone' ,dk: 'Alarm test / Smartphone aktiveret' + ,fi: 'Hälytyksien testaus / Älypuhelimien äänet päälle' ,nb: 'Alarmtest / Smartphone aktivering' } ,'Bolus Wizard' : { @@ -3345,6 +3583,7 @@ function init() { ,hr: 'Bolus wizard' ,it: 'Bolo guidato' ,dk: 'Bolusberegner' + ,fi: 'Annosopas' ,nb: 'Boluskalkulator' } ,'in the future' : { @@ -3359,6 +3598,7 @@ function init() { ,hr: 'U budućnosti' ,it: 'nel futuro' ,dk: 'fremtiden' + ,fi: 'tulevaisuudessa' ,nb: 'fremtiden' } ,'time ago' : { @@ -3373,6 +3613,7 @@ function init() { ,hr: 'prije' ,it: 'tempo fa' ,dk: 'tid siden' + ,fi: 'aikaa sitten' ,nb: 'tid siden' } ,'hr ago' : { @@ -3387,6 +3628,7 @@ function init() { ,hr: 'sat unazad' ,it: 'ora fa' ,dk: 'Time siden' + ,fi: 'tunti sitten' ,nb: 'Time siden' } ,'hrs ago' : { @@ -3401,6 +3643,7 @@ function init() { ,hr: 'sati unazad' ,it: 'ore fa' ,dk: 'Timer siden' + ,fi: 'tuntia sitten' ,nb: 'Timer siden' } ,'min ago' : { @@ -3415,6 +3658,7 @@ function init() { ,hr: 'minuta unazad' ,it: 'minuto fa' ,dk: 'minutter siden' + ,fi: 'minuuttia sitten' ,nb: 'minutter siden' } ,'mins ago' : { @@ -3429,6 +3673,7 @@ function init() { ,hr: 'minuta unazad' ,it: 'minuti fa' ,dk: 'minutter siden' + ,fi: 'minuuttia sitten' ,nb: 'minutter siden' } ,'day ago' : { @@ -3443,6 +3688,7 @@ function init() { ,hr: 'dan unazad' ,it: 'Giorno fa' ,dk: 'dag siden' + ,fi: 'päivä sitten' ,nb: 'dag siden' } ,'days ago' : { @@ -3457,6 +3703,7 @@ function init() { ,hr: 'dana unazad' ,it: 'giorni fa' ,dk: 'dage siden' + ,fi: 'päivää sitten' ,nb: 'dager siden' } ,'long ago' : { @@ -3471,6 +3718,7 @@ function init() { ,hr: 'prije dosta vremena' ,it: 'Molto tempo fa' ,dk: 'længe siden' + ,fi: 'Pitkän aikaa sitten' ,nb: 'lenge siden' } ,'Clean' : { @@ -3485,6 +3733,7 @@ function init() { ,hr: 'Čisto' ,it: 'Pulito' ,dk: 'Rent' + ,fi: 'Puhdas' ,nb: 'Rent' } ,'Light' : { @@ -3499,6 +3748,7 @@ function init() { ,hr: 'Lagano' ,it: 'Leggero' ,dk: 'Let' + ,fi: 'Kevyt' ,nb: 'Lite' } ,'Medium' : { @@ -3513,6 +3763,7 @@ function init() { ,hr: 'Srednje' ,it: 'Medio' ,dk: 'Middel' + ,fi: 'Keskiverto' ,nb: 'Middels' } ,'Heavy' : { @@ -3527,6 +3778,7 @@ function init() { ,hr: 'Teško' ,it: 'Pesante' ,dk: 'Voldsom' + ,fi: 'Raskas' ,nb: 'Mye' } ,'Treatment type' : { @@ -3541,6 +3793,7 @@ function init() { ,hr: 'Vrsta tretmana' ,it: 'Somministrazione' ,dk: 'Behandlingstype' + ,fi: 'Hoidon tyyppi' ,nb: 'Behandlingstype' } ,'Raw BG' : { @@ -3555,6 +3808,7 @@ function init() { ,hr: 'Sirovi podaci o GUK-u' ,it: 'Raw BG' ,dk: 'Råt BS' + ,fi: 'Raaka VS' ,nb: 'RAW-BS' } ,'Device' : { @@ -3569,6 +3823,7 @@ function init() { ,sv: 'Enhet' ,it: 'Dispositivo' ,dk: 'Enhed' + ,fi: 'Laite' ,nb: 'Enhet' } ,'Noise' : { @@ -3583,6 +3838,7 @@ function init() { ,hr: 'Šum' ,it: 'Rumore' ,dk: 'Støj' + ,fi: 'Kohina' ,nb: 'Støy' } ,'Calibration' : { @@ -3597,6 +3853,7 @@ function init() { ,hr: 'Kalibriranje' ,it: 'Calibratura' ,dk: 'Kalibrering' + ,fi: 'Kalibraatio' ,nb: 'Kalibrering' } ,'Show Plugins' : { @@ -3611,6 +3868,7 @@ function init() { ,sv: 'Visa tillägg' ,it: 'Mostra Plugin' ,dk: 'Vis plugins' + ,fi: 'Näytä pluginit' ,nb: 'Vis plugins' } ,'About' : { @@ -3625,6 +3883,7 @@ function init() { ,sv: 'Om' ,it: 'Informazioni' ,dk: 'Om' + ,fi: 'Nightscoutista' ,nb: 'Om' } ,'Value in' : { @@ -3639,6 +3898,7 @@ function init() { ,sv: 'Värde om' ,it: 'Valore in' ,dk: 'Værdi i' + ,fi: 'Arvo yksiköissä' ,nb: 'Verdi i' } ,'Carb Time' : { @@ -3651,13 +3911,16 @@ function init() { ,sv: 'Kolhydratstid' ,it: 'Tempo' ,dk: 'Kulhydratstid' + ,fi: 'Syöntiaika' ,nb: 'Karbohydrattid' } ,'Language' : { cs: 'Jazyk' + ,fi: 'Kieli' } ,'Update' : { // Update button cs: 'Aktualizovat' + ,fi: 'Päivitys' } }; From 315a414ed0414a350ee6196b9c31d03574ea7250 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 1 Sep 2015 12:21:08 -0700 Subject: [PATCH 776/937] also check for null's from local storage --- lib/client/browser-settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/client/browser-settings.js b/lib/client/browser-settings.js index 1d58d9adbd6..e49b05cb04f 100644 --- a/lib/client/browser-settings.js +++ b/lib/client/browser-settings.js @@ -142,7 +142,7 @@ function init (client, plugins, serverSettings, $) { try { settings.eachSetting(function setEach (name) { var stored = storage.get(name); - return stored !== undefined ? storage.get(name) : serverSettings.settings[name]; + return stored !== undefined && stored !== null ? storage.get(name) : serverSettings.settings[name]; }); } catch(err) { console.error(err); From cb6eae33660314cf278f9d38d161f89a2a0bd7f3 Mon Sep 17 00:00:00 2001 From: Ben West Date: Tue, 1 Sep 2015 12:40:40 -0700 Subject: [PATCH 777/937] more efficient use of regex If there's only one prefix, use it to help constrain search against index. --- lib/api/entries/index.js | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/lib/api/entries/index.js b/lib/api/entries/index.js index e91750feee2..c1de32d52ca 100644 --- a/lib/api/entries/index.js +++ b/lib/api/entries/index.js @@ -249,22 +249,33 @@ function configure (app, wares, ctx) { function prep_patterns (req, res, next) { var pattern = [ ]; - if (req.params.prefix) { + var prefix = expand(req.params.prefix || '.*'); + // new RegExp('^' + ); + if (prefix.length > 1) { pattern.push('^' + req.params.prefix); } if (req.params.regex) { pattern.push('.*' + req.params.regex); } pattern = expand(pattern.join('')); + function iter_regex (prefix, suffix) { + function make (pat) { + pat = (prefix ? prefix : '') + pat + (suffix ? suffix : ''); + return new RegExp(pat); + } + return make; + } req.pattern = pattern; - var matches = pattern.map(function iter (pat) { - return new RegExp(pat); - }); + var matches = pattern.map(iter_regex( )); var field = req.patternField; var query = { }; query[field] = { + // $regex: prefix, $in: matches }; + if (prefix.length == 1) { + query[field].$regex = prefix.map(iter_regex('^')).pop( ); + } if (req.query.find) { if (req.query.find[field]) { From ba69eb6476161a80383a140af6da72174765e623 Mon Sep 17 00:00:00 2001 From: Ben West Date: Tue, 1 Sep 2015 14:21:06 -0700 Subject: [PATCH 778/937] add some docs/comments --- lib/api/entries/index.js | 285 +++++++++++++++++++++++++++++++++++---- 1 file changed, 261 insertions(+), 24 deletions(-) diff --git a/lib/api/entries/index.js b/lib/api/entries/index.js index c1de32d52ca..7b8b0f966e4 100644 --- a/lib/api/entries/index.js +++ b/lib/api/entries/index.js @@ -11,10 +11,22 @@ function isId (value) { return value && ID_PATTERN.test(value) && value.length === 24; } -/**********\ - * Entries -\**********/ +/** + * @module Entries + * Entries module + */ + +/** + * @method configure + * Configure the entries module, given an existing express app, common + * middlewares, and the global app's `ctx`. + * @param Express app The express app we'll mount onto. + * @param Object wares Common middleware used by lots of apps. + * @param Object ctx The global ctx with all modules, storage, and event buses + * configured. + */ function configure (app, wares, ctx) { + // default storage biased towards entries. var entries = ctx.entries; var express = require('express'), api = express.Router( ) @@ -27,39 +39,68 @@ function configure (app, wares, ctx) { // json body types get handled as parsed json api.use(wares.bodyParser.json()); // shortcut to use extension to specify output content-type - // api.use(require('express-extension-to-accept')([ api.use(wares.extensions([ 'json', 'svg', 'csv', 'txt', 'png', 'html', 'tsv' ])); // also support url-encoded content-type api.use(wares.bodyParser.urlencoded({ extended: true })); + /** + * @method force_typed_data + * @returns {Stream} Creates a through stream which validates that all + * elements on the stream have a `type` field. + * Generate a stream that ensures elements have a `type` field. + */ function force_typed_data (opts) { + /** + * @function + * Iterate over every element in the stream, enforcing some data type. + */ function sync (data, next) { + // if element has no data type, but we know what the type should be if (!data.type && opts.type) { + // bless absence with known type data.type = opts.type; } + // continue control flow to next element in the stream next(null, data); } + // return configured stream return es.map(sync); } - // Middleware to format any response involving entries. + /** + * A final middleware to send payloads assembled by previous middlewares + * out to the http client. + * We expect a payload to be attached to `res.entries`. + // Middleware to format any response involving entries. + */ function format_entries (req, res) { + // deduce what type of records we might expect var type_params = { type: (req.query && req.query.find && req.query.find.type && req.query.find.type !== req.params.model) ? req.query.find.type : req.params.model }; + // prepare a stream of elements from some prepared payload var output = es.readArray(res.entries || [ ]); + // on other hand, if there's been some error, report that if (res.entries_err) { return res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', res.entries_err); } + // if no error, format the payload + // The general pattern here is to create an output stream that reformats + // the data correctly into the desired representation. + // The stream logic allows some streams to ensure that some basic rules, + // such as enforcing a type property to exist, are followed. return res.format({ text: function ( ) { + // sgvdata knows how to format sgv entries as text es.pipeline(output, sgvdata.format( ), res); }, json: function ( ) { + // so long as every element has a `type` field, and some kind of + // date, we'll consider it valid es.pipeline(output, force_typed_data(type_params), es.writeArray(function (err, out) { res.json(out); })); @@ -67,6 +108,12 @@ function configure (app, wares, ctx) { }); } + /** + * middleware to process "uploads" of sgv data + * This inspects the http requests's incoming payload. This creates a + * validating stream for the appropriate type of payload, which is piped + * into the configured storage layer, saving the results in mongodb. + */ // middleware to process "uploads" of sgv data function insert_entries (req, res, next) { // list of incoming records @@ -83,18 +130,19 @@ function configure (app, wares, ctx) { incoming = incoming.concat(req.body); } - /* - * inputs -> - * in node, pipe is the most interoperable interface - * inputs returns a readable stream representing all the potential - * records from the HTTP body. - * Most content-types are handled by express middeware. - * However, text/* types are given to us as a raw buffer, this - * function switches between these two variants to find the - * correct input stream. - * stream, so use svgdata to handle those. - * The inputs stream always emits sgv json objects. - */ + /** + * @returns {ReadableStream} Readable stream with all incoming elements + * in the stream. + * in node, pipe is the most interoperable interface + * inputs returns a readable stream representing all the potential + * records from the HTTP body. + * Most content-types are handled by express middeware. + * However, text/* types are given to us as a raw buffer, this + * function switches between these two variants to find the + * correct input stream. + * stream, so use svgdata to handle those. + * The inputs stream always emits sgv json objects. + */ function inputs ( ) { var input; // handle all text types @@ -107,7 +155,11 @@ function configure (app, wares, ctx) { return es.readArray(incoming); } - // return a writable persistent storage stream + /** + * @returns {WritableStream} a writable persistent storage stream + * Sends stream elements into storage layer. + * Configures the storage layer stream. + */ function persist (fn) { if (req.persist_entries) { // store everything @@ -117,8 +169,12 @@ function configure (app, wares, ctx) { return es.pipeline(entries.map( ), es.writeArray(fn)); } - // store results and move to the next middleware + /** + * Final callback store results on `res.entries`, after all I/O is done. + * store results and move to the next middleware + */ function done (err, result) { + // assign payload res.entries = result; res.entries_err = err; return next( ); @@ -129,6 +185,11 @@ function configure (app, wares, ctx) { es.pipeline(inputs( ), persist(done)); } + /** + * @param {Request} req The request to inspect + * @param {String} model The name of the model to use if not found. + * Sets `req.query.find.type` to your chosen model. + */ function prepReqModel(req, model) { var type = model || 'sgv'; if (!req.query.find) { @@ -140,11 +201,19 @@ function configure (app, wares, ctx) { } } + /** + * Prepare model based on explicit choice in route/path parameter. + */ api.param('model', function (req, res, next, model) { prepReqModel(req, model); next( ); }); + /** + * @route + * Get last entry. + * @response /definitions/Entries + */ api.get('/entries/current', function(req, res, next) { //assume sgv req.params.model = 'sgv'; @@ -155,7 +224,16 @@ function configure (app, wares, ctx) { }); }, format_entries); - // Fetch one entry by id + /** + * @route + * Fetch one entry by id + * @response /definitions/Entries + * @param String spec :spec is either the id of a record or model name to + * search. If it is an id, only the record with that id will be in the + * response. If the string is a model name, like `sgv`, `mbg`, et al, the + * usual query logic is performed biased towards that model type. + * Useful for filtering by type. + */ api.get('/entries/:spec', function(req, res, next) { if (isId(req.params.spec)) { entries.getEntry(req.params.spec, function(err, entry) { @@ -172,20 +250,44 @@ function configure (app, wares, ctx) { } }, format_entries); + + /** + * @route + * @response /definitions/Entries + * Use the `find` parameter to generate mongo queries. + * Default is `count=10`, for only 10 latest entries, reverse sorted by + * `find[date]`. + * + */ api.get('/entries', query_models, format_entries); + /** + * Output the generated query object itself, instead of the query results. + * Useful for understanding how REST api parameters translate into mongodb + * queries. + */ function echo_query (req, res) { var query = req.query; + // make a depth-wise copy of the original raw input var input = JSON.parse(JSON.stringify(query)); // If "?count=" is present, use that number to decided how many to return. if (!query.count) { query.count = 10; } + // bias towards entries, but allow expressing preference of storage layer var storage = req.params.echo || 'entries'; + // send payload with information about query itself res.json({ query: ctx[storage].query_for(query), input: input, params: req.params, storage: storage}); } + + /** + * Perform the standard query logic, translating API parameters into mongo + * db queries in a fairly regimented manner. + * This middleware executes the query, assigning the payload to results on + * `res.entries`. + */ function query_models (req, res, next) { var query = req.query; @@ -194,15 +296,24 @@ function configure (app, wares, ctx) { query.count = 10; } + // bias to entries, but allow expressing a preference var storage = req.storage || ctx.entries; - storage.list(query, function(err, entries) { + // perform the query + storage.list(query, function payload (err, entries) { + // assign payload res.entries = entries; res.entries_err = err; return next( ); }); } + /** + * @route + * Delete entries. The query logic works the same way as find/list. This + * endpoint uses same search logic to remove records from the database. + */ api.delete('/entries/:spec', function(req, res, next) { + // if ID, prepare to query for one record if (isId(req.params.id)) { prepReqModel(req, req.params.model); req.query = {find: {_id: req.params.spec}}; @@ -214,21 +325,31 @@ function configure (app, wares, ctx) { }, delete_records); + /** + * Delete entries. The query logic works the same way as find/list. This + * endpoint uses same search logic to remove records from the database. + */ function delete_records (req, res, next) { + // bias towards model, but allow expressing a preference if (!req.model) { req.model = ctx.entries; } var query = req.query; if (!query.count) { query.count = 10 } + // remove using the query req.model.remove(query, function(err, stat) { if (err) { return next(err); } + // yield some information about success of operation res.json(stat); return next( ); }); } + /** + * Middleware that prepares the :spec parameter in the routed path. + */ api.param('spec', function (req, res, next, spec) { if (isId(spec)) { prepReqModel(req, req.params.model); @@ -238,6 +359,11 @@ function configure (app, wares, ctx) { } next( ); }); + + /** + * The echo parameter in the path routing parameters allows the echo + * endpoints to customize the storage layer. + */ api.param('echo', function (req, res, next, echo) { console.log('echo', echo); if (!echo) { @@ -245,38 +371,91 @@ function configure (app, wares, ctx) { } next( ); }); + /** + * @routed + * Echo information about model/spec queries. + * Useful in understanding how REST API prepares queries against mongo. + */ api.get('/echo/:echo/:model?/:spec?', echo_query); + /** + * Prepare regexp patterns based on `prefix`, and `regex` parameters. + * Translates `/:prefix/:regex` strings into fancy mongo queries. + * @params String prefix + * @params String regex + * This performs bash style brace/glob pattern expansion in order to generate flexible series of regex patterns. + * Very useful in querying across days, but constrained hours of time. + * Consider the following examples: +``` +curl -s -g 'http://localhost:1337/api/v1/times/2015-04/T{13..18}:{00..15}'.json'?find[sgv][$gte]=120' | json -a dateString sgv +curl -s -g 'http://localhost:1337/api/v1/times/20{14..15}-04/T{13..18}:{00..15}'.json'?find[sgv][$gte]=120' | json -a dateString sgv +curl -s -g 'http://localhost:1337/api/v1/times/20{14..15}/T{13..18}:{00..15}'.json'?find[sgv][$gte]=120' | json -a dateString sgv + +``` + */ function prep_patterns (req, res, next) { + // initialize empty pattern list. var pattern = [ ]; + // initialize a basic prefix + // also perform bash brace/glob-style expansion var prefix = expand(req.params.prefix || '.*'); - // new RegExp('^' + ); + + // if expansion leads to more than one prefix if (prefix.length > 1) { + // pre-pend the prefix to the pattern list and wait to expand it as + // part of the full pattern pattern.push('^' + req.params.prefix); } + // append any regex parameters if (req.params.regex) { + // prepend "match any" rule to their rule pattern.push('.*' + req.params.regex); } + // create a single pattern with all inputs considered + // expand the pattern using bash/glob style brace expansion to generate + // an array of patterns. pattern = expand(pattern.join('')); + + /** + * Factory function to customize creation of RegExp patterns. + * @param String prefix Default null + * @param String suffix Default null + * @returns function(pat) which turns the given pattern into a new + * RegExp with the prefix and suffix prepended, and appended, + * respectively. + */ function iter_regex (prefix, suffix) { + /** + * @returns RegExp Make a RegExp with configured prefix and suffix + */ function make (pat) { + // concat the prefix, pattern, and suffix. pat = (prefix ? prefix : '') + pat + (suffix ? suffix : ''); + // return RegExp. return new RegExp(pat); } + // return functor return make; } + + // save pattern for other middlewares, eg echo, query, etc. req.pattern = pattern; var matches = pattern.map(iter_regex( )); + // prepare the query against a configurable field name. var field = req.patternField; var query = { }; query[field] = { // $regex: prefix, + // configure query to perform regex against list of potential regexp $in: matches }; if (prefix.length == 1) { + // If there is a single prefix pattern, mongo can optimize this against + // an indexed field query[field].$regex = prefix.map(iter_regex('^')).pop( ); } + // Merge into existing query structure. if (req.query.find) { if (req.query.find[field]) { req.query.find[field].$in = query[field].$in; @@ -286,20 +465,37 @@ function configure (app, wares, ctx) { } else { req.query.find = query; } + // Also assist in querying for the requested type. if (req.params.type) { req.query.find.type = req.params.type; } next( ); } + /** + * Ensure that `req.patternField` is set to assist other middleware in + * deciding which field to generate queries against. + * Default is `dateString`, because that's the iso8601 field for sgv + * entries. + */ function prep_pattern_field (req, res, next) { + // If req.params.field from routed path parameter is available use it. if (req.params.field) { req.patternField = req.params.field; } else { + // Default is `dateString`. req.patternField = 'dateString'; } next( ); } + + /** + * Prep storage layer for other middleware by setting `req.storage`. + * Some routed paths have a `storage` parameter available, when this is + * set, `req.storage will be set to that value. The default otherwise is + * the entries storage layer, because that's where sgv records are stored + * by default. + */ function prep_storage (req, res, next) { if (req.params.storage) { req.storage = ctx[req.params.storage]; @@ -309,22 +505,61 @@ function configure (app, wares, ctx) { next( ); } + /** + * Echo interface for the regex pattern generator. + * @routed + * Useful for understanding how the `/:prefix/:regex` route generates + * mongodb queries. + */ api.get('/times/echo/:prefix?/:regex?', prep_storage, prep_pattern_field, prep_patterns, prep_patterns, function (req, res, next) { res.json({ req: { params: req.params, query: req.query}, pattern: req.pattern}); }); + + /** + * Allows searching for modal times of day across days and months. +``` +/api/v1/times/2015-04/T{13..18}:{00..15}'.json'?find[sgv][$gte]=120 +/api/v1/times/20{14..15}-04/T{13..18}:{00..15}'.json'?find[sgv][$gte]=120 +/api/v1/times/20{14..15}/T{13..18}:{00..15}'.json'?find[sgv][$gte]=120 +``` + * @routed + * @response 200 /definitions/Entries + */ api.get('/times/:prefix?/:regex?', prep_storage, prep_pattern_field, prep_patterns, prep_patterns, query_models, format_entries); + + /** + * @routed + * @response 200 /definitions/Entries + * Allows searching for modal times of day across days and months. + * Also allows specifying field to perform regexp on, the storage layer to + * use, as well as which type of model to look for. +``` +/api/v1/slice/entries/dateString/mbg/2015.json +``` + */ api.get('/slice/:storage/:field/:type?/:prefix?/:regex?', prep_storage, prep_pattern_field, prep_patterns, query_models, format_entries); - // Allow previewing your post content, just echos everything you - // posted back out. + /** + * Allow previewing your post content, just echos everything you + * posted back out. + * Similar to the echo api, useful to lint/debug upload problems. + */ api.post('/entries/preview', function (req, res, next) { + // setting this flag tells insert_entries to not actually store the results req.persist_entries = false; next( ); }, insert_entries, format_entries); + // Protect endpoints with authenticated api. if (app.enabled('api')) { // Create and store new sgv entries + /** + * Allow posting content to store. + * Stores incoming payload that follows basic rules about having a + * `type` field in `entries` storage layer. + */ api.post('/entries/', wares.verifyAuthorization, function (req, res, next) { + // setting this flag tells insert_entries to store the results req.persist_entries = true; next( ); }, insert_entries, format_entries); @@ -332,4 +567,6 @@ function configure (app, wares, ctx) { return api; } + +// expose module module.exports = configure; From a927c3502b45c9acd1b2c2b2831cf56ed2eef5ee Mon Sep 17 00:00:00 2001 From: Ben West Date: Tue, 1 Sep 2015 14:22:51 -0700 Subject: [PATCH 779/937] move delete to be protected by auth --- lib/api/entries/index.js | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/lib/api/entries/index.js b/lib/api/entries/index.js index 7b8b0f966e4..60d215fd868 100644 --- a/lib/api/entries/index.js +++ b/lib/api/entries/index.js @@ -70,6 +70,7 @@ function configure (app, wares, ctx) { } /** + * @method format_entries * A final middleware to send payloads assembled by previous middlewares * out to the http client. * We expect a payload to be attached to `res.entries`. @@ -307,24 +308,6 @@ function configure (app, wares, ctx) { }); } - /** - * @route - * Delete entries. The query logic works the same way as find/list. This - * endpoint uses same search logic to remove records from the database. - */ - api.delete('/entries/:spec', function(req, res, next) { - // if ID, prepare to query for one record - if (isId(req.params.id)) { - prepReqModel(req, req.params.model); - req.query = {find: {_id: req.params.spec}}; - } else { - req.params.model = req.params.spec; - prepReqModel(req, req.params.model); - } - next( ); - }, delete_records); - - /** * Delete entries. The query logic works the same way as find/list. This * endpoint uses same search logic to remove records from the database. @@ -563,6 +546,25 @@ curl -s -g 'http://localhost:1337/api/v1/times/20{14..15}/T{13..18}:{00..15}'.js req.persist_entries = true; next( ); }, insert_entries, format_entries); + + /** + * @route + * Delete entries. The query logic works the same way as find/list. This + * endpoint uses same search logic to remove records from the database. + */ + api.delete('/entries/:spec', function(req, res, next) { + // if ID, prepare to query for one record + if (isId(req.params.id)) { + prepReqModel(req, req.params.model); + req.query = {find: {_id: req.params.spec}}; + } else { + req.params.model = req.params.spec; + prepReqModel(req, req.params.model); + } + next( ); + }, delete_records); + + } return api; From fa11bc09d89fe74684404ae05c6ba3950e946dba Mon Sep 17 00:00:00 2001 From: Ben West Date: Tue, 1 Sep 2015 14:23:32 -0700 Subject: [PATCH 780/937] protect delete behind auth --- lib/api/entries/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/api/entries/index.js b/lib/api/entries/index.js index 60d215fd868..4b8d59f6cda 100644 --- a/lib/api/entries/index.js +++ b/lib/api/entries/index.js @@ -552,7 +552,7 @@ curl -s -g 'http://localhost:1337/api/v1/times/20{14..15}/T{13..18}:{00..15}'.js * Delete entries. The query logic works the same way as find/list. This * endpoint uses same search logic to remove records from the database. */ - api.delete('/entries/:spec', function(req, res, next) { + api.delete('/entries/:spec', wares.verifyAuthorization, function (req, res, next) { // if ID, prepare to query for one record if (isId(req.params.id)) { prepReqModel(req, req.params.model); From dc392f1aeefd8489bff50759f6e18a30cc9f6a20 Mon Sep 17 00:00:00 2001 From: Ben West Date: Tue, 1 Sep 2015 14:31:05 -0700 Subject: [PATCH 781/937] tweak docs --- lib/api/entries/index.js | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/lib/api/entries/index.js b/lib/api/entries/index.js index 4b8d59f6cda..60f29759a7b 100644 --- a/lib/api/entries/index.js +++ b/lib/api/entries/index.js @@ -53,7 +53,7 @@ function configure (app, wares, ctx) { */ function force_typed_data (opts) { /** - * @function + * @function sync * Iterate over every element in the stream, enforcing some data type. */ function sync (data, next) { @@ -110,6 +110,7 @@ function configure (app, wares, ctx) { } /** + * @method insert_entries * middleware to process "uploads" of sgv data * This inspects the http requests's incoming payload. This creates a * validating stream for the appropriate type of payload, which is piped @@ -132,6 +133,7 @@ function configure (app, wares, ctx) { } /** + * @function inputs * @returns {ReadableStream} Readable stream with all incoming elements * in the stream. * in node, pipe is the most interoperable interface @@ -157,6 +159,7 @@ function configure (app, wares, ctx) { } /** + * @function persist * @returns {WritableStream} a writable persistent storage stream * Sends stream elements into storage layer. * Configures the storage layer stream. @@ -171,6 +174,7 @@ function configure (app, wares, ctx) { } /** + * @function done * Final callback store results on `res.entries`, after all I/O is done. * store results and move to the next middleware */ @@ -187,6 +191,7 @@ function configure (app, wares, ctx) { } /** + * @function prepReqModel * @param {Request} req The request to inspect * @param {String} model The name of the model to use if not found. * Sets `req.query.find.type` to your chosen model. @@ -203,6 +208,7 @@ function configure (app, wares, ctx) { } /** + * @param model * Prepare model based on explicit choice in route/path parameter. */ api.param('model', function (req, res, next, model) { @@ -211,6 +217,7 @@ function configure (app, wares, ctx) { }); /** + * @module get#/entries/current * @route * Get last entry. * @response /definitions/Entries @@ -226,6 +233,7 @@ function configure (app, wares, ctx) { }, format_entries); /** + * @module get#/entries/:spec * @route * Fetch one entry by id * @response /definitions/Entries @@ -253,6 +261,7 @@ function configure (app, wares, ctx) { /** + * @module get#/entries * @route * @response /definitions/Entries * Use the `find` parameter to generate mongo queries. @@ -263,6 +272,7 @@ function configure (app, wares, ctx) { api.get('/entries', query_models, format_entries); /** + * @function echo_query * Output the generated query object itself, instead of the query results. * Useful for understanding how REST api parameters translate into mongodb * queries. @@ -284,6 +294,7 @@ function configure (app, wares, ctx) { } /** + * @function query_models * Perform the standard query logic, translating API parameters into mongo * db queries in a fairly regimented manner. * This middleware executes the query, assigning the payload to results on @@ -309,6 +320,7 @@ function configure (app, wares, ctx) { } /** + * @function delete_records * Delete entries. The query logic works the same way as find/list. This * endpoint uses same search logic to remove records from the database. */ @@ -331,6 +343,7 @@ function configure (app, wares, ctx) { } /** + * @param spec * Middleware that prepares the :spec parameter in the routed path. */ api.param('spec', function (req, res, next, spec) { @@ -344,6 +357,7 @@ function configure (app, wares, ctx) { }); /** + * @param echo * The echo parameter in the path routing parameters allows the echo * endpoints to customize the storage layer. */ @@ -354,7 +368,9 @@ function configure (app, wares, ctx) { } next( ); }); + /** + * @module get#/echo/:echo/:model/:spec * @routed * Echo information about model/spec queries. * Useful in understanding how REST API prepares queries against mongo. @@ -364,6 +380,7 @@ function configure (app, wares, ctx) { /** * Prepare regexp patterns based on `prefix`, and `regex` parameters. * Translates `/:prefix/:regex` strings into fancy mongo queries. + * @method prep_patterns * @params String prefix * @params String regex * This performs bash style brace/glob pattern expansion in order to generate flexible series of regex patterns. @@ -401,6 +418,7 @@ curl -s -g 'http://localhost:1337/api/v1/times/20{14..15}/T{13..18}:{00..15}'.js /** * Factory function to customize creation of RegExp patterns. + * @method iter_regex * @param String prefix Default null * @param String suffix Default null * @returns function(pat) which turns the given pattern into a new @@ -409,6 +427,7 @@ curl -s -g 'http://localhost:1337/api/v1/times/20{14..15}/T{13..18}:{00..15}'.js */ function iter_regex (prefix, suffix) { /** + * @function make * @returns RegExp Make a RegExp with configured prefix and suffix */ function make (pat) { @@ -456,6 +475,7 @@ curl -s -g 'http://localhost:1337/api/v1/times/20{14..15}/T{13..18}:{00..15}'.js } /** + * @method prep_pattern_field * Ensure that `req.patternField` is set to assist other middleware in * deciding which field to generate queries against. * Default is `dateString`, because that's the iso8601 field for sgv @@ -473,6 +493,7 @@ curl -s -g 'http://localhost:1337/api/v1/times/20{14..15}/T{13..18}:{00..15}'.js } /** + * @method prep_storage * Prep storage layer for other middleware by setting `req.storage`. * Some routed paths have a `storage` parameter available, when this is * set, `req.storage will be set to that value. The default otherwise is @@ -489,6 +510,7 @@ curl -s -g 'http://localhost:1337/api/v1/times/20{14..15}/T{13..18}:{00..15}'.js } /** + * @module get#/times/echo/:prefix/:regex * Echo interface for the regex pattern generator. * @routed * Useful for understanding how the `/:prefix/:regex` route generates @@ -499,6 +521,7 @@ curl -s -g 'http://localhost:1337/api/v1/times/20{14..15}/T{13..18}:{00..15}'.js }); /** + * @module get#/times/:prefix/:regex * Allows searching for modal times of day across days and months. ``` /api/v1/times/2015-04/T{13..18}:{00..15}'.json'?find[sgv][$gte]=120 @@ -511,6 +534,7 @@ curl -s -g 'http://localhost:1337/api/v1/times/20{14..15}/T{13..18}:{00..15}'.js api.get('/times/:prefix?/:regex?', prep_storage, prep_pattern_field, prep_patterns, prep_patterns, query_models, format_entries); /** + * @module get#/slice/:storage/:field/:type/:prefix/:regex * @routed * @response 200 /definitions/Entries * Allows searching for modal times of day across days and months. @@ -523,6 +547,7 @@ curl -s -g 'http://localhost:1337/api/v1/times/20{14..15}/T{13..18}:{00..15}'.js api.get('/slice/:storage/:field/:type?/:prefix?/:regex?', prep_storage, prep_pattern_field, prep_patterns, query_models, format_entries); /** + * @module post#/entries/preview * Allow previewing your post content, just echos everything you * posted back out. * Similar to the echo api, useful to lint/debug upload problems. @@ -537,6 +562,7 @@ curl -s -g 'http://localhost:1337/api/v1/times/20{14..15}/T{13..18}:{00..15}'.js if (app.enabled('api')) { // Create and store new sgv entries /** + * @module post#/entries * Allow posting content to store. * Stores incoming payload that follows basic rules about having a * `type` field in `entries` storage layer. @@ -548,6 +574,7 @@ curl -s -g 'http://localhost:1337/api/v1/times/20{14..15}/T{13..18}:{00..15}'.js }, insert_entries, format_entries); /** + * @module delete#/entries/:spec * @route * Delete entries. The query logic works the same way as find/list. This * endpoint uses same search logic to remove records from the database. From 04f44e0ec9895c0968fa4acd93c7e94b1e4bba61 Mon Sep 17 00:00:00 2001 From: Ben West Date: Tue, 1 Sep 2015 17:15:27 -0700 Subject: [PATCH 782/937] fix tests --- lib/api/verifyauth.js | 18 ++++++------------ lib/middleware/verify-token.js | 6 +++--- tests/api.entries.test.js | 7 +++---- tests/update-throttle.test.js | 3 ++- 4 files changed, 14 insertions(+), 20 deletions(-) diff --git a/lib/api/verifyauth.js b/lib/api/verifyauth.js index 8e7a6378e2c..d92646928a1 100644 --- a/lib/api/verifyauth.js +++ b/lib/api/verifyauth.js @@ -6,18 +6,12 @@ function configure (app, env) { var express = require('express'), api = express.Router( ); - function config_authed (api) { - api.get('/verifyauth', function(req, res) { - var api_secret = env.api_secret; - var secret = req.params.secret ? req.params.secret : req.header('api-secret'); - var unauthorized = (typeof api_secret === 'undefined' || secret !== api_secret); - res.sendJSONStatus(res, consts.HTTP_OK, unauthorized ? 'UNAUTHORIZED' : 'OK'); - }); - } - - if (app.enabled('api')) { - config_authed(api); - } + api.get('/verifyauth', function(req, res) { + var api_secret = env.api_secret; + var secret = req.params.secret ? req.params.secret : req.header('api-secret'); + var authorized = (app.enabled('api') && api_secret && api_secret.length > 12) ? (secret === api_secret) : false; + res.sendJSONStatus(res, consts.HTTP_OK, authorized ? 'OK' : 'UNAUTHORIZED'); + }); return api; } diff --git a/lib/middleware/verify-token.js b/lib/middleware/verify-token.js index 714201c5ac2..1f48d16053c 100644 --- a/lib/middleware/verify-token.js +++ b/lib/middleware/verify-token.js @@ -8,14 +8,14 @@ function configure (env) { var secret = req.params.secret ? req.params.secret : req.header('api-secret'); // Return an error message if the authorization fails. - var unauthorized = (typeof api_secret === 'undefined' || secret !== api_secret); - if (unauthorized) { + var authorized = (api_secret && api_secret.length > 12) ? (secret === api_secret) : false; + if (!authorized) { res.sendJSONStatus(res, consts.HTTP_UNAUTHORIZED, 'Unauthorized', 'api-secret Request Header is incorrect or missing.'); } else { next(); } - return unauthorized; + return authorized; } return verifyAuthorization; diff --git a/tests/api.entries.test.js b/tests/api.entries.test.js index 060581b8261..a2918a8c408 100644 --- a/tests/api.entries.test.js +++ b/tests/api.entries.test.js @@ -92,12 +92,12 @@ describe('Entries REST api', function ( ) { }); }); - it('allow deletes', function (done) { + it('disallow deletes unauthorized', function (done) { var app = this.app; request(app) .delete('/entries/sgv?find[dateString][$gte]=2014-07-19&find[dateString][$lte]=2014-07-20') - .expect(200) + .expect(401) .end(function (err) { if (err) { done(err); @@ -106,12 +106,11 @@ describe('Entries REST api', function ( ) { .get('/entries/sgv.json?find[dateString][$gte]=2014-07-19&find[dateString][$lte]=2014-07-20') .expect(200) .end(function (err, res) { - res.body.should.be.instanceof(Array).and.have.lengthOf(0); + res.body.should.be.instanceof(Array).and.have.lengthOf(10); done(); }); } }); }); - }); diff --git a/tests/update-throttle.test.js b/tests/update-throttle.test.js index 9b15c68fb6b..384af352be6 100644 --- a/tests/update-throttle.test.js +++ b/tests/update-throttle.test.js @@ -9,6 +9,7 @@ describe('Throttle', function ( ) { var api = require('../lib/api/'); before(function (done) { + delete process.env.API_SECRET; process.env.API_SECRET = 'this is my long pass phrase'; self.env = require('../env')(); this.wares = require('../lib/middleware/')(self.env); @@ -48,4 +49,4 @@ describe('Throttle', function ( ) { _.times(10, post); }); -}); \ No newline at end of file +}); From e12d90cccb02386b39ffaf0d1491f7f5c1e02dd7 Mon Sep 17 00:00:00 2001 From: Ben West Date: Tue, 1 Sep 2015 17:38:25 -0700 Subject: [PATCH 783/937] add tests for new api endpoints --- tests/api.entries.test.js | 70 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/tests/api.entries.test.js b/tests/api.entries.test.js index a2918a8c408..bad97c2a12f 100644 --- a/tests/api.entries.test.js +++ b/tests/api.entries.test.js @@ -54,6 +54,76 @@ describe('Entries REST api', function ( ) { }); }); + it('/slice/ can slice time', function (done) { + var app = this.app; + var defaultCount = 10; + request(app) + .get('/slice/entries/dateString/sgv/2014-07.json?count=20') + .expect(200) + .end(function (err, res) { + res.body.should.be.instanceof(Array).and.have.lengthOf(20); + done( ); + }); + }); + + it('/slice/ can slice with multiple prefix', function (done) { + var app = this.app; + var defaultCount = 10; + request(app) + .get('/slice/entries/dateString/sgv/2014-07-{17..20}.json?count=20') + .expect(200) + .end(function (err, res) { + res.body.should.be.instanceof(Array).and.have.lengthOf(20); + done( ); + }); + }); + + it('/slice/ can slice time with prefix and no results', function (done) { + var app = this.app; + var defaultCount = 10; + request(app) + .get('/slice/entries/dateString/sgv/1999-07.json?count=20') + .expect(200) + .end(function (err, res) { + res.body.should.be.instanceof(Array).and.have.lengthOf(0); + done( ); + }); + }); + + it('/times/ can get modal times', function (done) { + var app = this.app; + var defaultCount = 10; + request(app) + .get('/times/2014-07-/{0..30}T.json?') + .expect(200) + .end(function (err, res) { + res.body.should.be.instanceof(Array).and.have.lengthOf(10); + done( ); + }); + }); + + it('/times/ can get modal minutes and times', function (done) { + var app = this.app; + request(app) + .get('/times/20{14..15}-07/T{09..10}.json?') + .expect(200) + .end(function (err, res) { + res.body.should.be.instanceof(Array).and.have.lengthOf(10); + done( ); + }); + }); + it('/times/ can get multiple prefixen and modal minutes and times', function (done) { + var app = this.app; + var defaultCount = 10; + request(app) + .get('/times/20{14..15}/T.*:{00..60}.json?') + .expect(200) + .end(function (err, res) { + res.body.should.be.instanceof(Array).and.have.lengthOf(10); + done( ); + }); + }); + it('/entries/current.json', function (done) { request(this.app) .get('/entries/current.json') From 5db2c344c406b5089e26373aa730c0b8ce896b03 Mon Sep 17 00:00:00 2001 From: Ben West Date: Tue, 1 Sep 2015 17:49:11 -0700 Subject: [PATCH 784/937] test both ways of using /entries/:spec --- tests/api.entries.test.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/api.entries.test.js b/tests/api.entries.test.js index bad97c2a12f..395a6e17eed 100644 --- a/tests/api.entries.test.js +++ b/tests/api.entries.test.js @@ -135,7 +135,7 @@ describe('Entries REST api', function ( ) { }); }); - it('/entries/sgv/ID', function (done) { + it('/entries/:id', function (done) { var app = this.app; this.archive.list({count: 1}, function(err, records) { var currentId = records.pop()._id.toString(); @@ -147,8 +147,18 @@ describe('Entries REST api', function ( ) { res.body[0]._id.should.equal(currentId); done( ); }); + }); }); + it('/entries/:model', function (done) { + var app = this.app; + request(app) + .get('/entries/sgv.json?count=4') + .expect(200) + .end(function (err, res) { + res.body.should.be.instanceof(Array).and.have.lengthOf(4); + done( ); + }); }); it('/entries/preview', function (done) { From cbe72c05b544120dba518775fd18e9e263b43681 Mon Sep 17 00:00:00 2001 From: Ben West Date: Tue, 1 Sep 2015 17:53:15 -0700 Subject: [PATCH 785/937] weird test --- tests/api.entries.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/api.entries.test.js b/tests/api.entries.test.js index 395a6e17eed..c1099947068 100644 --- a/tests/api.entries.test.js +++ b/tests/api.entries.test.js @@ -153,10 +153,10 @@ describe('Entries REST api', function ( ) { it('/entries/:model', function (done) { var app = this.app; request(app) - .get('/entries/sgv.json?count=4') + .get('/entries/sgv.json?count=1') .expect(200) .end(function (err, res) { - res.body.should.be.instanceof(Array).and.have.lengthOf(4); + res.body.should.be.instanceof(Array).and.have.lengthOf(1); done( ); }); }); From 8b3164f7e1309dd07bee4bcbb75c317fd040146e Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 1 Sep 2015 18:45:59 -0700 Subject: [PATCH 786/937] when an alarm is triggered by an announcement, show the message --- lib/client/index.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/client/index.js b/lib/client/index.js index 5c72761355a..f2dba0e9a2e 100644 --- a/lib/client/index.js +++ b/lib/client/index.js @@ -8,6 +8,7 @@ var language = require('../language')(); var sandbox = require('../sandbox')(); var profile = require('../profilefunctions')(); var units = require('../units')(); +var levels = require('../levels'); var times = require('../times'); var client = { }; @@ -376,9 +377,22 @@ client.init = function init(serverSettings, plugins) { return range; } - function generateAlarm(file, reason) { + function setAlarmMessage (notify) { + var announcementMessage = notify && notify.isAnnouncement && notify.message && notify.message.length > 1; + + if (announcementMessage) { + alarmMessage = levels.toDisplay(notify.level) + ': ' + notify.message; + } else if (notify) { + alarmMessage = notify.title; + } else { + alarmMessage = null; + } + } + + function generateAlarm (file, notify) { alarmInProgress = true; - alarmMessage = reason && reason.title; + + setAlarmMessage(notify); var selector = '.audio.alarms audio.' + file; if (!alarmingNow()) { From 9ea03e853a1eed51288eeb8c9a4270784e85a10a Mon Sep 17 00:00:00 2001 From: Ben West Date: Tue, 1 Sep 2015 19:04:12 -0700 Subject: [PATCH 787/937] add tests --- tests/api.unauthorized.test.js | 138 +++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 tests/api.unauthorized.test.js diff --git a/tests/api.unauthorized.test.js b/tests/api.unauthorized.test.js new file mode 100644 index 00000000000..0799378c9e7 --- /dev/null +++ b/tests/api.unauthorized.test.js @@ -0,0 +1,138 @@ +'use strict'; + +var request = require('supertest'); +var load = require('./fixtures/load'); +require('should'); + +describe('authed REST api', function ( ) { + var entries = require('../lib/api/entries/'); + + before(function (done) { + var known = 'b723e97aa97846eb92d5264f084b2823f57c4aa1'; + delete process.env.API_SECRET; + process.env.API_SECRET = 'this is my long pass phrase'; + var env = require('../env')( ); + this.wares = require('../lib/middleware/')(env); + this.archive = null; + this.app = require('express')( ); + this.app.enable('api'); + var self = this; + self.known_key = known; + require('../lib/bootevent')(env).boot(function booted (ctx) { + self.app.use('/', entries(self.app, self.wares, ctx)); + self.archive = require('../lib/entries')(env, ctx); + + var creating = load('json'); + // creating.push({type: 'sgv', sgv: 100, date: Date.now()}); + self.archive.create(creating, done); + }); + }); + + after(function (done) { + this.archive( ).remove({ }, done); + }); + + it('disallow unauthorized POST', function (done) { + var app = this.app; + + var new_entry = {type: 'sgv', sgv: 100, date: Date.now() }; + new_entry.dateString = new Date(new_entry.date).toISOString( ); + request(app) + .post('/entries.json?') + .send([new_entry]) + .expect(401) + .end(function (err, res) { + res.body.status.should.equal(401); + res.body.message.should.equal('Unauthorized'); + res.body.description.should.be.ok; + done(err); + }); + }); + + it('allow authorized POST', function (done) { + var app = this.app; + var known_key = this.known_key; + + var new_entry = {type: 'sgv', sgv: 100, date: Date.now() }; + new_entry.dateString = new Date(new_entry.date).toISOString( ); + request(app) + .post('/entries.json?') + .set('api-secret', known_key) + .send([new_entry]) + .expect(200) + .end(function (err, res) { + res.body.should.be.instanceof(Array).and.have.lengthOf(1); + request(app) + .get('/slice/entries/dateString/sgv/' + new_entry.dateString.split('T')[0] + '.json') + .expect(200) + .end(function (err, res) { + res.body.should.be.instanceof(Array).and.have.lengthOf(1); + + if (err) { + done(err); + } else { + request(app) + .delete('/entries/sgv?find[dateString]=' + new_entry.dateString) + .set('api-secret', known_key) + .expect(200) + .end(function (err) { + done(err); + }); + } + }) + }); + }); + + it('disallow deletes unauthorized', function (done) { + var app = this.app; + + request(app) + .get('/entries.json?find[dateString][$gte]=2014-07-18') + .expect(200) + .end(function (err, res) { + res.body.should.be.instanceof(Array).and.have.lengthOf(10); + request(app) + .delete('/entries/sgv?find[dateString][$gte]=2014-07-18&find[dateString][$lte]=2014-07-20') + // .set('api-secret', 'missing') + .expect(401) + .end(function (err) { + if (err) { + done(err); + } else { + request(app) + .get('/entries/sgv.json?find[dateString][$gte]=2014-07-18&find[dateString][$lte]=2014-07-20') + .expect(200) + .end(function (err, res) { + res.body.should.be.instanceof(Array).and.have.lengthOf(10); + done(); + }); + } + }); + }); + }); + + it('allow deletes when authorized', function (done) { + var app = this.app; + + request(app) + .delete('/entries/sgv?find[dateString][$gte]=2014-07-18&find[dateString][$lte]=2014-07-20') + .set('api-secret', this.known_key) + .expect(200) + .end(function (err) { + if (err) { + done(err); + } else { + request(app) + .get('/entries/sgv.json?find[dateString][$gte]=2014-07-18&find[dateString][$lte]=2014-07-20') + .expect(200) + .end(function (err, res) { + res.body.should.be.instanceof(Array).and.have.lengthOf(0); + done(); + }); + } + }); + }); + + + +}); From c605546f075010622604a598e5742900acaccd69 Mon Sep 17 00:00:00 2001 From: Ben West Date: Tue, 1 Sep 2015 19:15:44 -0700 Subject: [PATCH 788/937] broken tests/archive --- lib/entries.js | 2 +- tests/api.entries.test.js | 45 +++++++++++++++++++++++++++++++++------ 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/lib/entries.js b/lib/entries.js index 2d436252c5f..337f1cc3ca5 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -95,7 +95,7 @@ function storage(env, ctx) { docs.forEach(function(doc) { var query = (doc.sysTime && doc.type) ? {sysTime: doc.sysTime, type: doc.type} : doc; - collection.update(query, doc, {upsert: true}, function (err) { + collection.update(query, doc, {upsert: true}, function (err, stat) { firstErr = firstErr || err; if (++totalCreated === numDocs) { //TODO: this is triggering a read from Mongo, we can do better diff --git a/tests/api.entries.test.js b/tests/api.entries.test.js index c1099947068..7e5a4cc524f 100644 --- a/tests/api.entries.test.js +++ b/tests/api.entries.test.js @@ -2,30 +2,63 @@ var request = require('supertest'); var load = require('./fixtures/load'); +var es = require('event-stream'); +var bootevent = require('../lib/bootevent'); require('should'); describe('Entries REST api', function ( ) { var entries = require('../lib/api/entries/'); + this.timeout(10000); before(function (done) { var env = require('../env')( ); this.wares = require('../lib/middleware/')(env); - this.archive = null; + // this.archive = null; this.app = require('express')( ); this.app.enable('api'); var self = this; - require('../lib/bootevent')(env).boot(function booted (ctx) { + var x = 0; + function finish ( ) { + self.archive.list({ }, function (err, results) { + x++; + console.log('ALL UP', x, err, results.length); + if (results.length >= 30) { + return done( ); + } + if (x < 3) { + var creating = load('json'); + self.archive.create(creating, finish); + } + setTimeout(finish, 500); + }); + } + bootevent(env).boot(function booted (ctx) { self.app.use('/', entries(self.app, self.wares, ctx)); + console.log('CTX', ctx); self.archive = require('../lib/entries')(env, ctx); var creating = load('json'); creating.push({type: 'sgv', sgv: 100, date: Date.now()}); - self.archive.create(creating, done); + console.log("ALL INIT", creating.length); + self.archive.create(creating, finish); + // es.readArray(creating).pipe(self.archive.persist(finish)); }); }); after(function (done) { - this.archive( ).remove({ }, done); + var self = this; + var x = 0; + function finish ( ) { + self.archive.list({ }, function (err, results) { + x++; + console.log('ALL DOWN', x, results.length); + if (results.length < 1) { + return done( ); + } + setTimeout(finish, 500); + }); + } + this.archive( ).remove({ }, finish); }); // keep this test pinned at or near the top in order to validate all @@ -153,10 +186,10 @@ describe('Entries REST api', function ( ) { it('/entries/:model', function (done) { var app = this.app; request(app) - .get('/entries/sgv.json?count=1') + .get('/entries/sgv.json?count=10') .expect(200) .end(function (err, res) { - res.body.should.be.instanceof(Array).and.have.lengthOf(1); + res.body.should.be.instanceof(Array).and.have.lengthOf(5); done( ); }); }); From 3e33e7ec6f13683088640517f1444b3353388d38 Mon Sep 17 00:00:00 2001 From: Ben West Date: Tue, 1 Sep 2015 20:03:27 -0700 Subject: [PATCH 789/937] fixes tests Add beforeEach, afterEach to completely reset DB between each test. --- tests/api.entries.test.js | 50 ++++++++++------------------------ tests/api.unauthorized.test.js | 10 +++++++ 2 files changed, 25 insertions(+), 35 deletions(-) diff --git a/tests/api.entries.test.js b/tests/api.entries.test.js index 7e5a4cc524f..9a94c78454d 100644 --- a/tests/api.entries.test.js +++ b/tests/api.entries.test.js @@ -13,52 +13,32 @@ describe('Entries REST api', function ( ) { before(function (done) { var env = require('../env')( ); this.wares = require('../lib/middleware/')(env); - // this.archive = null; + this.archive = null; this.app = require('express')( ); this.app.enable('api'); var self = this; - var x = 0; - function finish ( ) { - self.archive.list({ }, function (err, results) { - x++; - console.log('ALL UP', x, err, results.length); - if (results.length >= 30) { - return done( ); - } - if (x < 3) { - var creating = load('json'); - self.archive.create(creating, finish); - } - setTimeout(finish, 500); - }); - } bootevent(env).boot(function booted (ctx) { self.app.use('/', entries(self.app, self.wares, ctx)); - console.log('CTX', ctx); self.archive = require('../lib/entries')(env, ctx); var creating = load('json'); creating.push({type: 'sgv', sgv: 100, date: Date.now()}); - console.log("ALL INIT", creating.length); - self.archive.create(creating, finish); - // es.readArray(creating).pipe(self.archive.persist(finish)); + self.archive.create(creating, done); }); }); + beforeEach(function (done) { + var creating = load('json'); + creating.push({type: 'sgv', sgv: 100, date: Date.now()}); + this.archive.create(creating, done); + }); + + afterEach(function (done) { + this.archive( ).remove({ }, done); + }); + after(function (done) { - var self = this; - var x = 0; - function finish ( ) { - self.archive.list({ }, function (err, results) { - x++; - console.log('ALL DOWN', x, results.length); - if (results.length < 1) { - return done( ); - } - setTimeout(finish, 500); - }); - } - this.archive( ).remove({ }, finish); + this.archive( ).remove({ }, done); }); // keep this test pinned at or near the top in order to validate all @@ -186,10 +166,10 @@ describe('Entries REST api', function ( ) { it('/entries/:model', function (done) { var app = this.app; request(app) - .get('/entries/sgv.json?count=10') + .get('/entries/sgv/.json?count=10&find[dateString][$gte]=2014') .expect(200) .end(function (err, res) { - res.body.should.be.instanceof(Array).and.have.lengthOf(5); + res.body.should.be.instanceof(Array).and.have.lengthOf(10); done( ); }); }); diff --git a/tests/api.unauthorized.test.js b/tests/api.unauthorized.test.js index 0799378c9e7..fb665ceea88 100644 --- a/tests/api.unauthorized.test.js +++ b/tests/api.unauthorized.test.js @@ -28,6 +28,16 @@ describe('authed REST api', function ( ) { }); }); + beforeEach(function (done) { + var creating = load('json'); + creating.push({type: 'sgv', sgv: 100, date: Date.now()}); + this.archive.create(creating, done); + }); + + afterEach(function (done) { + this.archive( ).remove({ }, done); + }); + after(function (done) { this.archive( ).remove({ }, done); }); From 08461737eacb30d363a4a9ecad36b2902cd9c978 Mon Sep 17 00:00:00 2001 From: Ben West Date: Tue, 1 Sep 2015 20:54:03 -0700 Subject: [PATCH 790/937] test slightly different edge case --- tests/api.entries.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/api.entries.test.js b/tests/api.entries.test.js index 9a94c78454d..61d153b1063 100644 --- a/tests/api.entries.test.js +++ b/tests/api.entries.test.js @@ -95,7 +95,7 @@ describe('Entries REST api', function ( ) { var app = this.app; var defaultCount = 10; request(app) - .get('/slice/entries/dateString/sgv/1999-07.json?count=20') + .get('/slice/entries/dateString/sgv/1999-07.json?count=20&find[sgv][$lte]=401') .expect(200) .end(function (err, res) { res.body.should.be.instanceof(Array).and.have.lengthOf(0); From 3f245ca9786baaf289a70200bc6ffffc3db45741 Mon Sep 17 00:00:00 2001 From: Ben West Date: Tue, 1 Sep 2015 21:17:36 -0700 Subject: [PATCH 791/937] Add test for echo api --- tests/api.entries.test.js | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/api.entries.test.js b/tests/api.entries.test.js index 61d153b1063..54e4a74b275 100644 --- a/tests/api.entries.test.js +++ b/tests/api.entries.test.js @@ -67,6 +67,21 @@ describe('Entries REST api', function ( ) { }); }); + it('/echo/ api shows query', function (done) { + var defaultCount = 10; + request(this.app) + .get('/echo/entries/sgv.json?find[dateString][$gte]=2014-07-19&find[dateString][$lte]=2014-07-20') + .expect(200) + .end(function (err, res) { + res.body.should.be.instanceof(Object); + res.body.query.should.be.instanceof(Object); + res.body.input.should.be.instanceof(Object); + res.body.input.find.should.be.instanceof(Object); + res.body.storage.should.equal('entries'); + done( ); + }); + }); + it('/slice/ can slice time', function (done) { var app = this.app; var defaultCount = 10; @@ -79,6 +94,21 @@ describe('Entries REST api', function ( ) { }); }); + + it('/times/echo can describe query', function (done) { + var app = this.app; + var defaultCount = 10; + request(app) + .get('/times/echo/2014-07/.*T{00..05}:.json?count=20&find[sgv][$gte]=160') + .expect(200) + .end(function (err, res) { + res.body.should.be.instanceof(Object); + res.body.req.should.have.property('query'); + res.body.should.have.property('pattern').with.lengthOf(6); + done( ); + }); + }); + it('/slice/ can slice with multiple prefix', function (done) { var app = this.app; var defaultCount = 10; From eeeab577050354bc0dfbb840a73397bc38ee7f41 Mon Sep 17 00:00:00 2001 From: Ben West Date: Tue, 1 Sep 2015 21:18:21 -0700 Subject: [PATCH 792/937] add echo to swagger, Debug tag --- swagger.yaml | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/swagger.yaml b/swagger.yaml index 4b14217f8d2..7365ba52bba 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -121,6 +121,47 @@ paths: schema: $ref: '#/definitions/Error' + /echo/{storage}/{spec}: + get: + summary: View generated Mongo Query object + description: | + Information about the mongo query object created by the query. + + parameters: + - name: storage + in: path + type: string + description: | + `entries`, or `treatments` to select the storage layer. + + default: sgv + required: true + - name: spec + in: path + type: string + description: | + entry id, such as `55cf81bc436037528ec75fa5` or a type filter such + as `sgv`, `mbg`, etc. + This parameter is optional. + + default: sgv + required: true + - name: find + in: query + description: | + The query used to find entries, support nested query syntax, for + example `find[dateString][$gte]=2015-08-27`. All find parameters + are interpreted as strings. + required: false + type: string + - name: count + in: query + description: Number of entries to return. + required: false + type: number + tags: + - Entries + - Debug /times/echo/{prefix}/{regex}: get: @@ -154,6 +195,7 @@ paths: type: number tags: - Entries + - Debug responses: "200": description: An array of entries From 46e91a1848f79a8f7db81ee1a42354be1bbf0956 Mon Sep 17 00:00:00 2001 From: Ben West Date: Tue, 1 Sep 2015 21:31:23 -0700 Subject: [PATCH 793/937] debug swagger --- swagger.yaml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/swagger.yaml b/swagger.yaml index 7365ba52bba..3dce8801407 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -162,6 +162,11 @@ paths: tags: - Entries - Debug + responses: + "200": + description: An array of entries + schema: + $ref: '#/definitions/MongoQuery' /times/echo/{prefix}/{regex}: get: @@ -200,7 +205,7 @@ paths: "200": description: An array of entries schema: - $ref: '#/definitions/Entries' + $ref: '#/definitions/MongoQuery' default: description: Unexpected error schema: @@ -616,6 +621,10 @@ definitions: ExtendedSettings: description: Extended settings of client side plugins + MongoQuery: + description: Mongo Query object. + + Error: properties: code: From e96e12038669620e019a62fa0ff1c37d3cb4aa36 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 1 Sep 2015 23:20:31 -0700 Subject: [PATCH 794/937] extened the cache time for pushover receipts --- lib/pushnotify.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pushnotify.js b/lib/pushnotify.js index f4ba24542d9..e6cd18d5c25 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -13,7 +13,7 @@ function init(env, ctx) { return pushnotify; } - var receipts = new NodeCache({ stdTTL: times.mins(15).secs, checkperiod: 120 }); + var receipts = new NodeCache({ stdTTL: times.hour().secs, checkperiod: times.mins(5).secs }); var recentlySent = new NodeCache({ stdTTL: times.mins(15).secs, checkperiod: 20 }); pushnotify.emitNotification = function emitNotification (notify) { From 4cc825f51f053e89cd4b7e645a7bf5fa110768d0 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 1 Sep 2015 23:35:25 -0700 Subject: [PATCH 795/937] fix some codacy issues --- lib/api/entries/index.js | 2 +- lib/entries.js | 2 +- tests/api.entries.test.js | 8 -------- tests/api.unauthorized.test.js | 4 ++-- tests/mqtt.test.js | 12 ++++++------ 5 files changed, 10 insertions(+), 18 deletions(-) diff --git a/lib/api/entries/index.js b/lib/api/entries/index.js index 60f29759a7b..53790d7509a 100644 --- a/lib/api/entries/index.js +++ b/lib/api/entries/index.js @@ -516,7 +516,7 @@ curl -s -g 'http://localhost:1337/api/v1/times/20{14..15}/T{13..18}:{00..15}'.js * Useful for understanding how the `/:prefix/:regex` route generates * mongodb queries. */ - api.get('/times/echo/:prefix?/:regex?', prep_storage, prep_pattern_field, prep_patterns, prep_patterns, function (req, res, next) { + api.get('/times/echo/:prefix?/:regex?', prep_storage, prep_pattern_field, prep_patterns, prep_patterns, function (req, res) { res.json({ req: { params: req.params, query: req.query}, pattern: req.pattern}); }); diff --git a/lib/entries.js b/lib/entries.js index 337f1cc3ca5..2d436252c5f 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -95,7 +95,7 @@ function storage(env, ctx) { docs.forEach(function(doc) { var query = (doc.sysTime && doc.type) ? {sysTime: doc.sysTime, type: doc.type} : doc; - collection.update(query, doc, {upsert: true}, function (err, stat) { + collection.update(query, doc, {upsert: true}, function (err) { firstErr = firstErr || err; if (++totalCreated === numDocs) { //TODO: this is triggering a read from Mongo, we can do better diff --git a/tests/api.entries.test.js b/tests/api.entries.test.js index 54e4a74b275..9696269d686 100644 --- a/tests/api.entries.test.js +++ b/tests/api.entries.test.js @@ -2,7 +2,6 @@ var request = require('supertest'); var load = require('./fixtures/load'); -var es = require('event-stream'); var bootevent = require('../lib/bootevent'); require('should'); @@ -68,7 +67,6 @@ describe('Entries REST api', function ( ) { }); it('/echo/ api shows query', function (done) { - var defaultCount = 10; request(this.app) .get('/echo/entries/sgv.json?find[dateString][$gte]=2014-07-19&find[dateString][$lte]=2014-07-20') .expect(200) @@ -84,7 +82,6 @@ describe('Entries REST api', function ( ) { it('/slice/ can slice time', function (done) { var app = this.app; - var defaultCount = 10; request(app) .get('/slice/entries/dateString/sgv/2014-07.json?count=20') .expect(200) @@ -97,7 +94,6 @@ describe('Entries REST api', function ( ) { it('/times/echo can describe query', function (done) { var app = this.app; - var defaultCount = 10; request(app) .get('/times/echo/2014-07/.*T{00..05}:.json?count=20&find[sgv][$gte]=160') .expect(200) @@ -111,7 +107,6 @@ describe('Entries REST api', function ( ) { it('/slice/ can slice with multiple prefix', function (done) { var app = this.app; - var defaultCount = 10; request(app) .get('/slice/entries/dateString/sgv/2014-07-{17..20}.json?count=20') .expect(200) @@ -123,7 +118,6 @@ describe('Entries REST api', function ( ) { it('/slice/ can slice time with prefix and no results', function (done) { var app = this.app; - var defaultCount = 10; request(app) .get('/slice/entries/dateString/sgv/1999-07.json?count=20&find[sgv][$lte]=401') .expect(200) @@ -135,7 +129,6 @@ describe('Entries REST api', function ( ) { it('/times/ can get modal times', function (done) { var app = this.app; - var defaultCount = 10; request(app) .get('/times/2014-07-/{0..30}T.json?') .expect(200) @@ -157,7 +150,6 @@ describe('Entries REST api', function ( ) { }); it('/times/ can get multiple prefixen and modal minutes and times', function (done) { var app = this.app; - var defaultCount = 10; request(app) .get('/times/20{14..15}/T.*:{00..60}.json?') .expect(200) diff --git a/tests/api.unauthorized.test.js b/tests/api.unauthorized.test.js index fb665ceea88..7557fc4fbaa 100644 --- a/tests/api.unauthorized.test.js +++ b/tests/api.unauthorized.test.js @@ -2,7 +2,7 @@ var request = require('supertest'); var load = require('./fixtures/load'); -require('should'); +var should = require('should'); describe('authed REST api', function ( ) { var entries = require('../lib/api/entries/'); @@ -54,7 +54,7 @@ describe('authed REST api', function ( ) { .end(function (err, res) { res.body.status.should.equal(401); res.body.message.should.equal('Unauthorized'); - res.body.description.should.be.ok; + should.exist(res.body.description); done(err); }); }); diff --git a/tests/mqtt.test.js b/tests/mqtt.test.js index f2999742c0f..c87413cb6b1 100644 --- a/tests/mqtt.test.js +++ b/tests/mqtt.test.js @@ -1,6 +1,6 @@ 'use strict'; -require('should'); +var should = require('should'); var FIVE_MINS = 5 * 60 * 1000; @@ -104,10 +104,10 @@ describe('mqtt', function ( ) { chunk.length.should.equal(1); var first = chunk[0]; console.log("FIRST", first); - first.sgv.should.be.ok; - first.noise.should.be.ok; - first.date.should.be.ok; - first.dateString.should.be.ok; + should.exist(first.sgv); + should.exist(first.noise); + should.exist(first.date); + should.exist(first.dateString); first.type.should.equal('sgv'); break; case 4: // cal @@ -121,7 +121,7 @@ describe('mqtt', function ( ) { self.results.end( ); } }); - self.results.on('end', function (chunk) { + self.results.on('end', function ( ) { done( ); }); self.mqtt.client.emit('message', '/downloads/protobuf', payload); From f17d7cff22a53608a2372bb27e59362fa99c5fae Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 2 Sep 2015 00:09:43 -0700 Subject: [PATCH 796/937] more codacy issues --- lib/api/entries/index.js | 2 +- tests/api.unauthorized.test.js | 2 +- tests/mqtt.test.js | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/api/entries/index.js b/lib/api/entries/index.js index 53790d7509a..f56afc421d0 100644 --- a/lib/api/entries/index.js +++ b/lib/api/entries/index.js @@ -451,7 +451,7 @@ curl -s -g 'http://localhost:1337/api/v1/times/20{14..15}/T{13..18}:{00..15}'.js // configure query to perform regex against list of potential regexp $in: matches }; - if (prefix.length == 1) { + if (prefix.length === 1) { // If there is a single prefix pattern, mongo can optimize this against // an indexed field query[field].$regex = prefix.map(iter_regex('^')).pop( ); diff --git a/tests/api.unauthorized.test.js b/tests/api.unauthorized.test.js index 7557fc4fbaa..64e7d254bd0 100644 --- a/tests/api.unauthorized.test.js +++ b/tests/api.unauthorized.test.js @@ -89,7 +89,7 @@ describe('authed REST api', function ( ) { done(err); }); } - }) + }); }); }); diff --git a/tests/mqtt.test.js b/tests/mqtt.test.js index c87413cb6b1..afbe9205ced 100644 --- a/tests/mqtt.test.js +++ b/tests/mqtt.test.js @@ -103,7 +103,6 @@ describe('mqtt', function ( ) { case 3: // sgv chunk.length.should.equal(1); var first = chunk[0]; - console.log("FIRST", first); should.exist(first.sgv); should.exist(first.noise); should.exist(first.date); From 80060e3bb92a77b7ba30b94f66b23f7c3618ea2c Mon Sep 17 00:00:00 2001 From: Paul Andrel Date: Wed, 2 Sep 2015 13:42:57 -0400 Subject: [PATCH 797/937] Updates to Heroku app.json manifest added Share bridge options and updated some descriptions also added DISABLE --- app.json | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/app.json b/app.json index 14f0d116cd9..bc30ae90082 100644 --- a/app.json +++ b/app.json @@ -8,17 +8,22 @@ "required": true }, "API_SECRET": { - "description": "REQUIRED: User generated password used for REST API and optional features (12 character minimum).", + "description": "REQUIRED: A secret passphrase that must be at least 12 characters long, required to enable POST and PUT; also required for the Care Portal", "value": "", "required": true }, "DISPLAY_UNITS": { - "description": "Server display units for BG values. Default null value implies 'mg/dl'. Set to 'mmol' to change the server to mmol mode.", + "description": "Choices: mg/dl and mmol. Setting to mmol puts the entire server into mmol mode by default, no further settings needed.", "value": "", "required": false }, "ENABLE": { - "description": "Space delimited list of optional features to enable, such as 'careportal'.", + "description": "Used to enable optional features, expects a space delimited list, such as: careportal rawbg iob, see https://github.com/nightscout/cgm-remote-monitor/blob/dev/README.md for more info", + "value": "", + "required": false + }, + "DISABLE": { + "description": "Used to disable default features, expects a space delimited list, such as: direction upbat, see https://github.com/nightscout/cgm-remote-monitor/blob/dev/README.md for more info", "value": "", "required": false }, @@ -71,6 +76,16 @@ "description": "Possible values always, never or noise", "value": "", "required": false + }, + "BRIDGE_USER_NAME": { + "description": "Share bridge - Your user name for the Share service. ENSURE bridge is in ENABLE if you want to use the share bridge", + "value": "", + "required": false + }, + "BRIDGE_PASSWORD": { + "description": "Share bridge - Your password for the Share service. ENSURE bridge is in ENABLE if you want to use the share bridge", + "value": "", + "required": false } }, "addons": [ From 112e973c94b538149828d1eb185ecfed376b2a80 Mon Sep 17 00:00:00 2001 From: Paul Andrel Date: Wed, 2 Sep 2015 15:21:01 -0400 Subject: [PATCH 798/937] Add browser settings options to heroku manifest --- app.json | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/app.json b/app.json index bc30ae90082..bb179273248 100644 --- a/app.json +++ b/app.json @@ -18,12 +18,12 @@ "required": false }, "ENABLE": { - "description": "Used to enable optional features, expects a space delimited list, such as: careportal rawbg iob, see https://github.com/nightscout/cgm-remote-monitor/blob/dev/README.md for more info", + "description": "Used to enable optional features, expects a space delimited list, such as: careportal rawbg iob, see https://github.com/nightscout/cgm-remote-monitor/blob/master/README.md for more info", "value": "", "required": false }, "DISABLE": { - "description": "Used to disable default features, expects a space delimited list, such as: direction upbat, see https://github.com/nightscout/cgm-remote-monitor/blob/dev/README.md for more info", + "description": "Used to disable default features, expects a space delimited list, such as: direction upbat, see https://github.com/nightscout/cgm-remote-monitor/blob/master/README.md for more info", "value": "", "required": false }, @@ -86,6 +86,66 @@ "description": "Share bridge - Your password for the Share service. ENSURE bridge is in ENABLE if you want to use the share bridge", "value": "", "required": false + }, + "TIME_FORMAT": { + "description": "Browser default time mode valid settings are 12 or 24", + "value": "12", + "required": false + }, + "NIGHT_MODE": { + "description": "Browser defaults to night mode valid settings are on or off", + "value": "off", + "required": false + }, + "SHOW_RAWBG": { + "description": "Browser default raw display mode vaild settings are always never or noise", + "value": "never", + "required": false + }, + "THEME": { + "description": "Browser default theme setting vaild settings are default or colors", + "value": "default", + "required": false + }, + "ALARM_URGENT_HIGH": { + "description": "Browser default urgent high alarm enabled vaild settings are on or off", + "value": "on", + "required": false + }, + "ALARM_HIGH": { + "description": "Browser default high alarm enabled vaild settings are on or off", + "value": "on", + "required": false + }, + "ALARM_LOW": { + "description": "Browser default low alarm enabled vaild settings are on or off", + "value": "on", + "required": false + }, + "ALARM_URGENT_LOW": { + "description": "Browser default urgent low alarm enabled vaild settings are on or off", + "value": "on", + "required": false + }, + "ALARM_TIMEAGO_WARN": { + "description": "Browser default warn after time of last data exceeds ALARM_TIMEAGO_WARN_MINS alarm enabled vaild settings are on or off", + "value": "on", + "required": false + }, + "ALARM_TIMEAGO_WARN_MINS": { + "description": "Browser default minutes since the last reading to trigger a warning", + "value": "15", + "required": false + }, + "ALARM_TIMEAGO_URGENT": { + "description": "Browser default urgent warning after time of last data exceeds ALARM_TIMEAGO_WARN_MINS alarm enabled vaild settings are on or off", + "value": "15", + "required": false + }, + "ALARM_TIMEAGO_URGENT_MINS": { + "description": "Browser default urgent warning after time of last data exceeds ALARM_TIMEAGO_WARN_MINS alarm enabled vaild settings are on or off", + "value": "15", + "required": false } }, "addons": [ From 2633e3fc8e3523cd3adeef967aba99b3e3fdbf51 Mon Sep 17 00:00:00 2001 From: Paul Andrel Date: Wed, 2 Sep 2015 15:28:40 -0400 Subject: [PATCH 799/937] Fix values for urgent time ago and add MAKER settings --- app.json | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/app.json b/app.json index bb179273248..15235e0fd3d 100644 --- a/app.json +++ b/app.json @@ -139,12 +139,22 @@ }, "ALARM_TIMEAGO_URGENT": { "description": "Browser default urgent warning after time of last data exceeds ALARM_TIMEAGO_WARN_MINS alarm enabled vaild settings are on or off", - "value": "15", + "value": "on", "required": false }, "ALARM_TIMEAGO_URGENT_MINS": { "description": "Browser default urgent warning after time of last data exceeds ALARM_TIMEAGO_WARN_MINS alarm enabled vaild settings are on or off", - "value": "15", + "value": "30", + "required": false + }, + "MAKER_KEY": { + "description": "Maker Key - Set this to your secret key Note for additional info see https://github.com/nightscout/cgm-remote-monitor/blob/dev/README.md#ifttt-maker , maker should be added to enable if you want to use maker", + "value": "", + "required": false + }, + "MAKER_ANNOUNCEMENT_KEY": { + "description": "Maker Key - Set this to your secret key for announcements Note for additional info see https://github.com/nightscout/cgm-remote-monitor/blob/dev/README.md#ifttt-maker , maker should be added to enable if you want to use maker", + "value": "", "required": false } }, From f741c3fb8326a5dbbfd873e161d3a196a92af39a Mon Sep 17 00:00:00 2001 From: Paul Andrel Date: Wed, 2 Sep 2015 15:43:53 -0400 Subject: [PATCH 800/937] Fix two descriptions --- app.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app.json b/app.json index 15235e0fd3d..99cd555fd74 100644 --- a/app.json +++ b/app.json @@ -138,12 +138,12 @@ "required": false }, "ALARM_TIMEAGO_URGENT": { - "description": "Browser default urgent warning after time of last data exceeds ALARM_TIMEAGO_WARN_MINS alarm enabled vaild settings are on or off", + "description": "Browser default urgent warning after time of last data exceeds ALARM_TIMEAGO_URGENT_MINS alarm enabled vaild settings are on or off", "value": "on", "required": false }, "ALARM_TIMEAGO_URGENT_MINS": { - "description": "Browser default urgent warning after time of last data exceeds ALARM_TIMEAGO_WARN_MINS alarm enabled vaild settings are on or off", + "description": "Browser default minutes since last reading to trigger an urgent alarm", "value": "30", "required": false }, @@ -153,7 +153,7 @@ "required": false }, "MAKER_ANNOUNCEMENT_KEY": { - "description": "Maker Key - Set this to your secret key for announcements Note for additional info see https://github.com/nightscout/cgm-remote-monitor/blob/dev/README.md#ifttt-maker , maker should be added to enable if you want to use maker", + "description": "Maker Announcement Key - Set this to your secret key for announcements Note for additional info see https://github.com/nightscout/cgm-remote-monitor/blob/dev/README.md#ifttt-maker , maker should be added to enable if you want to use maker", "value": "", "required": false } From a8fa65e342f28c335e2f3e4af21bb7b693142052 Mon Sep 17 00:00:00 2001 From: Paul Andrel Date: Wed, 2 Sep 2015 16:20:04 -0400 Subject: [PATCH 801/937] Alarm types description fix and add PUSHOVER_ANNOUNCEMENT_KEY --- app.json | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/app.json b/app.json index 99cd555fd74..c5a6417eed5 100644 --- a/app.json +++ b/app.json @@ -28,7 +28,7 @@ "required": false }, "ALARM_TYPES": { - "description": "Nightscout alarm behavior control. Default null value implies 'predict'. For adjustable alarm thresholds (set below), set to 'simple'.", + "description": "Alarm behavior currently 2 alarm types are supported simple and predict, and can be used independently or combined. The simple alarm type only compares the current BG to BG_ thresholds above, the predict alarm type uses highly tuned formula that forecasts where the BG is going based on it's trend. predict DOES NOT currently use any of the BG_* ENV's", "value": "", "required": false }, @@ -62,6 +62,11 @@ "value": "", "required": false }, + "PUSHOVER_ANNOUNCEMENT_KEY": { + "description": "An optional Pushover user/group key, will be used for system wide user generated announcements. If not defined this will fallback to `PUSHOVER_USER_KEY`. A possible use for this is sending important messages and alarms to a CWD that you don't want to send all notification too. This also support a space delimited list of keys. Leave blank if not using Pushover", + "value": "", + "required": false + }, "CUSTOM_TITLE": { "description": "Customize the name of the website, usually the name of T1.", "value": "", @@ -148,12 +153,12 @@ "required": false }, "MAKER_KEY": { - "description": "Maker Key - Set this to your secret key Note for additional info see https://github.com/nightscout/cgm-remote-monitor/blob/dev/README.md#ifttt-maker , maker should be added to enable if you want to use maker", + "description": "Maker Key - Set this to your secret key Note for additional info see https://github.com/nightscout/cgm-remote-monitor/blob/dev/README.md#ifttt-maker , maker should be added to enable if you want to use maker, Leave blank if not using maker", "value": "", "required": false }, "MAKER_ANNOUNCEMENT_KEY": { - "description": "Maker Announcement Key - Set this to your secret key for announcements Note for additional info see https://github.com/nightscout/cgm-remote-monitor/blob/dev/README.md#ifttt-maker , maker should be added to enable if you want to use maker", + "description": "Maker Announcement Key - Set this to your secret key for announcements Note for additional info see https://github.com/nightscout/cgm-remote-monitor/blob/dev/README.md#ifttt-maker , maker should be added to enable if you want to use maker Leave blank if not using maker", "value": "", "required": false } From 059fda044bdb2cd6fb4bf52772d885d9284eb5fe Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 2 Sep 2015 18:11:03 -0700 Subject: [PATCH 802/937] 0.8.0 Funnel Cake --- bower.json | 2 +- package.json | 2 +- static/index.html | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bower.json b/bower.json index 01d4f08d2ed..8931fb1247d 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "nightscout", - "version": "0.8.0-beta3", + "version": "0.8.0", "dependencies": { "angularjs": "1.3.0-beta.19", "bootstrap": "~3.2.0", diff --git a/package.json b/package.json index 904443549c7..a73740cbe41 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Nightscout", - "version": "0.8.0-beta3", + "version": "0.8.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": "AGPL3", "author": "Nightscout Team", diff --git a/static/index.html b/static/index.html index 4574917292d..49b39e28ece 100644 --- a/static/index.html +++ b/static/index.html @@ -25,10 +25,10 @@ - - + + - + From e7967669f0644302e440849a6203eebee99ce5ba Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Thu, 3 Sep 2015 21:26:08 +0200 Subject: [PATCH 803/937] Fix rolling minutes over hours --- lib/client/careportal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/client/careportal.js b/lib/client/careportal.js index 9c055d83e84..38ef086f628 100644 --- a/lib/client/careportal.js +++ b/lib/client/careportal.js @@ -123,7 +123,7 @@ function init (client, $) { var ele = $(this); var merged = mergeDateAndTime(); - if (ele.attr('oldminutes') === ' 59' && merged.minutes() === 0) { + if (ele.attr('oldminutes') === '59' && merged.minutes() === 0) { merged.add(1, 'hours'); } if (ele.attr('oldminutes') === '0' && merged.minutes() === 59) { From 344b0692fb1cbb063c0cef83cf1c1138b13ac3c5 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Fri, 4 Sep 2015 10:22:56 +0200 Subject: [PATCH 804/937] codacy pass 6 --- lib/report_plugins/hourlystats.js | 1 - lib/report_plugins/percentile.js | 1 - lib/report_plugins/success.js | 1 - lib/report_plugins/treatments.js | 7 ++----- tests/careportal.test.js | 2 +- 5 files changed, 3 insertions(+), 9 deletions(-) diff --git a/lib/report_plugins/hourlystats.js b/lib/report_plugins/hourlystats.js index 0fbe1d2b655..4031ed607a9 100644 --- a/lib/report_plugins/hourlystats.js +++ b/lib/report_plugins/hourlystats.js @@ -32,7 +32,6 @@ hourlystats.report = function report_hourlystats(datastorage, daystoshow, option var pivotedByHour = {}; var data = datastorage.allstatsrecords; - var days = datastorage.alldays; for (var i = 0; i < 24; i++) { pivotedByHour[i] = []; diff --git a/lib/report_plugins/percentile.js b/lib/report_plugins/percentile.js index 5fb748a0eb8..9518a757e30 100644 --- a/lib/report_plugins/percentile.js +++ b/lib/report_plugins/percentile.js @@ -31,7 +31,6 @@ percentile.report = function report_percentile(datastorage,daystoshow,options) { var minutewindow = 30; //minute-window should be a divisor of 60 var data = datastorage.allstatsrecords; - var days = datastorage.alldays; var bins = []; for (var hour = 0; hour < 24; hour++) { diff --git a/lib/report_plugins/success.js b/lib/report_plugins/success.js index 748898906b7..5a53a2892d7 100644 --- a/lib/report_plugins/success.js +++ b/lib/report_plugins/success.js @@ -30,7 +30,6 @@ success.report = function report_success(datastorage,daystoshow,options) { high = parseInt(options.targetHigh); var data = datastorage.allstatsrecords; - var days = datastorage.alldays; var now = Date.now(); var period = (7).days(); diff --git a/lib/report_plugins/treatments.js b/lib/report_plugins/treatments.js index c1b3e8fcf06..586ef4bbb7b 100644 --- a/lib/report_plugins/treatments.js +++ b/lib/report_plugins/treatments.js @@ -66,6 +66,8 @@ treatments.html = function html(client) { treatments.report = function report_treatments(datastorage, daystoshow, options) { var Nightscout = window.Nightscout; + var client = Nightscout.client; + var translate = client.translate; var report_plugins = Nightscout.report_plugins; function deleteTreatment(event) { @@ -213,11 +215,6 @@ treatments.report = function report_treatments(datastorage, daystoshow, options) return value; } - var Nightscout = window.Nightscout; - var client = Nightscout.client; - var translate = client.translate; - var report_plugins = Nightscout.report_plugins; - var icon_remove = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACrElEQVQ4T42Ty2sTQRzHv5tmk2yyjRNtpfZhL8V6s2KoUNC2XqwgaCsVQcGiFqpHi0c9iRdR/ANE9KR40FIQX4cueKoPaKFoLdSYNtE0abKT1+5s9iW7aUMiHtzTzO7v85md+c6PA4DrHbsPCKIgOWO1pA7dT6YXnXH949SE/F63pqwZtRrO+SCKgjQ5NUV+azpmHj2krMwaJC4c8Erj+/eRyloMMwWFKgbn1nC3ervlK1evkXBLGBZT8SOewotnTylTNLdgeg/pDgZDC2cPHSR8bB22DVC9hFe0SG/H0xFXcHlykjRHRDBWgJcZSCY38Xx2lhqMnRYE34Px/sN9vlQWeoHBAx2yXsRruVAVuFsIBaSJ8+eJGPaBqQV4NROJjTzez89jLBoFn6FgybQL54wS3uTyVDFQ3cL2IYpBv3RhdJSIIQ80tQyv7gEqJvS8AmUlBs7UXPhtjtZgh3UFNYngk86NHCfNAg9dMwHVBPu+CpsVkTXKeJeVG+AGgTOZ3tt6MSKKjy+NjEBjFrR4ElZmA4pdxstMFsyyJu6tZZ7Ux9vwB6EAL50ZGiRECEPPUOixVTRxHlicgSVWxEdZpuZWfNuS2hk48NjwMIkIYZglBnV5Cbqtws/5IaAJmsfCglrEl2y2QeKmEBJ80tixKmxrFpSVr0gV0viQoxho2YUuPohmeFD22PiklLC4ma5JuBvdrfLJI0dJd0s7bM0ES8aR/BXDXGaTskqlL+D3Lwy0tZEePoAd4EA5YF4tYymdonfjmQh3s6dTPjU4SHYGwjAKecSXFyGlM1TdytntE56T+ts7SC/vhw3gm6njc2Kd3vm5Ub1IwQAvnYhGiZpYw1wiWYPrIw7wnBTt7CLOOwdmut14kQQvqt24tfK/utGR6LaF+iRqMf4N/O/8D28HiiCRYqzAAAAAAElFTkSuQmCC'; var icon_edit = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABEUlEQVQ4jZ3MMUsCYQDG8ee8IySQbNCLyyEKG/RLNAXicqvQcAeNLrcFLlE0+xHuNpt8wy04rrYm8Q4HQRE56BSC3lSqU1BwCoxM39dnffj9BWyxXvVeEzvtctBwHyRebNu2Nk2lzMlrgJB+qBEeTByiKYpihl+fIO8jTI9PDJEVF1+K2iw+M6PhDuyag4NkQi/c3FkCK5Z3ZbM76qLltpCbn+vXxq0FABsDy9hzPdBvqvtXvvXzrw1swmsDLPjfACteGeDBfwK8+FdgGwwAIgC0ncsjxGRSH/eiPBgAJADY2z8sJ4JBfNBsDqlADVYMANIzKalv/bHaefKsTH9iPFb8ISsGAJym0+Qinz3jQktbAHcxvx3559eSAAAAAElFTkSuQmCC'; diff --git a/tests/careportal.test.js b/tests/careportal.test.js index 34539eb2aec..11821fd3ffe 100644 --- a/tests/careportal.test.js +++ b/tests/careportal.test.js @@ -59,7 +59,7 @@ describe('client', function ( ) { done(); return self.$.ajax(); } - , fail: function mockFail (fn) { + , fail: function mockFail ( ) { return self.$.ajax(); } }; From f42df1703e98588001b7cbd4929855e6431dcec5 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Fri, 4 Sep 2015 10:41:44 +0200 Subject: [PATCH 805/937] treatments.js refactoring --- lib/report_plugins/calibrations.js | 61 +++++++++++++++--------------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/lib/report_plugins/calibrations.js b/lib/report_plugins/calibrations.js index cb57903cbf5..c771a6452f1 100644 --- a/lib/report_plugins/calibrations.js +++ b/lib/report_plugins/calibrations.js @@ -55,7 +55,7 @@ calibrations.report = function report_calibrations(datastorage,daystoshow) { Object.keys(daystoshow).forEach(function (day) { mbgs = mbgs.concat(datastorage[day].mbg); }); - mbgs.forEach(function (mbg) { calibrations_calcmbg(mbg); }); + mbgs.forEach(function (mbg) { calcmbg(mbg); }); var events = treatments.concat(cals).concat(mbgs).sort(function(a, b) { return a.mills - b.mills; }); @@ -66,13 +66,9 @@ calibrations.report = function report_calibrations(datastorage,daystoshow) { var lastmbg = null; for (var i=0; i'; e.bgcolor = colors[colorindex]; @@ -100,41 +96,44 @@ calibrations.report = function report_calibrations(datastorage,daystoshow) { $('#calibrations-list').html(html); // select last 3 mbgs - var maxcals = 3; - for (i=events.length-1; i>0; i--) { - if (typeof events[i].device !== 'undefined') { - events[i].checked = true; - $('#calibrations-'+i).prop('checked',true); - if (--maxcals<1) { - break; + checkLastCheckboxes(3); + drawelements(); + + $('.calibrations-checkbox').change(checkboxevent); + + function checkLastCheckboxes (maxcals) { + for (i=events.length-1; i>0; i--) { + if (typeof events[i].device !== 'undefined') { + events[i].checked = true; + $('#calibrations-'+i).prop('checked',true); + if (--maxcals<1) { + break; + } } } } - calibrations_drawelements(); - - $('.calibrations-checkbox').change(calibrations_checkboxevent); - - function calibrations_checkboxevent(event) { + + function checkboxevent(event) { var index = $(this).attr('index'); events[index].checked = $(this).is(':checked'); - calibrations_drawelements(); + drawelements(); event.preventDefault(); } - function calibrations_drawelements() { - calibrations_drawChart(); + function drawelements() { + drawChart(); for (var i=0; i5*60*1000) { @@ -254,7 +253,7 @@ calibrations.report = function report_calibrations(datastorage,daystoshow) { } } - function calibrations_drawmbg(mbg) { + function drawmbg(mbg) { var color = mbg.bgcolor; if (mbg.raw) { calibration_context.append('circle') @@ -268,7 +267,7 @@ calibrations.report = function report_calibrations(datastorage,daystoshow) { } } - function calibrations_findlatest(date,storage) { + function findlatest(date,storage) { var last = null; var time = date.getTime(); for (var i=0; i Date: Fri, 4 Sep 2015 11:10:59 +0200 Subject: [PATCH 806/937] more treatments.js refactoring --- lib/report_plugins/treatments.js | 67 +++++++++++++++++++------------- static/report/js/report.js | 20 +++++----- 2 files changed, 49 insertions(+), 38 deletions(-) diff --git a/lib/report_plugins/treatments.js b/lib/report_plugins/treatments.js index 586ef4bbb7b..9eb50a1864f 100644 --- a/lib/report_plugins/treatments.js +++ b/lib/report_plugins/treatments.js @@ -1,5 +1,4 @@ 'use strict'; -var translate = require('../language')().translate; var treatments = { name: 'treatments' @@ -70,6 +69,31 @@ treatments.report = function report_treatments(datastorage, daystoshow, options) var translate = client.translate; var report_plugins = Nightscout.report_plugins; + function buildConfirmText(data) { + var text = [ + translate('Delete this treatment?')+'\n' + , '\n'+translate('Event Type')+': ' + translate(resolveEventName(data.eventType)) + ]; + + function pushIf (check, valueText) { + if (check) { + text.push(valueText); + } + } + + pushIf(data.glucose, translate('Blood Glucose') + ': ' + data.glucose); + pushIf(data.glucoseType, translate('Measurement Method') + ': ' + translate(data.glucoseType)); + + pushIf(data.carbs, translate('Carbs Given') + ': ' + data.carbs); + pushIf(data.insulin, translate('Insulin Given') + ': ' + data.insulin); + pushIf(data.preBolus, translate('Carb Time') + ': ' + data.preBolus + ' ' + translate('mins')); + pushIf(data.notes, translate('Notes') + ': ' + data.notes); + pushIf(data.enteredBy, translate('Entered By') + ': ' + data.enteredBy); + + text.push(translate('Event Time') + ': ' + (data.eventTime ? data.eventTime.toLocaleString() : new Date().toLocaleString())); + return text.join('\n'); + } + function deleteTreatment(event) { if (!client.hashauth.isAuthenticated()) { alert(translate('Your device is not authenticated yet')); @@ -79,34 +103,10 @@ treatments.report = function report_treatments(datastorage, daystoshow, options) var data = JSON.parse($(this).attr('data')); var day = $(this).attr('day'); - var ok = window.confirm( - translate('Delete this treatment?')+'\n' + - '\n'+translate('Event Type')+': ' + translate(resolveEventName(data.eventType)) + - (data.glucose ? '\n'+translate('Blood Glucose')+': ' + data.glucose : '')+ - (data.glucoseType ? '\n'+translate('Method')+': ' + data.glucoseType : '')+ - (data.carbs ? '\n'+translate('Carbs Given')+': ' + data.carbs : '' )+ - (data.insulin ? '\n'+translate('Insulin Given')+': ' + data.insulin : '')+ - (data.preBolus ? '\n'+translate('Pre Bolus')+': ' + data.preBolus : '')+ - (data.notes ? '\n'+translate('Notes')+': ' + data.notes : '' )+ - (data.enteredBy ? '\n'+translate('Entered By')+': ' + data.enteredBy : '' )+ - ('\n'+translate('Event Time')+': ' + new Date(data.created_at).toLocaleString()) - ); - - if (ok) { - deleteTreatmentRecord(data._id); - delete datastorage[day]; - report_plugins.show(); - } - if (event) { - event.preventDefault(); - } - return false; - } - - function deleteTreatmentRecord(_id) { + if (window.confirm(buildConfirmText(data))) { $.ajax({ method: 'DELETE' - , url: '/api/v1/treatments/' + _id + , url: '/api/v1/treatments/' + data._id , headers: { 'api-secret': client.hashauth.hash() } @@ -116,7 +116,11 @@ treatments.report = function report_treatments(datastorage, daystoshow, options) console.info('treatment delete failed', response); alert(translate('Deleting record failed') + '. ' + translate('Status') + ': ' + response.status); }); - return true; + delete datastorage[day]; + report_plugins.show(); + } + maybePrevent(event); + return false; } function editTreatment(event) { @@ -215,6 +219,13 @@ treatments.report = function report_treatments(datastorage, daystoshow, options) return value; } + function maybePrevent (event) { + if (event) { + event.preventDefault(); + } + return false; + } + var icon_remove = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACrElEQVQ4T42Ty2sTQRzHv5tmk2yyjRNtpfZhL8V6s2KoUNC2XqwgaCsVQcGiFqpHi0c9iRdR/ANE9KR40FIQX4cueKoPaKFoLdSYNtE0abKT1+5s9iW7aUMiHtzTzO7v85md+c6PA4DrHbsPCKIgOWO1pA7dT6YXnXH949SE/F63pqwZtRrO+SCKgjQ5NUV+azpmHj2krMwaJC4c8Erj+/eRyloMMwWFKgbn1nC3ervlK1evkXBLGBZT8SOewotnTylTNLdgeg/pDgZDC2cPHSR8bB22DVC9hFe0SG/H0xFXcHlykjRHRDBWgJcZSCY38Xx2lhqMnRYE34Px/sN9vlQWeoHBAx2yXsRruVAVuFsIBaSJ8+eJGPaBqQV4NROJjTzez89jLBoFn6FgybQL54wS3uTyVDFQ3cL2IYpBv3RhdJSIIQ80tQyv7gEqJvS8AmUlBs7UXPhtjtZgh3UFNYngk86NHCfNAg9dMwHVBPu+CpsVkTXKeJeVG+AGgTOZ3tt6MSKKjy+NjEBjFrR4ElZmA4pdxstMFsyyJu6tZZ7Ux9vwB6EAL50ZGiRECEPPUOixVTRxHlicgSVWxEdZpuZWfNuS2hk48NjwMIkIYZglBnV5Cbqtws/5IaAJmsfCglrEl2y2QeKmEBJ80tixKmxrFpSVr0gV0viQoxho2YUuPohmeFD22PiklLC4ma5JuBvdrfLJI0dJd0s7bM0ES8aR/BXDXGaTskqlL+D3Lwy0tZEePoAd4EA5YF4tYymdonfjmQh3s6dTPjU4SHYGwjAKecSXFyGlM1TdytntE56T+ts7SC/vhw3gm6njc2Kd3vm5Ub1IwQAvnYhGiZpYw1wiWYPrIw7wnBTt7CLOOwdmut14kQQvqt24tfK/utGR6LaF+iRqMf4N/O/8D28HiiCRYqzAAAAAAElFTkSuQmCC'; var icon_edit = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABEUlEQVQ4jZ3MMUsCYQDG8ee8IySQbNCLyyEKG/RLNAXicqvQcAeNLrcFLlE0+xHuNpt8wy04rrYm8Q4HQRE56BSC3lSqU1BwCoxM39dnffj9BWyxXvVeEzvtctBwHyRebNu2Nk2lzMlrgJB+qBEeTByiKYpihl+fIO8jTI9PDJEVF1+K2iw+M6PhDuyag4NkQi/c3FkCK5Z3ZbM76qLltpCbn+vXxq0FABsDy9hzPdBvqvtXvvXzrw1swmsDLPjfACteGeDBfwK8+FdgGwwAIgC0ncsjxGRSH/eiPBgAJADY2z8sJ4JBfNBsDqlADVYMANIzKalv/bHaefKsTH9iPFb8ISsGAJym0+Qinz3jQktbAHcxvx3559eSAAAAAElFTkSuQmCC'; diff --git a/static/report/js/report.js b/static/report/js/report.js index c8db0cedf94..bcfb0dba14b 100644 --- a/static/report/js/report.js +++ b/static/report/js/report.js @@ -64,7 +64,7 @@ $('#rp_subcategory').change(doFoodFilter); $('#rp_name').on('input',doFoodFilter); - return maybePreventDefault(event); + return maybePrevent(event); } function fillFoodSubcategories(event) { @@ -77,7 +77,7 @@ } } doFoodFilter(); - return maybePreventDefault(event); + return maybePrevent(event); } function doFoodFilter(event) { @@ -99,7 +99,7 @@ $('#rp_food').append(new Option(o,food_list[i]._id)); } - return maybePreventDefault(event); + return maybePrevent(event); } $('#info').html(''+translate('Loading food database')+' ...'); @@ -128,7 +128,7 @@ $('.rp_foodgui').css('display',''); $('#rp_food').change(function (event) { $('#rp_enablefood').prop('checked',true); - return maybePreventDefault(event); + return maybePrevent(event); }); } @@ -149,11 +149,11 @@ $('#rp_show').click(show); $('#rp_notes').bind('input', function (event) { $('#rp_enablenotes').prop('checked',true); - return maybePreventDefault(event); + return maybePrevent(event); }); $('#rp_eventtype').bind('input', function (event) { $('#rp_enableeventtype').prop('checked',true); - return maybePreventDefault(event); + return maybePrevent(event); }); // fill careportal events @@ -429,7 +429,7 @@ daystoshow = {}; datefilter(); - return maybePreventDefault(event); + return maybePrevent(event); } function showreports(options) { @@ -458,7 +458,7 @@ function setDataRange(event,days) { $('#rp_to').val(moment().format('YYYY-MM-DD')); $('#rp_from').val(moment().add(-days+1, 'days').format('YYYY-MM-DD')); - return maybePreventDefault(event); + return maybePrevent(event); } function switchreport_handler(event) { @@ -469,7 +469,7 @@ $('.tabplaceholder').css('display','none'); $('#'+id+'-placeholder').css('display',''); - return maybePreventDefault(event); + return maybePrevent(event); } function loadData(day, options, callback) { @@ -623,7 +623,7 @@ callback(); } - function maybePreventDefault(event) { + function maybePrevent(event) { if (event) { event.preventDefault(); } From 84259d9746ddb435d3e5292b63cda8883095086e Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Fri, 4 Sep 2015 12:08:43 +0200 Subject: [PATCH 807/937] even more treatments.js refactoring --- lib/report_plugins/treatments.js | 48 ++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/lib/report_plugins/treatments.js b/lib/report_plugins/treatments.js index 9eb50a1864f..b07a2eac804 100644 --- a/lib/report_plugins/treatments.js +++ b/lib/report_plugins/treatments.js @@ -229,29 +229,41 @@ treatments.report = function report_treatments(datastorage, daystoshow, options) var icon_remove = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACrElEQVQ4T42Ty2sTQRzHv5tmk2yyjRNtpfZhL8V6s2KoUNC2XqwgaCsVQcGiFqpHi0c9iRdR/ANE9KR40FIQX4cueKoPaKFoLdSYNtE0abKT1+5s9iW7aUMiHtzTzO7v85md+c6PA4DrHbsPCKIgOWO1pA7dT6YXnXH949SE/F63pqwZtRrO+SCKgjQ5NUV+azpmHj2krMwaJC4c8Erj+/eRyloMMwWFKgbn1nC3ervlK1evkXBLGBZT8SOewotnTylTNLdgeg/pDgZDC2cPHSR8bB22DVC9hFe0SG/H0xFXcHlykjRHRDBWgJcZSCY38Xx2lhqMnRYE34Px/sN9vlQWeoHBAx2yXsRruVAVuFsIBaSJ8+eJGPaBqQV4NROJjTzez89jLBoFn6FgybQL54wS3uTyVDFQ3cL2IYpBv3RhdJSIIQ80tQyv7gEqJvS8AmUlBs7UXPhtjtZgh3UFNYngk86NHCfNAg9dMwHVBPu+CpsVkTXKeJeVG+AGgTOZ3tt6MSKKjy+NjEBjFrR4ElZmA4pdxstMFsyyJu6tZZ7Ux9vwB6EAL50ZGiRECEPPUOixVTRxHlicgSVWxEdZpuZWfNuS2hk48NjwMIkIYZglBnV5Cbqtws/5IaAJmsfCglrEl2y2QeKmEBJ80tixKmxrFpSVr0gV0viQoxho2YUuPohmeFD22PiklLC4ma5JuBvdrfLJI0dJd0s7bM0ES8aR/BXDXGaTskqlL+D3Lwy0tZEePoAd4EA5YF4tYymdonfjmQh3s6dTPjU4SHYGwjAKecSXFyGlM1TdytntE56T+ts7SC/vhw3gm6njc2Kd3vm5Ub1IwQAvnYhGiZpYw1wiWYPrIw7wnBTt7CLOOwdmut14kQQvqt24tfK/utGR6LaF+iRqMf4N/O/8D28HiiCRYqzAAAAAAElFTkSuQmCC'; var icon_edit = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABEUlEQVQ4jZ3MMUsCYQDG8ee8IySQbNCLyyEKG/RLNAXicqvQcAeNLrcFLlE0+xHuNpt8wy04rrYm8Q4HQRE56BSC3lSqU1BwCoxM39dnffj9BWyxXvVeEzvtctBwHyRebNu2Nk2lzMlrgJB+qBEeTByiKYpihl+fIO8jTI9PDJEVF1+K2iw+M6PhDuyag4NkQi/c3FkCK5Z3ZbM76qLltpCbn+vXxq0FABsDy9hzPdBvqvtXvvXzrw1swmsDLPjfACteGeDBfwK8+FdgGwwAIgC0ncsjxGRSH/eiPBgAJADY2z8sJ4JBfNBsDqlADVYMANIzKalv/bHaefKsTH9iPFb8ISsGAJym0+Qinz3jQktbAHcxvx3559eSAAAAAElFTkSuQmCC'; - var table = ''; - table += ''; + var table = $('
    '+translate('Time')+''+translate('Event Type')+''+translate('Blood Glucose')+''+translate('Insulin')+''+translate('Carbs')+''+translate('Entered By')+''+translate('Notes')+'
    '); + table.append($('').css('background','gray') + .append($(''; + table.append($('') + .append($(''; - table += ''; - table += ''; - table += ''; - table += ''; - table += ''; - table += ''; - table += ''; - - table += ''; + table.append($('').addClass('border_bottom') + .append($('').should.be.greaterThan(-1); //dailystats result.indexOf('td class="tdborder" style="background-color:#8f8">Normal: ').should.be.greaterThan(-1); // distribution result.indexOf('').should.be.greaterThan(-1); // hourlystats - result.indexOf('
    ').should.be.greaterThan(-1); //success + result.indexOf('
    ').should.be.greaterThan(-1); //success result.indexOf('CAL: Scale: 1.10 Intercept: 31102 Slope: 776.91').should.be.greaterThan(-1); //calibrations result.indexOf('
    ').should.be.greaterThan(-1); //treatments From 8b1b17eba0bfc39ed868e7ad6a61745fe23925be Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Wed, 9 Sep 2015 20:40:54 +0200 Subject: [PATCH 887/937] try more clicking around --- tests/reports.test.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/reports.test.js b/tests/reports.test.js index 2af431fc5b9..234f4a5b3c4 100644 --- a/tests/reports.test.js +++ b/tests/reports.test.js @@ -26,7 +26,8 @@ var someData = { '/api/v1/treatments.json?find[created_at][$gte]=2015-08-13T00:00:00.000Z&find[created_at][$lt]=2015-08-14T00:00:00.000Z': [{"enteredBy":"Mom ","eventType":"Correction Bolus","glucose":250,"glucoseType":"Sensor","insulin":0.75,"units":"mg/dl","created_at":"2015-08-13T23:45:56.927Z","_id":"55cd2c3497fa97ac5d8bc53b"},{"enteredBy":"Mom ","eventType":"Correction Bolus","glucose":198,"glucoseType":"Sensor","insulin":1.1,"units":"mg/dl","created_at":"2015-08-13T23:11:00.293Z","_id":"55cd240497fa97ac5d8bc535"}], '/api/v1/entries.json?find[date][$gte]=1439510400000&find[date][$lt]=1439596800000&count=10000': [{"_id":"55ce80e338a8d88ad1b49397","unfiltered":179936,"filtered":202080,"direction":"SingleDown","device":"dexcom","rssi":179,"sgv":182,"dateString":"Fri Aug 14 16:58:20 PDT 2015","type":"sgv","date":1439596700000,"noise":1},{"_id":"55ce7fb738a8d88ad1b4938d","unfiltered":192288,"filtered":213792,"direction":"SingleDown","device":"dexcom","rssi":180,"sgv":197,"dateString":"Fri Aug 14 16:53:20 PDT 2015","type":"sgv","date":1439596400000,"noise":1}], '/api/v1/treatments.json?find[created_at][$gte]=2015-08-14T00:00:00.000Z&find[created_at][$lt]=2015-08-15T00:00:00.000Z': [{"enteredBy":"Dad","eventType":"Site Change","glucose":268,"glucoseType":"Finger","insulin":1.75,"units":"mg/dl","created_at":"2015-08-14T23:25:50.718Z","_id":"55ce78fe925aa80e7071e5d6"},{"enteredBy":"Mom ","eventType":"Meal Bolus","glucose":89,"glucoseType":"Finger","carbs":54,"insulin":3.15,"units":"mg/dl","created_at":"2015-08-14T21:00:00.000Z","_id":"55ce59bb925aa80e7071e5ba"}], - '/api/v1/entries.json?find[date][$gte]=1439596800000&find[date][$lt]=1439683200000&count=10000': [{"_id":"55cfd25f38a8d88ad1b49931","unfiltered":283136,"filtered":304768,"direction":"SingleDown","device":"dexcom","rssi":185,"sgv":306,"dateString":"Sat Aug 15 16:58:16 PDT 2015","type":"sgv","date":1439683096000,"noise":1},{"_id":"55cfd13338a8d88ad1b4992e","unfiltered":302528,"filtered":312576,"direction":"FortyFiveDown","device":"dexcom","rssi":179,"sgv":329,"dateString":"Sat Aug 15 16:53:16 PDT 2015","type":"sgv","date":1439682796000,"noise":1}] + '/api/v1/entries.json?find[date][$gte]=1439596800000&find[date][$lt]=1439683200000&count=10000': [{"_id":"55cfd25f38a8d88ad1b49931","unfiltered":283136,"filtered":304768,"direction":"SingleDown","device":"dexcom","rssi":185,"sgv":306,"dateString":"Sat Aug 15 16:58:16 PDT 2015","type":"sgv","date":1439683096000,"noise":1},{"_id":"55cfd13338a8d88ad1b4992e","unfiltered":302528,"filtered":312576,"direction":"FortyFiveDown","device":"dexcom","rssi":179,"sgv":329,"dateString":"Sat Aug 15 16:53:16 PDT 2015","type":"sgv","date":1439682796000,"noise":1}], + '/api/v1/food/regular.json': [{"_id":"552ece84a6947ea011db35bb","type":"food","category":"Zakladni","subcategory":"Sladkosti","name":"Bebe male","portion":18,"carbs":12,"gi":1,"unit":"pcs","created_at":"2015-04-15T20:48:04.966Z"}] }; var exampleProfile = [ @@ -144,6 +145,7 @@ describe('reports', function ( ) { benv.require(__dirname + '/../bundle/bundle.source.js'); benv.require(__dirname + '/../static/report/js/report.js'); benv.require(__dirname + '/../static/report/js/time.js'); + benv.require(__dirname + '/../bower_components/jQuery-Storage-API/jquery.storageapi.min.js'); done(); }); @@ -172,16 +174,23 @@ describe('reports', function ( ) { // Load profile, we need to operate in UTC client.sbx.data.profile.loadData(exampleProfile); + $('a.presetdates :first').click(); + $('#rp_notes').val('something'); + $('#rp_eventtype').val('BG Check'); $('#rp_from').val('2015/08/08'); $('#rp_to').val('2015/09/07'); $('#rp_optionsraw').prop('checked',true); $('#rp_optionsiob').prop('checked',true); $('#rp_optionscob').prop('checked',true); + $('#rp_enableeventtype').click(); + $('#rp_enablenotes').click(); + $('#rp_enablefood').click(); $('#rp_log').prop('checked',true); $('#rp_show').click(); $('#rp_linear').prop('checked',true); $('#rp_show').click(); + $('#dailystats').click(); var result = $('body').html(); //var filesys = require('fs'); From fa01ecb8af192e996466abccf4817ccd3e01bb5c Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Wed, 9 Sep 2015 20:50:38 +0200 Subject: [PATCH 888/937] wrong file to include --- 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 234f4a5b3c4..982cfb553df 100644 --- a/tests/reports.test.js +++ b/tests/reports.test.js @@ -145,7 +145,7 @@ describe('reports', function ( ) { benv.require(__dirname + '/../bundle/bundle.source.js'); benv.require(__dirname + '/../static/report/js/report.js'); benv.require(__dirname + '/../static/report/js/time.js'); - benv.require(__dirname + '/../bower_components/jQuery-Storage-API/jquery.storageapi.min.js'); + benv.require(__dirname + '/../static/bower_components/jquery-ui/jquery-ui.min.js'); done(); }); From 233a70bf89e69dc207ca7e936293b99927874a0c Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Wed, 9 Sep 2015 21:12:34 +0200 Subject: [PATCH 889/937] just refresh travis --- 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 982cfb553df..729ca006b5f 100644 --- a/tests/reports.test.js +++ b/tests/reports.test.js @@ -145,7 +145,7 @@ describe('reports', function ( ) { benv.require(__dirname + '/../bundle/bundle.source.js'); benv.require(__dirname + '/../static/report/js/report.js'); benv.require(__dirname + '/../static/report/js/time.js'); - benv.require(__dirname + '/../static/bower_components/jquery-ui/jquery-ui.min.js'); + benv.require(__dirname + '/../static/bower_components/jquery-ui/jquery-ui.min.js'); done(); }); From 853a306dc60f73c3318bfd4e8e6beee72b77ff05 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Fri, 11 Sep 2015 08:57:26 +0200 Subject: [PATCH 890/937] more clicking in test --- static/report/js/report.js | 4 +- tests/reports.test.js | 77 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 75 insertions(+), 6 deletions(-) diff --git a/static/report/js/report.js b/static/report/js/report.js index 76a8f026555..4dfdd109e60 100644 --- a/static/report/js/report.js +++ b/static/report/js/report.js @@ -95,7 +95,7 @@ } $('#rp_food').empty(); for (var i=0; i' + o + ''); } return maybePrevent(event); diff --git a/tests/reports.test.js b/tests/reports.test.js index 729ca006b5f..ac8d5d6b1a1 100644 --- a/tests/reports.test.js +++ b/tests/reports.test.js @@ -27,7 +27,73 @@ var someData = { '/api/v1/entries.json?find[date][$gte]=1439510400000&find[date][$lt]=1439596800000&count=10000': [{"_id":"55ce80e338a8d88ad1b49397","unfiltered":179936,"filtered":202080,"direction":"SingleDown","device":"dexcom","rssi":179,"sgv":182,"dateString":"Fri Aug 14 16:58:20 PDT 2015","type":"sgv","date":1439596700000,"noise":1},{"_id":"55ce7fb738a8d88ad1b4938d","unfiltered":192288,"filtered":213792,"direction":"SingleDown","device":"dexcom","rssi":180,"sgv":197,"dateString":"Fri Aug 14 16:53:20 PDT 2015","type":"sgv","date":1439596400000,"noise":1}], '/api/v1/treatments.json?find[created_at][$gte]=2015-08-14T00:00:00.000Z&find[created_at][$lt]=2015-08-15T00:00:00.000Z': [{"enteredBy":"Dad","eventType":"Site Change","glucose":268,"glucoseType":"Finger","insulin":1.75,"units":"mg/dl","created_at":"2015-08-14T23:25:50.718Z","_id":"55ce78fe925aa80e7071e5d6"},{"enteredBy":"Mom ","eventType":"Meal Bolus","glucose":89,"glucoseType":"Finger","carbs":54,"insulin":3.15,"units":"mg/dl","created_at":"2015-08-14T21:00:00.000Z","_id":"55ce59bb925aa80e7071e5ba"}], '/api/v1/entries.json?find[date][$gte]=1439596800000&find[date][$lt]=1439683200000&count=10000': [{"_id":"55cfd25f38a8d88ad1b49931","unfiltered":283136,"filtered":304768,"direction":"SingleDown","device":"dexcom","rssi":185,"sgv":306,"dateString":"Sat Aug 15 16:58:16 PDT 2015","type":"sgv","date":1439683096000,"noise":1},{"_id":"55cfd13338a8d88ad1b4992e","unfiltered":302528,"filtered":312576,"direction":"FortyFiveDown","device":"dexcom","rssi":179,"sgv":329,"dateString":"Sat Aug 15 16:53:16 PDT 2015","type":"sgv","date":1439682796000,"noise":1}], - '/api/v1/food/regular.json': [{"_id":"552ece84a6947ea011db35bb","type":"food","category":"Zakladni","subcategory":"Sladkosti","name":"Bebe male","portion":18,"carbs":12,"gi":1,"unit":"pcs","created_at":"2015-04-15T20:48:04.966Z"}] + '/api/v1/food/regular.json': [{"_id":"552ece84a6947ea011db35bb","type":"food","category":"Zakladni","subcategory":"Sladkosti","name":"Bebe male","portion":18,"carbs":12,"gi":1,"unit":"pcs","created_at":"2015-04-15T20:48:04.966Z"}], + '/api/v1/treatments.json?find[eventType]=/BG Check/i&find[created_at][$gte]=2015-08-07T22:00:00.000Z&find[created_at][$lt]=2015-09-06T22:00:00.000Z': [ + {"created_at":"2015-08-07T23:25:50.718Z"}, + {"created_at":"2015-08-08T23:25:50.718Z"}, + {"created_at":"2015-08-09T23:25:50.718Z"}, + {"created_at":"2015-08-10T23:25:50.718Z"}, + {"created_at":"2015-08-11T23:25:50.718Z"}, + {"created_at":"2015-08-12T23:25:50.718Z"}, + {"created_at":"2015-08-13T23:25:50.718Z"}, + {"created_at":"2015-08-14T23:25:50.718Z"}, + {"created_at":"2015-08-15T23:25:50.718Z"}, + {"created_at":"2015-08-16T23:25:50.718Z"}, + {"created_at":"2015-08-17T23:25:50.718Z"}, + {"created_at":"2015-08-18T23:25:50.718Z"}, + {"created_at":"2015-08-19T23:25:50.718Z"}, + {"created_at":"2015-08-20T23:25:50.718Z"}, + {"created_at":"2015-08-21T23:25:50.718Z"}, + {"created_at":"2015-08-22T23:25:50.718Z"}, + {"created_at":"2015-08-23T23:25:50.718Z"}, + {"created_at":"2015-08-24T23:25:50.718Z"}, + {"created_at":"2015-08-25T23:25:50.718Z"}, + {"created_at":"2015-08-26T23:25:50.718Z"}, + {"created_at":"2015-08-27T23:25:50.718Z"}, + {"created_at":"2015-08-28T23:25:50.718Z"}, + {"created_at":"2015-08-29T23:25:50.718Z"}, + {"created_at":"2015-08-30T23:25:50.718Z"}, + {"created_at":"2015-08-31T23:25:50.718Z"}, + {"created_at":"2015-09-01T23:25:50.718Z"}, + {"created_at":"2015-09-02T23:25:50.718Z"}, + {"created_at":"2015-09-03T23:25:50.718Z"}, + {"created_at":"2015-09-04T23:25:50.718Z"}, + {"created_at":"2015-09-05T23:25:50.718Z"}, + {"created_at":"2015-09-06T23:25:50.718Z"} + ], + '/api/v1/treatments.json?find[notes]=/something/i&find[created_at][$gte]=2015-08-07T22:00:00.000Z&find[created_at][$lt]=2015-09-06T22:00:00.000Z': [ + {"created_at":"2015-08-07T23:25:50.718Z"}, + {"created_at":"2015-08-08T23:25:50.718Z"}, + {"created_at":"2015-08-09T23:25:50.718Z"}, + {"created_at":"2015-08-10T23:25:50.718Z"}, + {"created_at":"2015-08-11T23:25:50.718Z"}, + {"created_at":"2015-08-12T23:25:50.718Z"}, + {"created_at":"2015-08-13T23:25:50.718Z"}, + {"created_at":"2015-08-14T23:25:50.718Z"}, + {"created_at":"2015-08-15T23:25:50.718Z"}, + {"created_at":"2015-08-16T23:25:50.718Z"}, + {"created_at":"2015-08-17T23:25:50.718Z"}, + {"created_at":"2015-08-18T23:25:50.718Z"}, + {"created_at":"2015-08-19T23:25:50.718Z"}, + {"created_at":"2015-08-20T23:25:50.718Z"}, + {"created_at":"2015-08-21T23:25:50.718Z"}, + {"created_at":"2015-08-22T23:25:50.718Z"}, + {"created_at":"2015-08-23T23:25:50.718Z"}, + {"created_at":"2015-08-24T23:25:50.718Z"}, + {"created_at":"2015-08-25T23:25:50.718Z"}, + {"created_at":"2015-08-26T23:25:50.718Z"}, + {"created_at":"2015-08-27T23:25:50.718Z"}, + {"created_at":"2015-08-28T23:25:50.718Z"}, + {"created_at":"2015-08-29T23:25:50.718Z"}, + {"created_at":"2015-08-30T23:25:50.718Z"}, + {"created_at":"2015-08-31T23:25:50.718Z"}, + {"created_at":"2015-09-01T23:25:50.718Z"}, + {"created_at":"2015-09-02T23:25:50.718Z"}, + {"created_at":"2015-09-03T23:25:50.718Z"}, + {"created_at":"2015-09-04T23:25:50.718Z"}, + {"created_at":"2015-09-05T23:25:50.718Z"}, + {"created_at":"2015-09-06T23:25:50.718Z"} + ] }; var exampleProfile = [ @@ -114,9 +180,11 @@ describe('reports', function ( ) { opts.success([]); } fn(); - return { - fail: function () {} - }; + return self.$.ajax(); + }, + fail: function mockFail (fn) { + fn(); + return self.$.ajax(); } }; }; @@ -185,6 +253,7 @@ describe('reports', function ( ) { $('#rp_enableeventtype').click(); $('#rp_enablenotes').click(); $('#rp_enablefood').click(); + $('#rp_enablefood').click(); $('#rp_log').prop('checked',true); $('#rp_show').click(); From 536ab649cc25754fce7fa40efd5ebb7585f9d1ce Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Fri, 11 Sep 2015 09:04:20 +0200 Subject: [PATCH 891/937] doublequotes --- tests/reports.test.js | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/reports.test.js b/tests/reports.test.js index ac8d5d6b1a1..35d08f8df20 100644 --- a/tests/reports.test.js +++ b/tests/reports.test.js @@ -12,22 +12,22 @@ var nowData = { }; var someData = { - '/api/v1/entries.json?find[date][$gte]=1438992000000&find[date][$lt]=1439078400000&count=10000': [{"_id":"55c697f9459cf1fa5ed71cd8","unfiltered":213888,"filtered":218560,"direction":"Flat","device":"dexcom","rssi":172,"sgv":208,"dateString":"Sat Aug 08 16:58:44 PDT 2015","type":"sgv","date":1439078324000,"noise":1},{"_id":"55c696cc459cf1fa5ed71cd7","unfiltered":217952,"filtered":220864,"direction":"Flat","device":"dexcom","rssi":430,"sgv":212,"dateString":"Sat Aug 08 16:53:45 PDT 2015","type":"sgv","date":1439078025000,"noise":1},{"_id":"55c5d0c6459cf1fa5ed71a04","device":"dexcom","scale":1.1,"dateString":"Sat Aug 08 02:48:05 PDT 2015","date":1439027285000,"type":"cal","intercept":31102.323470336833,"slope":776.9097574914869},{"_id":"55c5d0c5459cf1fa5ed71a03","device":"dexcom","dateString":"Sat Aug 08 02:48:03 PDT 2015","mbg":120,"date":1439027283000,"type":"mbg"}], - '/api/v1/treatments.json?find[created_at][$gte]=2015-08-08T00:00:00.000Z&find[created_at][$lt]=2015-08-09T00:00:00.000Z': [{"enteredBy":"Dad","eventType":"Correction Bolus","glucose":201,"glucoseType":"Finger","insulin":0.65,"units":"mg/dl","created_at":"2015-08-08T23:22:00.000Z","_id":"55c695628a00a3c97a6611ed"},{"enteredBy":"Mom ","eventType":"Correction Bolus","glucose":163,"glucoseType":"Sensor","insulin":0.7,"units":"mg/dl","created_at":"2015-08-08T22:53:11.021Z","_id":"55c68857cd6dd2036036705f"}], - '/api/v1/entries.json?find[date][$gte]=1439078400000&find[date][$lt]=1439164800000&count=10000': [{"_id":"55c7e85f459cf1fa5ed71dc8","unfiltered":183520,"filtered":193120,"direction":"NOT COMPUTABLE","device":"dexcom","rssi":161,"sgv":149,"dateString":"Sun Aug 09 16:53:40 PDT 2015","type":"sgv","date":1439164420000,"noise":1},{"_id":"55c7e270459cf1fa5ed71dc7","unfiltered":199328,"filtered":192608,"direction":"Flat","device":"dexcom","rssi":161,"sgv":166,"dateString":"Sun Aug 09 16:28:40 PDT 2015","type":"sgv","date":1439162920000,"noise":1}], - '/api/v1/treatments.json?find[created_at][$gte]=2015-08-09T00:00:00.000Z&find[created_at][$lt]=2015-08-10T00:00:00.000Z': [{"enteredBy":"Dad","eventType":"Snack Bolus","carbs":18,"insulin":1.1,"created_at":"2015-08-09T22:41:56.253Z","_id":"55c7d734270fbd97191013c2"},{"enteredBy":"Dad","eventType":"Carb Correction","carbs":5,"created_at":"2015-08-09T21:39:13.995Z","_id":"55c7c881270fbd97191013b4"}], - '/api/v1/entries.json?find[date][$gte]=1439164800000&find[date][$lt]=1439251200000&count=10000': [{"_id":"55c93af4459cf1fa5ed71ecc","unfiltered":193248,"filtered":188384,"direction":"NOT COMPUTABLE","device":"dexcom","rssi":194,"sgv":193,"dateString":"Mon Aug 10 16:58:36 PDT 2015","type":"sgv","date":1439251116000,"noise":1},{"_id":"55c939d8459cf1fa5ed71ecb","unfiltered":189888,"filtered":184960,"direction":"NOT COMPUTABLE","device":"dexcom","rssi":931,"sgv":188,"dateString":"Mon Aug 10 16:53:38 PDT 2015","type":"sgv","date":1439250818000,"noise":1}], - '/api/v1/treatments.json?find[created_at][$gte]=2015-08-10T00:00:00.000Z&find[created_at][$lt]=2015-08-11T00:00:00.000Z': [{"enteredBy":"Mom ","eventType":"Snack Bolus","glucose":180,"glucoseType":"Sensor","carbs":18,"insulin":1.9,"units":"mg/dl","created_at":"2015-08-10T23:53:31.970Z","_id":"55c9397b865550df020e3560"},{"enteredBy":"Mom ","eventType":"Meal Bolus","glucose":140,"glucoseType":"Finger","carbs":50,"insulin":3.4,"units":"mg/dl","created_at":"2015-08-10T20:41:23.516Z","_id":"55c90c73865550df020e3539"}], - '/api/v1/entries.json?find[date][$gte]=1439251200000&find[date][$lt]=1439337600000&count=10000': [{"_id":"55ca8c6e459cf1fa5ed71fe2","unfiltered":174080,"filtered":184576,"direction":"FortyFiveDown","device":"dexcom","rssi":169,"sgv":156,"dateString":"Tue Aug 11 16:58:32 PDT 2015","type":"sgv","date":1439337512000,"noise":1},{"_id":"55ca8b42459cf1fa5ed71fe1","unfiltered":180192,"filtered":192768,"direction":"FortyFiveDown","device":"dexcom","rssi":182,"sgv":163,"dateString":"Tue Aug 11 16:53:32 PDT 2015","type":"sgv","date":1439337212000,"noise":1}], - '/api/v1/treatments.json?find[created_at][$gte]=2015-08-11T00:00:00.000Z&find[created_at][$lt]=2015-08-12T00:00:00.000Z': [{"created_at":"2015-08-11T23:37:00.000Z","eventType":"Snack Bolus","carbs":18,"_id":"55ca8644ca3c57683d19c211"},{"enteredBy":"Mom ","eventType":"Snack Bolus","glucose":203,"glucoseType":"Sensor","insulin":1,"preBolus":15,"units":"mg/dl","created_at":"2015-08-11T23:22:00.000Z","_id":"55ca8644ca3c57683d19c210"}], - '/api/v1/entries.json?find[date][$gte]=1439337600000&find[date][$lt]=1439424000000&count=10000': [{"_id":"55cbddee38a8d88ad1b48647","unfiltered":165760,"filtered":167488,"direction":"Flat","device":"dexcom","rssi":165,"sgv":157,"dateString":"Wed Aug 12 16:58:28 PDT 2015","type":"sgv","date":1439423908000,"noise":1},{"_id":"55cbdccc38a8d88ad1b48644","unfiltered":167456,"filtered":169312,"direction":"Flat","device":"dexcom","rssi":168,"sgv":159,"dateString":"Wed Aug 12 16:53:28 PDT 2015","type":"sgv","date":1439423608000,"noise":1}], - '/api/v1/treatments.json?find[created_at][$gte]=2015-08-12T00:00:00.000Z&find[created_at][$lt]=2015-08-13T00:00:00.000Z': [{"enteredBy":"Dad","eventType":"Correction Bolus","insulin":0.8,"created_at":"2015-08-12T23:21:08.907Z","_id":"55cbd4e47e726599048a3f91"},{"enteredBy":"Dad","eventType":"Note","notes":"Milk now","created_at":"2015-08-12T21:23:00.000Z","_id":"55cbba4e7e726599048a3f79"}], - '/api/v1/entries.json?find[date][$gte]=1439424000000&find[date][$lt]=1439510400000&count=10000': [{"_id":"55cd2f6738a8d88ad1b48ca1","unfiltered":209792,"filtered":229344,"direction":"SingleDown","device":"dexcom","rssi":436,"sgv":205,"dateString":"Thu Aug 13 16:58:24 PDT 2015","type":"sgv","date":1439510304000,"noise":1},{"_id":"55cd2e3b38a8d88ad1b48c95","unfiltered":220928,"filtered":237472,"direction":"FortyFiveDown","device":"dexcom","rssi":418,"sgv":219,"dateString":"Thu Aug 13 16:53:24 PDT 2015","type":"sgv","date":1439510004000,"noise":1}], - '/api/v1/treatments.json?find[created_at][$gte]=2015-08-13T00:00:00.000Z&find[created_at][$lt]=2015-08-14T00:00:00.000Z': [{"enteredBy":"Mom ","eventType":"Correction Bolus","glucose":250,"glucoseType":"Sensor","insulin":0.75,"units":"mg/dl","created_at":"2015-08-13T23:45:56.927Z","_id":"55cd2c3497fa97ac5d8bc53b"},{"enteredBy":"Mom ","eventType":"Correction Bolus","glucose":198,"glucoseType":"Sensor","insulin":1.1,"units":"mg/dl","created_at":"2015-08-13T23:11:00.293Z","_id":"55cd240497fa97ac5d8bc535"}], - '/api/v1/entries.json?find[date][$gte]=1439510400000&find[date][$lt]=1439596800000&count=10000': [{"_id":"55ce80e338a8d88ad1b49397","unfiltered":179936,"filtered":202080,"direction":"SingleDown","device":"dexcom","rssi":179,"sgv":182,"dateString":"Fri Aug 14 16:58:20 PDT 2015","type":"sgv","date":1439596700000,"noise":1},{"_id":"55ce7fb738a8d88ad1b4938d","unfiltered":192288,"filtered":213792,"direction":"SingleDown","device":"dexcom","rssi":180,"sgv":197,"dateString":"Fri Aug 14 16:53:20 PDT 2015","type":"sgv","date":1439596400000,"noise":1}], - '/api/v1/treatments.json?find[created_at][$gte]=2015-08-14T00:00:00.000Z&find[created_at][$lt]=2015-08-15T00:00:00.000Z': [{"enteredBy":"Dad","eventType":"Site Change","glucose":268,"glucoseType":"Finger","insulin":1.75,"units":"mg/dl","created_at":"2015-08-14T23:25:50.718Z","_id":"55ce78fe925aa80e7071e5d6"},{"enteredBy":"Mom ","eventType":"Meal Bolus","glucose":89,"glucoseType":"Finger","carbs":54,"insulin":3.15,"units":"mg/dl","created_at":"2015-08-14T21:00:00.000Z","_id":"55ce59bb925aa80e7071e5ba"}], - '/api/v1/entries.json?find[date][$gte]=1439596800000&find[date][$lt]=1439683200000&count=10000': [{"_id":"55cfd25f38a8d88ad1b49931","unfiltered":283136,"filtered":304768,"direction":"SingleDown","device":"dexcom","rssi":185,"sgv":306,"dateString":"Sat Aug 15 16:58:16 PDT 2015","type":"sgv","date":1439683096000,"noise":1},{"_id":"55cfd13338a8d88ad1b4992e","unfiltered":302528,"filtered":312576,"direction":"FortyFiveDown","device":"dexcom","rssi":179,"sgv":329,"dateString":"Sat Aug 15 16:53:16 PDT 2015","type":"sgv","date":1439682796000,"noise":1}], - '/api/v1/food/regular.json': [{"_id":"552ece84a6947ea011db35bb","type":"food","category":"Zakladni","subcategory":"Sladkosti","name":"Bebe male","portion":18,"carbs":12,"gi":1,"unit":"pcs","created_at":"2015-04-15T20:48:04.966Z"}], + '/api/v1/entries.json?find[date][$gte]=1438992000000&find[date][$lt]=1439078400000&count=10000': [{'_id':'55c697f9459cf1fa5ed71cd8','unfiltered':213888,'filtered':218560,'direction':'Flat','device':'dexcom','rssi':172,'sgv':208,'dateString':'Sat Aug 08 16:58:44 PDT 2015','type':'sgv','date':1439078324000,'noise':1},{'_id':'55c696cc459cf1fa5ed71cd7','unfiltered':217952,'filtered':220864,'direction':'Flat','device':'dexcom','rssi':430,'sgv':212,'dateString':'Sat Aug 08 16:53:45 PDT 2015','type':'sgv','date':1439078025000,'noise':1},{'_id':'55c5d0c6459cf1fa5ed71a04','device':'dexcom','scale':1.1,'dateString':'Sat Aug 08 02:48:05 PDT 2015','date':1439027285000,'type':'cal','intercept':31102.323470336833,'slope':776.9097574914869},{'_id':'55c5d0c5459cf1fa5ed71a03','device':'dexcom','dateString':'Sat Aug 08 02:48:03 PDT 2015','mbg':120,'date':1439027283000,'type':'mbg'}], + '/api/v1/treatments.json?find[created_at][$gte]=2015-08-08T00:00:00.000Z&find[created_at][$lt]=2015-08-09T00:00:00.000Z': [{'enteredBy':'Dad','eventType':'Correction Bolus','glucose':201,'glucoseType':'Finger','insulin':0.65,'units':'mg/dl','created_at':'2015-08-08T23:22:00.000Z','_id':'55c695628a00a3c97a6611ed'},{'enteredBy':'Mom ','eventType':'Correction Bolus','glucose':163,'glucoseType':'Sensor','insulin':0.7,'units':'mg/dl','created_at':'2015-08-08T22:53:11.021Z','_id':'55c68857cd6dd2036036705f'}], + '/api/v1/entries.json?find[date][$gte]=1439078400000&find[date][$lt]=1439164800000&count=10000': [{'_id':'55c7e85f459cf1fa5ed71dc8','unfiltered':183520,'filtered':193120,'direction':'NOT COMPUTABLE','device':'dexcom','rssi':161,'sgv':149,'dateString':'Sun Aug 09 16:53:40 PDT 2015','type':'sgv','date':1439164420000,'noise':1},{'_id':'55c7e270459cf1fa5ed71dc7','unfiltered':199328,'filtered':192608,'direction':'Flat','device':'dexcom','rssi':161,'sgv':166,'dateString':'Sun Aug 09 16:28:40 PDT 2015','type':'sgv','date':1439162920000,'noise':1}], + '/api/v1/treatments.json?find[created_at][$gte]=2015-08-09T00:00:00.000Z&find[created_at][$lt]=2015-08-10T00:00:00.000Z': [{'enteredBy':'Dad','eventType':'Snack Bolus','carbs':18,'insulin':1.1,'created_at':'2015-08-09T22:41:56.253Z','_id':'55c7d734270fbd97191013c2'},{'enteredBy':'Dad','eventType':'Carb Correction','carbs':5,'created_at':'2015-08-09T21:39:13.995Z','_id':'55c7c881270fbd97191013b4'}], + '/api/v1/entries.json?find[date][$gte]=1439164800000&find[date][$lt]=1439251200000&count=10000': [{'_id':'55c93af4459cf1fa5ed71ecc','unfiltered':193248,'filtered':188384,'direction':'NOT COMPUTABLE','device':'dexcom','rssi':194,'sgv':193,'dateString':'Mon Aug 10 16:58:36 PDT 2015','type':'sgv','date':1439251116000,'noise':1},{'_id':'55c939d8459cf1fa5ed71ecb','unfiltered':189888,'filtered':184960,'direction':'NOT COMPUTABLE','device':'dexcom','rssi':931,'sgv':188,'dateString':'Mon Aug 10 16:53:38 PDT 2015','type':'sgv','date':1439250818000,'noise':1}], + '/api/v1/treatments.json?find[created_at][$gte]=2015-08-10T00:00:00.000Z&find[created_at][$lt]=2015-08-11T00:00:00.000Z': [{'enteredBy':'Mom ','eventType':'Snack Bolus','glucose':180,'glucoseType':'Sensor','carbs':18,'insulin':1.9,'units':'mg/dl','created_at':'2015-08-10T23:53:31.970Z','_id':'55c9397b865550df020e3560'},{'enteredBy':'Mom ','eventType':'Meal Bolus','glucose':140,'glucoseType':'Finger','carbs':50,'insulin':3.4,'units':'mg/dl','created_at':'2015-08-10T20:41:23.516Z','_id':'55c90c73865550df020e3539'}], + '/api/v1/entries.json?find[date][$gte]=1439251200000&find[date][$lt]=1439337600000&count=10000': [{'_id':'55ca8c6e459cf1fa5ed71fe2','unfiltered':174080,'filtered':184576,'direction':'FortyFiveDown','device':'dexcom','rssi':169,'sgv':156,'dateString':'Tue Aug 11 16:58:32 PDT 2015','type':'sgv','date':1439337512000,'noise':1},{'_id':'55ca8b42459cf1fa5ed71fe1','unfiltered':180192,'filtered':192768,'direction':'FortyFiveDown','device':'dexcom','rssi':182,'sgv':163,'dateString':'Tue Aug 11 16:53:32 PDT 2015','type':'sgv','date':1439337212000,'noise':1}], + '/api/v1/treatments.json?find[created_at][$gte]=2015-08-11T00:00:00.000Z&find[created_at][$lt]=2015-08-12T00:00:00.000Z': [{'created_at':'2015-08-11T23:37:00.000Z','eventType':'Snack Bolus','carbs':18,'_id':'55ca8644ca3c57683d19c211'},{'enteredBy':'Mom ','eventType':'Snack Bolus','glucose':203,'glucoseType':'Sensor','insulin':1,'preBolus':15,'units':'mg/dl','created_at':'2015-08-11T23:22:00.000Z','_id':'55ca8644ca3c57683d19c210'}], + '/api/v1/entries.json?find[date][$gte]=1439337600000&find[date][$lt]=1439424000000&count=10000': [{'_id':'55cbddee38a8d88ad1b48647','unfiltered':165760,'filtered':167488,'direction':'Flat','device':'dexcom','rssi':165,'sgv':157,'dateString':'Wed Aug 12 16:58:28 PDT 2015','type':'sgv','date':1439423908000,'noise':1},{'_id':'55cbdccc38a8d88ad1b48644','unfiltered':167456,'filtered':169312,'direction':'Flat','device':'dexcom','rssi':168,'sgv':159,'dateString':'Wed Aug 12 16:53:28 PDT 2015','type':'sgv','date':1439423608000,'noise':1}], + '/api/v1/treatments.json?find[created_at][$gte]=2015-08-12T00:00:00.000Z&find[created_at][$lt]=2015-08-13T00:00:00.000Z': [{'enteredBy':'Dad','eventType':'Correction Bolus','insulin':0.8,'created_at':'2015-08-12T23:21:08.907Z','_id':'55cbd4e47e726599048a3f91'},{'enteredBy':'Dad','eventType':'Note','notes':'Milk now','created_at':'2015-08-12T21:23:00.000Z','_id':'55cbba4e7e726599048a3f79'}], + '/api/v1/entries.json?find[date][$gte]=1439424000000&find[date][$lt]=1439510400000&count=10000': [{'_id':'55cd2f6738a8d88ad1b48ca1','unfiltered':209792,'filtered':229344,'direction':'SingleDown','device':'dexcom','rssi':436,'sgv':205,'dateString':'Thu Aug 13 16:58:24 PDT 2015','type':'sgv','date':1439510304000,'noise':1},{'_id':'55cd2e3b38a8d88ad1b48c95','unfiltered':220928,'filtered':237472,'direction':'FortyFiveDown','device':'dexcom','rssi':418,'sgv':219,'dateString':'Thu Aug 13 16:53:24 PDT 2015','type':'sgv','date':1439510004000,'noise':1}], + '/api/v1/treatments.json?find[created_at][$gte]=2015-08-13T00:00:00.000Z&find[created_at][$lt]=2015-08-14T00:00:00.000Z': [{'enteredBy':'Mom ','eventType':'Correction Bolus','glucose':250,'glucoseType':'Sensor','insulin':0.75,'units':'mg/dl','created_at':'2015-08-13T23:45:56.927Z','_id':'55cd2c3497fa97ac5d8bc53b'},{'enteredBy':'Mom ','eventType':'Correction Bolus','glucose':198,'glucoseType':'Sensor','insulin':1.1,'units':'mg/dl','created_at':'2015-08-13T23:11:00.293Z','_id':'55cd240497fa97ac5d8bc535'}], + '/api/v1/entries.json?find[date][$gte]=1439510400000&find[date][$lt]=1439596800000&count=10000': [{'_id':'55ce80e338a8d88ad1b49397','unfiltered':179936,'filtered':202080,'direction':'SingleDown','device':'dexcom','rssi':179,'sgv':182,'dateString':'Fri Aug 14 16:58:20 PDT 2015','type':'sgv','date':1439596700000,'noise':1},{'_id':'55ce7fb738a8d88ad1b4938d','unfiltered':192288,'filtered':213792,'direction':'SingleDown','device':'dexcom','rssi':180,'sgv':197,'dateString':'Fri Aug 14 16:53:20 PDT 2015','type':'sgv','date':1439596400000,'noise':1}], + '/api/v1/treatments.json?find[created_at][$gte]=2015-08-14T00:00:00.000Z&find[created_at][$lt]=2015-08-15T00:00:00.000Z': [{'enteredBy':'Dad','eventType':'Site Change','glucose':268,'glucoseType':'Finger','insulin':1.75,'units':'mg/dl','created_at':'2015-08-14T23:25:50.718Z','_id':'55ce78fe925aa80e7071e5d6'},{'enteredBy':'Mom ','eventType':'Meal Bolus','glucose':89,'glucoseType':'Finger','carbs':54,'insulin':3.15,'units':'mg/dl','created_at':'2015-08-14T21:00:00.000Z','_id':'55ce59bb925aa80e7071e5ba'}], + '/api/v1/entries.json?find[date][$gte]=1439596800000&find[date][$lt]=1439683200000&count=10000': [{'_id':'55cfd25f38a8d88ad1b49931','unfiltered':283136,'filtered':304768,'direction':'SingleDown','device':'dexcom','rssi':185,'sgv':306,'dateString':'Sat Aug 15 16:58:16 PDT 2015','type':'sgv','date':1439683096000,'noise':1},{'_id':'55cfd13338a8d88ad1b4992e','unfiltered':302528,'filtered':312576,'direction':'FortyFiveDown','device':'dexcom','rssi':179,'sgv':329,'dateString':'Sat Aug 15 16:53:16 PDT 2015','type':'sgv','date':1439682796000,'noise':1}], + '/api/v1/food/regular.json': [{'_id':'552ece84a6947ea011db35bb','type':'food','category':'Zakladni','subcategory':'Sladkosti','name':'Bebe male','portion':18,'carbs':12,'gi':1,'unit':'pcs','created_at':'2015-04-15T20:48:04.966Z'}], '/api/v1/treatments.json?find[eventType]=/BG Check/i&find[created_at][$gte]=2015-08-07T22:00:00.000Z&find[created_at][$lt]=2015-09-06T22:00:00.000Z': [ {"created_at":"2015-08-07T23:25:50.718Z"}, {"created_at":"2015-08-08T23:25:50.718Z"}, From a1ea94afa08653a4425f0cc2215d9330d48e8a25 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Fri, 11 Sep 2015 09:18:40 +0200 Subject: [PATCH 892/937] display html in travis --- tests/reports.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/reports.test.js b/tests/reports.test.js index 35d08f8df20..397116b1730 100644 --- a/tests/reports.test.js +++ b/tests/reports.test.js @@ -265,6 +265,8 @@ describe('reports', function ( ) { //var filesys = require('fs'); //var logfile = filesys.createWriteStream('out.txt', { flags: 'a'} ) //logfile.write($('body').html()); + + console.log(result); result.indexOf('Milk now').should.be.greaterThan(-1); // daytoday result.indexOf('50 g (1.67U)').should.be.greaterThan(-1); // daytoday From 69e318ab57deb2771bf2b7123bdf416e1c0359da Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Fri, 11 Sep 2015 09:31:25 +0200 Subject: [PATCH 893/937] add more log again --- static/report/js/report.js | 2 +- tests/reports.test.js | 126 ++++++++++++++++++------------------- 2 files changed, 64 insertions(+), 64 deletions(-) diff --git a/static/report/js/report.js b/static/report/js/report.js index 4dfdd109e60..d76292bb24b 100644 --- a/static/report/js/report.js +++ b/static/report/js/report.js @@ -238,7 +238,7 @@ }; // default time range if no time range specified in GUI - var timerange = '&find[created_at][$gte]='+new Date('1970-01-01').toISOString(); + var timerange = '&find[created_at][$gte]='+moment('1970-01-01').toDate().toISOString(); options.targetLow = parseFloat($('#rp_targetlow').val().replace(',','.')); options.targetHigh = parseFloat($('#rp_targethigh').val().replace(',','.')); diff --git a/tests/reports.test.js b/tests/reports.test.js index 397116b1730..899c675094e 100644 --- a/tests/reports.test.js +++ b/tests/reports.test.js @@ -30,69 +30,69 @@ var someData = { '/api/v1/food/regular.json': [{'_id':'552ece84a6947ea011db35bb','type':'food','category':'Zakladni','subcategory':'Sladkosti','name':'Bebe male','portion':18,'carbs':12,'gi':1,'unit':'pcs','created_at':'2015-04-15T20:48:04.966Z'}], '/api/v1/treatments.json?find[eventType]=/BG Check/i&find[created_at][$gte]=2015-08-07T22:00:00.000Z&find[created_at][$lt]=2015-09-06T22:00:00.000Z': [ {"created_at":"2015-08-07T23:25:50.718Z"}, - {"created_at":"2015-08-08T23:25:50.718Z"}, - {"created_at":"2015-08-09T23:25:50.718Z"}, - {"created_at":"2015-08-10T23:25:50.718Z"}, - {"created_at":"2015-08-11T23:25:50.718Z"}, - {"created_at":"2015-08-12T23:25:50.718Z"}, - {"created_at":"2015-08-13T23:25:50.718Z"}, - {"created_at":"2015-08-14T23:25:50.718Z"}, - {"created_at":"2015-08-15T23:25:50.718Z"}, - {"created_at":"2015-08-16T23:25:50.718Z"}, - {"created_at":"2015-08-17T23:25:50.718Z"}, - {"created_at":"2015-08-18T23:25:50.718Z"}, - {"created_at":"2015-08-19T23:25:50.718Z"}, - {"created_at":"2015-08-20T23:25:50.718Z"}, - {"created_at":"2015-08-21T23:25:50.718Z"}, - {"created_at":"2015-08-22T23:25:50.718Z"}, - {"created_at":"2015-08-23T23:25:50.718Z"}, - {"created_at":"2015-08-24T23:25:50.718Z"}, - {"created_at":"2015-08-25T23:25:50.718Z"}, - {"created_at":"2015-08-26T23:25:50.718Z"}, - {"created_at":"2015-08-27T23:25:50.718Z"}, - {"created_at":"2015-08-28T23:25:50.718Z"}, - {"created_at":"2015-08-29T23:25:50.718Z"}, - {"created_at":"2015-08-30T23:25:50.718Z"}, - {"created_at":"2015-08-31T23:25:50.718Z"}, - {"created_at":"2015-09-01T23:25:50.718Z"}, - {"created_at":"2015-09-02T23:25:50.718Z"}, - {"created_at":"2015-09-03T23:25:50.718Z"}, - {"created_at":"2015-09-04T23:25:50.718Z"}, - {"created_at":"2015-09-05T23:25:50.718Z"}, - {"created_at":"2015-09-06T23:25:50.718Z"} + {'created_at':'2015-08-08T23:25:50.718Z'}, + {'created_at':'2015-08-09T23:25:50.718Z'}, + {'created_at':'2015-08-10T23:25:50.718Z'}, + {'created_at':'2015-08-11T23:25:50.718Z'}, + {'created_at':'2015-08-12T23:25:50.718Z'}, + {'created_at':'2015-08-13T23:25:50.718Z'}, + {'created_at':'2015-08-14T23:25:50.718Z'}, + {'created_at':'2015-08-15T23:25:50.718Z'}, + {'created_at':'2015-08-16T23:25:50.718Z'}, + {'created_at':'2015-08-17T23:25:50.718Z'}, + {'created_at':'2015-08-18T23:25:50.718Z'}, + {'created_at':'2015-08-19T23:25:50.718Z'}, + {'created_at':'2015-08-20T23:25:50.718Z'}, + {'created_at':'2015-08-21T23:25:50.718Z'}, + {'created_at':'2015-08-22T23:25:50.718Z'}, + {'created_at':'2015-08-23T23:25:50.718Z'}, + {'created_at':'2015-08-24T23:25:50.718Z'}, + {'created_at':'2015-08-25T23:25:50.718Z'}, + {'created_at':'2015-08-26T23:25:50.718Z'}, + {'created_at':'2015-08-27T23:25:50.718Z'}, + {'created_at':'2015-08-28T23:25:50.718Z'}, + {'created_at':'2015-08-29T23:25:50.718Z'}, + {'created_at':'2015-08-30T23:25:50.718Z'}, + {'created_at':'2015-08-31T23:25:50.718Z'}, + {'created_at':'2015-09-01T23:25:50.718Z'}, + {'created_at':'2015-09-02T23:25:50.718Z'}, + {'created_at':'2015-09-03T23:25:50.718Z'}, + {'created_at':'2015-09-04T23:25:50.718Z'}, + {'created_at':'2015-09-05T23:25:50.718Z'}, + {'created_at':'2015-09-06T23:25:50.718Z'} ], '/api/v1/treatments.json?find[notes]=/something/i&find[created_at][$gte]=2015-08-07T22:00:00.000Z&find[created_at][$lt]=2015-09-06T22:00:00.000Z': [ - {"created_at":"2015-08-07T23:25:50.718Z"}, - {"created_at":"2015-08-08T23:25:50.718Z"}, - {"created_at":"2015-08-09T23:25:50.718Z"}, - {"created_at":"2015-08-10T23:25:50.718Z"}, - {"created_at":"2015-08-11T23:25:50.718Z"}, - {"created_at":"2015-08-12T23:25:50.718Z"}, - {"created_at":"2015-08-13T23:25:50.718Z"}, - {"created_at":"2015-08-14T23:25:50.718Z"}, - {"created_at":"2015-08-15T23:25:50.718Z"}, - {"created_at":"2015-08-16T23:25:50.718Z"}, - {"created_at":"2015-08-17T23:25:50.718Z"}, - {"created_at":"2015-08-18T23:25:50.718Z"}, - {"created_at":"2015-08-19T23:25:50.718Z"}, - {"created_at":"2015-08-20T23:25:50.718Z"}, - {"created_at":"2015-08-21T23:25:50.718Z"}, - {"created_at":"2015-08-22T23:25:50.718Z"}, - {"created_at":"2015-08-23T23:25:50.718Z"}, - {"created_at":"2015-08-24T23:25:50.718Z"}, - {"created_at":"2015-08-25T23:25:50.718Z"}, - {"created_at":"2015-08-26T23:25:50.718Z"}, - {"created_at":"2015-08-27T23:25:50.718Z"}, - {"created_at":"2015-08-28T23:25:50.718Z"}, - {"created_at":"2015-08-29T23:25:50.718Z"}, - {"created_at":"2015-08-30T23:25:50.718Z"}, - {"created_at":"2015-08-31T23:25:50.718Z"}, - {"created_at":"2015-09-01T23:25:50.718Z"}, - {"created_at":"2015-09-02T23:25:50.718Z"}, - {"created_at":"2015-09-03T23:25:50.718Z"}, - {"created_at":"2015-09-04T23:25:50.718Z"}, - {"created_at":"2015-09-05T23:25:50.718Z"}, - {"created_at":"2015-09-06T23:25:50.718Z"} + {'created_at':'2015-08-07T23:25:50.718Z'}, + {'created_at':'2015-08-08T23:25:50.718Z'}, + {'created_at':'2015-08-09T23:25:50.718Z'}, + {'created_at':'2015-08-10T23:25:50.718Z'}, + {'created_at':'2015-08-11T23:25:50.718Z'}, + {'created_at':'2015-08-12T23:25:50.718Z'}, + {'created_at':'2015-08-13T23:25:50.718Z'}, + {'created_at':'2015-08-14T23:25:50.718Z'}, + {'created_at':'2015-08-15T23:25:50.718Z'}, + {'created_at':'2015-08-16T23:25:50.718Z'}, + {'created_at':'2015-08-17T23:25:50.718Z'}, + {'created_at':'2015-08-18T23:25:50.718Z'}, + {'created_at':'2015-08-19T23:25:50.718Z'}, + {'created_at':'2015-08-20T23:25:50.718Z'}, + {'created_at':'2015-08-21T23:25:50.718Z'}, + {'created_at':'2015-08-22T23:25:50.718Z'}, + {'created_at':'2015-08-23T23:25:50.718Z'}, + {'created_at':'2015-08-24T23:25:50.718Z'}, + {'created_at':'2015-08-25T23:25:50.718Z'}, + {'created_at':'2015-08-26T23:25:50.718Z'}, + {'created_at':'2015-08-27T23:25:50.718Z'}, + {'created_at':'2015-08-28T23:25:50.718Z'}, + {'created_at':'2015-08-29T23:25:50.718Z'}, + {'created_at':'2015-08-30T23:25:50.718Z'}, + {'created_at':'2015-08-31T23:25:50.718Z'}, + {'created_at':'2015-09-01T23:25:50.718Z'}, + {'created_at':'2015-09-02T23:25:50.718Z'}, + {'created_at':'2015-09-03T23:25:50.718Z'}, + {'created_at':'2015-09-04T23:25:50.718Z'}, + {'created_at':'2015-09-05T23:25:50.718Z'}, + {'created_at':'2015-09-06T23:25:50.718Z'} ] }; @@ -173,10 +173,10 @@ describe('reports', function ( ) { return { done: function mockDone (fn) { if (opts && opts.success && opts.success.call && someData[url]) { - //console.log('+++++Data for ' + url + ' sent'); + console.log('+++++Data for ' + url + ' sent'); opts.success(someData[url]); } else { - //console.log('-----Data for ' + url + ' missing'); + console.log('-----Data for ' + url + ' missing'); opts.success([]); } fn(); From 28d5e31bb1ee2400963b73b9ec92a83c83f40053 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Fri, 11 Sep 2015 09:53:09 +0200 Subject: [PATCH 894/937] add timezone to query filter --- static/report/js/report.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/static/report/js/report.js b/static/report/js/report.js index d76292bb24b..ec5df1353d6 100644 --- a/static/report/js/report.js +++ b/static/report/js/report.js @@ -238,7 +238,9 @@ }; // default time range if no time range specified in GUI - var timerange = '&find[created_at][$gte]='+moment('1970-01-01').toDate().toISOString(); + var zone = client.sbx.data.profile.getTimezone(); + var timerange = '&find[created_at][$gte]='+moment.tz('1970-01-01',zone).toDate().toISOString(); +console.log(timerange,zone); options.targetLow = parseFloat($('#rp_targetlow').val().replace(',','.')); options.targetHigh = parseFloat($('#rp_targethigh').val().replace(',','.')); @@ -261,7 +263,8 @@ matchesneeded++; var from = moment($('#rp_from').val()); var to = moment($('#rp_to').val()); - timerange = '&find[created_at][$gte]='+new Date(from).toISOString()+'&find[created_at][$lt]='+new Date(to).toISOString(); + timerange = '&find[created_at][$gte]='+moment.tz(from,zone).toDate().toISOString()+'&find[created_at][$lt]='+moment.tz(to,zone).toDate().toISOString(); +console.log(timerange,zone); while (from <= to) { if (daystoshow[from.format('YYYY-MM-DD')]) { daystoshow[from.format('YYYY-MM-DD')]++; From 7c0e6e7dd8b81d39aacfe22794d4dcad12b7a52f Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Fri, 11 Sep 2015 09:59:15 +0200 Subject: [PATCH 895/937] modify mock urls --- static/report/js/report.js | 2 -- tests/reports.test.js | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/static/report/js/report.js b/static/report/js/report.js index ec5df1353d6..7fc1fcfb554 100644 --- a/static/report/js/report.js +++ b/static/report/js/report.js @@ -240,7 +240,6 @@ // default time range if no time range specified in GUI var zone = client.sbx.data.profile.getTimezone(); var timerange = '&find[created_at][$gte]='+moment.tz('1970-01-01',zone).toDate().toISOString(); -console.log(timerange,zone); options.targetLow = parseFloat($('#rp_targetlow').val().replace(',','.')); options.targetHigh = parseFloat($('#rp_targethigh').val().replace(',','.')); @@ -264,7 +263,6 @@ console.log(timerange,zone); var from = moment($('#rp_from').val()); var to = moment($('#rp_to').val()); timerange = '&find[created_at][$gte]='+moment.tz(from,zone).toDate().toISOString()+'&find[created_at][$lt]='+moment.tz(to,zone).toDate().toISOString(); -console.log(timerange,zone); while (from <= to) { if (daystoshow[from.format('YYYY-MM-DD')]) { daystoshow[from.format('YYYY-MM-DD')]++; diff --git a/tests/reports.test.js b/tests/reports.test.js index 899c675094e..8cf201df6fe 100644 --- a/tests/reports.test.js +++ b/tests/reports.test.js @@ -28,7 +28,7 @@ var someData = { '/api/v1/treatments.json?find[created_at][$gte]=2015-08-14T00:00:00.000Z&find[created_at][$lt]=2015-08-15T00:00:00.000Z': [{'enteredBy':'Dad','eventType':'Site Change','glucose':268,'glucoseType':'Finger','insulin':1.75,'units':'mg/dl','created_at':'2015-08-14T23:25:50.718Z','_id':'55ce78fe925aa80e7071e5d6'},{'enteredBy':'Mom ','eventType':'Meal Bolus','glucose':89,'glucoseType':'Finger','carbs':54,'insulin':3.15,'units':'mg/dl','created_at':'2015-08-14T21:00:00.000Z','_id':'55ce59bb925aa80e7071e5ba'}], '/api/v1/entries.json?find[date][$gte]=1439596800000&find[date][$lt]=1439683200000&count=10000': [{'_id':'55cfd25f38a8d88ad1b49931','unfiltered':283136,'filtered':304768,'direction':'SingleDown','device':'dexcom','rssi':185,'sgv':306,'dateString':'Sat Aug 15 16:58:16 PDT 2015','type':'sgv','date':1439683096000,'noise':1},{'_id':'55cfd13338a8d88ad1b4992e','unfiltered':302528,'filtered':312576,'direction':'FortyFiveDown','device':'dexcom','rssi':179,'sgv':329,'dateString':'Sat Aug 15 16:53:16 PDT 2015','type':'sgv','date':1439682796000,'noise':1}], '/api/v1/food/regular.json': [{'_id':'552ece84a6947ea011db35bb','type':'food','category':'Zakladni','subcategory':'Sladkosti','name':'Bebe male','portion':18,'carbs':12,'gi':1,'unit':'pcs','created_at':'2015-04-15T20:48:04.966Z'}], - '/api/v1/treatments.json?find[eventType]=/BG Check/i&find[created_at][$gte]=2015-08-07T22:00:00.000Z&find[created_at][$lt]=2015-09-06T22:00:00.000Z': [ + '/api/v1/treatments.json?find[eventType]=/BG Check/i&find[created_at][$gte]=2015-08-07T00:00:00.000Z&find[created_at][$lt]=2015-09-06T00:00:00.000Z': [ {"created_at":"2015-08-07T23:25:50.718Z"}, {'created_at':'2015-08-08T23:25:50.718Z'}, {'created_at':'2015-08-09T23:25:50.718Z'}, @@ -61,7 +61,7 @@ var someData = { {'created_at':'2015-09-05T23:25:50.718Z'}, {'created_at':'2015-09-06T23:25:50.718Z'} ], - '/api/v1/treatments.json?find[notes]=/something/i&find[created_at][$gte]=2015-08-07T22:00:00.000Z&find[created_at][$lt]=2015-09-06T22:00:00.000Z': [ + '/api/v1/treatments.json?find[notes]=/something/i&find[created_at][$gte]=2015-08-08T00:00:00.000Z&find[created_at][$lt]=2015-09-07T00:00:00.000Z': [ {'created_at':'2015-08-07T23:25:50.718Z'}, {'created_at':'2015-08-08T23:25:50.718Z'}, {'created_at':'2015-08-09T23:25:50.718Z'}, From afbbcc4a4d6ac4fee9da71ae8a6078c778eea77e Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Fri, 11 Sep 2015 10:03:09 +0200 Subject: [PATCH 896/937] add timezone to query filter 2nd --- 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 8cf201df6fe..ddb7626a19d 100644 --- a/tests/reports.test.js +++ b/tests/reports.test.js @@ -28,7 +28,7 @@ var someData = { '/api/v1/treatments.json?find[created_at][$gte]=2015-08-14T00:00:00.000Z&find[created_at][$lt]=2015-08-15T00:00:00.000Z': [{'enteredBy':'Dad','eventType':'Site Change','glucose':268,'glucoseType':'Finger','insulin':1.75,'units':'mg/dl','created_at':'2015-08-14T23:25:50.718Z','_id':'55ce78fe925aa80e7071e5d6'},{'enteredBy':'Mom ','eventType':'Meal Bolus','glucose':89,'glucoseType':'Finger','carbs':54,'insulin':3.15,'units':'mg/dl','created_at':'2015-08-14T21:00:00.000Z','_id':'55ce59bb925aa80e7071e5ba'}], '/api/v1/entries.json?find[date][$gte]=1439596800000&find[date][$lt]=1439683200000&count=10000': [{'_id':'55cfd25f38a8d88ad1b49931','unfiltered':283136,'filtered':304768,'direction':'SingleDown','device':'dexcom','rssi':185,'sgv':306,'dateString':'Sat Aug 15 16:58:16 PDT 2015','type':'sgv','date':1439683096000,'noise':1},{'_id':'55cfd13338a8d88ad1b4992e','unfiltered':302528,'filtered':312576,'direction':'FortyFiveDown','device':'dexcom','rssi':179,'sgv':329,'dateString':'Sat Aug 15 16:53:16 PDT 2015','type':'sgv','date':1439682796000,'noise':1}], '/api/v1/food/regular.json': [{'_id':'552ece84a6947ea011db35bb','type':'food','category':'Zakladni','subcategory':'Sladkosti','name':'Bebe male','portion':18,'carbs':12,'gi':1,'unit':'pcs','created_at':'2015-04-15T20:48:04.966Z'}], - '/api/v1/treatments.json?find[eventType]=/BG Check/i&find[created_at][$gte]=2015-08-07T00:00:00.000Z&find[created_at][$lt]=2015-09-06T00:00:00.000Z': [ + '/api/v1/treatments.json?find[eventType]=/BG Check/i&find[created_at][$gte]=2015-08-08T00:00:00.000Z&find[created_at][$lt]=2015-09-07T00:00:00.000Z': [ {"created_at":"2015-08-07T23:25:50.718Z"}, {'created_at':'2015-08-08T23:25:50.718Z'}, {'created_at':'2015-08-09T23:25:50.718Z'}, From 04180c0d1f4b8c214803de1cbb566113f3565aed Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Fri, 11 Sep 2015 10:09:55 +0200 Subject: [PATCH 897/937] wrong change --- static/report/js/report.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/report/js/report.js b/static/report/js/report.js index 7fc1fcfb554..50bafe77d18 100644 --- a/static/report/js/report.js +++ b/static/report/js/report.js @@ -95,7 +95,7 @@ } $('#rp_food').empty(); for (var i=0; i Date: Fri, 11 Sep 2015 10:14:43 +0200 Subject: [PATCH 898/937] one more day needed in utc --- tests/reports.test.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/reports.test.js b/tests/reports.test.js index ddb7626a19d..fc87a4b1fbd 100644 --- a/tests/reports.test.js +++ b/tests/reports.test.js @@ -59,7 +59,8 @@ var someData = { {'created_at':'2015-09-03T23:25:50.718Z'}, {'created_at':'2015-09-04T23:25:50.718Z'}, {'created_at':'2015-09-05T23:25:50.718Z'}, - {'created_at':'2015-09-06T23:25:50.718Z'} + {'created_at':'2015-09-06T23:25:50.718Z'}, + {'created_at':'2015-09-07T23:25:50.718Z'} ], '/api/v1/treatments.json?find[notes]=/something/i&find[created_at][$gte]=2015-08-08T00:00:00.000Z&find[created_at][$lt]=2015-09-07T00:00:00.000Z': [ {'created_at':'2015-08-07T23:25:50.718Z'}, @@ -92,7 +93,8 @@ var someData = { {'created_at':'2015-09-03T23:25:50.718Z'}, {'created_at':'2015-09-04T23:25:50.718Z'}, {'created_at':'2015-09-05T23:25:50.718Z'}, - {'created_at':'2015-09-06T23:25:50.718Z'} + {'created_at':'2015-09-06T23:25:50.718Z'}, + {'created_at':'2015-09-07T23:25:50.718Z'} ] }; From 14cfe3d3faf48e3749ea1c0df316c19c02aa889f Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Fri, 11 Sep 2015 10:19:37 +0200 Subject: [PATCH 899/937] add log --- static/report/js/report.js | 1 + tests/reports.test.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/static/report/js/report.js b/static/report/js/report.js index 50bafe77d18..360592dbfb1 100644 --- a/static/report/js/report.js +++ b/static/report/js/report.js @@ -455,6 +455,7 @@ datastorage.allstatsrecords = []; datastorage.alldays = 0; Object.keys(daystoshow).forEach(function (day) { +console.log(day); datastorage.allstatsrecords = datastorage.allstatsrecords.concat(datastorage[day].statsrecords); datastorage.alldays++; }); diff --git a/tests/reports.test.js b/tests/reports.test.js index fc87a4b1fbd..4a2dc3cdd9e 100644 --- a/tests/reports.test.js +++ b/tests/reports.test.js @@ -29,7 +29,7 @@ var someData = { '/api/v1/entries.json?find[date][$gte]=1439596800000&find[date][$lt]=1439683200000&count=10000': [{'_id':'55cfd25f38a8d88ad1b49931','unfiltered':283136,'filtered':304768,'direction':'SingleDown','device':'dexcom','rssi':185,'sgv':306,'dateString':'Sat Aug 15 16:58:16 PDT 2015','type':'sgv','date':1439683096000,'noise':1},{'_id':'55cfd13338a8d88ad1b4992e','unfiltered':302528,'filtered':312576,'direction':'FortyFiveDown','device':'dexcom','rssi':179,'sgv':329,'dateString':'Sat Aug 15 16:53:16 PDT 2015','type':'sgv','date':1439682796000,'noise':1}], '/api/v1/food/regular.json': [{'_id':'552ece84a6947ea011db35bb','type':'food','category':'Zakladni','subcategory':'Sladkosti','name':'Bebe male','portion':18,'carbs':12,'gi':1,'unit':'pcs','created_at':'2015-04-15T20:48:04.966Z'}], '/api/v1/treatments.json?find[eventType]=/BG Check/i&find[created_at][$gte]=2015-08-08T00:00:00.000Z&find[created_at][$lt]=2015-09-07T00:00:00.000Z': [ - {"created_at":"2015-08-07T23:25:50.718Z"}, + {'created_at":"2015-08-07T23:25:50.718Z'}, {'created_at':'2015-08-08T23:25:50.718Z'}, {'created_at':'2015-08-09T23:25:50.718Z'}, {'created_at':'2015-08-10T23:25:50.718Z'}, From d77a240e90f38e46b7c760e7d8be03b557473a1b Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Fri, 11 Sep 2015 10:22:33 +0200 Subject: [PATCH 900/937] double quotes --- 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 4a2dc3cdd9e..605815c9e22 100644 --- a/tests/reports.test.js +++ b/tests/reports.test.js @@ -29,7 +29,7 @@ var someData = { '/api/v1/entries.json?find[date][$gte]=1439596800000&find[date][$lt]=1439683200000&count=10000': [{'_id':'55cfd25f38a8d88ad1b49931','unfiltered':283136,'filtered':304768,'direction':'SingleDown','device':'dexcom','rssi':185,'sgv':306,'dateString':'Sat Aug 15 16:58:16 PDT 2015','type':'sgv','date':1439683096000,'noise':1},{'_id':'55cfd13338a8d88ad1b4992e','unfiltered':302528,'filtered':312576,'direction':'FortyFiveDown','device':'dexcom','rssi':179,'sgv':329,'dateString':'Sat Aug 15 16:53:16 PDT 2015','type':'sgv','date':1439682796000,'noise':1}], '/api/v1/food/regular.json': [{'_id':'552ece84a6947ea011db35bb','type':'food','category':'Zakladni','subcategory':'Sladkosti','name':'Bebe male','portion':18,'carbs':12,'gi':1,'unit':'pcs','created_at':'2015-04-15T20:48:04.966Z'}], '/api/v1/treatments.json?find[eventType]=/BG Check/i&find[created_at][$gte]=2015-08-08T00:00:00.000Z&find[created_at][$lt]=2015-09-07T00:00:00.000Z': [ - {'created_at":"2015-08-07T23:25:50.718Z'}, + {'created_at':'2015-08-07T23:25:50.718Z'}, {'created_at':'2015-08-08T23:25:50.718Z'}, {'created_at':'2015-08-09T23:25:50.718Z'}, {'created_at':'2015-08-10T23:25:50.718Z'}, From 950117cfe8e5cd859dcb265196c78b2e1dd37b83 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Fri, 11 Sep 2015 10:28:30 +0200 Subject: [PATCH 901/937] try to remove failing data --- tests/reports.test.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/reports.test.js b/tests/reports.test.js index 605815c9e22..93ed09dfad0 100644 --- a/tests/reports.test.js +++ b/tests/reports.test.js @@ -29,7 +29,6 @@ var someData = { '/api/v1/entries.json?find[date][$gte]=1439596800000&find[date][$lt]=1439683200000&count=10000': [{'_id':'55cfd25f38a8d88ad1b49931','unfiltered':283136,'filtered':304768,'direction':'SingleDown','device':'dexcom','rssi':185,'sgv':306,'dateString':'Sat Aug 15 16:58:16 PDT 2015','type':'sgv','date':1439683096000,'noise':1},{'_id':'55cfd13338a8d88ad1b4992e','unfiltered':302528,'filtered':312576,'direction':'FortyFiveDown','device':'dexcom','rssi':179,'sgv':329,'dateString':'Sat Aug 15 16:53:16 PDT 2015','type':'sgv','date':1439682796000,'noise':1}], '/api/v1/food/regular.json': [{'_id':'552ece84a6947ea011db35bb','type':'food','category':'Zakladni','subcategory':'Sladkosti','name':'Bebe male','portion':18,'carbs':12,'gi':1,'unit':'pcs','created_at':'2015-04-15T20:48:04.966Z'}], '/api/v1/treatments.json?find[eventType]=/BG Check/i&find[created_at][$gte]=2015-08-08T00:00:00.000Z&find[created_at][$lt]=2015-09-07T00:00:00.000Z': [ - {'created_at':'2015-08-07T23:25:50.718Z'}, {'created_at':'2015-08-08T23:25:50.718Z'}, {'created_at':'2015-08-09T23:25:50.718Z'}, {'created_at':'2015-08-10T23:25:50.718Z'}, @@ -63,7 +62,6 @@ var someData = { {'created_at':'2015-09-07T23:25:50.718Z'} ], '/api/v1/treatments.json?find[notes]=/something/i&find[created_at][$gte]=2015-08-08T00:00:00.000Z&find[created_at][$lt]=2015-09-07T00:00:00.000Z': [ - {'created_at':'2015-08-07T23:25:50.718Z'}, {'created_at':'2015-08-08T23:25:50.718Z'}, {'created_at':'2015-08-09T23:25:50.718Z'}, {'created_at':'2015-08-10T23:25:50.718Z'}, From d530af7ec7666afff36508163ed238a245fc1aa1 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Fri, 11 Sep 2015 10:40:56 +0200 Subject: [PATCH 902/937] fix daystoshow --- static/report/js/report.js | 1 + 1 file changed, 1 insertion(+) diff --git a/static/report/js/report.js b/static/report/js/report.js index 360592dbfb1..fc4cb77a863 100644 --- a/static/report/js/report.js +++ b/static/report/js/report.js @@ -411,6 +411,7 @@ loadData(d, options, dataLoadedCallback); } else { $('#info').append($('
    '+d+' '+translate('not displayed')+'.
    ')); + delete daystoshow[d]; } } else { delete daystoshow[d]; From b8d9e3278aeeaad8734194bc4128cf08e57a1bc0 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Fri, 11 Sep 2015 11:00:19 +0200 Subject: [PATCH 903/937] remove log & fix some codacy --- static/report/js/report.js | 27 +++++++++++++++------------ tests/reports.test.js | 2 +- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/static/report/js/report.js b/static/report/js/report.js index fc4cb77a863..0e8fc8aae27 100644 --- a/static/report/js/report.js +++ b/static/report/js/report.js @@ -387,14 +387,16 @@ function daysfilter() { matchesneeded++; for (var d in daystoshow) { - var day = new Date(d).getDay(); - if (day===0 && $('#rp_su').is(':checked')) { daystoshow[d]++; } - if (day===1 && $('#rp_mo').is(':checked')) { daystoshow[d]++; } - if (day===2 && $('#rp_tu').is(':checked')) { daystoshow[d]++; } - if (day===3 && $('#rp_we').is(':checked')) { daystoshow[d]++; } - if (day===4 && $('#rp_th').is(':checked')) { daystoshow[d]++; } - if (day===5 && $('#rp_fr').is(':checked')) { daystoshow[d]++; } - if (day===6 && $('#rp_sa').is(':checked')) { daystoshow[d]++; } + if (daystoshow.hasOwnProperty(d)) { + var day = new Date(d).getDay(); + if (day===0 && $('#rp_su').is(':checked')) { daystoshow[d]++; } + if (day===1 && $('#rp_mo').is(':checked')) { daystoshow[d]++; } + if (day===2 && $('#rp_tu').is(':checked')) { daystoshow[d]++; } + if (day===3 && $('#rp_we').is(':checked')) { daystoshow[d]++; } + if (day===4 && $('#rp_th').is(':checked')) { daystoshow[d]++; } + if (day===5 && $('#rp_fr').is(':checked')) { daystoshow[d]++; } + if (day===6 && $('#rp_sa').is(':checked')) { daystoshow[d]++; } + } } countDays(); display(); @@ -428,9 +430,11 @@ function countDays() { for (var d in daystoshow) { - if (daystoshow[d]===matchesneeded) { - if (dayscount < maxdays) { - dayscount++; + if (daystoshow.hasOwnProperty(d)) { + if (daystoshow[d]===matchesneeded) { + if (dayscount < maxdays) { + dayscount++; + } } } } @@ -456,7 +460,6 @@ datastorage.allstatsrecords = []; datastorage.alldays = 0; Object.keys(daystoshow).forEach(function (day) { -console.log(day); datastorage.allstatsrecords = datastorage.allstatsrecords.concat(datastorage[day].statsrecords); datastorage.alldays++; }); diff --git a/tests/reports.test.js b/tests/reports.test.js index 93ed09dfad0..c5a20f0b315 100644 --- a/tests/reports.test.js +++ b/tests/reports.test.js @@ -266,7 +266,7 @@ describe('reports', function ( ) { //var logfile = filesys.createWriteStream('out.txt', { flags: 'a'} ) //logfile.write($('body').html()); - console.log(result); + //console.log(result); result.indexOf('Milk now').should.be.greaterThan(-1); // daytoday result.indexOf('50 g (1.67U)').should.be.greaterThan(-1); // daytoday From 9fd1d0b9b5a1feda8bd362d0a773a2551e518e4b Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Fri, 11 Sep 2015 11:05:02 +0200 Subject: [PATCH 904/937] try to click delete record --- tests/reports.test.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/reports.test.js b/tests/reports.test.js index c5a20f0b315..df6c4105416 100644 --- a/tests/reports.test.js +++ b/tests/reports.test.js @@ -235,6 +235,9 @@ describe('reports', function ( ) { next(true); }; + window.confirm = function mockConfirm () { + return true; + } client.init(serverSettings, plugins); client.dataUpdate(nowData); @@ -260,6 +263,8 @@ describe('reports', function ( ) { $('#rp_linear').prop('checked',true); $('#rp_show').click(); $('#dailystats').click(); + + $('img.deleteTreatment :first').click(); var result = $('body').html(); //var filesys = require('fs'); From 7b25a9dcdb70968bd32e92d0e41d4ae9a0d2d48b Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Fri, 11 Sep 2015 11:27:27 +0200 Subject: [PATCH 905/937] .. and click save --- tests/reports.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/reports.test.js b/tests/reports.test.js index df6c4105416..69f8942103e 100644 --- a/tests/reports.test.js +++ b/tests/reports.test.js @@ -265,6 +265,8 @@ describe('reports', function ( ) { $('#dailystats').click(); $('img.deleteTreatment :first').click(); + $('img.editTreatment :first').click(); + $('.ui-button:contains("Save")').click() var result = $('body').html(); //var filesys = require('fs'); From 8b7405265d204fd2c313bc4109f1a1de5fea6c54 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Fri, 11 Sep 2015 11:41:47 +0200 Subject: [PATCH 906/937] unwanted space --- tests/reports.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/reports.test.js b/tests/reports.test.js index 69f8942103e..63caf26d2c7 100644 --- a/tests/reports.test.js +++ b/tests/reports.test.js @@ -264,8 +264,8 @@ describe('reports', function ( ) { $('#rp_show').click(); $('#dailystats').click(); - $('img.deleteTreatment :first').click(); - $('img.editTreatment :first').click(); + $('img.deleteTreatment:first').click(); + $('img.editTreatment:first').click(); $('.ui-button:contains("Save")').click() var result = $('body').html(); From 3fab1a55bb19e77425a3b02f1fe74b37ea11f545 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Fri, 11 Sep 2015 11:51:40 +0200 Subject: [PATCH 907/937] modify mock func --- tests/reports.test.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/tests/reports.test.js b/tests/reports.test.js index 63caf26d2c7..190b82591f8 100644 --- a/tests/reports.test.js +++ b/tests/reports.test.js @@ -172,12 +172,14 @@ describe('reports', function ( ) { //logfile.write(url+'\n'); return { done: function mockDone (fn) { - if (opts && opts.success && opts.success.call && someData[url]) { - console.log('+++++Data for ' + url + ' sent'); - opts.success(someData[url]); - } else { - console.log('-----Data for ' + url + ' missing'); - opts.success([]); + if (opts && opts.success && opts.success.call) { + if (someData[url]) { + console.log('+++++Data for ' + url + ' sent'); + opts.success(someData[url]); + } else { + console.log('-----Data for ' + url + ' missing'); + opts.success([]); + } } fn(); return self.$.ajax(); @@ -266,7 +268,7 @@ describe('reports', function ( ) { $('img.deleteTreatment:first').click(); $('img.editTreatment:first').click(); - $('.ui-button:contains("Save")').click() + $('.ui-button:contains("Save")').click(); var result = $('body').html(); //var filesys = require('fs'); From 8662568641563d3e1ac9d645fcd302a1d1520968 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Fri, 11 Sep 2015 11:57:51 +0200 Subject: [PATCH 908/937] add mock window.alert --- lib/report_plugins/treatments.js | 8 ++++---- tests/reports.test.js | 6 +++++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/report_plugins/treatments.js b/lib/report_plugins/treatments.js index 9a4220432c2..be887ac9a84 100644 --- a/lib/report_plugins/treatments.js +++ b/lib/report_plugins/treatments.js @@ -98,7 +98,7 @@ treatments.report = function report_treatments(datastorage, daystoshow, options) function deleteTreatment(event) { if (!client.hashauth.isAuthenticated()) { - alert(translate('Your device is not authenticated yet')); + window.alert(translate('Your device is not authenticated yet')); return false; } @@ -116,7 +116,7 @@ treatments.report = function report_treatments(datastorage, daystoshow, options) console.info('treatment deleted', response); }).fail(function treatmentDeleteFail (response) { console.info('treatment delete failed', response); - alert(translate('Deleting record failed') + '. ' + translate('Status') + ': ' + response.status); + window.alert(translate('Deleting record failed') + '. ' + translate('Status') + ': ' + response.status); }); delete datastorage[day]; report_plugins.show(); @@ -191,7 +191,7 @@ treatments.report = function report_treatments(datastorage, daystoshow, options) function saveTreatmentRecord(data) { if (!client.hashauth.isAuthenticated()) { - alert(translate('Your device is not authenticated yet')); + window.alert(translate('Your device is not authenticated yet')); return false; } @@ -206,7 +206,7 @@ treatments.report = function report_treatments(datastorage, daystoshow, options) console.info('treatment saved', response); }).fail(function treatmentSaveFail (response) { console.info('treatment save failed', response); - alert(translate('Saving record failed') + '. ' + translate('Status') + ': ' + response.status); + window.alert(translate('Saving record failed') + '. ' + translate('Status') + ': ' + response.status); }); return true; diff --git a/tests/reports.test.js b/tests/reports.test.js index 190b82591f8..b2bc5e8a9f1 100644 --- a/tests/reports.test.js +++ b/tests/reports.test.js @@ -239,7 +239,11 @@ describe('reports', function ( ) { window.confirm = function mockConfirm () { return true; - } + }; + + window.alert = function mockAlert () { + return true; + }; client.init(serverSettings, plugins); client.dataUpdate(nowData); From 04056757b12542e64261b8b30180839486f2d5e6 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Fri, 11 Sep 2015 12:06:16 +0200 Subject: [PATCH 909/937] mock fail return status --- tests/reports.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/reports.test.js b/tests/reports.test.js index b2bc5e8a9f1..e001efe6bd7 100644 --- a/tests/reports.test.js +++ b/tests/reports.test.js @@ -174,10 +174,10 @@ describe('reports', function ( ) { done: function mockDone (fn) { if (opts && opts.success && opts.success.call) { if (someData[url]) { - console.log('+++++Data for ' + url + ' sent'); + //console.log('+++++Data for ' + url + ' sent'); opts.success(someData[url]); } else { - console.log('-----Data for ' + url + ' missing'); + //console.log('-----Data for ' + url + ' missing'); opts.success([]); } } @@ -185,7 +185,7 @@ describe('reports', function ( ) { return self.$.ajax(); }, fail: function mockFail (fn) { - fn(); + fn({status: 400}); return self.$.ajax(); } }; From bd40940a1fe165797511a03ae8bd46059801c1fa Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Fri, 11 Sep 2015 12:10:39 +0200 Subject: [PATCH 910/937] add moment --- lib/report_plugins/treatments.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/report_plugins/treatments.js b/lib/report_plugins/treatments.js index be887ac9a84..d95fcf43a67 100644 --- a/lib/report_plugins/treatments.js +++ b/lib/report_plugins/treatments.js @@ -1,6 +1,7 @@ 'use strict'; var _ = window._; +var moment = window.moment; var treatments = { name: 'treatments' From a53c3515deeca24f18627816f4b1c54cfbbdce60 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Fri, 11 Sep 2015 18:44:59 +0200 Subject: [PATCH 911/937] local test fix --- static/report/js/report.js | 13 ++-- tests/reports.test.js | 126 ++++++++++++++++++------------------- 2 files changed, 71 insertions(+), 68 deletions(-) diff --git a/static/report/js/report.js b/static/report/js/report.js index 0e8fc8aae27..1f6c78c51cc 100644 --- a/static/report/js/report.js +++ b/static/report/js/report.js @@ -239,8 +239,8 @@ // default time range if no time range specified in GUI var zone = client.sbx.data.profile.getTimezone(); - var timerange = '&find[created_at][$gte]='+moment.tz('1970-01-01',zone).toDate().toISOString(); - + var timerange = '&find[created_at][$gte]='+moment.tz('2000-01-01',zone).toISOString(); + //console.log(timerange,zone); options.targetLow = parseFloat($('#rp_targetlow').val().replace(',','.')); options.targetHigh = parseFloat($('#rp_targethigh').val().replace(',','.')); options.raw = $('#rp_optionsraw').is(':checked'); @@ -260,9 +260,12 @@ function datefilter() { if ($('#rp_enabledate').is(':checked')) { matchesneeded++; - var from = moment($('#rp_from').val()); - var to = moment($('#rp_to').val()); - timerange = '&find[created_at][$gte]='+moment.tz(from,zone).toDate().toISOString()+'&find[created_at][$lt]='+moment.tz(to,zone).toDate().toISOString(); + var fromdate = new Date($('#rp_from').val()); + var todate = new Date($('#rp_to').val()); + var from = moment.tz([fromdate.getFullYear(), fromdate.getMonth(), fromdate.getDate()],zone); + var to = moment.tz([todate.getFullYear(), todate.getMonth(), todate.getDate()],zone); + timerange = '&find[created_at][$gte]='+from.toISOString()+'&find[created_at][$lt]='+to.toISOString(); + //console.log($('#rp_from').val(),$('#rp_to').val(),zone,timerange); while (from <= to) { if (daystoshow[from.format('YYYY-MM-DD')]) { daystoshow[from.format('YYYY-MM-DD')]++; diff --git a/tests/reports.test.js b/tests/reports.test.js index e001efe6bd7..41ef419c47e 100644 --- a/tests/reports.test.js +++ b/tests/reports.test.js @@ -25,74 +25,74 @@ var someData = { '/api/v1/entries.json?find[date][$gte]=1439424000000&find[date][$lt]=1439510400000&count=10000': [{'_id':'55cd2f6738a8d88ad1b48ca1','unfiltered':209792,'filtered':229344,'direction':'SingleDown','device':'dexcom','rssi':436,'sgv':205,'dateString':'Thu Aug 13 16:58:24 PDT 2015','type':'sgv','date':1439510304000,'noise':1},{'_id':'55cd2e3b38a8d88ad1b48c95','unfiltered':220928,'filtered':237472,'direction':'FortyFiveDown','device':'dexcom','rssi':418,'sgv':219,'dateString':'Thu Aug 13 16:53:24 PDT 2015','type':'sgv','date':1439510004000,'noise':1}], '/api/v1/treatments.json?find[created_at][$gte]=2015-08-13T00:00:00.000Z&find[created_at][$lt]=2015-08-14T00:00:00.000Z': [{'enteredBy':'Mom ','eventType':'Correction Bolus','glucose':250,'glucoseType':'Sensor','insulin':0.75,'units':'mg/dl','created_at':'2015-08-13T23:45:56.927Z','_id':'55cd2c3497fa97ac5d8bc53b'},{'enteredBy':'Mom ','eventType':'Correction Bolus','glucose':198,'glucoseType':'Sensor','insulin':1.1,'units':'mg/dl','created_at':'2015-08-13T23:11:00.293Z','_id':'55cd240497fa97ac5d8bc535'}], '/api/v1/entries.json?find[date][$gte]=1439510400000&find[date][$lt]=1439596800000&count=10000': [{'_id':'55ce80e338a8d88ad1b49397','unfiltered':179936,'filtered':202080,'direction':'SingleDown','device':'dexcom','rssi':179,'sgv':182,'dateString':'Fri Aug 14 16:58:20 PDT 2015','type':'sgv','date':1439596700000,'noise':1},{'_id':'55ce7fb738a8d88ad1b4938d','unfiltered':192288,'filtered':213792,'direction':'SingleDown','device':'dexcom','rssi':180,'sgv':197,'dateString':'Fri Aug 14 16:53:20 PDT 2015','type':'sgv','date':1439596400000,'noise':1}], - '/api/v1/treatments.json?find[created_at][$gte]=2015-08-14T00:00:00.000Z&find[created_at][$lt]=2015-08-15T00:00:00.000Z': [{'enteredBy':'Dad','eventType':'Site Change','glucose':268,'glucoseType':'Finger','insulin':1.75,'units':'mg/dl','created_at':'2015-08-14T23:25:50.718Z','_id':'55ce78fe925aa80e7071e5d6'},{'enteredBy':'Mom ','eventType':'Meal Bolus','glucose':89,'glucoseType':'Finger','carbs':54,'insulin':3.15,'units':'mg/dl','created_at':'2015-08-14T21:00:00.000Z','_id':'55ce59bb925aa80e7071e5ba'}], + '/api/v1/treatments.json?find[created_at][$gte]=2015-08-14T00:00:00.000Z&find[created_at][$lt]=2015-08-15T00:00:00.000Z': [{'enteredBy':'Dad','eventType':'Site Change','glucose':268,'glucoseType':'Finger','insulin':1.75,'units':'mg/dl','created_at':'2015-08-14T00:00:00.000Z','_id':'55ce78fe925aa80e7071e5d6'},{'enteredBy':'Mom ','eventType':'Meal Bolus','glucose':89,'glucoseType':'Finger','carbs':54,'insulin':3.15,'units':'mg/dl','created_at':'2015-08-14T21:00:00.000Z','_id':'55ce59bb925aa80e7071e5ba'}], '/api/v1/entries.json?find[date][$gte]=1439596800000&find[date][$lt]=1439683200000&count=10000': [{'_id':'55cfd25f38a8d88ad1b49931','unfiltered':283136,'filtered':304768,'direction':'SingleDown','device':'dexcom','rssi':185,'sgv':306,'dateString':'Sat Aug 15 16:58:16 PDT 2015','type':'sgv','date':1439683096000,'noise':1},{'_id':'55cfd13338a8d88ad1b4992e','unfiltered':302528,'filtered':312576,'direction':'FortyFiveDown','device':'dexcom','rssi':179,'sgv':329,'dateString':'Sat Aug 15 16:53:16 PDT 2015','type':'sgv','date':1439682796000,'noise':1}], '/api/v1/food/regular.json': [{'_id':'552ece84a6947ea011db35bb','type':'food','category':'Zakladni','subcategory':'Sladkosti','name':'Bebe male','portion':18,'carbs':12,'gi':1,'unit':'pcs','created_at':'2015-04-15T20:48:04.966Z'}], '/api/v1/treatments.json?find[eventType]=/BG Check/i&find[created_at][$gte]=2015-08-08T00:00:00.000Z&find[created_at][$lt]=2015-09-07T00:00:00.000Z': [ - {'created_at':'2015-08-08T23:25:50.718Z'}, - {'created_at':'2015-08-09T23:25:50.718Z'}, - {'created_at':'2015-08-10T23:25:50.718Z'}, - {'created_at':'2015-08-11T23:25:50.718Z'}, - {'created_at':'2015-08-12T23:25:50.718Z'}, - {'created_at':'2015-08-13T23:25:50.718Z'}, - {'created_at':'2015-08-14T23:25:50.718Z'}, - {'created_at':'2015-08-15T23:25:50.718Z'}, - {'created_at':'2015-08-16T23:25:50.718Z'}, - {'created_at':'2015-08-17T23:25:50.718Z'}, - {'created_at':'2015-08-18T23:25:50.718Z'}, - {'created_at':'2015-08-19T23:25:50.718Z'}, - {'created_at':'2015-08-20T23:25:50.718Z'}, - {'created_at':'2015-08-21T23:25:50.718Z'}, - {'created_at':'2015-08-22T23:25:50.718Z'}, - {'created_at':'2015-08-23T23:25:50.718Z'}, - {'created_at':'2015-08-24T23:25:50.718Z'}, - {'created_at':'2015-08-25T23:25:50.718Z'}, - {'created_at':'2015-08-26T23:25:50.718Z'}, - {'created_at':'2015-08-27T23:25:50.718Z'}, - {'created_at':'2015-08-28T23:25:50.718Z'}, - {'created_at':'2015-08-29T23:25:50.718Z'}, - {'created_at':'2015-08-30T23:25:50.718Z'}, - {'created_at':'2015-08-31T23:25:50.718Z'}, - {'created_at':'2015-09-01T23:25:50.718Z'}, - {'created_at':'2015-09-02T23:25:50.718Z'}, - {'created_at':'2015-09-03T23:25:50.718Z'}, - {'created_at':'2015-09-04T23:25:50.718Z'}, - {'created_at':'2015-09-05T23:25:50.718Z'}, - {'created_at':'2015-09-06T23:25:50.718Z'}, - {'created_at':'2015-09-07T23:25:50.718Z'} + {'created_at':'2015-08-08T00:00:00.000Z'}, + {'created_at':'2015-08-09T00:00:00.000Z'}, + {'created_at':'2015-08-10T00:00:00.000Z'}, + {'created_at':'2015-08-11T00:00:00.000Z'}, + {'created_at':'2015-08-12T00:00:00.000Z'}, + {'created_at':'2015-08-13T00:00:00.000Z'}, + {'created_at':'2015-08-14T00:00:00.000Z'}, + {'created_at':'2015-08-15T00:00:00.000Z'}, + {'created_at':'2015-08-16T00:00:00.000Z'}, + {'created_at':'2015-08-17T00:00:00.000Z'}, + {'created_at':'2015-08-18T00:00:00.000Z'}, + {'created_at':'2015-08-19T00:00:00.000Z'}, + {'created_at':'2015-08-20T00:00:00.000Z'}, + {'created_at':'2015-08-21T00:00:00.000Z'}, + {'created_at':'2015-08-22T00:00:00.000Z'}, + {'created_at':'2015-08-23T00:00:00.000Z'}, + {'created_at':'2015-08-24T00:00:00.000Z'}, + {'created_at':'2015-08-25T00:00:00.000Z'}, + {'created_at':'2015-08-26T00:00:00.000Z'}, + {'created_at':'2015-08-27T00:00:00.000Z'}, + {'created_at':'2015-08-28T00:00:00.000Z'}, + {'created_at':'2015-08-29T00:00:00.000Z'}, + {'created_at':'2015-08-30T00:00:00.000Z'}, + {'created_at':'2015-08-31T00:00:00.000Z'}, + {'created_at':'2015-09-01T00:00:00.000Z'}, + {'created_at':'2015-09-02T00:00:00.000Z'}, + {'created_at':'2015-09-03T00:00:00.000Z'}, + {'created_at':'2015-09-04T00:00:00.000Z'}, + {'created_at':'2015-09-05T00:00:00.000Z'}, + {'created_at':'2015-09-06T00:00:00.000Z'}, + {'created_at':'2015-09-07T00:00:00.000Z'} ], '/api/v1/treatments.json?find[notes]=/something/i&find[created_at][$gte]=2015-08-08T00:00:00.000Z&find[created_at][$lt]=2015-09-07T00:00:00.000Z': [ - {'created_at':'2015-08-08T23:25:50.718Z'}, - {'created_at':'2015-08-09T23:25:50.718Z'}, - {'created_at':'2015-08-10T23:25:50.718Z'}, - {'created_at':'2015-08-11T23:25:50.718Z'}, - {'created_at':'2015-08-12T23:25:50.718Z'}, - {'created_at':'2015-08-13T23:25:50.718Z'}, - {'created_at':'2015-08-14T23:25:50.718Z'}, - {'created_at':'2015-08-15T23:25:50.718Z'}, - {'created_at':'2015-08-16T23:25:50.718Z'}, - {'created_at':'2015-08-17T23:25:50.718Z'}, - {'created_at':'2015-08-18T23:25:50.718Z'}, - {'created_at':'2015-08-19T23:25:50.718Z'}, - {'created_at':'2015-08-20T23:25:50.718Z'}, - {'created_at':'2015-08-21T23:25:50.718Z'}, - {'created_at':'2015-08-22T23:25:50.718Z'}, - {'created_at':'2015-08-23T23:25:50.718Z'}, - {'created_at':'2015-08-24T23:25:50.718Z'}, - {'created_at':'2015-08-25T23:25:50.718Z'}, - {'created_at':'2015-08-26T23:25:50.718Z'}, - {'created_at':'2015-08-27T23:25:50.718Z'}, - {'created_at':'2015-08-28T23:25:50.718Z'}, - {'created_at':'2015-08-29T23:25:50.718Z'}, - {'created_at':'2015-08-30T23:25:50.718Z'}, - {'created_at':'2015-08-31T23:25:50.718Z'}, - {'created_at':'2015-09-01T23:25:50.718Z'}, - {'created_at':'2015-09-02T23:25:50.718Z'}, - {'created_at':'2015-09-03T23:25:50.718Z'}, - {'created_at':'2015-09-04T23:25:50.718Z'}, - {'created_at':'2015-09-05T23:25:50.718Z'}, - {'created_at':'2015-09-06T23:25:50.718Z'}, - {'created_at':'2015-09-07T23:25:50.718Z'} + {'created_at':'2015-08-08T00:00:00.000Z'}, + {'created_at':'2015-08-09T00:00:00.000Z'}, + {'created_at':'2015-08-10T00:00:00.000Z'}, + {'created_at':'2015-08-11T00:00:00.000Z'}, + {'created_at':'2015-08-12T00:00:00.000Z'}, + {'created_at':'2015-08-13T00:00:00.000Z'}, + {'created_at':'2015-08-14T00:00:00.000Z'}, + {'created_at':'2015-08-15T00:00:00.000Z'}, + {'created_at':'2015-08-16T00:00:00.000Z'}, + {'created_at':'2015-08-17T00:00:00.000Z'}, + {'created_at':'2015-08-18T00:00:00.000Z'}, + {'created_at':'2015-08-19T00:00:00.000Z'}, + {'created_at':'2015-08-20T00:00:00.000Z'}, + {'created_at':'2015-08-21T00:00:00.000Z'}, + {'created_at':'2015-08-22T00:00:00.000Z'}, + {'created_at':'2015-08-23T00:00:00.000Z'}, + {'created_at':'2015-08-24T00:00:00.000Z'}, + {'created_at':'2015-08-25T00:00:00.000Z'}, + {'created_at':'2015-08-26T00:00:00.000Z'}, + {'created_at':'2015-08-27T00:00:00.000Z'}, + {'created_at':'2015-08-28T00:00:00.000Z'}, + {'created_at':'2015-08-29T00:00:00.000Z'}, + {'created_at':'2015-08-30T00:00:00.000Z'}, + {'created_at':'2015-08-31T00:00:00.000Z'}, + {'created_at':'2015-09-01T00:00:00.000Z'}, + {'created_at':'2015-09-02T00:00:00.000Z'}, + {'created_at':'2015-09-03T00:00:00.000Z'}, + {'created_at':'2015-09-04T00:00:00.000Z'}, + {'created_at':'2015-09-05T00:00:00.000Z'}, + {'created_at':'2015-09-06T00:00:00.000Z'}, + {'created_at':'2015-09-07T00:00:00.000Z'} ] }; From 1a0e9c3330f022d6b8d1fea1509dcea30da12868 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Tue, 15 Sep 2015 19:44:38 +0200 Subject: [PATCH 912/937] timezones to records in filters --- static/report/js/report.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/static/report/js/report.js b/static/report/js/report.js index 1f6c78c51cc..eac70b7524e 100644 --- a/static/report/js/report.js +++ b/static/report/js/report.js @@ -290,7 +290,7 @@ $.ajax('/api/v1/treatments.json'+tquery, { success: function (xhr) { treatmentData = xhr.map(function (treatment) { - return moment(treatment.created_at).format('YYYY-MM-DD'); + return moment.tz(treatment.created_at,zone).format('YYYY-MM-DD'); }); // unique it treatmentData = $.grep(treatmentData, function(v, k){ @@ -326,7 +326,7 @@ $.ajax('/api/v1/treatments.json' + tquery + timerange, { success: function (xhr) { treatmentData = xhr.map(function (treatment) { - return moment(treatment.created_at).format('YYYY-MM-DD'); + return moment.tz(treatment.created_at,zone).format('YYYY-MM-DD'); }); // unique it treatmentData = $.grep(treatmentData, function(v, k){ @@ -362,7 +362,7 @@ $.ajax('/api/v1/treatments.json' + tquery + timerange, { success: function (xhr) { treatmentData = xhr.map(function (treatment) { - return moment(treatment.created_at).format('YYYY-MM-DD'); + return moment.tz(treatment.created_at,zone).format('YYYY-MM-DD'); }); // unique it treatmentData = $.grep(treatmentData, function(v, k){ From 13aafe280522376877f3618548f848fe1223d68e Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Tue, 15 Sep 2015 21:46:58 +0200 Subject: [PATCH 913/937] ordering in reports, tranlation fixes --- lib/language.js | 11 ++++++++++- lib/report_plugins/calibrations.js | 10 +++++----- lib/report_plugins/dailystats.js | 4 ++-- lib/report_plugins/daytoday.js | 15 ++++++++------- lib/report_plugins/glucosedistribution.js | 2 +- lib/report_plugins/hourlystats.js | 13 ++----------- lib/report_plugins/index.js | 2 ++ lib/report_plugins/percentile.js | 2 +- lib/report_plugins/success.js | 2 +- lib/report_plugins/treatments.js | 9 ++++++--- static/report/index.html | 11 +++++++++++ static/report/js/report.js | 13 ++++++++++++- 12 files changed, 61 insertions(+), 33 deletions(-) diff --git a/lib/language.js b/lib/language.js index 6f94313f2ee..92e89be3ad8 100644 --- a/lib/language.js +++ b/lib/language.js @@ -2989,7 +2989,7 @@ function init() { ,nb: 'Trening' } ,'Pump Site Change' : { - cs: 'Přepíchnutí kanyly' + cs: 'Výměna setu' ,de: 'Pumpen-Katheter Wechsel' ,es: 'Cambio de catéter' ,fr: 'Changement de site pompe' @@ -3911,6 +3911,15 @@ function init() { cs: 'Aktualizovat' ,pt: 'Atualizar' } + ,'Order' : { + cs: 'Pořadí' + } + ,'oldest on top' : { + cs: 'nejstarší nahoře' + } + ,'newest on top' : { + cs: 'nejnovější nahoře' + } }; diff --git a/lib/report_plugins/calibrations.js b/lib/report_plugins/calibrations.js index c771a6452f1..72d6f5e3239 100644 --- a/lib/report_plugins/calibrations.js +++ b/lib/report_plugins/calibrations.js @@ -23,13 +23,13 @@ calibrations.html = function html(client) { return ret; }; -calibrations.report = function report_calibrations(datastorage,daystoshow) { +calibrations.report = function report_calibrations(datastorage,sorteddaystoshow) { var Nightscout = window.Nightscout; var report_plugins = Nightscout.report_plugins; var padding = { top: 15, right: 15, bottom: 30, left: 70 }; var treatments = []; - Object.keys(daystoshow).forEach(function (day) { + sorteddaystoshow.forEach(function (day) { treatments = treatments.concat(datastorage[day].treatments.filter(function (t) { if (t.eventType === 'Sensor Start') { return true; @@ -42,17 +42,17 @@ calibrations.report = function report_calibrations(datastorage,daystoshow) { }); var cals = []; - Object.keys(daystoshow).forEach(function (day) { + sorteddaystoshow.forEach(function (day) { cals = cals.concat(datastorage[day].cal); }); var sgvs = []; - Object.keys(daystoshow).forEach(function (day) { + sorteddaystoshow.forEach(function (day) { sgvs = sgvs.concat(datastorage[day].sgv); }); var mbgs = []; - Object.keys(daystoshow).forEach(function (day) { + sorteddaystoshow.forEach(function (day) { mbgs = mbgs.concat(datastorage[day].mbg); }); mbgs.forEach(function (mbg) { calcmbg(mbg); }); diff --git a/lib/report_plugins/dailystats.js b/lib/report_plugins/dailystats.js index f1d81e0979a..59438c9083f 100644 --- a/lib/report_plugins/dailystats.js +++ b/lib/report_plugins/dailystats.js @@ -21,7 +21,7 @@ dailystats.html = function html(client) { return ret; }; -dailystats.report = function report_dailystats(datastorage,daystoshow,options) { +dailystats.report = function report_dailystats(datastorage,sorteddaystoshow,options) { var Nightscout = window.Nightscout; var client = Nightscout.client; var translate = client.translate; @@ -51,7 +51,7 @@ dailystats.report = function report_dailystats(datastorage,daystoshow,options) { $('
    ').appendTo(thead); thead.appendTo(table); - Object.keys(daystoshow).forEach(function (day) { + sorteddaystoshow.forEach(function (day) { var tr = $(''); var daysRecords = datastorage[day].statsrecords; diff --git a/lib/report_plugins/daytoday.js b/lib/report_plugins/daytoday.js index cd9d32c1c5f..18ba0f23531 100644 --- a/lib/report_plugins/daytoday.js +++ b/lib/report_plugins/daytoday.js @@ -46,24 +46,25 @@ daytoday.html = function html(client) { return ret; }; -daytoday.prepareHtml = function daytodayPrepareHtml(daystoshow) { +daytoday.prepareHtml = function daytodayPrepareHtml(sorteddaystoshow) { $('#daytodaycharts').html(''); - for (var d in daystoshow) { + sorteddaystoshow.forEach(function eachDay(d) { $('#daytodaycharts').append($('
    ')); - } + }); }; -daytoday.report = function report_daytoday(datastorage,daystoshow,options) { +daytoday.report = function report_daytoday(datastorage,sorteddaystoshow,options) { var Nightscout = window.Nightscout; var client = Nightscout.client; + var translate = client.translate; var profile = client.sbx.data.profile; var report_plugins = Nightscout.report_plugins; var scaledTreatmentBG = report_plugins.utils.scaledTreatmentBG; var padding = { top: 15, right: 22, bottom: 30, left: 35 }; - daytoday.prepareHtml(daystoshow) ; - _.each(daystoshow, function (n, day) { + daytoday.prepareHtml(sorteddaystoshow) ; + sorteddaystoshow.forEach( function eachDay(day) { drawChart(day,datastorage[day],options); }); @@ -419,7 +420,7 @@ daytoday.report = function report_daytoday(datastorage,daystoshow,options) { .attr('fill', 'purple') .attr('y', yScale2(scaledTreatmentBG(treatment,data.sgv)) + padding.top -10) .attr('x', xScale2(treatment.mills) + padding.left + 10) - .text(treatment.eventType); + .text(translate(client.careportal.resolveEventName(treatment.eventType))); } }); } diff --git a/lib/report_plugins/glucosedistribution.js b/lib/report_plugins/glucosedistribution.js index 5f5fef990a4..a606d3d91bc 100644 --- a/lib/report_plugins/glucosedistribution.js +++ b/lib/report_plugins/glucosedistribution.js @@ -46,7 +46,7 @@ glucosedistribution.css = -glucosedistribution.report = function report_glucosedistribution(datastorage,daystoshow,options) { +glucosedistribution.report = function report_glucosedistribution(datastorage,sorteddaystoshow,options) { var Nightscout = window.Nightscout; var client = Nightscout.client; var translate = client.translate; diff --git a/lib/report_plugins/hourlystats.js b/lib/report_plugins/hourlystats.js index 4ba0bd6f1da..552f5a460e2 100644 --- a/lib/report_plugins/hourlystats.js +++ b/lib/report_plugins/hourlystats.js @@ -22,7 +22,7 @@ hourlystats.html = function html(client) { return ret; }; -hourlystats.report = function report_hourlystats(datastorage, daystoshow, options) { +hourlystats.report = function report_hourlystats(datastorage, sorteddaystoshow, options) { //console.log(window); var ss = require('simple-statistics'); var Nightscout = window.Nightscout; @@ -57,16 +57,7 @@ hourlystats.report = function report_hourlystats(datastorage, daystoshow, option [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23].forEach(function(hour) { var tr = $(''); - var display = hour % 12; - if (hour === 0) { - display = '12'; - } - display += ':00 '; - if (hour >= 12) { - display += 'PM'; - } else { - display += 'AM'; - } + var display = new Date(0, 0 , 1, hour, 0, 0, 0).toLocaleTimeString().replace(/([\d]+:[\d]{2})(:[\d]{2})(.*)/, '$1$3'); var avg = Math.floor(pivotedByHour[hour].map(function(r) { return r.sgv; }).reduce(function(o,v){ return o+v; }, 0) / pivotedByHour[hour].length); var d = new Date(hour.hours()); diff --git a/lib/report_plugins/index.js b/lib/report_plugins/index.js index b2c929ec778..91c8ea4a66c 100644 --- a/lib/report_plugins/index.js +++ b/lib/report_plugins/index.js @@ -6,6 +6,8 @@ function init() { var consts = { SCALE_LINEAR: 0 , SCALE_LOG: 1 + , ORDER_OLDESTONTOP: 0 + , ORDER_NEWESTONTOP: 1 } , allPlugins = [ require('./daytoday')() diff --git a/lib/report_plugins/percentile.js b/lib/report_plugins/percentile.js index 43ab7123719..9da62a906ad 100644 --- a/lib/report_plugins/percentile.js +++ b/lib/report_plugins/percentile.js @@ -23,7 +23,7 @@ percentile.html = function html(client) { return ret; }; -percentile.report = function report_percentile(datastorage,daystoshow,options) { +percentile.report = function report_percentile(datastorage, sorteddaystoshow, options) { var Nightscout = window.Nightscout; var client = Nightscout.client; var translate = client.translate; diff --git a/lib/report_plugins/success.js b/lib/report_plugins/success.js index f0f9929004d..8ecc924b5df 100644 --- a/lib/report_plugins/success.js +++ b/lib/report_plugins/success.js @@ -21,7 +21,7 @@ success.html = function html(client) { return ret; }; -success.report = function report_success(datastorage,daystoshow,options) { +success.report = function report_success(datastorage, sorteddaystoshow, options) { var Nightscout = window.Nightscout; var client = Nightscout.client; var translate = client.translate; diff --git a/lib/report_plugins/treatments.js b/lib/report_plugins/treatments.js index d95fcf43a67..443ef6eadb6 100644 --- a/lib/report_plugins/treatments.js +++ b/lib/report_plugins/treatments.js @@ -66,7 +66,7 @@ treatments.html = function html(client) { return ret; }; -treatments.report = function report_treatments(datastorage, daystoshow, options) { +treatments.report = function report_treatments(datastorage, sorteddaystoshow, options) { var Nightscout = window.Nightscout; var client = Nightscout.client; var translate = client.translate; @@ -235,13 +235,16 @@ treatments.report = function report_treatments(datastorage, daystoshow, options) .append($('') .append($(' + + + +
    ')) + .append($('').css('width','80px').attr('align','left').append(translate('Time'))) + .append($('').css('width','150px').attr('align','left').append(translate('Event Type'))) + .append($('').css('width','150px').attr('align','left').append(translate('Blood Glucose'))) + .append($('').css('width','50px').attr('align','left').append(translate('Insulin'))) + .append($('').css('width','50px').attr('align','left').append(translate('Carbs'))) + .append($('').css('width','150px').attr('align','left').append(translate('Entered By'))) + .append($('').css('width','300px').attr('align','left').append(translate('Notes'))) + ); Object.keys(daystoshow).forEach(function (day) { - table += '
    '+report_plugins.utils.localeDate(day)+'
    ').attr('colspan','8').css('background','lightgray') + .append($('').append(report_plugins.utils.localeDate(day))) + ) + ); var treatments = datastorage[day].treatments; for (var t=0; t'; - table += ' '; - table += ''; - table += ''+(new Date(tr.created_at).toLocaleTimeString().replace(/([\d]+:[\d]{2})(:[\d]{2})(.*)/, '$1$3'))+''+(tr.eventType ? translate(resolveEventName(tr.eventType)) : '')+''+(tr.glucose ? tr.glucose + ' ('+translate(tr.glucoseType)+')' : '')+''+(tr.insulin ? tr.insulin : '')+''+(tr.carbs ? tr.carbs : '')+''+(tr.enteredBy ? tr.enteredBy : '')+''+(tr.notes ? tr.notes : '')+'
    ') + .append($('').addClass('deleteTreatment').css('cursor','pointer').attr('title',translate('Delete record')).attr('src',icon_remove).attr('data',JSON.stringify(tr)).attr('day',day)) + .append(' ') + .append($('').addClass('editTreatment').css('cursor','pointer').attr('title',translate('Edit record')).attr('src',icon_edit).attr('data',JSON.stringify(tr)).attr('day',day)) + ) + .append($('').append(new Date(tr.created_at).toLocaleTimeString().replace(/([\d]+:[\d]{2})(:[\d]{2})(.*)/, '$1$3'))) + .append($('').append(tr.eventType ? translate(resolveEventName(tr.eventType)) : '')) + .append($('').attr('align','center').append(tr.glucose ? tr.glucose + ' ('+translate(tr.glucoseType)+')' : '')) + .append($('').attr('align','center').append(tr.insulin ? tr.insulin : '')) + .append($('').attr('align','center').append(tr.carbs ? tr.carbs : '')) + .append($('').append(tr.enteredBy ? tr.enteredBy : '')) + .append($('').append(tr.notes ? tr.notes : '')) + ); } }); $('#treatments-report').html(table); From 816caa296e182e3a58e8417ad00825988565fa8b Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Fri, 4 Sep 2015 23:21:31 +0200 Subject: [PATCH 808/937] fix mg/dl vs mg/dL issue --- static/report/js/report.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/static/report/js/report.js b/static/report/js/report.js index bcfb0dba14b..6b54a558958 100644 --- a/static/report/js/report.js +++ b/static/report/js/report.js @@ -163,8 +163,8 @@ }); $('#rp_eventtype').append(''); - $('#rp_targetlow').val(targetBGdefault[client.settings.units].low); - $('#rp_targethigh').val(targetBGdefault[client.settings.units].high); + $('#rp_targetlow').val(targetBGdefault[client.settings.units.toLowerCase()].low); + $('#rp_targethigh').val(targetBGdefault[client.settings.units.toLowerCase()].high); $('.menutab').click(switchreport_handler); From 33f4555a792b225a7dbc100e9b1f23165b313b7b Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Fri, 4 Sep 2015 23:47:07 +0200 Subject: [PATCH 809/937] check basal object in profileeditor too --- static/profile/js/profileeditor.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/static/profile/js/profileeditor.js b/static/profile/js/profileeditor.js index 7f8c3e39f24..92309e89523 100644 --- a/static/profile/js/profileeditor.js +++ b/static/profile/js/profileeditor.js @@ -122,6 +122,7 @@ if (typeof profile.sens !== 'object') { profile.sens = [{ 'time': '00:00', 'value': profile.sens }]; } if (typeof profile.target_low !== 'object') { profile.target_low = [{ 'time': '00:00', 'value': profile.target_low }]; } if (typeof profile.target_high !== 'object') { profile.target_high = [{ 'time': '00:00', 'value': profile.target_high }]; } + if (typeof profile.basal !== 'object') { profile.basal = [{ 'time': '00:00', 'value': profile.basal }]; } if (profile.target_high.length !== profile.target_low.length) { alert('Time ranges of target_low and target_high don\'t match. Values are restored to defaults.'); profile.target_low = _.cloneDeep(defaultprofile.target_low); @@ -513,4 +514,4 @@ return false; } -})(); \ No newline at end of file +})(); From 68cf970aa1b7f50c352265716649c030073e568a Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Sat, 5 Sep 2015 00:13:00 +0200 Subject: [PATCH 810/937] use profile timezone --- static/report/js/report.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/static/report/js/report.js b/static/report/js/report.js index 6b54a558958..64a24fa12c4 100644 --- a/static/report/js/report.js +++ b/static/report/js/report.js @@ -140,6 +140,16 @@ // ****** FOOD CODE END ****** + function getTimeZoneOffset () { + var offset; + if (client.sbx.data.profile.getTimezone()) { + offset = moment().tz(client.sbx.data.profile.getTimezone())._offset; + } else { + offset = new Date().getTimezoneOffset(); + } + return offset; + } + function prepareGUI() { $('.presetdates').click(function(event) { var days = $(this).attr('days'); @@ -486,7 +496,7 @@ , calData = [] ; var dt = new Date(day); - var from = dt.getTime() + dt.getTimezoneOffset() * 60 * 1000; + var from = dt.getTime() + getTimeZoneOffset() * 60 * 1000; var to = from + 1000 * 60 * 60 * 24; var query = '?find[date][$gte]='+from+'&find[date][$lt]='+to+'&count=10000'; @@ -588,7 +598,7 @@ data.sgv = data.sgv.concat(data.mbg.map(function (obj) { return { date: new Date(obj.mills), y: obj.y, sgv: client.utils.scaleMgdl(obj.y), color: 'red', type: 'mbg', device: obj.device } })); // make sure data range will be exactly 24h - var from = new Date(new Date(day).getTime() + (new Date().getTimezoneOffset()*60*1000)); + var from = new Date(new Date(day).getTime() + (getTimeZoneOffset() * 60 * 1000)); var to = new Date(from.getTime() + 1000 * 60 * 60 * 24); data.sgv.push({ date: from, y: 40, sgv: 40, color: 'transparent', type: 'rawbg'}); data.sgv.push({ date: to, y: 40, sgv: 40, color: 'transparent', type: 'rawbg'}); From bbe760abc2ef0d067c31f7ddb46b132dbe7f317c Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Sat, 5 Sep 2015 00:49:46 +0200 Subject: [PATCH 811/937] 1 day shift fix --- lib/report_plugins/utils.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/report_plugins/utils.js b/lib/report_plugins/utils.js index d5e00cc3f1d..fb3d1e190b5 100644 --- a/lib/report_plugins/utils.js +++ b/lib/report_plugins/utils.js @@ -13,12 +13,12 @@ utils.localeDate = function localeDate(day) { var ret = [translate('Sunday'),translate('Monday'),translate('Tuesday'),translate('Wednesday'),translate('Thursday'),translate('Friday'),translate('Saturday')][new Date(day).getDay()]; ret += ' '; - ret += new Date(day).toLocaleDateString(); + ret += new Date(day + ' 00:00:00').toLocaleDateString(); return ret; }; utils.localeDateTime = function localeDateTime(day) { - var ret = new Date(day).toLocaleDateString() + ' ' + new Date(day).toLocaleTimeString(); + var ret = new Date(day + ' 00:00:00').toLocaleDateString() + ' ' + new Date(day).toLocaleTimeString(); return ret; }; From 2164ea2ace0e2138e65b7fd7fc1a43ae6f77c1a1 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Sat, 5 Sep 2015 08:59:42 +0200 Subject: [PATCH 812/937] fixed day name too --- lib/report_plugins/utils.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/report_plugins/utils.js b/lib/report_plugins/utils.js index fb3d1e190b5..e8cf86158f6 100644 --- a/lib/report_plugins/utils.js +++ b/lib/report_plugins/utils.js @@ -10,15 +10,17 @@ module.exports = init; utils.localeDate = function localeDate(day) { var translate = Nightscout.client.translate; + var date = new Date(day + ' 00:00:00'); var ret = - [translate('Sunday'),translate('Monday'),translate('Tuesday'),translate('Wednesday'),translate('Thursday'),translate('Friday'),translate('Saturday')][new Date(day).getDay()]; + [translate('Sunday'),translate('Monday'),translate('Tuesday'),translate('Wednesday'),translate('Thursday'),translate('Friday'),translate('Saturday')][date.getDay()]; ret += ' '; - ret += new Date(day + ' 00:00:00').toLocaleDateString(); + ret += date.toLocaleDateString(); return ret; }; utils.localeDateTime = function localeDateTime(day) { - var ret = new Date(day + ' 00:00:00').toLocaleDateString() + ' ' + new Date(day).toLocaleTimeString(); + var date = new Date(day + ' 00:00:00'); + var ret = date.toLocaleDateString() + ' ' + date.toLocaleTimeString(); return ret; }; From dd015ba1020769c3cf16a78f8ad24531992b3def Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 5 Sep 2015 00:20:04 -0700 Subject: [PATCH 813/937] support for per event alarm settings, use same setting for browser and pushover --- README.md | 24 +++++++++---- lib/client/index.js | 7 ++++ lib/pushnotify.js | 7 ++-- lib/settings.js | 77 ++++++++++++++++++++++++++++++++++++++++-- static/index.html | 7 +--- tests/settings.test.js | 23 +++++++++++++ 6 files changed, 129 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index fe85ee1a0b5..84a9b73143e 100644 --- a/README.md +++ b/README.md @@ -142,18 +142,34 @@ To learn more about the Nightscout API, visit https://YOUR-SITE.com/api-docs.htm * `MONGO_CONNECTION` - Your mongo uri, for example: `mongodb://sally:sallypass@ds099999.mongolab.com:99999/nightscout` * `DISPLAY_UNITS` (`mg/dl`) - Choices: `mg/dl` and `mmol`. Setting to `mmol` puts the entire server into `mmol` mode by default, no further settings needed. + * `BASE_URL` - Used for building links to your sites api, ie pushover callbacks, usually the URL of your Nightscout site you may want https instead of http ### Features/Labs * `ENABLE` - Used to enable optional features, expects a space delimited list, such as: `careportal rawbg iob`, see [plugins](#plugins) below * `DISABLE` - Used to disable default features, expects a space delimited list, such as: `direction upbat`, see [plugins](#plugins) below * `API_SECRET` - A secret passphrase that must be at least 12 characters long, required to enable `POST` and `PUT`; also required for the Care Portal + + +### Alarms + + These alarm setting effect all delivery methods (browser, pushover, maker, etc), some settings can be overridden per client (web browser) + + * `ALARM_TYPES` (`simple` if any `BG_`* ENV's are set, otherwise `predict`) - currently 2 alarm types are supported, and can be used independently or combined. The `simple` alarm type only compares the current BG to `BG_` thresholds above, the `predict` alarm type uses highly tuned formula that forecasts where the BG is going based on it's trend. `predict` **DOES NOT** currently use any of the `BG_`* ENV's * `BG_HIGH` (`260`) - must be set using mg/dl units; the high BG outside the target range that is considered urgent * `BG_TARGET_TOP` (`180`) - must be set using mg/dl units; the top of the target range, also used to draw the line on the chart * `BG_TARGET_BOTTOM` (`80`) - must be set using mg/dl units; the bottom of the target range, also used to draw the line on the chart * `BG_LOW` (`55`) - must be set using mg/dl units; the low BG outside the target range that is considered urgent - * `ALARM_TYPES` (`simple` if any `BG_`* ENV's are set, otherwise `predict`) - currently 2 alarm types are supported, and can be used independently or combined. The `simple` alarm type only compares the current BG to `BG_` thresholds above, the `predict` alarm type uses highly tuned formula that forecasts where the BG is going based on it's trend. `predict` **DOES NOT** currently use any of the `BG_`* ENV's - * `BASE_URL` - Used for building links to your sites api, ie pushover callbacks, usually the URL of your Nightscout site you may want https instead of http + * `ALARM_URGENT_HIGH` (`on`) - possible values `on` or `off` + * `ALARM_URGENT_HIGH_MINS` (`30 60 90 120`) - Number of minutes to snooze urgent high alarms, space separated for options in browser, first used for pushover + * `ALARM_HIGH` (`on`) - possible values `on` or `off` + * `ALARM_HIGH_MINS` (`30 60 90 120`) - Number of minutes to snooze high alarms, space separated for options in browser, first used for pushover + * `ALARM_LOW` (`on`) - possible values `on` or `off` + * `ALARM_LOW_MINS` (`30 60 90 120`) - Number of minutes to snooze low alarms, space separated for options in browser, first used for pushover + * `ALARM_URGENT_LOW` (`on`) - possible values `on` or `off` + * `ALARM_URGENT_LOW_MINS` (`30 60 90 120`) - Number of minutes to snooze urgent low alarms, space separated for options in browser, first used for pushover + * `ALARM_URGENT_MINS` (`30 60 90 120`) - Number of minutes to snooze urgent alarms (that aren't tagged as high or low), space separated for options in browser, first used for pushover + * `ALARM_WARN_MINS` (`30 60 90 120`) - Number of minutes to snooze warning alarms (that aren't tagged as high or low), space separated for options in browser, first used for pushover ### Core @@ -173,10 +189,6 @@ To learn more about the Nightscout API, visit https://YOUR-SITE.com/api-docs.htm * `SHOW_RAWBG` (`never`) - possible values `always`, `never` or `noise` * `CUSTOM_TITLE` (`Nightscout`) - Usually name of T1 * `THEME` (`default`) - possible values `default` or `colors` - * `ALARM_URGENT_HIGH` (`on`) - possible values `on` or `off` - * `ALARM_HIGH` (`on`) - possible values `on` or `off` - * `ALARM_LOW` (`on`) - possible values `on` or `off` - * `ALARM_URGENT_LOW` (`on`) - possible values `on` or `off` * `ALARM_TIMEAGO_WARN` (`on`) - possible values `on` or `off` * `ALARM_TIMEAGO_WARN_MINS` (`15`) - minutes since the last reading to trigger a warning * `ALARM_TIMEAGO_URGENT` (`on`) - possible values `on` or `off` diff --git a/lib/client/index.js b/lib/client/index.js index f2dba0e9a2e..c78afe64dc4 100644 --- a/lib/client/index.js +++ b/lib/client/index.js @@ -405,6 +405,13 @@ client.init = function init(serverSettings, plugins) { container.addClass('alarming').addClass(file === urgentAlarmSound ? 'urgent' : 'warning'); + var silenceBtn = $('#silenceBtn'); + silenceBtn.empty(); + + _.each(client.settings.snoozeMinsForAlarmEvent(notify), function eachOption (mins) { + silenceBtn.append('
  • Silence for ' + mins + ' minutes
  • '); + }); + updateTitle(); } diff --git a/lib/pushnotify.js b/lib/pushnotify.js index e6cd18d5c25..8ceaa71dcf3 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -40,7 +40,9 @@ function init(env, ctx) { if (recentlySent.get(key)) { console.info('notify: ' + key + ' has ALREADY been sent'); - + return; + } else if (!env.settings.isAlarmEventEnabled(notify)) { + console.info('notify: ' + key + ' will NOT be sent, it\'s been disabled'); return; } @@ -57,7 +59,8 @@ function init(env, ctx) { var notify = receipts.get(response.receipt); console.info('push ack, response: ', response, ', notify: ', notify); if (notify) { - ctx.notifications.ack(notify.level, times.mins(30).msecs, true); + var snoozeMins = env.settings.snoozeFirstMinsForAlarmEvent(notify); + ctx.notifications.ack(notify.level, times.mins(snoozeMins).msecs, true); receipts.del(response.receipt); } return !!notify; diff --git a/lib/settings.js b/lib/settings.js index c2f2c7d3669..52fa6086117 100644 --- a/lib/settings.js +++ b/lib/settings.js @@ -1,6 +1,7 @@ 'use strict'; var _ = require('lodash'); +var levels = require('./levels'); function init ( ) { @@ -12,9 +13,15 @@ function init ( ) { , customTitle: 'Nightscout' , theme: 'default' , alarmUrgentHigh: true + , alarmUrgentHighMins: [30, 60, 90, 120] , alarmHigh: true + , alarmHighMins: [30, 60, 90, 120] , alarmLow: true + , alarmLowMins: [15, 30, 45, 60] , alarmUrgentLow: true + , alarmUrgentLowMins: [15, 30, 45] + , alarmUrgentMins: [30, 60, 90, 120] + , alarmWarnMins: [30, 60, 90, 120] , alarmTimeagoWarn: true , alarmTimeagoWarnMins: 15 , alarmTimeagoUrgent: true @@ -29,13 +36,37 @@ function init ( ) { } }; + var valueMappers = { + alarmUrgentHighMins: mapNumberArray + , alarmHighMins: mapNumberArray + , alarmLowMins: mapNumberArray + , alarmUrgentLowMins: mapNumberArray + , alarmUrgentMins: mapNumberArray + , alarmWarnMins: mapNumberArray + }; + + function mapNumberArray (value) { + if (!value || _.isArray(value)) { + return value; + } + + if (isNaN(value)) { + var rawValues = value && value.split(' ') || []; + return _.map(rawValues, function (num) { + return isNaN(num) ? null : Number(num) + }); + } else { + return value; + } + } + //TODO: getting sent in status.json, shouldn't be settings.DEFAULT_FEATURES = ['delta', 'direction', 'upbat', 'errorcodes']; var wasSet = []; function isSimple (value) { - return typeof value !== 'function' && typeof value !== 'object'; + return _.isArray(value) || (typeof value !== 'function' && typeof value !== 'object'); } function nameFromKey (key, nameType) { @@ -49,8 +80,9 @@ function init ( ) { if (isSimple(value)) { var newValue = accessor(nameFromKey(key, nameType)); if (newValue !== undefined) { + var mapper = valueMappers[key]; wasSet.push(key); - keys[key] = newValue; + keys[key] = mapper ? mapper(newValue) : newValue; } } }); @@ -183,9 +215,50 @@ function init ( ) { return enabled; } + function isAlarmEventEnabled (notify) { + var enabled = false; + + if (!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; + } + + return enabled; + } + + function snoozeMinsForAlarmEvent (notify) { + var snoozeTime = [30]; + + if (notify.eventName === 'high' && notify.level === levels.URGENT && settings.alarmUrgentHigh) { + snoozeTime = settings.alarmUrgentHighMins || snoozeTime; + } else if (notify.eventName === 'high' && settings.alarmHigh) { + snoozeTime = settings.alarmHighMins || snoozeTime; + } else if (notify.eventName === 'low' && notify.level === levels.URGENT && settings.alarmUrgentLow) { + snoozeTime = settings.alarmUrgentLowMins || snoozeTime; + } else if (notify.eventName === 'low' && settings.alarmLow) { + snoozeTime = settings.alarmLowMins || snoozeTime; + } + + return snoozeTime; + } + + function snoozeFirstMinsForAlarmEvent (notify) { + return _.first(snoozeMinsForAlarmEvent(notify)); + } + settings.eachSetting = eachSettingAs(); settings.eachSettingAsEnv = eachSettingAs('env'); settings.isEnabled = isEnabled; + settings.isAlarmEventEnabled = isAlarmEventEnabled; + settings.snoozeMinsForAlarmEvent = snoozeMinsForAlarmEvent; + settings.snoozeFirstMinsForAlarmEvent = snoozeFirstMinsForAlarmEvent; return settings; diff --git a/static/index.html b/static/index.html index 49b39e28ece..6025134e61e 100644 --- a/static/index.html +++ b/static/index.html @@ -52,12 +52,7 @@ --- - +
    diff --git a/tests/settings.test.js b/tests/settings.test.js index 9baaffaff35..712928a2a29 100644 --- a/tests/settings.test.js +++ b/tests/settings.test.js @@ -2,6 +2,7 @@ var _ = require('lodash'); var should = require('should'); +var levels = require('../lib/levels'); describe('settings', function ( ) { var settings = require('../lib/settings')(); @@ -13,9 +14,15 @@ describe('settings', function ( ) { settings.customTitle.should.equal('Nightscout'); settings.theme.should.equal('default'); settings.alarmUrgentHigh.should.equal(true); + settings.alarmUrgentHighMins.should.eql([30, 60, 90, 120]); settings.alarmHigh.should.equal(true); + settings.alarmHighMins.should.eql([30, 60, 90, 120]); settings.alarmLow.should.equal(true); + settings.alarmLowMins.should.eql([15, 30, 45, 60]); settings.alarmUrgentLow.should.equal(true); + settings.alarmUrgentLowMins.should.eql([15, 30, 45]); + settings.alarmUrgentMins.should.eql([30, 60, 90, 120]); + settings.alarmWarnMins.should.eql([30, 60, 90, 120]); settings.alarmTimeagoWarn.should.equal(true); settings.alarmTimeagoWarnMins.should.equal(15); settings.alarmTimeagoUrgent.should.equal(true); @@ -128,6 +135,22 @@ describe('settings', function ( ) { fresh.enable.length.should.equal(0); }); + it('parse custom snooze mins', function () { + var userSetting = { + ALARM_URGENT_LOW_MINS: '5 10 15' + }; + + var fresh = require('../lib/settings')(); + fresh.eachSettingAsEnv(function (name) { + return userSetting[name]; + }); + + fresh.alarmUrgentLowMins.should.eql([5, 10, 15]); + + fresh.snoozeMinsForAlarmEvent({eventName: 'low', level: levels.URGENT}).should.eql([5, 10, 15]); + fresh.snoozeFirstMinsForAlarmEvent({eventName: 'low', level: levels.URGENT}).should.equal(5); + }); + it('set thresholds', function () { var userThresholds = { BG_HIGH: '200' From 09a8f62444166f42a41328e55b2bd17f73939074 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 5 Sep 2015 00:26:57 -0700 Subject: [PATCH 814/937] fix license should be a valid SPDX license expression warning --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a73740cbe41..cee77d81bd5 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "Nightscout", "version": "0.8.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": "AGPL3", + "license": "AGPL-3.0", "author": "Nightscout Team", "homepage": "http://nightscout.github.io/", "keywords": [ From abca6a097ca3db54f48a22a7c6d7b9daf51b9d28 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 5 Sep 2015 00:30:34 -0700 Subject: [PATCH 815/937] update readme toc --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 84a9b73143e..bb25961fb4b 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,7 @@ Community maintained fork of the - [Environment](#environment) - [Required](#required) - [Features/Labs](#featureslabs) + - [Alarms](#alarms) - [Core](#core) - [Predefined values for your browser settings (optional)](#predefined-values-for-your-browser-settings-optional) - [Plugins](#plugins) From 4b4633ccc35f681217c9a46c9314152144700b16 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 5 Sep 2015 01:47:59 -0700 Subject: [PATCH 816/937] include serverTime in status --- lib/api/status.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/api/status.js b/lib/api/status.js index 26bbe9d133e..2e3eeefb8a2 100644 --- a/lib/api/status.js +++ b/lib/api/status.js @@ -13,6 +13,7 @@ function configure (app, wares, env) { var info = { status: 'ok' , name: app.get('name') , version: app.get('version') + , serverTime: new Date().toISOString() , apiEnabled: app.enabled('api') , careportalEnabled: app.enabled('api') && env.settings.enable.indexOf('careportal') > -1 , head: wares.get_head( ) From 0123da3cdaaf9e56bf0d07bf5976b8ff9c35dbe2 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 5 Sep 2015 01:48:45 -0700 Subject: [PATCH 817/937] move baseUrl to settings and add heartbeat to env and readme --- README.md | 1 + env.js | 1 - lib/bus.js | 4 +++- lib/plugins/pushover.js | 4 ++-- lib/settings.js | 2 ++ tests/pushover.test.js | 4 +++- 6 files changed, 11 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index fe85ee1a0b5..ac94951a9de 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,7 @@ To learn more about the Nightscout API, visit https://YOUR-SITE.com/api-docs.htm * `SSL_KEY` - Path to your ssl key file, so that ssl(https) can be enabled directly in node.js * `SSL_CERT` - Path to your ssl cert file, so that ssl(https) can be enabled directly in node.js * `SSL_CA` - Path to your ssl ca file, so that ssl(https) can be enabled directly in node.js + * `HEARTBEAT` (`60`) - Number of seconds to wait in between database checks ### Predefined values for your browser settings (optional) diff --git a/env.js b/env.js index c12c4c32a4a..ec44b0a5616 100644 --- a/env.js +++ b/env.js @@ -17,7 +17,6 @@ function config ( ) { */ env.DISPLAY_UNITS = readENV('DISPLAY_UNITS', 'mg/dl'); env.PORT = readENV('PORT', 1337); - env.baseUrl = readENV('BASE_URL'); env.static_files = readENV('NIGHTSCOUT_STATIC_FILES', __dirname + '/static/'); setSSL(); diff --git a/lib/bus.js b/lib/bus.js index 10dbad2e3d1..11dd7337939 100644 --- a/lib/bus.js +++ b/lib/bus.js @@ -1,9 +1,11 @@ +'use strict'; + var Stream = require('stream'); function init (env) { var beats = 0; var started = new Date( ); - var interval = env.HEARTBEAT || 60000; + var interval = env.settings.heartbeat * 1000; var stream = new Stream; diff --git a/lib/plugins/pushover.js b/lib/plugins/pushover.js index 23d615fc84c..f05892006b3 100644 --- a/lib/plugins/pushover.js +++ b/lib/plugins/pushover.js @@ -30,8 +30,8 @@ function init (env) { if (levels.isAlarm(notify.level)) { //ADJUST RETRY TIME based on WARN or URGENT msg.retry = notify.level === levels.URGENT ? times.mins(2).secs : times.mins(15).secs; - if (env.baseUrl) { - msg.callback = env.baseUrl + '/api/v1/notifications/pushovercallback'; + if (env.settings && env.settings.baseUrl) { + msg.callback = env.settings.baseUrl + '/api/v1/notifications/pushovercallback'; } } diff --git a/lib/settings.js b/lib/settings.js index c2f2c7d3669..5e3394ec170 100644 --- a/lib/settings.js +++ b/lib/settings.js @@ -21,6 +21,8 @@ function init ( ) { , alarmTimeagoUrgentMins: 30 , language: 'en' , showPlugins: '' + , heartbeat: 60 + , baseURL: '' , thresholds: { bgHigh: 260 , bgTargetTop: 180 diff --git a/tests/pushover.test.js b/tests/pushover.test.js index 609ebf63f11..88d9b7dec86 100644 --- a/tests/pushover.test.js +++ b/tests/pushover.test.js @@ -8,7 +8,9 @@ describe('pushover', function ( ) { var baseurl = 'https://nightscout.test'; var env = { - baseUrl: baseurl + settings: { + baseUrl: baseurl + } , extendedSettings: { pushover: { userKey: '12345' From 34297807b9d3d38958dfeaacf04dbcd2516e92a2 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 5 Sep 2015 02:33:34 -0700 Subject: [PATCH 818/937] added new PUSHOVER_ALARM_KEY with this pushover alarms can be disabled also info level pushes (treatments) can be disabled --- README.md | 5 ++-- lib/plugins/pushover.js | 53 ++++++++++++++++++++++++++++++++++------- 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index bb25961fb4b..bd561017977 100644 --- a/README.md +++ b/README.md @@ -266,8 +266,9 @@ To learn more about the Nightscout API, visit https://YOUR-SITE.com/api-docs.htm * `ENABLE` - `pushover` should be added to the list of plugin, for example: `ENABLE="pushover"`. * `PUSHOVER_API_TOKEN` - Used to enable pushover notifications, this token is specific to the application you create from in [Pushover](https://pushover.net/), ***[additional pushover information](#pushover)*** below. - * `PUSHOVER_USER_KEY` - Your Pushover user key, can be found in the top left of the [Pushover](https://pushover.net/) site, this can also be a pushover delivery group key to send to a group rather than just a single user. This also support a space delimited list of keys. - * `PUSHOVER_ANNOUNCEMENT_KEY` - An optional Pushover user/group key, will be used for system wide user generated announcements. If not defined this will fallback to `PUSHOVER_USER_KEY`. A possible use for this is sending important messages and alarms to a CWD that you don't want to send all notification too. This also support a space delimited list of keys. + * `PUSHOVER_USER_KEY` - Your Pushover user key, can be found in the top left of the [Pushover](https://pushover.net/) site, this can also be a pushover delivery group key to send to a group rather than just a single user. This also supports a space delimited list of keys. To disable `INFO` level pushes set this to `off`. + * `PUSHOVER_ALARMS_KEY` - An optional Pushover user/group key, will be used for system wide alarms (level > `WARN`). If not defined this will fallback to `PUSHOVER_USER_KEY`. A possible use for this is sending important messages and alarms to a CWD that you don't want to send all notification too. This also support a space delimited list of keys. To disable Alarm pushes set this to `off`. + * `PUSHOVER_ANNOUNCEMENT_KEY` - An optional Pushover user/group key, will be used for system wide user generated announcements. If not defined this will fallback to `PUSHOVER_USER_KEY` or `PUSHOVER_ANNOUNCEMENT_KEY`. A possible use for this is sending important messages and alarms to a CWD that you don't want to send all notification too. This also support a space delimited list of keys. To disable Announcement pushes set this to `off`. * `BASE_URL` - Used for pushover callbacks, usually the URL of your Nightscout site, use https when possible. * `API_SECRET` - Used for signing the pushover callback request for acknowledgments. diff --git a/lib/plugins/pushover.js b/lib/plugins/pushover.js index 23d615fc84c..d88786d4b2b 100644 --- a/lib/plugins/pushover.js +++ b/lib/plugins/pushover.js @@ -14,8 +14,29 @@ function init (env) { var pushoverAPI = setupPushover(env); - pushover.send = function wrapSend(notify, callback) { - var selectedKeys = notify.isAnnouncement ? pushoverAPI.announcementKeys : pushoverAPI.userKeys; + function selectKeys (notify) { + var keys = null; + + if (notify.isAnnouncement) { + keys = pushoverAPI.announcementKeys; + } else if (levels.isAlarm(notify.level)) { + keys = pushoverAPI.alarmKeys; + } else { + keys = pushoverAPI.userKeys; + } + + return keys; + } + + pushover.send = function wrapSend (notify, callback) { + var selectedKeys = selectKeys(notify); + + if (selectedKeys.length == 0) { + console.info('No Pushover Keys defined for', notify); + if (callback) { + return callback(); + } + } var msg = { expire: times.mins(15).secs @@ -76,24 +97,38 @@ function init (env) { function setupPushover (env) { var apiToken = env.extendedSettings && env.extendedSettings.pushover && env.extendedSettings.pushover.apiToken; - var userKeys = (env.extendedSettings && env.extendedSettings.pushover && - env.extendedSettings.pushover.userKey && env.extendedSettings.pushover.userKey.split(' ')) || []; + function keysByType (type, fallback) { + fallback = fallback || []; + + var key = env.extendedSettings && env.extendedSettings.pushover && env.extendedSettings.pushover[type]; + + if (key === 'off') { + return []; //don't consider fallback, this type has been disabled + } else if (key && key.split) { + return key.split(' ') || fallback; + } else { + return fallback; + } + } + + var userKeys = keysByType('userKey', []); if (userKeys.length === 0) { - userKeys = (env.extendedSettings && env.extendedSettings.pushover && - env.extendedSettings.pushover.groupKey && env.extendedSettings.pushover.groupKey.split(' ')) || []; + userKeys = keysByType('groupKey') || []; } - var announcementKeys = (env.extendedSettings && env.extendedSettings.pushover && - env.extendedSettings.pushover.announcementKey && env.extendedSettings.pushover.announcementKey.split(' ')) || userKeys; + var alarmKeys = keysByType('alarmKey', userKeys); + + var announcementKeys = keysByType('announcementKey', userKeys || alarmKeys); - if (apiToken && (userKeys.length > 0 || announcementKeys.length > 0)) { + if (apiToken && (userKeys.length > 0 || alarmKeys.length > 0 || announcementKeys.length > 0)) { var pushoverAPI = new Pushover({ token: apiToken }); pushoverAPI.apiToken = apiToken; pushoverAPI.userKeys = userKeys; + pushoverAPI.alarmKeys = alarmKeys; pushoverAPI.announcementKeys = announcementKeys; return pushoverAPI; From addf8e6454cd5f78f3653eb769f2d5dddf20f1e7 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 5 Sep 2015 02:38:46 -0700 Subject: [PATCH 819/937] fix typos --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bd561017977..7710f40c45d 100644 --- a/README.md +++ b/README.md @@ -267,8 +267,8 @@ To learn more about the Nightscout API, visit https://YOUR-SITE.com/api-docs.htm * `ENABLE` - `pushover` should be added to the list of plugin, for example: `ENABLE="pushover"`. * `PUSHOVER_API_TOKEN` - Used to enable pushover notifications, this token is specific to the application you create from in [Pushover](https://pushover.net/), ***[additional pushover information](#pushover)*** below. * `PUSHOVER_USER_KEY` - Your Pushover user key, can be found in the top left of the [Pushover](https://pushover.net/) site, this can also be a pushover delivery group key to send to a group rather than just a single user. This also supports a space delimited list of keys. To disable `INFO` level pushes set this to `off`. - * `PUSHOVER_ALARMS_KEY` - An optional Pushover user/group key, will be used for system wide alarms (level > `WARN`). If not defined this will fallback to `PUSHOVER_USER_KEY`. A possible use for this is sending important messages and alarms to a CWD that you don't want to send all notification too. This also support a space delimited list of keys. To disable Alarm pushes set this to `off`. - * `PUSHOVER_ANNOUNCEMENT_KEY` - An optional Pushover user/group key, will be used for system wide user generated announcements. If not defined this will fallback to `PUSHOVER_USER_KEY` or `PUSHOVER_ANNOUNCEMENT_KEY`. A possible use for this is sending important messages and alarms to a CWD that you don't want to send all notification too. This also support a space delimited list of keys. To disable Announcement pushes set this to `off`. + * `PUSHOVER_ALARM_KEY` - An optional Pushover user/group key, will be used for system wide alarms (level > `WARN`). If not defined this will fallback to `PUSHOVER_USER_KEY`. A possible use for this is sending important messages and alarms to a CWD that you don't want to send all notification too. This also support a space delimited list of keys. To disable Alarm pushes set this to `off`. + * `PUSHOVER_ANNOUNCEMENT_KEY` - An optional Pushover user/group key, will be used for system wide user generated announcements. If not defined this will fallback to `PUSHOVER_USER_KEY` or `PUSHOVER_ALARM_KEY`. A possible use for this is sending important messages and alarms to a CWD that you don't want to send all notification too. This also support a space delimited list of keys. To disable Announcement pushes set this to `off`. * `BASE_URL` - Used for pushover callbacks, usually the URL of your Nightscout site, use https when possible. * `API_SECRET` - Used for signing the pushover callback request for acknowledgments. From c71b43edd53e2cf63eecd5a0f8ba7c0c9bdc42ef Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 5 Sep 2015 02:53:43 -0700 Subject: [PATCH 820/937] clean for codacy issues --- lib/plugins/pushover.js | 41 +++++++++++++++++++++++------------------ lib/settings.js | 2 +- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/lib/plugins/pushover.js b/lib/plugins/pushover.js index d88786d4b2b..b4a8666749a 100644 --- a/lib/plugins/pushover.js +++ b/lib/plugins/pushover.js @@ -31,30 +31,35 @@ function init (env) { pushover.send = function wrapSend (notify, callback) { var selectedKeys = selectKeys(notify); - if (selectedKeys.length == 0) { + function prepareMessage() { + var msg = { + expire: times.mins(15).secs + , title: notify.title + , message: notify.message + , sound: notify.pushoverSound || 'gamelan' + , timestamp: new Date() + //USE PUSHOVER_EMERGENCY for WARN and URGENT so we get the acks + , priority: notify.level >= levels.WARN ? pushover.PRIORITY_EMERGENCY : pushover.PRIORITY_NORMAL + }; + + if (levels.isAlarm(notify.level)) { + //ADJUST RETRY TIME based on WARN or URGENT + msg.retry = notify.level === levels.URGENT ? times.mins(2).secs : times.mins(15).secs; + if (env.baseUrl) { + msg.callback = env.baseUrl + '/api/v1/notifications/pushovercallback'; + } + } + return msg; + } + + if (selectedKeys.length === 0) { console.info('No Pushover Keys defined for', notify); if (callback) { return callback(); } } - var msg = { - expire: times.mins(15).secs - , title: notify.title - , message: notify.message - , sound: notify.pushoverSound || 'gamelan' - , timestamp: new Date() - //USE PUSHOVER_EMERGENCY for WARN and URGENT so we get the acks - , priority: notify.level >= levels.WARN ? pushover.PRIORITY_EMERGENCY : pushover.PRIORITY_NORMAL - }; - - if (levels.isAlarm(notify.level)) { - //ADJUST RETRY TIME based on WARN or URGENT - msg.retry = notify.level === levels.URGENT ? times.mins(2).secs : times.mins(15).secs; - if (env.baseUrl) { - msg.callback = env.baseUrl + '/api/v1/notifications/pushovercallback'; - } - } + var msg = prepareMessage(); _.each(selectedKeys, function eachKey(key) { msg.user = key; diff --git a/lib/settings.js b/lib/settings.js index 52fa6086117..5be924eeceb 100644 --- a/lib/settings.js +++ b/lib/settings.js @@ -53,7 +53,7 @@ function init ( ) { if (isNaN(value)) { var rawValues = value && value.split(' ') || []; return _.map(rawValues, function (num) { - return isNaN(num) ? null : Number(num) + return isNaN(num) ? null : Number(num); }); } else { return value; From 1ab11de9727179d23f6f4d5113b7f679b65d63d9 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Sat, 5 Sep 2015 17:30:42 +0200 Subject: [PATCH 821/937] another date issue --- lib/report_plugins/utils.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/report_plugins/utils.js b/lib/report_plugins/utils.js index e8cf86158f6..ae33759fb05 100644 --- a/lib/report_plugins/utils.js +++ b/lib/report_plugins/utils.js @@ -10,7 +10,12 @@ module.exports = init; utils.localeDate = function localeDate(day) { var translate = Nightscout.client.translate; - var date = new Date(day + ' 00:00:00'); + var date; + if (typeof day === 'string') { + date = new Date(day + ' 00:00:00'); + } else { + date = day; + } var ret = [translate('Sunday'),translate('Monday'),translate('Tuesday'),translate('Wednesday'),translate('Thursday'),translate('Friday'),translate('Saturday')][date.getDay()]; ret += ' '; @@ -19,7 +24,12 @@ utils.localeDate = function localeDate(day) { }; utils.localeDateTime = function localeDateTime(day) { - var date = new Date(day + ' 00:00:00'); + var date; + if (typeof day === 'string') { + date = new Date(day + ' 00:00:00'); + } else { + date = day; + } var ret = date.toLocaleDateString() + ' ' + date.toLocaleTimeString(); return ret; }; From 93e520d39eb4f1e7eea6022de19f05d2f82cffcc Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 5 Sep 2015 09:45:56 -0700 Subject: [PATCH 822/937] fix snooze bug by adding click handler as new snooze options are added --- lib/client/index.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/client/index.js b/lib/client/index.js index c78afe64dc4..2784def8218 100644 --- a/lib/client/index.js +++ b/lib/client/index.js @@ -409,12 +409,19 @@ client.init = function init(serverSettings, plugins) { silenceBtn.empty(); _.each(client.settings.snoozeMinsForAlarmEvent(notify), function eachOption (mins) { - silenceBtn.append('
  • Silence for ' + mins + ' minutes
  • '); + var snoozeOption = $('
  • Silence for ' + mins + ' minutes
  • '); + snoozeOption.click(snoozeAlarm); + silenceBtn.append(snoozeOption); }); updateTitle(); } + function snoozeAlarm (e) { + stopAlarm(true, $(this).data('snooze-time')); + e.preventDefault(); + } + function playAlarm(audio) { // ?mute=true disables alarms to testers. if (client.browserUtils.queryParms().mute !== 'true') { @@ -625,11 +632,6 @@ client.init = function init(serverSettings, plugins) { } }); - $('#silenceBtn').find('a').click(function (e) { - stopAlarm(true, $(this).data('snooze-time')); - e.preventDefault(); - }); - $('.focus-range li').click(function(e) { var li = $(e.target); $('.focus-range li').removeClass('selected'); From bbb8b0512c34ff64c9d49a2c55565fc5e4f32cf4 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 5 Sep 2015 10:00:52 -0700 Subject: [PATCH 823/937] fixed a regresion bug with mmol in the clock mode --- bundle/bundle.source.js | 1 + static/clock.html | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index f9c9e195411..5548de99df5 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -7,6 +7,7 @@ window.Nightscout = { client: require('../lib/client') + , units: require('../lib/units')() , plugins: require('../lib/plugins/')().registerClientDefaults() }; diff --git a/static/clock.html b/static/clock.html index f633e177ec1..149265cfb13 100644 --- a/static/clock.html +++ b/static/clock.html @@ -53,7 +53,7 @@

    var rec = xhr[0]; var last = new Date(rec.date); var now = new Date( ); - if (window.serverSettings.units == 'mmol') { + if (window.serverSettings.settings.units == 'mmol') { rec.sgv = Nightscout.units.mgdlToMMOL(rec.sgv); } $('.bgnow').text(rec.sgv); From 01464c80699f54446eef6337d42be72d58f8429e Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 5 Sep 2015 10:34:32 -0700 Subject: [PATCH 824/937] also look for alarmUrgentMins and alarmWarnMins if not high/low --- lib/settings.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/settings.js b/lib/settings.js index 5be924eeceb..aae7ab67e10 100644 --- a/lib/settings.js +++ b/lib/settings.js @@ -244,6 +244,10 @@ function init ( ) { snoozeTime = settings.alarmUrgentLowMins || snoozeTime; } else if (notify.eventName === 'low' && settings.alarmLow) { snoozeTime = settings.alarmLowMins || snoozeTime; + } else if (notify.level === levels.URGENT) { + snoozeTime = settings.alarmUrgentMins || snoozeTime; + } else if (notify.level === levels.WARN) { + snoozeTime = settings.alarmWarnMins || snoozeTime; } return snoozeTime; From ea7907c52a171d063903b26b9519453c10cef17b Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 5 Sep 2015 11:46:33 -0700 Subject: [PATCH 825/937] fix bug that made announcement alarms impossible to ack added TODO to clean up later when there's more time to test a bigger/better fix --- lib/client/index.js | 6 ++++-- lib/notifications.js | 10 +++++++--- lib/websocket.js | 8 +++++++- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/lib/client/index.js b/lib/client/index.js index f2dba0e9a2e..9ef1c64917c 100644 --- a/lib/client/index.js +++ b/lib/client/index.js @@ -702,7 +702,8 @@ client.init = function init(serverSettings, plugins) { var enabled = (isAlarmForHigh() && client.settings.alarmHigh) || (isAlarmForLow() && client.settings.alarmLow); if (enabled) { console.log('Alarm raised!'); - currentAlarmType = 'alarm'; + //TODO: Announcement hack a1/a2 + currentAlarmType = notify.isAnnouncement ? 'a' + notify.level : 'alarm'; generateAlarm(alarmSound,notify); } else { console.info('alarm was disabled locally', client.latestSGV.mgdl, client.settings); @@ -717,7 +718,8 @@ client.init = function init(serverSettings, plugins) { var enabled = (isAlarmForHigh() && client.settings.alarmUrgentHigh) || (isAlarmForLow() && client.settings.alarmUrgentLow); if (enabled) { console.log('Urgent alarm raised!'); - currentAlarmType = 'urgent_alarm'; + //TODO: Announcement hack a1/a2 + currentAlarmType = notify.isAnnouncement ? 'a' + notify.level : 'urgent_alarm'; generateAlarm(urgentAlarmSound,notify); } else { console.info('urgent alarm was disabled locally', client.latestSGV.mgdl, client.settings); diff --git a/lib/notifications.js b/lib/notifications.js index 1a498a84c5b..236c4848499 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -23,7 +23,9 @@ function init (env, ctx) { function getAlarm (level) { var alarm = alarms[level]; if (!alarm) { - alarm = new Alarm(level, levels.toDisplay(level)); + //TODO: Announcement hack a1/a2 + var display = level.indexOf && level.indexOf('a') === 0 ? 'Announcement:' + level : levels.toDisplay(level); + alarm = new Alarm(level, display); alarms[level] = alarm; } @@ -54,8 +56,10 @@ function init (env, ctx) { } function emitNotification (notify) { - var alarm = getAlarm(notify.level); - if (ctx.data.lastUpdated > alarm.lastAckTime + alarm.silenceTime || notify.isAnnouncement) { + //TODO: Announcement hack a1/a2 + var level = notify.isAnnouncement ? 'a' + notify.level : notify.level; + var alarm = getAlarm(level); + if (ctx.data.lastUpdated > alarm.lastAckTime + alarm.silenceTime) { ctx.bus.emit('notification', notify); alarm.lastEmitTime = ctx.data.lastUpdated; logEmitEvent(notify); diff --git a/lib/websocket.js b/lib/websocket.js index 2d63c141f89..0e3b883cb12 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -12,6 +12,11 @@ function init (env, ctx, server) { var watchers = 0; var lastData = {}; + var alarmType2Level = { + urgent_alarm: levels.URGENT + , alarm: levels.WARN + }; + function start ( ) { io = require('socket.io')({ 'transports': ['xhr-polling'], 'log level': 0 @@ -36,7 +41,8 @@ function init (env, ctx, server) { socket.emit('dataUpdate',lastData); io.emit('clients', ++watchers); socket.on('ack', function(alarmType, silenceTime) { - var level = alarmType === 'urgent_alarm' ? 2 : 1; + //TODO: Announcement hack a1/a2 + var level = alarmType2Level[alarmType] || alarmType; ctx.notifications.ack(level, silenceTime, true); }); socket.on('disconnect', function () { From f27e12ef6efaa3098a2768d9829e31deaa281de9 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 5 Sep 2015 12:00:05 -0700 Subject: [PATCH 826/937] Hide the GI specific value till it's integrated --- static/profile/index.html | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/static/profile/index.html b/static/profile/index.html index 54e51fe2c23..91cac548ba1 100644 --- a/static/profile/index.html +++ b/static/profile/index.html @@ -85,14 +85,16 @@

    Profile editor

    - Carbs - ( - - - use GI specific values - - ) - + Carbs + + + + + + + + +
    Carbs activity / absorption rate: [g/hour]
    From c0ef77c85ca311c9132c2ad0bd74b9151c6b861c Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 5 Sep 2015 12:57:44 -0700 Subject: [PATCH 827/937] fix issue where the right of the tooltip goes off the screen --- lib/plugins/pluginbase.js | 7 ++++++- static/css/main.css | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/plugins/pluginbase.js b/lib/plugins/pluginbase.js index e7943af5ec7..fe00b805082 100644 --- a/lib/plugins/pluginbase.js +++ b/lib/plugins/pluginbase.js @@ -2,6 +2,8 @@ var _ = require('lodash'); +var TOOLTIP_WIDTH = 250; //min-width + padding + function init (majorPills, minorPills, statusPills, bgStatus, tooltip) { var pluginBase = { }; @@ -78,8 +80,11 @@ function init (majorPills, minorPills, statusPills, bgStatus, tooltip) { pill.mouseover(function pillMouseover (event) { tooltip.transition().duration(200).style('opacity', .9); + + var windowWidth = $(tooltip).parent().parent().width(); + var left = event.pageX + TOOLTIP_WIDTH < windowWidth ? event.pageX : windowWidth - TOOLTIP_WIDTH; tooltip.html(html) - .style('left', (event.pageX) + 'px') + .style('left', left + 'px') .style('top', (event.pageY + 15) + 'px'); }); diff --git a/static/css/main.css b/static/css/main.css index ac92ef617c7..7852c2ec954 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -290,6 +290,7 @@ body { border: 0; border-radius: 8px; float: right; + min-width: 150px; } .alarms { From b24cc641c881646a4f98f9863392b3218774ce4e Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 6 Sep 2015 09:26:04 -0700 Subject: [PATCH 828/937] beter logging/handling of case where there isn't a pushover key for a specific notification --- lib/plugins/pushover.js | 3 +-- lib/pushnotify.js | 4 +++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/plugins/pushover.js b/lib/plugins/pushover.js index 8fd07a627cd..2f6f46dd2e0 100644 --- a/lib/plugins/pushover.js +++ b/lib/plugins/pushover.js @@ -53,9 +53,8 @@ function init (env) { } if (selectedKeys.length === 0) { - console.info('No Pushover Keys defined for', notify); if (callback) { - return callback(); + return callback('no-key-defined'); } } diff --git a/lib/pushnotify.js b/lib/pushnotify.js index 8ceaa71dcf3..3cca136131f 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -87,7 +87,9 @@ function init(env, ctx) { if (ctx.pushover) { //add the key to the cache before sending, but with a short TTL ctx.pushover.send(notify, function pushoverCallback(err, result) { - if (!err) { + if (err) { + console.warn('Unable to send pushover', err, notify); + } else { //result comes back as a string here, so fix it result = JSON.parse(result); //after successfully sent, increase the TTL From 105fd193924a1851df763afbdb5e8615590b09e0 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 6 Sep 2015 10:00:46 -0700 Subject: [PATCH 829/937] make errorcode to level mappings configurable --- README.md | 4 ++++ lib/plugins/errorcodes.js | 36 +++++++++++++++++++++++++++++------- tests/errorcodes.test.js | 19 +++++++++++++++++++ 3 files changed, 52 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 144bc336f68..3fb0121b7eb 100644 --- a/README.md +++ b/README.md @@ -212,6 +212,10 @@ To learn more about the Nightscout API, visit https://YOUR-SITE.com/api-docs.htm * `direction` (BG Direction) - Displays the trend direction. * `upbat` (Uploader Battery) - Displays the most recent battery status from the uploader phone. * `errorcodes` (CGM Error Codes) - Generates alarms for CGM codes `9` (hourglass) and `10` (???). + * Use [extended settings](#extended-settings) to adjust what errorcodes trigger notifications and alarms: + * `ERRORCODES_INFO` (`5`) - By default the needs calibration (blood drop) generates an info level notification, set to a space separate list of number or `off` to disable + * `ERRORCODES_WARN` (`off`) - By default there are no warning configured, set to a space separate list of numbers or `off` to disable + * `ERRORCODES_URGENT` (`9 10`) - By default the hourglass and ??? generate an urgent alarm, set to a space separate list of numbers or `off` to disable * `ar2` ([Forcasting using AR2 algorithm](https://github.com/nightscout/nightscout.github.io/wiki/Forecasting)) - Generates alarms based on forecasted values. * Enabled by default if no thresholds are set **OR** `ALARM_TYPES` includes `predict`. * Use [extended settings](#extended-settings) to adjust AR2 behavior: diff --git a/lib/plugins/errorcodes.js b/lib/plugins/errorcodes.js index c0cba79899e..65b80911fda 100644 --- a/lib/plugins/errorcodes.js +++ b/lib/plugins/errorcodes.js @@ -1,5 +1,6 @@ 'use strict'; +var _ = require('lodash'); var levels = require('../levels'); var times = require('../times'); @@ -22,13 +23,6 @@ function init() { , 12: '?RF' //BAD_RF }; - //TODO: move notification levels to constants - var code2Level = { - 5: levels.INFO - , 9: levels.URGENT - , 10: levels.URGENT - }; - var code2PushoverSound = { 5: 'intermission' , 9: 'alien' @@ -45,6 +39,8 @@ function init() { var now = Date.now(); var lastSGV = sbx.lastSGVEntry(); + var code2Level = buildMappingFromSettings(sbx.extendedSettings); + if (lastSGV && now - lastSGV.mills < times.mins(10).msecs && lastSGV.mgdl < 39) { var errorDisplay = toDisplay(lastSGV.mgdl); @@ -67,6 +63,32 @@ function init() { } }; + function buildMappingFromSettings (extendedSettings) { + var mapping = {}; + + function addValuesToMapping (value, level) { + if (!value || !value.split) { + return []; + } + + var rawValues = value && value.split(' ') || []; + _.each(rawValues, function (num) { + if (!isNaN(num)) { + mapping[Number(num)] = level + } + }); + } + + addValuesToMapping(extendedSettings.info || '5', levels.INFO); + addValuesToMapping(extendedSettings.warn || 'off', levels.WARN); + addValuesToMapping(extendedSettings.urgent || '9 10', levels.URGENT); + + return mapping; + } + + //for tests + errorcodes.buildMappingFromSettings = buildMappingFromSettings; + return errorcodes; diff --git a/tests/errorcodes.test.js b/tests/errorcodes.test.js index b2b65a96c07..de634040d82 100644 --- a/tests/errorcodes.test.js +++ b/tests/errorcodes.test.js @@ -82,4 +82,23 @@ describe('errorcodes', function ( ) { errorcodes.toDisplay(10).should.equal('???'); }); + it('have default code to level mappings', function () { + var mapping = errorcodes.buildMappingFromSettings({}); + mapping[5].should.equal(levels.INFO); + mapping[9].should.equal(levels.URGENT); + mapping[10].should.equal(levels.URGENT); + _.keys(mapping).length.should.equal(3); + }); + + it('allow config of custom code to level mappings', function () { + var mapping = errorcodes.buildMappingFromSettings({ + info: 'off' + , warn: '9 10' + , urgent: 'off' + }); + mapping[9].should.equal(levels.WARN); + mapping[10].should.equal(levels.WARN); + _.keys(mapping).length.should.equal(2); + }); + }); \ No newline at end of file From 6f10f4d02963805c5fdcc2948625d68b93888674 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Sun, 6 Sep 2015 19:07:00 +0200 Subject: [PATCH 830/937] solved axis timezone issues --- lib/report_plugins/daytoday.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/report_plugins/daytoday.js b/lib/report_plugins/daytoday.js index 797e141c7a0..34947fcd743 100644 --- a/lib/report_plugins/daytoday.js +++ b/lib/report_plugins/daytoday.js @@ -75,6 +75,18 @@ daytoday.report = function report_daytoday(datastorage,daystoshow,options) { return timeFormat; } + function timeTicks(n,i) { + var t12 = [ + '12 AM', '01 AM', '02 AM', '03 AM', '04 AM', '05 AM', '06 AM', '07 AM', '08 AM', '09 AM', '10 AM', '11 AM', + '12 PM', '01 PM', '02 PM', '03 PM', '04 PM', '05 PM', '06 PM', '07 PM', '08 PM', '09 PM', '10 PM', '11 PM' + ]; + if (Nightscout.client.settings.timeFormat === '24') { + return ('00' + i).slice(-2); + } else { + return t12[i]; + } + } + function drawChart(day,data,options) { var tickValues , charts @@ -157,7 +169,7 @@ daytoday.report = function report_daytoday(datastorage,daystoshow,options) { // define the parts of the axis that aren't dependent on width or height xScale2 = d3.time.scale() - .domain(d3.extent(data.sgv, function (d) { return d.date; })); + .domain(d3.extent(data.sgv, dateFn)); if (options.scale === report_plugins.consts.SCALE_LOG) { yScale2 = d3.scale.log() @@ -175,7 +187,8 @@ daytoday.report = function report_daytoday(datastorage,daystoshow,options) { xAxis2 = d3.svg.axis() .scale(xScale2) - .tickFormat(d3.time.format(getTimeFormat(true))) + //.tickFormat(d3.time.format(getTimeFormat(true))) + .tickFormat(timeTicks) .ticks(24) .orient('bottom'); From 531820c3a3cc06fddd75ef527c2d8b26ab96dfcd Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 6 Sep 2015 10:10:18 -0700 Subject: [PATCH 831/937] if code isn't mapped it's level NONE, not LOW --- lib/plugins/errorcodes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/errorcodes.js b/lib/plugins/errorcodes.js index 65b80911fda..e4fb825f7ff 100644 --- a/lib/plugins/errorcodes.js +++ b/lib/plugins/errorcodes.js @@ -45,7 +45,7 @@ function init() { var errorDisplay = toDisplay(lastSGV.mgdl); var pushoverSound = code2PushoverSound[lastSGV.mgdl] || null; - var notifyLevel = code2Level[lastSGV.mgdl] || levels.LOW; + var notifyLevel = code2Level[lastSGV.mgdl] || levels.NONE; if (notifyLevel > levels.NONE) { sbx.notifications.requestNotify({ From 47d3ab55d706c963fa89cecdb0a6d0303a37c72f Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Sun, 6 Sep 2015 19:16:08 +0200 Subject: [PATCH 832/937] removed unneeded code --- lib/report_plugins/daytoday.js | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/lib/report_plugins/daytoday.js b/lib/report_plugins/daytoday.js index 34947fcd743..f950ff2880c 100644 --- a/lib/report_plugins/daytoday.js +++ b/lib/report_plugins/daytoday.js @@ -58,23 +58,12 @@ daytoday.report = function report_daytoday(datastorage,daystoshow,options) { var scaledTreatmentBG = report_plugins.utils.scaledTreatmentBG; var padding = { top: 15, right: 15, bottom: 30, left: 35 }; - var - FORMAT_TIME_12 = '%I' - , FORMAT_TIME_24 = '%H'; daytoday.prepareHtml(daystoshow) ; _.each(daystoshow, function (n, day) { drawChart(day,datastorage[day],options); }); - function getTimeFormat() { - var timeFormat = FORMAT_TIME_12; - if (Nightscout.client.settings.timeFormat === '24') { - timeFormat = FORMAT_TIME_24; - } - return timeFormat; - } - function timeTicks(n,i) { var t12 = [ '12 AM', '01 AM', '02 AM', '03 AM', '04 AM', '05 AM', '06 AM', '07 AM', '08 AM', '09 AM', '10 AM', '11 AM', @@ -187,7 +176,6 @@ daytoday.report = function report_daytoday(datastorage,daystoshow,options) { xAxis2 = d3.svg.axis() .scale(xScale2) - //.tickFormat(d3.time.format(getTimeFormat(true))) .tickFormat(timeTicks) .ticks(24) .orient('bottom'); From ffe88beb09d125d4c65e879b7bd3ae87641ee265 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 6 Sep 2015 10:35:44 -0700 Subject: [PATCH 833/937] fix codacy issue --- tests/storage.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/storage.test.js b/tests/storage.test.js index 641b710d920..de9d265325a 100644 --- a/tests/storage.test.js +++ b/tests/storage.test.js @@ -12,7 +12,7 @@ describe('STORAGE', function () { }); it('The storage class should be OK.', function (done) { - should(require('../lib/storage')).be.ok; + should.exist(require('../lib/storage')); done(); }); From f6878883dce2a14b3f27507dbd6df8c28a40e07b Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 6 Sep 2015 10:36:42 -0700 Subject: [PATCH 834/937] to maintain compatability codes 1-8 should be info level, fix bugs --- README.md | 2 +- lib/plugins/errorcodes.js | 9 ++++----- tests/errorcodes.test.js | 13 ++++++++++--- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 3fb0121b7eb..ec180b31452 100644 --- a/README.md +++ b/README.md @@ -213,7 +213,7 @@ To learn more about the Nightscout API, visit https://YOUR-SITE.com/api-docs.htm * `upbat` (Uploader Battery) - Displays the most recent battery status from the uploader phone. * `errorcodes` (CGM Error Codes) - Generates alarms for CGM codes `9` (hourglass) and `10` (???). * Use [extended settings](#extended-settings) to adjust what errorcodes trigger notifications and alarms: - * `ERRORCODES_INFO` (`5`) - By default the needs calibration (blood drop) generates an info level notification, set to a space separate list of number or `off` to disable + * `ERRORCODES_INFO` (`1 2 3 4 5 6 7 8`) - By default the needs calibration (blood drop) and other codes below 9 generate an info level notification, set to a space separate list of number or `off` to disable * `ERRORCODES_WARN` (`off`) - By default there are no warning configured, set to a space separate list of numbers or `off` to disable * `ERRORCODES_URGENT` (`9 10`) - By default the hourglass and ??? generate an urgent alarm, set to a space separate list of numbers or `off` to disable * `ar2` ([Forcasting using AR2 algorithm](https://github.com/nightscout/nightscout.github.io/wiki/Forecasting)) - Generates alarms based on forecasted values. diff --git a/lib/plugins/errorcodes.js b/lib/plugins/errorcodes.js index e4fb825f7ff..1b1504cd754 100644 --- a/lib/plugins/errorcodes.js +++ b/lib/plugins/errorcodes.js @@ -42,12 +42,11 @@ function init() { var code2Level = buildMappingFromSettings(sbx.extendedSettings); if (lastSGV && now - lastSGV.mills < times.mins(10).msecs && lastSGV.mgdl < 39) { - var errorDisplay = toDisplay(lastSGV.mgdl); var pushoverSound = code2PushoverSound[lastSGV.mgdl] || null; - var notifyLevel = code2Level[lastSGV.mgdl] || levels.NONE; + var notifyLevel = code2Level[lastSGV.mgdl]; - if (notifyLevel > levels.NONE) { + if (notifyLevel !== undefined) { sbx.notifications.requestNotify({ level: notifyLevel , title: 'CGM Error Code' @@ -74,12 +73,12 @@ function init() { var rawValues = value && value.split(' ') || []; _.each(rawValues, function (num) { if (!isNaN(num)) { - mapping[Number(num)] = level + mapping[Number(num)] = level; } }); } - addValuesToMapping(extendedSettings.info || '5', levels.INFO); + addValuesToMapping(extendedSettings.info || '1 2 3 4 5 6 7 8', levels.INFO); addValuesToMapping(extendedSettings.warn || 'off', levels.WARN); addValuesToMapping(extendedSettings.urgent || '9 10', levels.URGENT); diff --git a/tests/errorcodes.test.js b/tests/errorcodes.test.js index de634040d82..ed1b158637b 100644 --- a/tests/errorcodes.test.js +++ b/tests/errorcodes.test.js @@ -56,7 +56,7 @@ describe('errorcodes', function ( ) { errorcodes.checkNotifications(sbx); should.not.exist(ctx.notifications.findHighestAlarm()); var info = _.first(ctx.notifications.findUnSnoozeable()); - info.level.should.equal(levels.LOW); + info.level.should.equal(levels.INFO); info.pushoverSound.should.equal('intermission'); done(); @@ -64,7 +64,7 @@ describe('errorcodes', function ( ) { it('should trigger a low notification when code < 9', function (done) { - for (var i = 0; i < 9; i++) { + for (var i = 1; i < 9; i++) { ctx.notifications.initRequests(); ctx.data.sgvs = [{mgdl: i, mills: now}]; @@ -84,10 +84,17 @@ describe('errorcodes', function ( ) { it('have default code to level mappings', function () { var mapping = errorcodes.buildMappingFromSettings({}); + mapping[1].should.equal(levels.INFO); + mapping[2].should.equal(levels.INFO); + mapping[3].should.equal(levels.INFO); + mapping[4].should.equal(levels.INFO); mapping[5].should.equal(levels.INFO); + mapping[6].should.equal(levels.INFO); + mapping[7].should.equal(levels.INFO); + mapping[8].should.equal(levels.INFO); mapping[9].should.equal(levels.URGENT); mapping[10].should.equal(levels.URGENT); - _.keys(mapping).length.should.equal(3); + _.keys(mapping).length.should.equal(10); }); it('allow config of custom code to level mappings', function () { From 8a1fbe98d015623319bc1318c8366679ceb2c4b5 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Sun, 6 Sep 2015 19:49:23 +0200 Subject: [PATCH 835/937] improved 12h axis format --- lib/report_plugins/daytoday.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/report_plugins/daytoday.js b/lib/report_plugins/daytoday.js index f950ff2880c..043fa304977 100644 --- a/lib/report_plugins/daytoday.js +++ b/lib/report_plugins/daytoday.js @@ -57,7 +57,7 @@ daytoday.report = function report_daytoday(datastorage,daystoshow,options) { var report_plugins = Nightscout.report_plugins; var scaledTreatmentBG = report_plugins.utils.scaledTreatmentBG; - var padding = { top: 15, right: 15, bottom: 30, left: 35 }; + var padding = { top: 15, right: 22, bottom: 30, left: 35 }; daytoday.prepareHtml(daystoshow) ; _.each(daystoshow, function (n, day) { @@ -66,8 +66,8 @@ daytoday.report = function report_daytoday(datastorage,daystoshow,options) { function timeTicks(n,i) { var t12 = [ - '12 AM', '01 AM', '02 AM', '03 AM', '04 AM', '05 AM', '06 AM', '07 AM', '08 AM', '09 AM', '10 AM', '11 AM', - '12 PM', '01 PM', '02 PM', '03 PM', '04 PM', '05 PM', '06 PM', '07 PM', '08 PM', '09 PM', '10 PM', '11 PM' + '12am', '', '2am', '', '4am', '', '6am', '', '8am', '', '10am', '', + '12pm', '', '2pm', '', '4pm', '', '6pm', '', '8pm', '', '10pm', '', '12am' ]; if (Nightscout.client.settings.timeFormat === '24') { return ('00' + i).slice(-2); From 86881a64b00086b1b66640a53462d724f84cf00e Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 6 Sep 2015 11:34:14 -0700 Subject: [PATCH 836/937] fix readme to match defaults for ALARM_LOW_MINS and ALARM_URGENT_MINS --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ec180b31452..5f21d4f83bb 100644 --- a/README.md +++ b/README.md @@ -166,9 +166,9 @@ To learn more about the Nightscout API, visit https://YOUR-SITE.com/api-docs.htm * `ALARM_HIGH` (`on`) - possible values `on` or `off` * `ALARM_HIGH_MINS` (`30 60 90 120`) - Number of minutes to snooze high alarms, space separated for options in browser, first used for pushover * `ALARM_LOW` (`on`) - possible values `on` or `off` - * `ALARM_LOW_MINS` (`30 60 90 120`) - Number of minutes to snooze low alarms, space separated for options in browser, first used for pushover + * `ALARM_LOW_MINS` (`15 30 45 60`) - Number of minutes to snooze low alarms, space separated for options in browser, first used for pushover * `ALARM_URGENT_LOW` (`on`) - possible values `on` or `off` - * `ALARM_URGENT_LOW_MINS` (`30 60 90 120`) - Number of minutes to snooze urgent low alarms, space separated for options in browser, first used for pushover + * `ALARM_URGENT_LOW_MINS` (`15 30 45`) - Number of minutes to snooze urgent low alarms, space separated for options in browser, first used for pushover * `ALARM_URGENT_MINS` (`30 60 90 120`) - Number of minutes to snooze urgent alarms (that aren't tagged as high or low), space separated for options in browser, first used for pushover * `ALARM_WARN_MINS` (`30 60 90 120`) - Number of minutes to snooze warning alarms (that aren't tagged as high or low), space separated for options in browser, first used for pushover From eb3513cf6a8350864dd9977a935fdf075f19dbca Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 6 Sep 2015 11:42:08 -0700 Subject: [PATCH 837/937] more info on Pushover config options --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5f21d4f83bb..ce46457e802 100644 --- a/README.md +++ b/README.md @@ -276,7 +276,13 @@ To learn more about the Nightscout API, visit https://YOUR-SITE.com/api-docs.htm * `PUSHOVER_ANNOUNCEMENT_KEY` - An optional Pushover user/group key, will be used for system wide user generated announcements. If not defined this will fallback to `PUSHOVER_USER_KEY` or `PUSHOVER_ALARM_KEY`. A possible use for this is sending important messages and alarms to a CWD that you don't want to send all notification too. This also support a space delimited list of keys. To disable Announcement pushes set this to `off`. * `BASE_URL` - Used for pushover callbacks, usually the URL of your Nightscout site, use https when possible. * `API_SECRET` - Used for signing the pushover callback request for acknowledgments. - + + If you never want to get info level notifications (treatments) use `PUSHOVER_USER_KEY="off"` + If you never want to get an alarm via pushover use `PUSHOVER_ALARM_KEY="off"` + If you never want to get an announcement via pushover use `PUSHOVER_ANNOUNCEMENT_KEY="off"` + + If only `PUSHOVER_USER_KEY` is set it will be used for all info notifications, alarms, and announcements + For testing/development try [localtunnel](http://localtunnel.me/). #### IFTTT Maker From a21323145883aaccc163bc8be92ee4feb2b9b812 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 6 Sep 2015 11:44:26 -0700 Subject: [PATCH 838/937] 0.8.1 version bump to prepare for patch release --- bower.json | 2 +- package.json | 2 +- static/index.html | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/bower.json b/bower.json index 8931fb1247d..3b37b8ba312 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "nightscout", - "version": "0.8.0", + "version": "0.8.1", "dependencies": { "angularjs": "1.3.0-beta.19", "bootstrap": "~3.2.0", diff --git a/package.json b/package.json index cee77d81bd5..c8f043e202d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Nightscout", - "version": "0.8.0", + "version": "0.8.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/static/index.html b/static/index.html index 6025134e61e..7a144a6355a 100644 --- a/static/index.html +++ b/static/index.html @@ -25,10 +25,10 @@ - - + + - + @@ -261,8 +261,8 @@
    - - + + From ebf57a85848bcecd144ef30b9312ca21e5562003 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Sun, 6 Sep 2015 21:14:31 +0200 Subject: [PATCH 839/937] upper case in Entered By --- lib/client/renderer.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/client/renderer.js b/lib/client/renderer.js index bab8b4d420e..c475c0815cd 100644 --- a/lib/client/renderer.js +++ b/lib/client/renderer.js @@ -169,7 +169,7 @@ function init (client, d3) { return ''+translate('Time')+': ' + client.formatTime(new Date(d.mills)) + '
    ' + (d.eventType ? ''+translate('Treatment type')+': ' + d.eventType + '
    ' : '') + (d.glucose ? ''+translate('BG')+': ' + d.glucose + (d.glucoseType ? ' (' + translate(d.glucoseType) + ')': '') + '
    ' : '') + - (d.enteredBy ? ''+translate('Entered by')+': ' + d.enteredBy + '
    ' : '') + + (d.enteredBy ? ''+translate('Entered By')+': ' + d.enteredBy + '
    ' : '') + (d.notes ? ''+translate('Notes')+': ' + d.notes : ''); } @@ -177,7 +177,7 @@ function init (client, d3) { return ''+translate('Time')+': ' + client.formatTime(new Date(d.mills)) + '
    ' + (d.eventType ? ''+translate('Announcement')+'
    ' : '') + (d.notes && d.notes.length > 1 ? ''+translate('Message')+': ' + d.notes + '
    ' : '') + - (d.enteredBy ? ''+translate('Entered by')+': ' + d.enteredBy + '
    ' : ''); + (d.enteredBy ? ''+translate('Entered By')+': ' + d.enteredBy + '
    ' : ''); } //NOTE: treatments with insulin or carbs are drawn by drawTreatment() @@ -341,7 +341,7 @@ function init (client, d3) { (treatment.carbs ? '' + translate('Carbs') + ': ' + treatment.carbs + '
    ' : '') + (treatment.insulin ? '' + translate('Insulin') + ': ' + treatment.insulin + '
    ' : '') + (treatment.glucose ? '' + translate('BG') + ': ' + treatment.glucose + (treatment.glucoseType ? ' (' + translate(treatment.glucoseType) + ')' : '') + '
    ' : '') + - (treatment.enteredBy ? '' + translate('Entered by') + ': ' + treatment.enteredBy + '
    ' : '') + + (treatment.enteredBy ? '' + translate('Entered By') + ': ' + treatment.enteredBy + '
    ' : '') + (treatment.notes ? '' + translate('Notes') + ': ' + treatment.notes : '') ) .style('left', (d3.event.pageX) + 'px') From a5e1b836b0e9792e2c334be40caf28875474fbb7 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 6 Sep 2015 12:59:11 -0700 Subject: [PATCH 840/937] removed copy paste typo form readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ce46457e802..44a952c49aa 100644 --- a/README.md +++ b/README.md @@ -273,7 +273,7 @@ To learn more about the Nightscout API, visit https://YOUR-SITE.com/api-docs.htm * `PUSHOVER_API_TOKEN` - Used to enable pushover notifications, this token is specific to the application you create from in [Pushover](https://pushover.net/), ***[additional pushover information](#pushover)*** below. * `PUSHOVER_USER_KEY` - Your Pushover user key, can be found in the top left of the [Pushover](https://pushover.net/) site, this can also be a pushover delivery group key to send to a group rather than just a single user. This also supports a space delimited list of keys. To disable `INFO` level pushes set this to `off`. * `PUSHOVER_ALARM_KEY` - An optional Pushover user/group key, will be used for system wide alarms (level > `WARN`). If not defined this will fallback to `PUSHOVER_USER_KEY`. A possible use for this is sending important messages and alarms to a CWD that you don't want to send all notification too. This also support a space delimited list of keys. To disable Alarm pushes set this to `off`. - * `PUSHOVER_ANNOUNCEMENT_KEY` - An optional Pushover user/group key, will be used for system wide user generated announcements. If not defined this will fallback to `PUSHOVER_USER_KEY` or `PUSHOVER_ALARM_KEY`. A possible use for this is sending important messages and alarms to a CWD that you don't want to send all notification too. This also support a space delimited list of keys. To disable Announcement pushes set this to `off`. + * `PUSHOVER_ANNOUNCEMENT_KEY` - An optional Pushover user/group key, will be used for system wide user generated announcements. If not defined this will fallback to `PUSHOVER_USER_KEY` or `PUSHOVER_ALARM_KEY`. This also support a space delimited list of keys. To disable Announcement pushes set this to `off`. * `BASE_URL` - Used for pushover callbacks, usually the URL of your Nightscout site, use https when possible. * `API_SECRET` - Used for signing the pushover callback request for acknowledgments. From e4f7d35b976f5449cbf4c6ad723eb2c787019bf3 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Mon, 7 Sep 2015 00:09:52 +0300 Subject: [PATCH 841/937] Hopefully clearer BWP messaging to explain the negative BWP --- lib/plugins/boluswizardpreview.js | 35 ++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index e544c4d8531..5dc331e81ec 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -3,6 +3,7 @@ var _ = require('lodash'); var levels = require('../levels'); var times = require('../times'); +var CONST_60_MINUTES_IN_MS = 3600000; function init() { @@ -124,7 +125,20 @@ function init() { results.effect = iob * profile.getSensitivity(sbx.time); results.outcome = scaled - results.effect; var delta = 0; + + var timeSinceLastMeal = 1000000000; + var now = (new Date).getTime(); + for (var i = 0; i < sbx.data.treatments.length; i++) { + var treatment = sbx.data.treatments[i]; + var delta = now - treatment.mills; + if (delta < timeSinceLastMeal) { timeSinceLastMeal = delta;} + } + + results.timeSinceLastMeal = timeSinceLastMeal; + results.mealEatenRecently = false; + if (timeSinceLastMeal < CONST_60_MINUTES_IN_MS) { results.mealEatenRecently = true; } + var target_high = profile.getHighBGTarget(sbx.time); var sens = profile.getSensitivity(sbx.time); @@ -137,6 +151,11 @@ function init() { var target_low = profile.getLowBGTarget(sbx.time); + results.belowLowTarget = false; + if (scaled < target_low) { + results.belowLowTarget = true; + } + if (results.outcome < target_low) { delta = Math.abs(results.outcome - target_low); results.bolusEstimate = delta / sens * -1; @@ -197,6 +216,7 @@ function pushInfo(prop, info, sbx) { }); } else if (prop) { info.push({label: 'Insulin on Board', value: prop.displayIOB + 'U'}); + info.push({label: 'Current target', value: 'Low: '+sbx.data.profile.getLowBGTarget(sbx.time) + ' High: ' + sbx.data.profile.getHighBGTarget(sbx.time)}); info.push({label: 'Sensitivity', value: '-' + sbx.data.profile.getSensitivity(sbx.time) + ' ' + sbx.settings.units + '/U'}); info.push({label: 'Expected effect', value: prop.displayIOB + ' x -' + sbx.data.profile.getSensitivity(sbx.time) + ' = -' + prop.effectDisplay + ' ' + sbx.settings.units}); info.push({label: 'Expected outcome', value: sbx.lastScaledSGV() + '-' + prop.effectDisplay + ' = ' + prop.outcomeDisplay + ' ' + sbx.settings.units}); @@ -205,7 +225,20 @@ function pushInfo(prop, info, sbx) { var carbEquivalent = Math.ceil(Math.abs(sbx.data.profile.getCarbRatio() * prop.bolusEstimateDisplay)); info.unshift({label: 'Carb Equivalent', value: prop.bolusEstimateDisplay + 'U * ' + sbx.data.profile.getCarbRatio() + ' = ' + carbEquivalent + 'g'}); info.unshift({label: 'Current Carb Ratio', value: '1U / ' + sbx.data.profile.getCarbRatio() + ' g'}); - info.unshift({label: '-BWP', value: prop.bolusEstimateDisplay + 'U, maybe covered by carbs?'}); + + if (prop.mealEatenRecently) info.unshift({label: 'Meal eaten recently', value: 'excess IOB is probably your meal bolus?'}); + + if (!prop.belowLowTarget) { + info.unshift({label: '-BWP', value: 'Excess insulin equivalent ' + prop.bolusEstimateDisplay + 'U more than needed to reach low target, not account for carbs'}); + } + + if (prop.belowLowTarget) { + if (prop.iob > 0) { + info.unshift({label: '-BWP', value: 'Excess insulin equivalent ' + prop.bolusEstimateDisplay + 'U more than needed to reach low target, MAKE SURE IOB IS COVERED BY CARBS'}); + } else { + info.unshift({label: '-BWP', value: prop.bolusEstimateDisplay + 'U reduction needed in active insulin to reach low target, too much basal?'}); + } + } } } else { info.push({label: 'Notice', value: 'required info missing'}); From 2acda1d775ad089e07fe01deeaffb645e0e9c258 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Mon, 7 Sep 2015 00:11:18 +0300 Subject: [PATCH 842/937] Remove the 'undefined' message from logging --- lib/websocket.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/websocket.js b/lib/websocket.js index 0e3b883cb12..7af5f8e009e 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -30,7 +30,7 @@ function init (env, ctx, server) { function emitData (delta) { if (lastData.cals) { - console.log('running websocket.emitData', ctx.data.lastUpdated, delta.recentsgvs && delta.sgvdataupdate.length); + console.log('running websocket.emitData', ctx.data.lastUpdated); io.emit('dataUpdate', delta); } } From f4b0d496e6a0693584a825551e4bc7f253aba4af Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 6 Sep 2015 15:42:37 -0700 Subject: [PATCH 843/937] use event.target instead of this --- lib/client/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/client/index.js b/lib/client/index.js index 2c6138e6c67..906c8010eb0 100644 --- a/lib/client/index.js +++ b/lib/client/index.js @@ -417,9 +417,9 @@ client.init = function init(serverSettings, plugins) { updateTitle(); } - function snoozeAlarm (e) { - stopAlarm(true, $(this).data('snooze-time')); - e.preventDefault(); + function snoozeAlarm (event) { + stopAlarm(true, $(event.target).data('snooze-time')); + event.preventDefault(); } function playAlarm(audio) { From 383c95cffe5b2ab25fa9ceded002f677bb91615c Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 6 Sep 2015 16:03:57 -0700 Subject: [PATCH 844/937] codacy fixes; use times module instead of another time constant --- lib/plugins/boluswizardpreview.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 5dc331e81ec..ee99130be86 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -3,7 +3,6 @@ var _ = require('lodash'); var levels = require('../levels'); var times = require('../times'); -var CONST_60_MINUTES_IN_MS = 3600000; function init() { @@ -133,12 +132,11 @@ function init() { var treatment = sbx.data.treatments[i]; var delta = now - treatment.mills; if (delta < timeSinceLastMeal) { timeSinceLastMeal = delta;} - } - - results.timeSinceLastMeal = timeSinceLastMeal; - results.mealEatenRecently = false; - if (timeSinceLastMeal < CONST_60_MINUTES_IN_MS) { results.mealEatenRecently = true; } - + } + + results.timeSinceLastMeal = timeSinceLastMeal; + results.mealEatenRecently = timeSinceLastMeal < times.mins(60).msecs; + var target_high = profile.getHighBGTarget(sbx.time); var sens = profile.getSensitivity(sbx.time); @@ -226,7 +224,9 @@ function pushInfo(prop, info, sbx) { info.unshift({label: 'Carb Equivalent', value: prop.bolusEstimateDisplay + 'U * ' + sbx.data.profile.getCarbRatio() + ' = ' + carbEquivalent + 'g'}); info.unshift({label: 'Current Carb Ratio', value: '1U / ' + sbx.data.profile.getCarbRatio() + ' g'}); - if (prop.mealEatenRecently) info.unshift({label: 'Meal eaten recently', value: 'excess IOB is probably your meal bolus?'}); + if (prop.mealEatenRecently) { + info.unshift({label: 'Meal eaten recently', value: 'excess IOB is probably your meal bolus?'}); + } if (!prop.belowLowTarget) { info.unshift({label: '-BWP', value: 'Excess insulin equivalent ' + prop.bolusEstimateDisplay + 'U more than needed to reach low target, not account for carbs'}); From e9791e32b39a06d16c666e742cd4d24b94d543fc Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 6 Sep 2015 16:43:09 -0700 Subject: [PATCH 845/937] fixes to use sbx.time so this works in retro mode --- lib/plugins/boluswizardpreview.js | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index ee99130be86..c5b0b0a50d9 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -125,17 +125,15 @@ function init() { results.outcome = scaled - results.effect; var delta = 0; - var timeSinceLastMeal = 1000000000; - var now = (new Date).getTime(); - - for (var i = 0; i < sbx.data.treatments.length; i++) { - var treatment = sbx.data.treatments[i]; - var delta = now - treatment.mills; - if (delta < timeSinceLastMeal) { timeSinceLastMeal = delta;} - } + var lastMeal = _.findLast(sbx.data.treatments, function eachTreatment (treatment) { + console.info('>>>findLast', treatment, new Date(sbx.time)); + return treatment.mills <= sbx.time && treatment.carbs > 0; + }); - results.timeSinceLastMeal = timeSinceLastMeal; - results.mealEatenRecently = timeSinceLastMeal < times.mins(60).msecs; + results.timeSinceLastMeal = lastMeal ? sbx.time - lastMeal.mills : 1000000000; + results.mealEatenRecently = results.timeSinceLastMeal < times.mins(60).msecs; + + console.info('>>>>>lastMeal', lastMeal, results); var target_high = profile.getHighBGTarget(sbx.time); var sens = profile.getSensitivity(sbx.time); @@ -149,10 +147,10 @@ function init() { var target_low = profile.getLowBGTarget(sbx.time); - results.belowLowTarget = false; - if (scaled < target_low) { - results.belowLowTarget = true; - } + results.belowLowTarget = false; + if (scaled < target_low) { + results.belowLowTarget = true; + } if (results.outcome < target_low) { delta = Math.abs(results.outcome - target_low); From b7282597433573ce769d3880dc5a5dfee0f70948 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 6 Sep 2015 17:14:22 -0700 Subject: [PATCH 846/937] remove debug --- lib/plugins/boluswizardpreview.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index c5b0b0a50d9..45eecf72ca5 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -126,15 +126,12 @@ function init() { var delta = 0; var lastMeal = _.findLast(sbx.data.treatments, function eachTreatment (treatment) { - console.info('>>>findLast', treatment, new Date(sbx.time)); return treatment.mills <= sbx.time && treatment.carbs > 0; }); results.timeSinceLastMeal = lastMeal ? sbx.time - lastMeal.mills : 1000000000; results.mealEatenRecently = results.timeSinceLastMeal < times.mins(60).msecs; - console.info('>>>>>lastMeal', lastMeal, results); - var target_high = profile.getHighBGTarget(sbx.time); var sens = profile.getSensitivity(sbx.time); From bb9494cd643ebea0852b90471c2191f8af35bbef Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 6 Sep 2015 17:17:17 -0700 Subject: [PATCH 847/937] include level when creating stale data alarms so we use the correct snooze options --- lib/client/index.js | 7 +++++-- lib/settings.js | 16 ++++++++-------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/lib/client/index.js b/lib/client/index.js index 906c8010eb0..de0c626c0f2 100644 --- a/lib/client/index.js +++ b/lib/client/index.js @@ -516,9 +516,12 @@ client.init = function init(serverSettings, plugins) { currentAlarmType = alarm.type; console.info('generating timeAgoAlarm', alarm.type); container.addClass('alarming-timeago'); - var message = {'title': 'Last data received ' + [ago.value, ago.label].join(' ')}; + var notify = { + title: 'Last data received ' + [ago.value, ago.label].join(' ') + , level: level === 'urgent' ? 2 : 1 + }; var sound = level === 'warn' ? alarmSound : urgentAlarmSound; - generateAlarm(sound, message); + generateAlarm(sound, notify); } container.toggleClass('alarming-timeago', ago.status !== 'current'); diff --git a/lib/settings.js b/lib/settings.js index 4d8848d87e9..dbb8cb1af68 100644 --- a/lib/settings.js +++ b/lib/settings.js @@ -236,20 +236,20 @@ function init ( ) { } function snoozeMinsForAlarmEvent (notify) { - var snoozeTime = [30]; + var snoozeTime; if (notify.eventName === 'high' && notify.level === levels.URGENT && settings.alarmUrgentHigh) { - snoozeTime = settings.alarmUrgentHighMins || snoozeTime; + snoozeTime = settings.alarmUrgentHighMins; } else if (notify.eventName === 'high' && settings.alarmHigh) { - snoozeTime = settings.alarmHighMins || snoozeTime; + snoozeTime = settings.alarmHighMins; } else if (notify.eventName === 'low' && notify.level === levels.URGENT && settings.alarmUrgentLow) { - snoozeTime = settings.alarmUrgentLowMins || snoozeTime; + snoozeTime = settings.alarmUrgentLowMins; } else if (notify.eventName === 'low' && settings.alarmLow) { - snoozeTime = settings.alarmLowMins || snoozeTime; + snoozeTime = settings.alarmLowMins; } else if (notify.level === levels.URGENT) { - snoozeTime = settings.alarmUrgentMins || snoozeTime; - } else if (notify.level === levels.WARN) { - snoozeTime = settings.alarmWarnMins || snoozeTime; + snoozeTime = settings.alarmUrgentMins; + } else { + snoozeTime = settings.alarmWarnMins; } return snoozeTime; From 5f67f1f33f35f881f1d1135c082cd1b9015a3402 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 6 Sep 2015 17:44:20 -0700 Subject: [PATCH 848/937] changing 'meal eaten recently' to 'recent carbs' based on some feedback --- lib/plugins/boluswizardpreview.js | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 45eecf72ca5..ad80856fd80 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -125,12 +125,13 @@ function init() { results.outcome = scaled - results.effect; var delta = 0; - var lastMeal = _.findLast(sbx.data.treatments, function eachTreatment (treatment) { - return treatment.mills <= sbx.time && treatment.carbs > 0; + var recentCarbs = _.findLast(sbx.data.treatments, function eachTreatment (treatment) { + return treatment.mills <= sbx.time && + sbx.time - treatment.mills < times.mins(60).msecs && + treatment.carbs > 0; }); - results.timeSinceLastMeal = lastMeal ? sbx.time - lastMeal.mills : 1000000000; - results.mealEatenRecently = results.timeSinceLastMeal < times.mins(60).msecs; + results.recentCarbs = recentCarbs; var target_high = profile.getHighBGTarget(sbx.time); var sens = profile.getSensitivity(sbx.time); @@ -219,19 +220,27 @@ function pushInfo(prop, info, sbx) { info.unshift({label: 'Carb Equivalent', value: prop.bolusEstimateDisplay + 'U * ' + sbx.data.profile.getCarbRatio() + ' = ' + carbEquivalent + 'g'}); info.unshift({label: 'Current Carb Ratio', value: '1U / ' + sbx.data.profile.getCarbRatio() + ' g'}); - if (prop.mealEatenRecently) { - info.unshift({label: 'Meal eaten recently', value: 'excess IOB is probably your meal bolus?'}); + if (prop.recentCarbs) { + info.unshift({ + label: 'Recent carbs' + , value: prop.recentCarbs.carbs + 'g @ ' + moment(prop.recentCarbs.mills).format('LT')}); } if (!prop.belowLowTarget) { - info.unshift({label: '-BWP', value: 'Excess insulin equivalent ' + prop.bolusEstimateDisplay + 'U more than needed to reach low target, not account for carbs'}); + info.unshift({ + label: '-BWP' + , value: 'Excess insulin equivalent ' + prop.bolusEstimateDisplay + 'U more than needed to reach low target, not account for carbs'}); } if (prop.belowLowTarget) { if (prop.iob > 0) { - info.unshift({label: '-BWP', value: 'Excess insulin equivalent ' + prop.bolusEstimateDisplay + 'U more than needed to reach low target, MAKE SURE IOB IS COVERED BY CARBS'}); + info.unshift({ + label: '-BWP' + , value: 'Excess insulin equivalent ' + prop.bolusEstimateDisplay + 'U more than needed to reach low target, MAKE SURE IOB IS COVERED BY CARBS'}); } else { - info.unshift({label: '-BWP', value: prop.bolusEstimateDisplay + 'U reduction needed in active insulin to reach low target, too much basal?'}); + info.unshift({ + label: '-BWP' + , value: prop.bolusEstimateDisplay + 'U reduction needed in active insulin to reach low target, too much basal?'}); } } } From de1f000b92b6c1658b1a74af79f9e90b5b705d8f Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 7 Sep 2015 01:37:53 -0700 Subject: [PATCH 849/937] get thresholds from settings --- static/report/js/report.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/static/report/js/report.js b/static/report/js/report.js index 64a24fa12c4..8639e0985a8 100644 --- a/static/report/js/report.js +++ b/static/report/js/report.js @@ -34,8 +34,14 @@ var daystoshow = {}; var targetBGdefault = { - 'mg/dl': { low: 72, high: 180 }, - 'mmol': { low: 4, high: 10 } + 'mg/dl': { + low: client.settings.thresholds.bgTargetBottom + , high: client.settings.thresholds.bgTargetTop + } + , 'mmol': { + low: client.utils.scaleMgdl(client.settings.thresholds.bgTargetBottom) + , high: client.utils.scaleMgdl(client.settings.thresholds.bgTargetTop) + } }; var ONE_MIN_IN_MS = 60000; From fbdec7a3fa9491a5ea4a1c5854c6181758f6e06b Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 7 Sep 2015 02:26:31 -0700 Subject: [PATCH 850/937] version bump for 0.8.1-beta1 --- bower.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index cc508793f99..bc5aefbfcd8 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "nightscout", - "version": "0.8.1", + "version": "0.8.1-beta1", "dependencies": { "angularjs": "1.3.0-beta.19", "bootstrap": "~3.2.0", diff --git a/package.json b/package.json index c8f043e202d..c0fcc280ca3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Nightscout", - "version": "0.8.1", + "version": "0.8.1-beta1", "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", From 313ab47465d75c51548c6726f45f7bcc66205eba Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Mon, 7 Sep 2015 15:27:55 +0200 Subject: [PATCH 851/937] jquery-ui missing in index.html --- static/index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/static/index.html b/static/index.html index 5c2b9d58b8f..122af64b1e1 100644 --- a/static/index.html +++ b/static/index.html @@ -254,6 +254,7 @@ + From 3f55e5c497cfe3ae5f086fd1b39a8c2fcd883a2d Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Mon, 7 Sep 2015 19:32:07 +0200 Subject: [PATCH 852/937] Update index.html --- static/index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/static/index.html b/static/index.html index 122af64b1e1..cd9995cbb57 100644 --- a/static/index.html +++ b/static/index.html @@ -30,6 +30,7 @@ +
    From 5a5f1418750353fe1ecccf2dd775eed9e99de7f8 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 7 Sep 2015 10:51:59 -0700 Subject: [PATCH 853/937] bump to 0.8.1-beta1b --- bower.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index bc5aefbfcd8..db6a531d295 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "nightscout", - "version": "0.8.1-beta1", + "version": "0.8.1-beta1b", "dependencies": { "angularjs": "1.3.0-beta.19", "bootstrap": "~3.2.0", diff --git a/package.json b/package.json index c0fcc280ca3..6398d65ecc7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Nightscout", - "version": "0.8.1-beta1", + "version": "0.8.1-beta1b", "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", From c82f8fd9f14c5d9e37a062ae829fca7c622d016b Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Mon, 7 Sep 2015 20:59:20 +0200 Subject: [PATCH 854/937] fixed bug on counting scale when date are already cached --- static/css/report.css | 4 ++-- static/report/js/report.js | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/static/css/report.css b/static/css/report.css index d311c8d4b58..d0aef41c81e 100644 --- a/static/css/report.css +++ b/static/css/report.css @@ -19,8 +19,8 @@ body { } #glucosedistribution-overviewchart { - width: 3.0in; - height: 3in; + width: 2.8in; + height: 2.8in; float:left; } #percentile-chart { diff --git a/static/report/js/report.js b/static/report/js/report.js index 8639e0985a8..40814ab58cc 100644 --- a/static/report/js/report.js +++ b/static/report/js/report.js @@ -456,6 +456,9 @@ datastorage.allstatsrecords = datastorage.allstatsrecords.concat(datastorage[day].statsrecords); datastorage.alldays++; }); + options.maxInsulinValue = maxInsulinValue; + options.maxCarbsValue = maxCarbsValue; + report_plugins.eachPlugin(function (plugin) { // jquery plot doesn't draw to hidden div @@ -634,8 +637,6 @@ datastorage[day] = data; - options.maxInsulinValue = maxInsulinValue; - options.maxCarbsValue = maxCarbsValue; callback(); } From b27a08e810ef7e5b54ec4cff8dfaf900fb43f7f1 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Mon, 7 Sep 2015 21:21:30 +0200 Subject: [PATCH 855/937] allow css injection from plugin --- lib/report_plugins/glucosedistribution.js | 16 ++++++++++++++++ lib/report_plugins/index.js | 7 +++++++ static/css/report.css | 17 ++--------------- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/lib/report_plugins/glucosedistribution.js b/lib/report_plugins/glucosedistribution.js index ce03b12d791..2158dfdf3fb 100644 --- a/lib/report_plugins/glucosedistribution.js +++ b/lib/report_plugins/glucosedistribution.js @@ -30,6 +30,22 @@ glucosedistribution.html = function html(client) { return ret; }; +glucosedistribution.css = + '#glucosedistribution-overviewchart { \ + width: 2.8in; \ + height: 2.8in; \ + float:left; \ + } \ + #glucosedistribution-placeholder .tdborder { \ + width:80px; \ + border: 1px #ccc solid; \ + margin: 0; \ + padding: 1px; \ + }' + ; + + + glucosedistribution.report = function report_glucosedistribution(datastorage,daystoshow,options) { var Nightscout = window.Nightscout; var client = Nightscout.client; diff --git a/lib/report_plugins/index.js b/lib/report_plugins/index.js index f8470a4b6be..b2c929ec778 100644 --- a/lib/report_plugins/index.js +++ b/lib/report_plugins/index.js @@ -44,6 +44,13 @@ function init() { if (p.html && ! $('#' + p.name).length) { $('#tabnav').append($('
  • ').attr('id',p.name).addClass('menutab').append(client.translate(p.label))); } + // add css + if (p.css) { + $(' - - - - - -
    -

    Nightscout: Treatments

    - - - - - - - - - - - - - - - - - - - - - - - -
    TimeEvent TypeBGInsulinCarbsEntered ByNotes
    {{treatment.created_at | date:'short'}}{{treatment.eventType}}{{glucoseDisplay(treatment)}}{{treatment.insulin | number: 2}}{{treatment.carbs}}{{treatment.enteredBy}}{{treatment.notes}}
    -
    - - - From d38f01298442dcd8d07a2ecfbcef0855e857decf Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 13 Sep 2015 10:26:15 -0700 Subject: [PATCH 874/937] 0.8.1-beta2 bump --- bower.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index 077f9662606..f2dc60ab9a9 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "nightscout", - "version": "0.8.1-beta1b", + "version": "0.8.1-beta2", "dependencies": { "jquery": "2.1.0", "jQuery-Storage-API": "~1.7.2", diff --git a/package.json b/package.json index 3bced14c946..a6b20979b22 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Nightscout", - "version": "0.8.1-beta1b", + "version": "0.8.1-beta2", "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", From 74f675c25465c6da61f1d17b6332a2b1bb753646 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Sun, 13 Sep 2015 21:27:55 +0200 Subject: [PATCH 875/937] fix translation in careportal --- lib/client/careportal.js | 11 ++++++++++- lib/report_plugins/treatments.js | 13 ++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/client/careportal.js b/lib/client/careportal.js index 1346b98c024..68cd30df540 100644 --- a/lib/client/careportal.js +++ b/lib/client/careportal.js @@ -57,6 +57,15 @@ function init (client, $) { }); }; + careportal.resolveEventName = function resolveEventName(value) { + _.each(Nightscout.client.careportal.events, function eachEvent(e) { + if (e.val === value) { + value = e.name; + } + }); + return value; + } + careportal.prepare = function prepare ( ) { careportal.prepareEvents(); $('#eventType').val('BG Check'); @@ -100,7 +109,7 @@ function init (client, $) { function buildConfirmText(data) { var text = [ translate('Please verify that the data entered is correct') + ': ' - , translate('Event Type') + ': ' + translate(data.eventType) + , translate('Event Type') + ': ' + translate(careportal.resolveEventName(data.eventType)) ]; function pushIf (check, valueText) { diff --git a/lib/report_plugins/treatments.js b/lib/report_plugins/treatments.js index 85c2ce8d803..9a4220432c2 100644 --- a/lib/report_plugins/treatments.js +++ b/lib/report_plugins/treatments.js @@ -74,7 +74,7 @@ treatments.report = function report_treatments(datastorage, daystoshow, options) function buildConfirmText(data) { var text = [ translate('Delete this treatment?')+'\n' - , '\n'+translate('Event Type')+': ' + translate(resolveEventName(data.eventType)) + , '\n'+translate('Event Type')+': ' + translate(client.careportal.resolveEventName(data.eventType)) ]; function pushIf (check, valueText) { @@ -212,15 +212,6 @@ treatments.report = function report_treatments(datastorage, daystoshow, options) return true; } - function resolveEventName(value) { - _.each(Nightscout.client.careportal.events, function eachEvent(e) { - if (e.val === value) { - value = e.name; - } - }); - return value; - } - function maybePrevent (event) { if (event) { event.preventDefault(); @@ -259,7 +250,7 @@ treatments.report = function report_treatments(datastorage, daystoshow, options) .append($('').addClass('editTreatment').css('cursor','pointer').attr('title',translate('Edit record')).attr('src',icon_edit).attr('data',JSON.stringify(tr)).attr('day',day)) ) .append($('
  • ').append(new Date(tr.created_at).toLocaleTimeString().replace(/([\d]+:[\d]{2})(:[\d]{2})(.*)/, '$1$3'))) - .append($('').append(tr.eventType ? translate(resolveEventName(tr.eventType)) : '')) + .append($('').append(tr.eventType ? translate(client.careportal.resolveEventName(tr.eventType)) : '')) .append($('').attr('align','center').append(tr.glucose ? tr.glucose + ' ('+translate(tr.glucoseType)+')' : '')) .append($('').attr('align','center').append(tr.insulin ? tr.insulin : '')) .append($('').attr('align','center').append(tr.carbs ? tr.carbs : '')) From 87be449bd98cd94cfd5c96a061668a39d0cda9f6 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Sun, 13 Sep 2015 21:31:05 +0200 Subject: [PATCH 876/937] remove unneeded refrence --- lib/client/careportal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/client/careportal.js b/lib/client/careportal.js index 68cd30df540..0fb58c316d8 100644 --- a/lib/client/careportal.js +++ b/lib/client/careportal.js @@ -58,7 +58,7 @@ function init (client, $) { }; careportal.resolveEventName = function resolveEventName(value) { - _.each(Nightscout.client.careportal.events, function eachEvent(e) { + _.each(careportal.events, function eachEvent(e) { if (e.val === value) { value = e.name; } From d3721fa2da4d904d18315b13dda81d5be1bfad70 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Sun, 13 Sep 2015 21:34:58 +0200 Subject: [PATCH 877/937] show glucoseType in confirm dialog only when glucose entered --- lib/client/careportal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/client/careportal.js b/lib/client/careportal.js index 0fb58c316d8..9d7a051d969 100644 --- a/lib/client/careportal.js +++ b/lib/client/careportal.js @@ -119,7 +119,7 @@ function init (client, $) { } pushIf(data.glucose, translate('Blood Glucose') + ': ' + data.glucose); - pushIf(data.glucoseType, translate('Measurement Method') + ': ' + translate(data.glucoseType)); + pushIf(data.glucose, translate('Measurement Method') + ': ' + translate(data.glucoseType)); pushIf(data.carbs, translate('Carbs Given') + ': ' + data.carbs); pushIf(data.insulin, translate('Insulin Given') + ': ' + data.insulin); From e86c61f62fb9283e40437dde137123b8e2364fac Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 13 Sep 2015 13:04:09 -0700 Subject: [PATCH 878/937] add gear icon before the Setting legend --- static/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/index.html b/static/index.html index 1e61ffaaeca..3140c402d6e 100644 --- a/static/index.html +++ b/static/index.html @@ -85,7 +85,7 @@
  • Profile Editor
  • - Settings + Settings
    Units
    From 111913d47d15be37cc6ec989fa87f4f579d34f8f Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 13 Sep 2015 13:04:55 -0700 Subject: [PATCH 879/937] close drawer when menu links are clicked and prepare form before opening --- lib/client/browser-utils.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/client/browser-utils.js b/lib/client/browser-utils.js index ba3ca71989a..c6f17735c74 100644 --- a/lib/client/browser-utils.js +++ b/lib/client/browser-utils.js @@ -37,6 +37,10 @@ function init ($) { event.preventDefault(); }); + $('.navigation a').click(function navigationClick ( ) { + closeDrawer('#drawer'); + }); + function reload() { //strip '#' so form submission does not fail var url = window.location.href; @@ -80,9 +84,8 @@ function init ($) { closeOpenDraw(function () { lastOpenedDrawer = id; - $(id).css('display', 'block').animate({right: '0'}, 300, function () { - if (callback) { callback(); } - }); + if (callback) { callback(); } + $(id).css('display', 'block').animate({right: '0'}, 300); }); } From d39293338336c7878c7092977474a3ba72643ea3 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 13 Sep 2015 13:14:35 -0700 Subject: [PATCH 880/937] rename open callback to prepare since it's called before opening --- lib/client/browser-utils.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/client/browser-utils.js b/lib/client/browser-utils.js index c6f17735c74..fc7baf193a1 100644 --- a/lib/client/browser-utils.js +++ b/lib/client/browser-utils.js @@ -73,7 +73,7 @@ function init ($) { }); } - function openDrawer(id, callback) { + function openDrawer(id, prepare) { function closeOpenDraw(callback) { if (lastOpenedDrawer) { closeDrawer(lastOpenedDrawer, callback); @@ -84,17 +84,17 @@ function init ($) { closeOpenDraw(function () { lastOpenedDrawer = id; - if (callback) { callback(); } + if (prepare) { prepare(); } $(id).css('display', 'block').animate({right: '0'}, 300); }); } - function toggleDrawer(id, openCallback, closeCallback) { + function toggleDrawer(id, openPrepare, closeCallback) { if (lastOpenedDrawer === id) { closeDrawer(id, closeCallback); } else { - openDrawer(id, openCallback); + openDrawer(id, openPrepare); } } From 8c88e4655f8c8d886196a81537ad99ea11cf14e2 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Sun, 13 Sep 2015 23:22:06 +0200 Subject: [PATCH 881/937] uppercase in Profile Editor --- lib/language.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/language.js b/lib/language.js index 8a665a8fed8..9ddfefd965b 100644 --- a/lib/language.js +++ b/lib/language.js @@ -2367,7 +2367,7 @@ function init() { ,fi: 'Lähetä tiedot' ,nb: 'Lagre' } - ,'Profile editor' : { + ,'Profile Editor' : { cs: 'Editor profilu' ,de: 'Profil-Einstellungen' ,es: 'Editor de perfil' From 876dc53f3da5eb7caf017ff5eb15824e5813240b Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Sun, 13 Sep 2015 23:29:57 +0200 Subject: [PATCH 882/937] Reporting tool -> Reports --- lib/language.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/language.js b/lib/language.js index 9ddfefd965b..ab02767fca3 100644 --- a/lib/language.js +++ b/lib/language.js @@ -2382,7 +2382,7 @@ function init() { ,fi: 'Profiilin muokkaus' ,nb: 'Profileditor' } - ,'Reporting tool' : { + ,'Reports' : { cs: 'Výkazy' ,de: 'Berichte' ,es: 'Herramienta de informes' From b66d60185e12709b1ae1fda5efb06c527af8dca8 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Mon, 14 Sep 2015 12:59:20 +0200 Subject: [PATCH 883/937] codacy --- lib/client/careportal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/client/careportal.js b/lib/client/careportal.js index 9d7a051d969..3d153e5ee42 100644 --- a/lib/client/careportal.js +++ b/lib/client/careportal.js @@ -64,7 +64,7 @@ function init (client, $) { } }); return value; - } + }; careportal.prepare = function prepare ( ) { careportal.prepareEvents(); From 8c0dac67325fece4eeefa4d813ee16e78d0663a9 Mon Sep 17 00:00:00 2001 From: juliatakeuti Date: Mon, 14 Sep 2015 17:25:39 -0300 Subject: [PATCH 884/937] Brazilian Portuguese update --- lib/language.js | 67 ++++++++++++++++++++++++++----------------------- 1 file changed, 36 insertions(+), 31 deletions(-) diff --git a/lib/language.js b/lib/language.js index ab02767fca3..6f94313f2ee 100644 --- a/lib/language.js +++ b/lib/language.js @@ -121,7 +121,7 @@ function init() { ,de: 'Sa' ,es: 'Sab' ,fr: 'Sa' - ,pt: 'Sa' + ,pt: 'Sab' ,sv: 'Lör' ,ro: 'Sa' ,bg: 'Съб' @@ -421,7 +421,7 @@ function init() { ,de: 'Bis' ,es: 'Hasta' ,fr: 'À' - ,pt: 'Para' + ,pt: 'a' ,ro: 'La' ,bg: 'До' ,hr: 'Do' @@ -556,7 +556,7 @@ function init() { ,de: 'Darstellen' ,es: 'Visualizar' ,fr: 'Afficher' - ,pt: 'Mostrar' + ,pt: 'Visualizar' ,ro: 'Afișează' ,bg: 'Покажи' ,hr: 'Prikaži' @@ -842,7 +842,7 @@ function init() { ,de: 'Keine Daten verfügbar' ,es: 'No hay datos disponibles' ,fr: 'Pas de données disponibles' - ,pt: 'não há dados' + ,pt: 'Não há dados' ,ro: 'Fără date' ,bg: 'Няма данни за показване' ,hr: 'Nema raspoloživih podataka' @@ -1097,7 +1097,7 @@ function init() { ,de: 'Gesamttage' ,es: 'Total de días' ,fr: 'jours totaux' - ,pt: 'dias total' + ,pt: 'dias no total' ,sv: 'antal dagar' ,ro: 'total zile' ,bg: 'общо за деня' @@ -1202,7 +1202,7 @@ function init() { ,de: 'Max' ,es: 'Max' ,fr: 'Max' - ,pt: 'Max' + ,pt: 'Máx' ,sv: 'Max' ,ro: 'Max' ,bg: 'Макс.' @@ -1217,7 +1217,7 @@ function init() { ,de: 'Min' ,es: 'Min' ,fr: 'Min' - ,pt: 'Min' + ,pt: 'Mín' ,sv: 'Min' ,ro: 'Min' ,bg: 'Мин.' @@ -1232,7 +1232,7 @@ function init() { ,de: 'Prognose HbA1c*' ,es: 'Estimación de HbA1c*' ,fr: 'Estimation HbA1c*' - ,pt: 'A1c estimada' + ,pt: 'HbA1c estimada*' ,ro: 'HbA1C estimată' ,bg: 'Очакван HbA1c' ,hr: 'Procjena HbA1c-a' @@ -1293,7 +1293,7 @@ function init() { ,de: 'Keine API-Prüfsumme gespeichert. Bitte API-Prüfsumme eingeben.' ,es: 'No se ha almacenado ningún hash todavía. Debe introducir su secreto API.' ,fr: 'Pas de secret API existant. Vous devez l\'en entrer.' - ,pt: 'Hash de segredo de API inexistente. Entre um segredo de API' + ,pt: 'Hash de segredo de API inexistente. Insira um segredo de API.' ,ro: 'Încă nu există cheie API secretă. Aceasta trebuie introdusă.' ,bg: 'Няма запаметена API парола. Tрябва да въведете API парола' ,hr: 'Nema pohranjenog API tajnog hasha. Unesite tajni API' @@ -1503,7 +1503,7 @@ function init() { ,de: 'Fehlerhafte API-Prüfsumme' ,es: 'Secreto API incorrecto' ,fr: 'Secret API erroné' - ,pt: 'Segredo de API fraco' + ,pt: 'Segredo de API incorreto' ,ro: 'Cheie API greșită' ,bg: 'Некоректна API парола' ,hr: 'Neispravan tajni API' @@ -1713,7 +1713,7 @@ function init() { ,de: 'Bearbeitung' ,es: 'Tratamientos' ,fr: 'Traitements' - ,pt: 'Tratamentos' + ,pt: 'Procedimentos' ,sv: 'Behandling' ,ro: 'Tratamente' ,bg: 'Събития' @@ -1788,7 +1788,7 @@ function init() { ,de: 'Bearbeitung löschen' ,es: '¿Borrar este tratamiento?' ,fr: 'Effacer ce traitement?' - ,pt: 'Apagar este tratamento' + ,pt: 'Apagar este procedimento?' ,ro: 'Șterge acest eveniment?' ,bg: 'Изтрий това събитие' ,hr: 'Izbriši ovaj tretman?' @@ -1998,7 +1998,7 @@ function init() { ,de: 'Verwende verzehrte Kohlenhydrate zur Kalkulation' ,es: 'Usar la corrección de COB en los cálculos' ,fr: 'Utiliser les COB dans les calculs' - ,pt: 'Usar COB no cálculo' + ,pt: 'Usar correção de COB no cálculo' ,ro: 'Folosește COB în calcule' ,bg: 'Включи активните ВХ в изчислението' ,hr: 'Koristi aktivne UH u izračunu' @@ -2223,6 +2223,7 @@ function init() { ,de: '15 Min. später' ,es: '15 min más tarde' ,fr: '15 min après' + ,pt: '15 min depois' ,ro: 'după 15 min' ,bg: 'След 15 минути' ,hr: '15 minuta kasnije' @@ -2357,7 +2358,7 @@ function init() { ,de: 'Eingabe senden' ,es: 'Enviar formulario' ,fr: 'Formulaire de soumission' - ,pt: 'Submeter formulário' + ,pt: 'Enviar formulário' ,sv: 'Överför händelse' ,ro: 'Trimite formularul' ,bg: 'Въвеждане на данните' @@ -2387,7 +2388,7 @@ function init() { ,de: 'Berichte' ,es: 'Herramienta de informes' ,fr: 'Outil de rapport' - ,pt: 'Ferramenta de relatórios' + ,pt: 'Relatórios' ,sv: 'Rapportverktyg' ,ro: 'Instrument de rapoarte' ,bg: 'Статистика' @@ -2582,7 +2583,7 @@ function init() { ,de: 'Finger' ,es: 'Dedo' ,fr: 'Doigt' - ,pt: 'Dedo' + ,pt: 'Ponta de dedo' ,sv: 'Finger' ,ro: 'Deget' ,bg: 'От пръстта' @@ -2777,7 +2778,7 @@ function init() { ,de: 'Einstellungen' ,es: 'Ajustes' ,fr: 'Paramètres' - ,pt: 'Definições' + ,pt: 'Ajustes' ,sv: 'Inställningar' ,ro: 'Setări' ,bg: 'Настройки' @@ -2852,7 +2853,7 @@ function init() { ,de: 'Dateneingabe' ,es: 'Apuntar un tratamiento' ,fr: 'Entrer un traitement' - ,pt: 'Entre um tratamento' + ,pt: 'Entre um procedimento' ,ro: 'Înregistrează un eveniment' ,bg: 'Въвеждане на събитие' ,hr: 'Evidencija tretmana' @@ -2927,7 +2928,7 @@ function init() { ,de: 'Kohlenhydrate Korrektur' ,es: 'Hidratos de carbono de corrección' ,fr: 'Correction glucide' - ,pt: 'Carboidrato de correção' + ,pt: 'Correção com carboidrato' ,ro: 'Corecție de carbohidrați' ,bg: 'Корекция чрез въглехидрати' ,hr: 'Bolus za hranu' @@ -2970,6 +2971,7 @@ function init() { ,'Announcement' : { bg: 'Известяване' , fi: 'Tiedoitus' + ,pt: 'Aviso' } ,'Exercise' : { cs: 'Cvičení' @@ -3036,7 +3038,7 @@ function init() { ,de: 'Dexcom Sensor Start' ,es: 'Inicio de sensor Dexcom' ,fr: 'Démarrage senseur Dexcom' - ,pt: 'Início de sensor Dexcom' + ,pt: 'Início de sensor' ,sv: 'Dexcom sensorstart' ,ro: 'Pornire senzor Dexcom' ,bg: 'Поставяне на Декском сензор' @@ -3051,7 +3053,7 @@ function init() { ,de: 'Dexcom Sensor Wechsel' ,es: 'Cambio de sensor Dexcom' ,fr: 'Changement senseur Dexcom' - ,pt: 'Troca de sensor Dexcom' + ,pt: 'Troca de sensor' ,sv: 'Dexcom sensorbyte' ,ro: 'Schimbare senzor Dexcom' ,bg: 'Смяна на Декском сензор' @@ -3186,7 +3188,7 @@ function init() { ,de: 'Zeige alle Eingaben' ,es: 'Visualizar todos los tratamientos' ,fr: 'Voir tous les traitements' - ,pt: 'Visualizar todos os tratamentos' + ,pt: 'Visualizar todos os procedimentos' ,sv: 'Visa behandlingar' ,ro: 'Vezi toate evenimentele' ,bg: 'Преглед на всички събития' @@ -3231,7 +3233,7 @@ function init() { ,de: 'Achtung Hoch Alarm' ,es: 'Alarma de glucemia alta urgente' ,fr: 'Alarme haute urgente' - ,pt: 'Alarme de alto urgente' + ,pt: 'URGENTE: Alarme de glicemia alta' ,sv: 'Brådskande högt larmvärde' ,ro: 'Alarmă urgentă hiper' ,bg: 'Много висока КЗ' @@ -3246,7 +3248,7 @@ function init() { ,de: 'Hoch Alarm' ,es: 'Alarma de glucemia alta' ,fr: 'Alarme haute' - ,pt: 'Alarme de alto' + ,pt: 'Alarme de glicemia alta' ,sv: 'Högt larmvärde' ,ro: 'Alarmă hiper' ,bg: 'Висока КЗ' @@ -3261,7 +3263,7 @@ function init() { ,de: 'Tief Alarm' ,es: 'Alarma de glucemia baja' ,fr: 'Alarme basse' - ,pt: 'Alarme de baixo' + ,pt: 'Alarme de glicemia baixa' ,sv: 'Lågt larmvärde' ,ro: 'Alarmă hipo' ,bg: 'Ниска КЗ' @@ -3276,7 +3278,7 @@ function init() { ,de: 'Achtung Tief Alarm' ,es: 'Alarma de glucemia baja urgente' ,fr: 'Alarme basse urgente' - ,pt: 'Alarme de baixo urgente' + ,pt: 'URGENTE: Alarme de glicemia baixa' ,sv: 'Brådskande lågt larmvärde' ,ro: 'Alarmă urgentă hipo' ,bg: 'Много ниска КЗ' @@ -3351,7 +3353,7 @@ function init() { ,de: 'Wenn aktiviert wird die Anzeige von 22 Uhr - 6 Uhr gedimmt' ,es: 'Cuando esté activo, el brillo de la página bajará de 10pm a 6am.' ,fr: 'Si activé, la page sera assombire de 22:00 à 6:00' - ,pt: 'Se ativado, a página será escurecida de 22h a 6h' + ,pt: 'Se ativado, a página será escurecida entre 22h e 6h' ,sv: 'När aktiverad dimmas sidan mellan 22:00 - 06:00' ,ro: 'La activare va scădea iluminarea între 22 și 6' ,bg: 'Когато е активирано, страницата ще е затъмнена от 22-06ч' @@ -3441,7 +3443,7 @@ function init() { ,de: 'Bei Aktivierung erscheinen kleine weiße Punkte für Roh-Blutglukose Daten' ,es: 'Cuando esté activo, pequeños puntos blancos mostrarán los datos en crudo' ,fr: 'Si activé, des points blancs représenteront les données brutes' - ,pt: 'Se ativado, pontinhos brancos representarão os dados de glicemia não processados' + ,pt: 'Se ativado, pontos brancos representarão os dados de glicemia não processados' ,sv: 'När aktiverad visar de vita punkterna RAW-blodglukosevärden' ,ro: 'La activare vor apărea puncte albe reprezentând citirea brută a glicemiei' ,bg: 'Когато е активно, малки бели точки ще показват RAW данните' @@ -3501,7 +3503,7 @@ function init() { ,de: 'Farben' ,es: 'Colores' ,fr: 'Couleurs' - ,pt: 'Cores' + ,pt: 'Colorido' ,sv: 'Färg' ,ro: 'Colorată' ,bg: 'Цветна' @@ -3561,7 +3563,7 @@ function init() { ,de: 'Bolus Kalkulator' ,es: 'Bolus Wizard' ,fr: 'Calculateur de bolus' - ,pt: 'Bolus Wizard' + ,pt: 'Calculadora de bolus' ,sv: 'Boluskalkylator' ,ro: 'Calculator sugestie bolus' ,bg: 'Съветник при изчисление на болуса' @@ -3771,7 +3773,7 @@ function init() { ,de: 'Eingabe Typ' ,es: 'Tipo de tratamiento' ,fr: 'Type de traitement' - ,pt: 'Tipo de tratamento' + ,pt: 'Tipo de procedimento' ,sv: 'Behandlingstyp' ,ro: 'Tip tratament' ,bg: 'Вид събитие' @@ -3891,6 +3893,7 @@ function init() { ,de: 'Kohlenhydrate Zeit' ,es: 'Momento de la ingesta' ,fr: 'Moment de Glucide' + ,pt: 'Hora do carboidrato' ,bg: 'Ядене след' ,hr: 'Vrijeme unosa UH' ,sv: 'Kolhydratstid' @@ -3902,9 +3905,11 @@ function init() { ,'Language' : { cs: 'Jazyk' ,fi: 'Kieli' + ,pt: 'Língua' } ,'Update' : { // Update button cs: 'Aktualizovat' + ,pt: 'Atualizar' } }; From cc28d373a7c9e53221dbf98c5657bdef673a8ebb Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Wed, 9 Sep 2015 20:20:56 +0200 Subject: [PATCH 885/937] report.test.js coverage increase --- tests/reports.test.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/reports.test.js b/tests/reports.test.js index 9ce93c5bc31..4fc7c4cb9a8 100644 --- a/tests/reports.test.js +++ b/tests/reports.test.js @@ -174,6 +174,13 @@ describe('reports', function ( ) { $('#rp_from').val('2015/08/08'); $('#rp_to').val('2015/09/07'); + $('#rp_optionsraw').prop('checked',true); + $('#rp_optionsiob').prop('checked',true); + $('#rp_optionscob').prop('checked',true); + $('#rp_log').prop('checked',true); + $('#rp_show').click(); + + $('#rp_linear').prop('checked',true); $('#rp_show').click(); var result = $('body').html(); From feb8ad6a3f832aa490823f7a4ab8f4d936cf5b29 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Wed, 9 Sep 2015 20:26:25 +0200 Subject: [PATCH 886/937] commented out success report --- 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 4fc7c4cb9a8..2af431fc5b9 100644 --- a/tests/reports.test.js +++ b/tests/reports.test.js @@ -193,7 +193,7 @@ describe('reports', function ( ) { result.indexOf('
    0%100%0%28.338%68.78.80.216 (100%)118.38.910.611.718.32.7Correction Bolus250 (Sensor)0.75Mom '+translate('75%')+'
    ').css('width','300px').attr('align','left').append(translate('Notes'))) ); - Object.keys(daystoshow).forEach(function (day) { + sorteddaystoshow.forEach(function (day) { table.append($('
    ').attr('colspan','8').css('background','lightgray') .append($('').append(report_plugins.utils.localeDate(day))) ) ); - var treatments = datastorage[day].treatments; + var treatments = _.clone(datastorage[day].treatments); + if (options.order === report_plugins.consts.ORDER_NEWESTONTOP) { + treatments.reverse(); + } for (var t=0; t').addClass('border_bottom') diff --git a/static/report/index.html b/static/report/index.html index 7cfa86f787e..300372d157e 100644 --- a/static/report/index.html +++ b/static/report/index.html @@ -96,6 +96,17 @@

    Nightscout reporting

    + Order: + + +   + + +
    diff --git a/static/report/js/report.js b/static/report/js/report.js index eac70b7524e..f497532a260 100644 --- a/static/report/js/report.js +++ b/static/report/js/report.js @@ -33,6 +33,7 @@ var maxdays = 3 * 31; var datastorage = {}; var daystoshow = {}; + var sorteddaystoshow = []; var targetBGdefault = { 'mg/dl': { @@ -251,6 +252,7 @@ options.insulin = $('#rp_optionsinsulin').is(':checked'); options.carbs = $('#rp_optionscarbs').is(':checked'); options.scale = ( $('#rp_linear').is(':checked') ? report_plugins.consts.SCALE_LINEAR : report_plugins.consts.SCALE_LOG ); + options.order = ( $('#rp_oldestontop').is(':checked') ? report_plugins.consts.ORDER_OLDESTONTOP : report_plugins.consts.ORDER_NEWESTONTOP ); options.width = parseInt($('#rp_size :selected').attr('x')); options.height = parseInt($('#rp_size :selected').attr('y')); @@ -447,6 +449,15 @@ function dataLoadedCallback () { loadeddays++; if (loadeddays === dayscount) { + // sort array + sorteddaystoshow = []; + Object.keys(daystoshow).forEach(function (day) { + sorteddaystoshow.push(day); + }); + sorteddaystoshow.sort(); + if (options.order === report_plugins.consts.ORDER_NEWESTONTOP) { + sorteddaystoshow.reverse(); + } showreports(options); } } @@ -474,7 +485,7 @@ // jquery plot doesn't draw to hidden div $('#'+plugin.name+'-placeholder').css('display',''); //console.log('Drawing ',plugin.name); - plugin.report(datastorage,daystoshow,options); + plugin.report(datastorage,sorteddaystoshow,options); if (!$('#'+plugin.name).hasClass('selected')) { $('#'+plugin.name+'-placeholder').css('display','none'); } From eb96f9acd0c5e1b27ed7b22c4f5766bcf3e2a6e7 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Tue, 15 Sep 2015 23:00:53 +0200 Subject: [PATCH 914/937] codacy flotcandle.js --- static/report/js/flotcandle.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/static/report/js/flotcandle.js b/static/report/js/flotcandle.js index 0a6c23d6c2c..8a965e0f4bf 100644 --- a/static/report/js/flotcandle.js +++ b/static/report/js/flotcandle.js @@ -36,15 +36,15 @@ } function drawCandle(ctx, serie, width, dt, open, low, close, high){ if (open < close){ //Rising - y = offset.top + serie.yaxis.p2c(open) + y = offset.top + serie.yaxis.p2c(open); height = serie.yaxis.p2c(close) - serie.yaxis.p2c(open); - ctx.fillStyle = "#51FF21"; + ctx.fillStyle = '#51FF21'; } else { //Decending - y = offset.top + serie.yaxis.p2c(close) + y = offset.top + serie.yaxis.p2c(close); height = serie.yaxis.p2c(open) - serie.yaxis.p2c(close); - ctx.fillStyle = "#FF0000"; + ctx.fillStyle = '#FF0000'; } - ctx.strokeStyle = "#000000"; + ctx.strokeStyle = '#000000'; ctx.lineWidth = 0; x = offset.left + serie.xaxis.p2c(dt); From c7200f121f6252417fa1460a0b59c5fa681bc333 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Tue, 15 Sep 2015 23:36:01 +0200 Subject: [PATCH 915/937] reports css cleanup --- lib/report_plugins/dailystats.js | 14 +++ lib/report_plugins/glucosedistribution.js | 1 + lib/report_plugins/hourlystats.js | 12 ++- lib/report_plugins/percentile.js | 7 ++ lib/report_plugins/success.js | 28 ++++++ lib/report_plugins/treatments.js | 6 ++ static/css/report.css | 108 ++++++---------------- static/report/js/report.js | 2 - 8 files changed, 97 insertions(+), 81 deletions(-) diff --git a/lib/report_plugins/dailystats.js b/lib/report_plugins/dailystats.js index 59438c9083f..7fcbd3a15f8 100644 --- a/lib/report_plugins/dailystats.js +++ b/lib/report_plugins/dailystats.js @@ -21,6 +21,20 @@ dailystats.html = function html(client) { return ret; }; +dailystats.css = + '#dailystats-placeholder .tdborder {' + + ' width:80px;' + + ' border: 1px #ccc solid;' + + ' margin: 0;' + + ' padding: 1px;' + + ' text-align:center;' + + '}' + + '#dailystats-placeholder .inlinepiechart {' + + ' width: 2.0in;' + + ' height: 0.9in;' + + '}' + ; + dailystats.report = function report_dailystats(datastorage,sorteddaystoshow,options) { var Nightscout = window.Nightscout; var client = Nightscout.client; diff --git a/lib/report_plugins/glucosedistribution.js b/lib/report_plugins/glucosedistribution.js index a606d3d91bc..30f62761b3a 100644 --- a/lib/report_plugins/glucosedistribution.js +++ b/lib/report_plugins/glucosedistribution.js @@ -41,6 +41,7 @@ glucosedistribution.css = + ' border: 1px #ccc solid;' + ' margin: 0;' + ' padding: 1px;' + + ' text-align:center;' + '}' ; diff --git a/lib/report_plugins/hourlystats.js b/lib/report_plugins/hourlystats.js index 552f5a460e2..999b18d513b 100644 --- a/lib/report_plugins/hourlystats.js +++ b/lib/report_plugins/hourlystats.js @@ -22,6 +22,16 @@ hourlystats.html = function html(client) { return ret; }; +hourlystats.css = + '#hourlystats-overviewchart {' + + ' width: 100%;' + + ' min-width: 6.5in;' + + ' height: 5in;' + + '}' + + '#hourlystats-placeholder td {' + + ' text-align:center;' + + '}' ; + hourlystats.report = function report_hourlystats(datastorage, sorteddaystoshow, options) { //console.log(window); var ss = require('simple-statistics'); @@ -42,7 +52,7 @@ hourlystats.report = function report_hourlystats(datastorage, sorteddaystoshow, var d = new Date(record.displayTime); pivotedByHour[d.getHours()].push(record); }); - var table = $(''); + var table = $('
    '); var thead = $(''); $('').appendTo(thead); $('').appendTo(thead); diff --git a/lib/report_plugins/percentile.js b/lib/report_plugins/percentile.js index 9da62a906ad..680e7f66d86 100644 --- a/lib/report_plugins/percentile.js +++ b/lib/report_plugins/percentile.js @@ -23,6 +23,13 @@ percentile.html = function html(client) { return ret; }; +percentile.css = + '#percentile-chart {' + + ' width: 100%;' + + ' height: 100%;' + + '}' + ; + percentile.report = function report_percentile(datastorage, sorteddaystoshow, options) { var Nightscout = window.Nightscout; var client = Nightscout.client; diff --git a/lib/report_plugins/success.js b/lib/report_plugins/success.js index 8ecc924b5df..1ef01f1a16a 100644 --- a/lib/report_plugins/success.js +++ b/lib/report_plugins/success.js @@ -21,6 +21,34 @@ success.html = function html(client) { return ret; }; +success.css = + '#success-placeholder td {'+ + ' border: 1px #ccc solid;'+ + ' margin: 0;'+ + ' padding: 1px;'+ + ' text-align:center;'+ + '}'+ + '#success-placeholder .bad {'+ + ' background-color: #fcc;'+ + '}'+ + + '#success-placeholder .good {'+ + ' background-color: #cfc;'+ + '}'+ + + '#success-placeholder th:first-child {'+ + ' width: 30%;'+ + '}'+ + '#success-placeholder th {'+ + ' width: 10%;'+ + '}'+ + '#success-placeholder table {'+ + ' width: 100%;'+ + '}' + ; + + + success.report = function report_success(datastorage, sorteddaystoshow, options) { var Nightscout = window.Nightscout; var client = Nightscout.client; diff --git a/lib/report_plugins/treatments.js b/lib/report_plugins/treatments.js index 443ef6eadb6..899ae39bcc4 100644 --- a/lib/report_plugins/treatments.js +++ b/lib/report_plugins/treatments.js @@ -65,6 +65,12 @@ treatments.html = function html(client) { return ret; }; + +treatments.css = + '.border_bottom td {' + + ' border-bottom:1pt solid #eee;' + + '}' + ; treatments.report = function report_treatments(datastorage, sorteddaystoshow, options) { var Nightscout = window.Nightscout; diff --git a/static/css/report.css b/static/css/report.css index 030c8535244..991bf73be17 100644 --- a/static/css/report.css +++ b/static/css/report.css @@ -4,96 +4,48 @@ html, body { - height: 100%; - margin: 0; - padding: 0; + height: 100%; + margin: 0; + padding: 0; } body { - font-family: 'Open Sans', Helvetica, Arial, sans-serif; - background: white; - color: black; -} -.inlinepiechart { - width: 2.0in; - height: 0.9in; -} - -#percentile-chart { - width: 100%; - height: 100%; -} -#hourlystats-overviewchart { - width: 100%; - min-width: 6.5in; - height: 5in; -} -#success-placeholder table td { - border: 1px #ccc solid; - margin: 0; - padding: 1px; -} - -#success-placeholder td.bad { - background-color: #fcc; -} - -#success-placeholder td.good { - background-color: #cfc; -} - -#success-placeholder th:first-child { - width: 30%; -} -#success-placeholder th { - width: 10%; -} -#success-placeholder table { - width: 100%; -} - -.centeraligned { - text-align:center; -} - -#dailystats-placeholder td.tdborder { - width:80px; - border: 1px #ccc solid; - margin: 0; - padding: 1px; + font-family: 'Open Sans', Helvetica, Arial, sans-serif; + background: white; + color: black; } -ul#tabnav { /* general settings */ -text-align: left; /* set to left, right or center */ -margin: 1em 0 1em 0; /* set margins as desired */ -font: bold 11px verdana, arial, sans-serif; /* set font as desired */ -border-bottom: 1px solid #6c6; /* set border COLOR as desired */ -list-style-type: none; -padding: 3px 10px 3px 10px; /* THIRD number must change with respect to padding-top (X) below */ +#.centeraligned { + text-align:center; } -ul#tabnav li { /* do not change */ -display: inline; +ul#tabnav { + text-align: left; + margin: 1em 0 1em 0; + font: bold 11px verdana, arial, sans-serif; + border-bottom: 1px solid #6c6; + list-style-type: none; + padding: 3px 10px 3px 10px; } -ul#tabnav li{ /* settings for all tab links */ -padding: 3px 4px; /* set padding (tab size) as desired; FIRST number must change with respect to padding-top (X) above */ -border: 1px solid #6c6; /* set border COLOR as desired; usually matches border color specified in #tabnav */ -background-color: #cfc; /* set unselected tab background color as desired */ -color: #666; /* set unselected tab link color as desired */ -margin-right: 0px; /* set additional spacing between tabs as desired */ -text-decoration: none; -border-bottom: none; +ul#tabnav li { + display: inline; } -ul#tabnav li.selected { /* settings selected tab */ -background: #fff; /* set desired selected color */ +ul#tabnav li{ + padding: 3px 4px; + border: 1px solid #6c6; + background-color: #cfc; + color: #666; + margin-right: 0; + text-decoration: none; + border-bottom: none; } -ul#tabnav li:hover { /* settings for hover effect */ -background: #9f9; /* set desired hover color */ -cursor:pointer; +ul#tabnav li.selected { + background: #fff; } -tr.border_bottom td { - border-bottom:1pt solid #eee; +ul#tabnav li:hover { + background: #9f9; + cursor:pointer; } diff --git a/static/report/js/report.js b/static/report/js/report.js index f497532a260..bcb5154e513 100644 --- a/static/report/js/report.js +++ b/static/report/js/report.js @@ -1,8 +1,6 @@ // TODO: // - bypass nightmode in reports // - get rid of /static/report/js/time.js -// - load css dynamic + optimize -// - add tests // - on save/delete treatment ctx.bus.emit('data-received'); is not enough. we must add something like 'data-updated' (function () { From e9907c079a0b332476888642445efaaded30baed Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Tue, 15 Sep 2015 23:47:44 +0200 Subject: [PATCH 916/937] more codacy flotcandle.js --- static/report/js/flotcandle.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/static/report/js/flotcandle.js b/static/report/js/flotcandle.js index 8a965e0f4bf..3ac641d1cf5 100644 --- a/static/report/js/flotcandle.js +++ b/static/report/js/flotcandle.js @@ -35,6 +35,7 @@ drawCandle(ctx, serie, width, dt, open, low, close, high); } function drawCandle(ctx, serie, width, dt, open, low, close, high){ + var height; if (open < close){ //Rising y = offset.top + serie.yaxis.p2c(open); height = serie.yaxis.p2c(close) - serie.yaxis.p2c(open); @@ -56,9 +57,10 @@ var lowY = serie.yaxis.p2c(low); //top + var lineX; if (highY < y + height){ ctx.beginPath(); - var lineX = x + (width /2); + lineX = x + (width /2); ctx.moveTo(lineX,y + height); ctx.lineTo(lineX,highY); ctx.closePath(); From 499ed62e0ef4edf7437ec63e8f9ebbb318060c29 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Wed, 16 Sep 2015 00:20:09 +0200 Subject: [PATCH 917/937] get rid of time.js from reports --- lib/report_plugins/hourlystats.js | 6 +- lib/report_plugins/success.js | 6 +- static/report/index.html | 1 - static/report/js/time.js | 318 ------------------------------ 4 files changed, 8 insertions(+), 323 deletions(-) delete mode 100644 static/report/js/time.js diff --git a/lib/report_plugins/hourlystats.js b/lib/report_plugins/hourlystats.js index 999b18d513b..59f86be5209 100644 --- a/lib/report_plugins/hourlystats.js +++ b/lib/report_plugins/hourlystats.js @@ -1,5 +1,7 @@ 'use strict'; +var times = require('../times'); + var hourlystats = { name: 'hourlystats' , label: 'Hourly stats' @@ -70,7 +72,7 @@ hourlystats.report = function report_hourlystats(datastorage, sorteddaystoshow, var display = new Date(0, 0 , 1, hour, 0, 0, 0).toLocaleTimeString().replace(/([\d]+:[\d]{2})(:[\d]{2})(.*)/, '$1$3'); var avg = Math.floor(pivotedByHour[hour].map(function(r) { return r.sgv; }).reduce(function(o,v){ return o+v; }, 0) / pivotedByHour[hour].length); - var d = new Date(hour.hours()); + var d = new Date(times.hours(hour).msecs); var dev = ss.standard_deviation(pivotedByHour[hour].map(function(r) { return r.sgv; })); stats.push([ @@ -111,7 +113,7 @@ hourlystats.report = function report_hourlystats(datastorage, sorteddaystoshow, mode: 'time', timeFormat: '%h:00', min: 0, - max: (24).hours()-(1).seconds() + max: times.hours(24).msecs - times.secs(1).msecs }, yaxis: { min: 0, diff --git a/lib/report_plugins/success.js b/lib/report_plugins/success.js index 1ef01f1a16a..0199c453bf1 100644 --- a/lib/report_plugins/success.js +++ b/lib/report_plugins/success.js @@ -1,5 +1,7 @@ 'use strict'; +var times = require('../times'); + var success = { name: 'success' , label: 'Weekly success' @@ -62,7 +64,7 @@ success.report = function report_success(datastorage, sorteddaystoshow, options) var data = datastorage.allstatsrecords; var now = Date.now(); - var period = (7).days(); + var period = 7 * times.hours(24).msecs; var firstDataPoint = data.reduce(function(min, record) { return Math.min(min, record.displayTime); }, Number.MAX_VALUE); @@ -176,7 +178,7 @@ success.report = function report_success(datastorage, sorteddaystoshow, options) }).map(function(quarter) { var INVERT = true; return '' + [ - quarter.starting.format('M d Y') + ' - ' + quarter.ending.format('M d Y'), + quarter.starting.toLocaleDateString() + ' - ' + quarter.ending.toLocaleDateString(), { klass: lowComparison(quarter, averages, 'percentLow'), text: Math.round(quarter.percentLow) + '%' diff --git a/static/report/index.html b/static/report/index.html index 300372d157e..b3ab5d23305 100644 --- a/static/report/index.html +++ b/static/report/index.html @@ -127,7 +127,6 @@

    Nightscout reporting

    - diff --git a/static/report/js/time.js b/static/report/js/time.js deleted file mode 100644 index 0b34d7d47bf..00000000000 --- a/static/report/js/time.js +++ /dev/null @@ -1,318 +0,0 @@ -if (!("milliseconds" in Number.prototype)) - Number.prototype.milliseconds = function() { return this; }; -if (!("seconds" in Number.prototype)) - Number.prototype.seconds = function() { return this.milliseconds() * 1000; }; -if (!("minutes" in Number.prototype)) - Number.prototype.minutes = function() { return this.seconds() * 60; }; -if (!("hours" in Number.prototype)) - Number.prototype.hours = function() { return this.minutes() * 60; }; -if (!("days" in Number.prototype)) - Number.prototype.days = function() { return this.hours() * 24; }; -if (!("weeks" in Number.prototype)) - Number.prototype.weeks = function() { return this.days() * 7; }; -if (!("months" in Number.prototype)) - Number.prototype.months = function() { return this.days() * 30; }; - -if (!("toDays" in Number.prototype)) - Number.prototype.toDays = function() { return this.toHours() / 24; }; -if (!("toHours" in Number.prototype)) - Number.prototype.toHours = function() { return this.toMinutes() / 60; }; -if (!("toMinutes" in Number.prototype)) - Number.prototype.toMinutes = function() { return this.toSeconds() / 60; }; -if (!("toSeconds" in Number.prototype)) - Number.prototype.toSeconds = function() { return this.toMilliseconds() / 1000; }; -if (!("toMilliseconds" in Number.prototype)) - Number.prototype.toMilliseconds = function() { return this; }; - -Date.prototype.format = function(format) { - // discuss at: http://phpjs.org/functions/date/ - // original by: Carlos R. L. Rodrigues (http://www.jsfromhell.com) - // original by: gettimeofday - // parts by: Peter-Paul Koch (http://www.quirksmode.org/js/beat.html) - // improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) - // improved by: MeEtc (http://yass.meetcweb.com) - // improved by: Brad Touesnard - // improved by: Tim Wiel - // improved by: Bryan Elliott - // improved by: David Randall - // improved by: Theriault - // improved by: Theriault - // improved by: Brett Zamir (http://brett-zamir.me) - // improved by: Theriault - // improved by: Thomas Beaucourt (http://www.webapp.fr) - // improved by: JT - // improved by: Theriault - // improved by: Rafał Kukawski (http://blog.kukawski.pl) - // improved by: Theriault - // input by: Brett Zamir (http://brett-zamir.me) - // input by: majak - // input by: Alex - // input by: Martin - // input by: Alex Wilson - // input by: Haravikk - // bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) - // bugfixed by: majak - // bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) - // bugfixed by: Brett Zamir (http://brett-zamir.me) - // bugfixed by: omid (http://phpjs.org/functions/380:380#comment_137122) - // bugfixed by: Chris (http://www.devotis.nl/) - // note: Uses global: php_js to store the default timezone - // note: Although the function potentially allows timezone info (see notes), it currently does not set - // note: per a timezone specified by date_default_timezone_set(). Implementers might use - // note: this.php_js.currentTimezoneOffset and this.php_js.currentTimezoneDST set by that function - // note: in order to adjust the dates in this function (or our other date functions!) accordingly - // example 1: date('H:m:s \\m \\i\\s \\m\\o\\n\\t\\h', 1062402400); - // returns 1: '09:09:40 m is month' - // example 2: date('F j, Y, g:i a', 1062462400); - // returns 2: 'September 2, 2003, 2:26 am' - // example 3: date('Y W o', 1062462400); - // returns 3: '2003 36 2003' - // example 4: x = date('Y m d', (new Date()).getTime()/1000); - // example 4: (x+'').length == 10 // 2009 01 09 - // returns 4: true - // example 5: date('W', 1104534000); - // returns 5: '53' - // example 6: date('B t', 1104534000); - // returns 6: '999 31' - // example 7: date('W U', 1293750000.82); // 2010-12-31 - // returns 7: '52 1293750000' - // example 8: date('W', 1293836400); // 2011-01-01 - // returns 8: '52' - // example 9: date('W Y-m-d', 1293974054); // 2011-01-02 - // returns 9: '52 2011-01-02' - - var that = this, timestamp = this; - var jsdate, f; - // Keep this here (works, but for code commented-out below for file size reasons) - // var tal= []; - var txt_words = [ - 'Sun', 'Mon', 'Tues', 'Wednes', 'Thurs', 'Fri', 'Satur', - 'January', 'February', 'March', 'April', 'May', 'June', - 'July', 'August', 'September', 'October', 'November', 'December' - ]; - // trailing backslash -> (dropped) - // a backslash followed by any character (including backslash) -> the character - // empty string -> empty string - var formatChr = /\\?(.?)/gi; - var formatChrCb = function(t, s) { - return f[t] ? f[t]() : s; - }; - var _pad = function(n, c) { - n = String(n); - while (n.length < c) { - n = '0' + n; - } - return n; - }; - f = { - // Day - d: function() { // Day of month w/leading 0; 01..31 - return _pad(f.j(), 2); - }, - D: function() { // Shorthand day name; Mon...Sun - return f.l() - .slice(0, 3); - }, - j: function() { // Day of month; 1..31 - return jsdate.getDate(); - }, - l: function() { // Full day name; Monday...Sunday - return txt_words[f.w()] + 'day'; - }, - N: function() { // ISO-8601 day of week; 1[Mon]..7[Sun] - return f.w() || 7; - }, - S: function() { // Ordinal suffix for day of month; st, nd, rd, th - var j = f.j(); - var i = j % 10; - if (i <= 3 && parseInt((j % 100) / 10, 10) == 1) { - i = 0; - } - return ['st', 'nd', 'rd'][i - 1] || 'th'; - }, - w: function() { // Day of week; 0[Sun]..6[Sat] - return jsdate.getDay(); - }, - z: function() { // Day of year; 0..365 - var a = new Date(f.Y(), f.n() - 1, f.j()); - var b = new Date(f.Y(), 0, 1); - return Math.round((a - b) / 864e5); - }, - - // Week - W: function() { // ISO-8601 week number - var a = new Date(f.Y(), f.n() - 1, f.j() - f.N() + 3); - var b = new Date(a.getFullYear(), 0, 4); - return _pad(1 + Math.round((a - b) / 864e5 / 7), 2); - }, - - // Month - F: function() { // Full month name; January...December - return txt_words[6 + f.n()]; - }, - m: function() { // Month w/leading 0; 01...12 - return _pad(f.n(), 2); - }, - M: function() { // Shorthand month name; Jan...Dec - return f.F() - .slice(0, 3); - }, - n: function() { // Month; 1...12 - return jsdate.getMonth() + 1; - }, - t: function() { // Days in month; 28...31 - return (new Date(f.Y(), f.n(), 0)) - .getDate(); - }, - - // Year - L: function() { // Is leap year?; 0 or 1 - var j = f.Y(); - return j % 4 === 0 & j % 100 !== 0 | j % 400 === 0; - }, - o: function() { // ISO-8601 year - var n = f.n(); - var W = f.W(); - var Y = f.Y(); - return Y + (n === 12 && W < 9 ? 1 : n === 1 && W > 9 ? -1 : 0); - }, - Y: function() { // Full year; e.g. 1980...2010 - return jsdate.getFullYear(); - }, - y: function() { // Last two digits of year; 00...99 - return f.Y() - .toString() - .slice(-2); - }, - - // Time - a: function() { // am or pm - return jsdate.getHours() > 11 ? 'pm' : 'am'; - }, - A: function() { // AM or PM - return f.a() - .toUpperCase(); - }, - B: function() { // Swatch Internet time; 000..999 - var H = jsdate.getUTCHours() * 36e2; - // Hours - var i = jsdate.getUTCMinutes() * 60; - // Minutes - var s = jsdate.getUTCSeconds(); // Seconds - return _pad(Math.floor((H + i + s + 36e2) / 86.4) % 1e3, 3); - }, - g: function() { // 12-Hours; 1..12 - return f.G() % 12 || 12; - }, - G: function() { // 24-Hours; 0..23 - return jsdate.getHours(); - }, - h: function() { // 12-Hours w/leading 0; 01..12 - return _pad(f.g(), 2); - }, - H: function() { // 24-Hours w/leading 0; 00..23 - return _pad(f.G(), 2); - }, - i: function() { // Minutes w/leading 0; 00..59 - return _pad(jsdate.getMinutes(), 2); - }, - s: function() { // Seconds w/leading 0; 00..59 - return _pad(jsdate.getSeconds(), 2); - }, - u: function() { // Microseconds; 000000-999000 - return _pad(jsdate.getMilliseconds() * 1000, 6); - }, - - // Timezone - e: function() { // Timezone identifier; e.g. Atlantic/Azores, ... - // The following works, but requires inclusion of the very large - // timezone_abbreviations_list() function. - /* return that.date_default_timezone_get(); - */ - throw 'Not supported (see source code of date() for timezone on how to add support)'; - }, - I: function() { // DST observed?; 0 or 1 - // Compares Jan 1 minus Jan 1 UTC to Jul 1 minus Jul 1 UTC. - // If they are not equal, then DST is observed. - var a = new Date(f.Y(), 0); - // Jan 1 - var c = Date.UTC(f.Y(), 0); - // Jan 1 UTC - var b = new Date(f.Y(), 6); - // Jul 1 - var d = Date.UTC(f.Y(), 6); // Jul 1 UTC - return ((a - c) !== (b - d)) ? 1 : 0; - }, - O: function() { // Difference to GMT in hour format; e.g. +0200 - var tzo = jsdate.getTimezoneOffset(); - var a = Math.abs(tzo); - return (tzo > 0 ? '-' : '+') + _pad(Math.floor(a / 60) * 100 + a % 60, 4); - }, - P: function() { // Difference to GMT w/colon; e.g. +02:00 - var O = f.O(); - return (O.substr(0, 3) + ':' + O.substr(3, 2)); - }, - T: function() { // Timezone abbreviation; e.g. EST, MDT, ... - // The following works, but requires inclusion of the very - // large timezone_abbreviations_list() function. - /* var abbr, i, os, _default; - if (!tal.length) { - tal = that.timezone_abbreviations_list(); - } - if (that.php_js && that.php_js.default_timezone) { - _default = that.php_js.default_timezone; - for (abbr in tal) { - for (i = 0; i < tal[abbr].length; i++) { - if (tal[abbr][i].timezone_id === _default) { - return abbr.toUpperCase(); - } - } - } - } - for (abbr in tal) { - for (i = 0; i < tal[abbr].length; i++) { - os = -jsdate.getTimezoneOffset() * 60; - if (tal[abbr][i].offset === os) { - return abbr.toUpperCase(); - } - } - } - */ - return 'UTC'; - }, - Z: function() { // Timezone offset in seconds (-43200...50400) - return -jsdate.getTimezoneOffset() * 60; - }, - - // Full Date/Time - c: function() { // ISO-8601 date. - return 'Y-m-d\\TH:i:sP'.replace(formatChr, formatChrCb); - }, - r: function() { // RFC 2822 - return 'D, d M Y H:i:s O'.replace(formatChr, formatChrCb); - }, - U: function() { // Seconds since UNIX epoch - return jsdate / 1000 | 0; - } - }; - this.date = function(format, timestamp) { - that = this; - jsdate = (timestamp === undefined ? new Date() : // Not provided - (timestamp instanceof Date) ? new Date(timestamp) : // JS Date() - new Date(timestamp * 1000) // UNIX timestamp (auto-convert to int) - ); - return format.replace(formatChr, formatChrCb); - }; - return this.date(format, timestamp); -} - -// http://stackoverflow.com/questions/11887934/check-if-daylight-saving-time-is-in-effect-and-if-it-is-for-how-many-hours -Date.prototype.stdTimezoneOffset = function() { - var jan = new Date(this.getFullYear(), 0, 1); - var jul = new Date(this.getFullYear(), 6, 1); - return Math.max(jan.getTimezoneOffset(), jul.getTimezoneOffset()); -}; - -Date.prototype.dst = function() { - return this.getTimezoneOffset() < this.stdTimezoneOffset(); -}; \ No newline at end of file From ac37f5c1d32b8783a2ea6ef3fb19c2d1deef2775 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Wed, 16 Sep 2015 00:24:49 +0200 Subject: [PATCH 918/937] remove todo, remove include from test --- static/report/js/report.js | 1 - tests/reports.test.js | 1 - 2 files changed, 2 deletions(-) diff --git a/static/report/js/report.js b/static/report/js/report.js index bcb5154e513..83a19c3a10e 100644 --- a/static/report/js/report.js +++ b/static/report/js/report.js @@ -1,6 +1,5 @@ // TODO: // - bypass nightmode in reports -// - get rid of /static/report/js/time.js // - on save/delete treatment ctx.bus.emit('data-received'); is not enough. we must add something like 'data-updated' (function () { diff --git a/tests/reports.test.js b/tests/reports.test.js index 41ef419c47e..751db63f5df 100644 --- a/tests/reports.test.js +++ b/tests/reports.test.js @@ -214,7 +214,6 @@ describe('reports', function ( ) { benv.require(__dirname + '/../bundle/bundle.source.js'); benv.require(__dirname + '/../static/report/js/report.js'); - benv.require(__dirname + '/../static/report/js/time.js'); benv.require(__dirname + '/../static/bower_components/jquery-ui/jquery-ui.min.js'); done(); From bffd2a7ea9a9744c1d81e610de1b7dab9e34f1f1 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 15 Sep 2015 15:53:19 -0700 Subject: [PATCH 919/937] only check if low/high events are enabled for now --- lib/settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/settings.js b/lib/settings.js index dbb8cb1af68..9cc486929cc 100644 --- a/lib/settings.js +++ b/lib/settings.js @@ -220,7 +220,7 @@ function init ( ) { function isAlarmEventEnabled (notify) { var enabled = false; - if (!notify.eventName) { + if ('high' !== notify.eventName && 'low' !== notify.eventName) { enabled = true; } else if (notify.eventName === 'high' && notify.level === levels.URGENT && settings.alarmUrgentHigh) { enabled = true; From 6675243df12a24171b16d2340d0033b6267037b7 Mon Sep 17 00:00:00 2001 From: xpucuto Date: Wed, 16 Sep 2015 15:42:37 +0300 Subject: [PATCH 920/937] bulgarian language - 16.Sept.2015 --- lib/language.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/language.js b/lib/language.js index 6f94313f2ee..3f8949287dd 100644 --- a/lib/language.js +++ b/lib/language.js @@ -8,7 +8,7 @@ function init() { } language.languages = [ - { code: 'bg', language: 'Български език' } + { code: 'bg', language: 'Български' } , { code: 'cz', language: 'Čeština' } , { code: 'dk', language: 'Dansk' } , { code: 'de', language: 'Deutsch' } @@ -3906,10 +3906,12 @@ function init() { cs: 'Jazyk' ,fi: 'Kieli' ,pt: 'Língua' + ,bg: 'Език' } ,'Update' : { // Update button cs: 'Aktualizovat' ,pt: 'Atualizar' + ,bg: 'Актуализирай' } }; From 2384f6a4bcc6765e7ec5cea81709bc7a1ee50a03 Mon Sep 17 00:00:00 2001 From: avielf Date: Wed, 16 Sep 2015 17:57:16 +0300 Subject: [PATCH 921/937] Initial Hebrew transltation Translated only days. Need to make sure if should be written backwards because of right-to-left compatibility. --- lib/language.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/language.js b/lib/language.js index 6f94313f2ee..e8cde5e64ad 100644 --- a/lib/language.js +++ b/lib/language.js @@ -22,6 +22,7 @@ function init() { , { code: 'ro', language: 'Română' } , { code: 'sv', language: 'Svenska' } , { code: 'fi', language: 'Suomi' } + , { code: 'he', language: 'עברית' } ]; var translations = { @@ -39,6 +40,7 @@ function init() { ,dk: 'Lytter på port' ,fi: 'Kuuntelen porttia' ,nb: 'Lytter på port' + ,he: 'מקשיב על פתחה' } // Client ,'Mo' : { @@ -55,6 +57,7 @@ function init() { ,dk: 'Man' ,fi: 'Ma' ,nb: 'Man' + ,he: 'ב' } ,'Tu' : { cs: 'Út' @@ -70,6 +73,7 @@ function init() { ,dk: 'Tir' ,fi: 'Ti' ,nb: 'Tir' + ,he: 'ג' }, ',We' : { cs: 'St' @@ -85,6 +89,7 @@ function init() { ,dk: 'Ons' ,fi: 'Ke' ,nb: 'Ons' + ,he: 'ד' } ,'Th' : { cs: 'Čt' @@ -100,6 +105,7 @@ function init() { ,dk: 'Tor' ,fi: 'To' ,no: 'Tor' + ,he: 'ה' } ,'Fr' : { cs: 'Pá' @@ -115,6 +121,7 @@ function init() { ,dk: 'Fre' ,fi: 'Pe' ,nb: 'Fre' + ,he: 'ו' } ,'Sa' : { cs: 'So' @@ -130,6 +137,7 @@ function init() { ,dk: 'Lør' ,fi: 'La' ,nb: 'Lør' + ,he: 'ש' } ,'Su' : { cs: 'Ne' @@ -145,6 +153,7 @@ function init() { ,dk: 'Søn' ,fi: 'Su' ,nb: 'Søn' + ,he: 'א' } ,'Monday' : { cs: 'Pondělí' @@ -160,6 +169,7 @@ function init() { ,dk: 'Mandag' ,fi: 'Maanantai' ,nb: 'Mandag' + ,he: 'שני' } ,'Tuesday' : { cs: 'Úterý' @@ -175,6 +185,7 @@ function init() { ,dk: 'Tirsdag' ,fi: 'Tiistai' ,nb: 'Tirsdag' + ,he: 'שלישי' } ,'Wednesday' : { cs: 'Středa' @@ -190,6 +201,7 @@ function init() { ,dk: 'Onsdag' ,fi: 'Keskiviikko' ,nb: 'Onsdag' + ,he: 'רביעי' } ,'Thursday' : { cs: 'Čtvrtek' @@ -205,6 +217,7 @@ function init() { ,dk: 'Torsdag' ,fi: 'Torstai' ,nb: 'Torsdag' + ,he: 'חמישי' } ,'Friday' : { cs: 'Pátek' @@ -220,6 +233,7 @@ function init() { ,dk: 'Fredag' ,fi: 'Perjantai' ,nb: 'Fredag' + ,he: 'שישי' } ,'Saturday' : { cs: 'Sobota' @@ -235,6 +249,7 @@ function init() { ,dk: 'Lørdag' ,fi: 'Lauantai' ,nb: 'Lørdag' + ,he: 'שבת' } ,'Sunday' : { cs: 'Neděle' @@ -250,6 +265,7 @@ function init() { ,dk: 'Søndag' ,fi: 'Sunnuntai' ,nb: 'Søndag' + ,he: 'ראשון' } ,'Category' : { cs: 'Kategorie' From fe95c718a16e068e6a89e51c1db9fa8085637c33 Mon Sep 17 00:00:00 2001 From: avielf Date: Thu, 17 Sep 2015 09:29:15 +0300 Subject: [PATCH 922/937] More Hebrew added --- lib/language.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/lib/language.js b/lib/language.js index e8cde5e64ad..fdfb7af4bc9 100644 --- a/lib/language.js +++ b/lib/language.js @@ -281,6 +281,7 @@ function init() { ,dk: 'Kategori' ,fi: 'Luokka' ,nb: 'Kategori' + ,he: 'קטגוריה' } ,'Subcategory' : { cs: 'Podkategorie' @@ -296,6 +297,7 @@ function init() { ,dk: 'Underkategori' ,fi: 'Alaluokka' ,nb: 'Underkategori' + ,he: 'תת-קטגוריה' } ,'Name' : { cs: 'Jméno' @@ -311,6 +313,7 @@ function init() { ,dk: 'Navn' ,fi: 'Nimi' ,nb: 'Navn' + ,he: 'שם' } ,'Today' : { cs: 'Dnes' @@ -326,6 +329,7 @@ function init() { ,dk: 'Idag' ,fi: 'Tänään' ,nb: 'Idag' + ,he: 'היום' } ,'Last 2 days' : { cs: 'Poslední 2 dny' @@ -341,6 +345,7 @@ function init() { ,dk: 'Sidste 2 dage' ,fi: 'Edelliset 2 päivää' ,nb: 'Siste 2 dager' + ,he: 'יומיים אחרונים' } ,'Last 3 days' : { cs: 'Poslední 3 dny' @@ -356,6 +361,7 @@ function init() { ,dk: 'Sidste 3 dage' ,fi: 'Edelliset 3 päivää' ,nb: 'Siste 3 dager' + ,he: '3 ימים אחרונים' } ,'Last week' : { cs: 'Poslední týden' @@ -371,6 +377,7 @@ function init() { ,dk: 'Sidste uge' ,fi: 'Viime viikko' ,nb: 'Siste uke' + ,he: 'שבוע אחרון' } ,'Last 2 weeks' : { cs: 'Poslední 2 týdny' @@ -386,6 +393,7 @@ function init() { ,dk: 'Sidste 2 uger' ,fi: 'Viimeiset 2 viikkoa' ,nb: 'Siste 2 uker' + ,he: 'שבועיים אחרונים' } ,'Last month' : { cs: 'Poslední měsíc' @@ -401,6 +409,7 @@ function init() { ,dk: 'Sidste måned' ,fi: 'Viime kuu' ,nb: 'Siste måned' + ,he: 'חודש אחרון' } ,'Last 3 months' : { cs: 'Poslední 3 měsíce' @@ -416,6 +425,7 @@ function init() { ,dk: 'Sidste 3 måneder' ,fi: 'Viimeiset 3 kuukautta' ,nb: 'Siste 3 måneder' + ,he: '3 חודשים אחרונים' } ,'From' : { cs: 'Od' @@ -431,6 +441,7 @@ function init() { ,dk: 'Fra' ,fi: 'Alkaen' ,nb: 'Fra' + ,he: 'מ' } ,'To' : { cs: 'Do' @@ -446,6 +457,7 @@ function init() { ,dk: 'Til' ,fi: 'Asti' ,nb: 'Til' + ,he: 'עד' } ,'Notes' : { cs: 'Poznámky' @@ -461,6 +473,7 @@ function init() { ,dk: 'Noter' ,fi: 'Merkinnät' ,nb: 'Notater' + ,he: 'הערות' } ,'Food' : { cs: 'Jídlo' @@ -476,6 +489,7 @@ function init() { ,dk: 'Mad' ,fi: 'Ruoka' ,nb: 'Mat' + ,he: 'אוכל' } ,'Insulin' : { cs: 'Inzulín' @@ -491,6 +505,7 @@ function init() { ,dk: 'Insulin' ,fi: 'Insuliini' ,nb: 'Insulin' + ,he: 'אינסולין' } ,'Carbs' : { cs: 'Sacharidy' @@ -506,6 +521,7 @@ function init() { ,dk: 'Kulhydrater' ,fi: 'Hiilihydraatit' ,nb: 'Karbohydrater' + ,he: 'פחמימות' } ,'Notes contain' : { cs: 'Poznámky obsahují' @@ -536,6 +552,7 @@ function init() { ,dk: 'Nedre grænse for blodsukkerværdier' ,fi: 'Tavoitealueen alaraja' ,nb: 'Nedre grense for blodsukkerverdier' + ,he: 'טווח מטרה סף תחתון' } ,'top' : { cs: 'horní' @@ -581,6 +598,7 @@ function init() { ,dk: 'Vis' ,fi: 'Näyttö' ,nb: 'Vis' + ,he: 'תצוגה' } ,'Loading' : { cs: 'Nahrávám' @@ -596,6 +614,7 @@ function init() { ,dk: 'Indlæser' ,fi: 'Lataan' ,nb: 'Laster' + ,he: 'טוען' } ,'Loading profile' : { cs: 'Nahrávám profil' @@ -611,6 +630,7 @@ function init() { ,dk: 'Indlæser profil' ,fi: 'Lataan profiilia' ,nb: 'Leser profil' + ,he: 'טוען פרופיל' } ,'Loading status' : { cs: 'Nahrávám status' @@ -626,6 +646,7 @@ function init() { ,dk: 'Indlæsnings status' ,fi: 'Lataan tilaa' ,nb: 'Leser status' + ,he: 'טוען סטטוס' } ,'Loading food database' : { cs: 'Nahrávám databázi jídel' @@ -716,6 +737,7 @@ function init() { ,dk: 'Portion' ,fi: 'Annos' ,nb: 'Porsjon' + ,he: 'מנה' } ,'Size' : { cs: 'Rozměr' @@ -731,6 +753,7 @@ function init() { ,dk: 'Størrelse' ,fi: 'Koko' ,nb: 'Størrelse' + ,he: 'גודל' } ,'(none)' : { cs: '(Prázdný)' @@ -761,6 +784,7 @@ function init() { ,dk: 'Tomt resultat' ,fi: 'Ei tuloksia' ,nb: 'Tomt resultat' + ,he: 'אין תוצאה' } // ported reporting ,'Day to day' : { From 84254988aef02b4dc114a2c7cc29e2d7a214831a Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 17 Sep 2015 01:30:26 -0700 Subject: [PATCH 923/937] mock dialog instead of pulling in jquery-ui and messing up coverage stats --- tests/reports.test.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/tests/reports.test.js b/tests/reports.test.js index 751db63f5df..5c09b130206 100644 --- a/tests/reports.test.js +++ b/tests/reports.test.js @@ -1,6 +1,7 @@ 'use strict'; require('should'); +var _ = require('lodash'); var benv = require('benv'); var read = require('fs').readFileSync; var serverSettings = require('./fixtures/default-server-settings'); @@ -162,6 +163,20 @@ describe('reports', function ( ) { self.$.fn.tipsy = function mockTipsy ( ) { }; + self.$.fn.dialog = function mockDialog (opts) { + function maybeCall (name, obj) { + if (obj[name] && obj[name].call) { + obj[name](); + } + + } + maybeCall('open', opts); + + _.forEach(opts.buttons, function (button) { + maybeCall('click', button); + }); + }; + var indexHtml = read(__dirname + '/../static/report/index.html', 'utf8'); self.$('body').html(indexHtml); @@ -214,7 +229,6 @@ describe('reports', function ( ) { benv.require(__dirname + '/../bundle/bundle.source.js'); benv.require(__dirname + '/../static/report/js/report.js'); - benv.require(__dirname + '/../static/bower_components/jquery-ui/jquery-ui.min.js'); done(); }); From 5feb5f840f62396e8f89042f1bde8ac3e0a89a12 Mon Sep 17 00:00:00 2001 From: avielf Date: Thu, 17 Sep 2015 11:39:38 +0300 Subject: [PATCH 924/937] More Hebrew --- lib/language.js | 75 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/lib/language.js b/lib/language.js index fdfb7af4bc9..ea8418c4250 100644 --- a/lib/language.js +++ b/lib/language.js @@ -15,6 +15,7 @@ function init() { , { code: 'en', language: 'English' } , { code: 'es', language: 'Español' } , { code: 'fr', language: 'Français' } + , { code: 'he', language: 'עברית' } , { code: 'hr', language: 'Hrvatski' } , { code: 'it', language: 'Italiano' } , { code: 'nb', language: 'Norsk (Bokmål)' } @@ -22,7 +23,6 @@ function init() { , { code: 'ro', language: 'Română' } , { code: 'sv', language: 'Svenska' } , { code: 'fi', language: 'Suomi' } - , { code: 'he', language: 'עברית' } ]; var translations = { @@ -75,7 +75,7 @@ function init() { ,nb: 'Tir' ,he: 'ג' }, - ',We' : { + ,'We' : { cs: 'St' ,de: 'Mi' ,es: 'Mie' @@ -568,6 +568,7 @@ function init() { ,dk: 'Top' ,fi: 'yläraja' ,nb: 'Topp' + ,he: 'למעלה' } ,'Show' : { cs: 'Zobraz' @@ -583,6 +584,7 @@ function init() { ,dk: 'Vis' ,fi: 'Näytä' ,nb: 'Vis' + ,he: 'הצג' } ,'Display' : { cs: 'Zobraz' @@ -662,6 +664,7 @@ function init() { ,dk: 'Indlæser mad database' ,fi: 'Lataan ruokatietokantaa' ,nb: 'Leser matdatabase' + ,he: 'טוען נתוני אוכל' } ,'not displayed' : { cs: 'není zobrazeno' @@ -677,6 +680,7 @@ function init() { ,dk: 'Vises ikke' ,fi: 'ei näytetä' ,nb: 'Vises ikke' + ,he: 'לא מוצג' } ,'Loading CGM data of' : { cs: 'Nahrávám CGM data' @@ -692,6 +696,7 @@ function init() { ,dk: 'Indlæser CGM-data for' ,fi: 'Lataan sensoritietoja: ' ,nb: 'Leser CGM-data for' + ,he: 'טוען נתוני חיישן סוכר של' } ,'Loading treatments data of' : { cs: 'Nahrávám data ošetření' @@ -707,6 +712,7 @@ function init() { ,dk: 'Indlæser data for' ,fi: 'Lataan toimenpidetietoja: ' ,nb: 'Leser behandlingsdata for' + ,he: 'טוען נתוני טיפולים של' } ,'Processing data of' : { cs: 'Zpracovávám data' @@ -722,6 +728,7 @@ function init() { ,dk: 'Behandler data for' ,fi: 'Käsittelen tietoja: ' ,nb: 'Behandler data for' + ,he: 'מעבד נתונים של' } ,'Portion' : { cs: 'Porce' @@ -769,6 +776,7 @@ function init() { ,dk: '(ingen)' ,fi: '(tyhjä)' ,nb: '(ingen)' + ,he: '(ללא)' } ,'Result is empty' : { cs: 'Prázdný výsledek' @@ -801,6 +809,7 @@ function init() { ,dk: 'Dag til dag' ,fi: 'Päivittäinen' ,nb: 'Dag til dag' + ,he: 'יום-יום' } ,'Daily Stats' : { cs: 'Denní statistiky' @@ -816,6 +825,7 @@ function init() { ,dk: 'Daglig statistik' ,fi: 'Päivittäiset tilastot' ,nb: 'Daglig statistikk' + ,he: 'סטטיסטיקה יומית' } ,'Percentile Chart' : { cs: 'Percentil' @@ -831,6 +841,7 @@ function init() { ,dk: 'Procentgraf' ,fi: 'Suhteellinen kuvaaja' ,nb: 'Prosentgraf' + ,he: 'טבלת עשירונים' } ,'Distribution' : { cs: 'Rozložení' @@ -846,6 +857,7 @@ function init() { ,dk: 'Distribution' ,fi: 'Jakauma' ,nb: 'Distribusjon' + ,he: 'פיזור' } ,'Hourly stats' : { cs: 'Statistika po hodinách' @@ -861,6 +873,7 @@ function init() { ,dk: 'Timestatistik' ,fi: 'Tunneittainen tilasto' ,nb: 'Timestatistikk' + ,he: 'סטטיסטיקה שעתית' } ,'Weekly success' : { cs: 'Statistika po týdnech' @@ -891,6 +904,7 @@ function init() { ,dk: 'Mangler data' ,fi: 'Tietoja ei saatavilla' ,nb: 'Mangler data' + ,he: 'אין מידע זמין' } ,'Low' : { cs: 'Nízká' @@ -906,6 +920,7 @@ function init() { ,dk: 'Lav' ,fi: 'Matala' ,nb: 'Lav' + ,he: 'נמוך' } ,'In Range' : { cs: 'V rozsahu' @@ -921,6 +936,7 @@ function init() { ,dk: 'Indenfor intervallet' ,fi: 'Tavoitealueella' ,nb: 'Innenfor intervallet' + ,he: 'בטווח' } ,'Period' : { cs: 'Období' @@ -936,6 +952,7 @@ function init() { ,dk: 'Period' ,fi: 'Aikaväli' ,nb: 'Periode' + he:, 'תקופה' } ,'High' : { cs: 'Vysoká' @@ -951,6 +968,7 @@ function init() { ,dk: 'Høj' ,fi: 'Korkea' ,nb: 'Høy' + ,he: 'גבוה' } ,'Average' : { cs: 'Průměrná' @@ -966,6 +984,7 @@ function init() { ,dk: 'Gennemsnit' ,fi: 'Keskiarvo' ,nb: 'Gjennomsnitt' + ,he: 'ממוצע' } ,'Low Quartile' : { cs: 'Nízký kvartil' @@ -981,6 +1000,7 @@ function init() { ,dk: 'Nedre kvartil' ,fi: 'Alin neljäsosa' ,nb: 'Nedre kvartil' + ,he: 'רבעון נמוך' } ,'Upper Quartile' : { cs: 'Vysoký kvartil' @@ -996,6 +1016,7 @@ function init() { ,dk: 'Øvre kvartil' ,fi: 'Ylin neljäsosa' ,nb: 'Øvre kvartil' + ,he: 'רבעון גבוה' } ,'Quartile' : { cs: 'Kvartil' @@ -1011,6 +1032,7 @@ function init() { ,dk: 'Kvartil' ,fi: 'Neljäsosa' ,nb: 'Kvartil' + ,he: 'רבעון' } ,'Date' : { cs: 'Datum' @@ -1026,6 +1048,7 @@ function init() { ,dk: 'Dato' ,fi: 'Päivämäärä' ,nb: 'Dato' + ,he: 'תאריך' } ,'Normal' : { cs: 'Normální' @@ -1056,6 +1079,7 @@ function init() { ,dk: 'Median' ,fi: 'Mediaani' ,nb: 'Median' + ,he: 'חציון' } ,'Readings' : { cs: 'Záznamů' @@ -1071,6 +1095,7 @@ function init() { ,dk: 'Aflæsning' ,fi: 'Lukemia' ,nb: 'Avlesning' + ,he: 'קריאות' } ,'StDev' : { cs: 'St. odchylka' @@ -1086,6 +1111,7 @@ function init() { ,dk: 'Standard afvigelse' ,fi: 'Keskijakauma' ,nb: 'Standardavvik' + ,he: 'סטיית תקן' } ,'Daily stats report' : { cs: 'Denní statistiky' @@ -1101,6 +1127,7 @@ function init() { ,dk: 'Daglig statistik rapport' ,fi: 'Päivittäinen tilasto' ,nb: 'Daglig statistikkrapport' + ,he: 'דוח סטטיסטיקה יומית' } ,'Glucose Percentile report' : { cs: 'Tabulka percentil glykémií' @@ -1131,6 +1158,7 @@ function init() { ,dk: 'Glukosefordeling' ,fi: 'Glukoosijakauma' ,nb: 'Glukosefordeling' + ,he: 'פיזור סוכר' } ,'days total' : { cs: 'dní celkem' @@ -1146,6 +1174,7 @@ function init() { ,dk: 'antal dage' ,fi: 'päivä yhteensä' ,nb: 'antall dager' + ,he: 'מספר ימים' } ,'Overall' : { cs: 'Celkem' @@ -1161,6 +1190,7 @@ function init() { ,dk: 'Overall' ,fi: 'Kaiken kaikkiaan' ,nb: 'Generelt' + ,he: 'סך הכל' } ,'Range' : { cs: 'Rozsah' @@ -1176,6 +1206,7 @@ function init() { ,dk: 'Interval' ,fi: 'Alue' ,nb: 'Intervall' + ,he: 'טווח' } ,'% of Readings' : { cs: '% záznamů' @@ -1191,6 +1222,7 @@ function init() { ,dk: '% af aflæsningerne' ,fi: '% lukemista' ,nb: '% af avlesningene' + ,he: 'אחוז קריאות' } ,'# of Readings' : { cs: 'počet záznamů' @@ -1206,6 +1238,7 @@ function init() { ,dk: 'Antal af aflæsninger' ,fi: 'Lukemien määrä' ,nb: 'Antall avlesninger' + ,he: 'מספר קריאות' } ,'Mean' : { cs: 'Střední hodnota' @@ -1221,6 +1254,7 @@ function init() { ,dk: 'Gennemsnit' ,fi: 'Keskiarvo' ,nb: 'Gjennomsnitt' + ,he: 'ממוצע' } ,'Standard Deviation' : { cs: 'Standardní odchylka' @@ -1236,6 +1270,7 @@ function init() { ,dk: 'Standardafgivelse' ,fi: 'Keskijakauma' ,nb: 'Standardavvik' + ,he: 'סטיית תקן' } ,'Max' : { cs: 'Max' @@ -1251,6 +1286,7 @@ function init() { ,dk: 'Max' ,fi: 'Maks' ,nb: 'Max' + ,he: 'מקסימאלי' } ,'Min' : { cs: 'Min' @@ -1266,6 +1302,7 @@ function init() { ,dk: 'Min' ,fi: 'Min' ,nb: 'Min' + ,he: 'מינימאלי' } ,'A1c estimation*' : { cs: 'Předpokládané HBA1c*' @@ -1281,6 +1318,7 @@ function init() { ,dk: 'Beregnet A1c-værdi ' ,fi: 'A1c arvio*' ,nb: 'Beregnet HbA1c' + ,he: 'משוער A1c' } ,'Weekly Success' : { cs: 'Týdenní úspěšnost' @@ -1311,6 +1349,7 @@ function init() { ,dk: 'Der er utilstrækkeligt data til at generere rapporten. Vælg flere dage.' ,fi: 'Raporttia ei voida luoda liian vähäisen tiedon vuoksi. Valitse useampia päiviä.' ,nb: 'Der er ikke nok data til å lage rapporten. Velg flere dager.' + ,he: 'לא נמצא מספיק מידע ליצירת הדוח. בחר ימים נוספים.' } // food editor ,'Using stored API secret hash' : { @@ -1372,6 +1411,7 @@ function init() { ,dk: 'Fejl: Database kan ikke indlæses' ,fi: 'Virhe: Tietokannan lataaminen epäonnistui' ,nb: 'Feil: Database kan ikke leses' + ,he: 'שגיאה: לא ניתן לטעון בסיס נתונים' } ,'Create new record' : { cs: 'Vytvořit nový záznam' @@ -1387,6 +1427,7 @@ function init() { ,dk: 'Danner ny post' ,fi: 'Luo uusi tallenne' ,nb: 'Lager ny registrering' + ,he: 'צור רשומה חדשה' } ,'Save record' : { cs: 'Uložit záznam' @@ -1402,6 +1443,7 @@ function init() { ,dk: 'Gemmer post' ,fi: 'Tallenna' ,nb: 'Lagrer registrering' + ,he: 'שמור רשומה' } ,'Portions' : { cs: 'Porcí' @@ -1417,6 +1459,7 @@ function init() { ,dk: 'Portioner' ,fi: 'Annokset' ,nb: 'Porsjoner' + ,he: 'מנות' } ,'Unit' : { cs: 'Jedn' @@ -1432,6 +1475,7 @@ function init() { ,dk: 'Enheder' ,fi: 'Yksikkö' ,nb: 'Enhet' + ,he: 'יחידות' } ,'GI' : { cs: 'GI' @@ -1462,6 +1506,7 @@ function init() { ,dk: 'Editere post' ,fi: 'Muokkaa tallennetta' ,nb: 'Editere registrering' + ,he: 'ערוך רשומה' } ,'Delete record' : { cs: 'Smazat záznam' @@ -1477,6 +1522,7 @@ function init() { ,dk: 'Slet post' ,fi: 'Tuhoa tallenne' ,nb: 'Slette registrering' + ,he: 'מחק רשומה' } ,'Move to the top' : { cs: 'Přesuň na začátek' @@ -1492,6 +1538,7 @@ function init() { ,dk: 'Gå til toppen' ,fi: 'Siirrä ylimmäksi' ,nb: 'Gå til toppen' + ,he: 'עבור למעלה' } ,'Hidden' : { cs: 'Skrytý' @@ -1507,6 +1554,7 @@ function init() { ,dk: 'Skjult' ,fi: 'Piilotettu' ,nb: 'Skjult' + ,he: 'מוסתר' } ,'Hide after use' : { cs: 'Skryj po použití' @@ -1522,6 +1570,7 @@ function init() { ,dk: 'Skjul efter brug' ,fi: 'Piilota käytön jälkeen' ,nb: 'Skjul etter bruk' + ,he: 'הסתר לאחר שימוש' } ,'Your API secret must be at least 12 characters long' : { cs: 'Vaše API heslo musí mít alespoň 12 znaků' @@ -1582,6 +1631,7 @@ function init() { ,dk: 'Status' ,fi: 'Tila' ,nb: 'Status' + ,he: 'מצב מערכת' } ,'Not loaded' : { cs: 'Nenačtený' @@ -1627,6 +1677,7 @@ function init() { ,dk: 'Din database' ,fi: 'Tietokantasi' ,nb: 'Din database' + ,he: 'בסיס הנתונים שלך' } ,'Filter' : { cs: 'Filtr' @@ -1642,6 +1693,7 @@ function init() { ,dk: 'Filter' ,fi: 'Suodatin' ,nb: 'Filter' + ,he: 'סנן' } ,'Save' : { cs: 'Ulož' @@ -1657,6 +1709,7 @@ function init() { ,dk: 'Gem' ,fi: 'Tallenna' ,nb: 'Lagre' + ,he: 'שמור' } ,'Clear' : { cs: 'Vymaž' @@ -1672,6 +1725,7 @@ function init() { ,dk: 'Rense' ,fi: 'Tyhjennä' ,nb: 'Tøm' + ,he: 'נקה' } ,'Record' : { cs: 'Záznam' @@ -1687,6 +1741,7 @@ function init() { ,dk: 'Post' ,fi: 'Tietue' ,nb: 'Registrering' + ,he: 'רשומה' } ,'Quick picks' : { cs: 'Rychlý výběr' @@ -1702,6 +1757,7 @@ function init() { ,dk: 'Hurtig valg' ,fi: 'Nopeat valinnat' ,nb: 'Hurtigvalg' + ,he: 'בחירה מהירה' } ,'Show hidden' : { cs: 'Zobraz skryté' @@ -1717,6 +1773,7 @@ function init() { ,dk: 'Vis skjulte' ,fi: 'Näytä piilotettu' ,nb: 'Vis skjulte' + ,he: 'הצג נתונים מוסתרים' } ,'Your API secret' : { cs: 'Vaše API heslo' @@ -1762,6 +1819,7 @@ function init() { ,dk: 'Behandling' ,fi: 'Hoitotoimenpiteet' ,nb: 'Behandlinger' + ,he: 'טיפולים' } ,'Time' : { cs: 'Čas' @@ -1777,6 +1835,7 @@ function init() { ,dk: 'Tid' ,fi: 'Aika' ,nb: 'Tid' + ,he: 'זמן' } ,'Event Type' : { cs: 'Typ události' @@ -1792,6 +1851,7 @@ function init() { ,dk: 'Hændelsestype' ,fi: 'Tapahtumatyyppi' ,nb: 'Type' + ,he: 'סוג אירוע' } ,'Blood Glucose' : { cs: 'Glykémie' @@ -1807,6 +1867,7 @@ function init() { ,dk: 'Glukoseværdi' ,fi: 'Verensokeri' ,nb: 'Blodsukker' + ,he: 'סוכר בדם' } ,'Entered By' : { cs: 'Zadal' @@ -1822,6 +1883,7 @@ function init() { ,dk: 'Indtastet af' ,fi: 'Tiedot syötti' ,nb: 'Lagt inn av' + ,he: 'הוזן על-ידי' } ,'Delete this treatment?' : { cs: 'Vymazat toto ošetření?' @@ -1837,6 +1899,7 @@ function init() { ,dk: 'Slet denne hændelse?' ,fi: 'Tuhoa tämä hoitotoimenpide?' ,nb: 'Slett denne hendelsen?' + ,he: 'למחוק רשומה זו?' } ,'Carbs Given' : { cs: 'Sacharidů' @@ -1852,6 +1915,7 @@ function init() { ,dk: 'Antal kulhydrater' ,fi: 'Hiilihydraatit' ,nb: 'Karbo' + ,he: 'פחמימות שנאכלו' } ,'Inzulin Given' : { cs: 'Inzulínu' @@ -1867,6 +1931,7 @@ function init() { ,dk: 'Insulin' ,fi: 'Insuliiniannos' ,nb: 'Insulin' + ,he: 'אינסולין שניתן' } ,'Event Time' : { cs: 'Čas události' @@ -1882,6 +1947,7 @@ function init() { ,dk: 'Tidspunkt for hændelsen' ,fi: 'Aika' ,nb: 'Tidspunkt for hendelsen' + ,he: 'זמן האירוע' } ,'Please verify that the data entered is correct' : { cs: 'Prosím zkontrolujte, zda jsou údaje zadány správně' @@ -1897,6 +1963,7 @@ function init() { ,dk: 'Venligst verificer at indtastet data er korrekt' ,fi: 'Varmista, että tiedot ovat oikein' ,nb: 'Vennligst verifiser at inntastet data er korrekt' + ,he: 'נא לוודא שהמידע שהוזן הוא נכון ומדוייק' } ,'BG' : { cs: 'Glykémie' @@ -2002,6 +2069,7 @@ function init() { ,dk: 'eller' ,fi: 'tai' ,nb: 'eller' + ,he: 'או' } ,'Add from database' : { cs: 'Přidat z databáze' @@ -2017,6 +2085,7 @@ function init() { ,dk: 'Tilføj fra database' ,fi: 'Lisää tietokannasta' ,nb: 'Legg til fra database' + ,he: 'הוסף מבסיס נתונים' } ,'Use carbs correction in calculation' : { cs: 'Použij korekci na sacharidy' @@ -2122,6 +2191,7 @@ function init() { ,dk: 'Insulin påkrævet' ,fi: 'Insuliinitarve' ,nb: 'Insulin nødvendig' + ,he: 'אינסולין נדרש' } ,'Carbs needed' : { cs: 'Potřebné sach' @@ -2137,6 +2207,7 @@ function init() { ,dk: 'Kulhydrater påkrævet' ,fi: 'Hiilihydraattitarve' ,nb: 'Karbohydrater nødvendig' + ,he: 'פחמימות נדרשות' } ,'Carbs needed if Insulin total is negative value' : { cs: 'Chybějící sacharidy v případě, že výsledek je záporný' From 35a9e729c4731f3ae9c8cae10a647e1e26d4de28 Mon Sep 17 00:00:00 2001 From: avielf Date: Thu, 17 Sep 2015 11:48:26 +0300 Subject: [PATCH 925/937] Removed redundant semicolum --- lib/language.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/language.js b/lib/language.js index ea8418c4250..7ea39687ace 100644 --- a/lib/language.js +++ b/lib/language.js @@ -74,7 +74,7 @@ function init() { ,fi: 'Ti' ,nb: 'Tir' ,he: 'ג' - }, + } ,'We' : { cs: 'St' ,de: 'Mi' From f3d2d1ec4e9f87ddcba9d60fa52b9ef157fdff09 Mon Sep 17 00:00:00 2001 From: avielf Date: Thu, 17 Sep 2015 11:53:34 +0300 Subject: [PATCH 926/937] Missing semicolon on 955 --- lib/language.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/language.js b/lib/language.js index 7ea39687ace..f75a36b353d 100644 --- a/lib/language.js +++ b/lib/language.js @@ -952,7 +952,7 @@ function init() { ,dk: 'Period' ,fi: 'Aikaväli' ,nb: 'Periode' - he:, 'תקופה' + ,he:, 'תקופה' } ,'High' : { cs: 'Vysoká' From 23ef15f57b996027df7b1eacba9be2f1cbe9b736 Mon Sep 17 00:00:00 2001 From: avielf Date: Thu, 17 Sep 2015 11:58:22 +0300 Subject: [PATCH 927/937] Update language.js --- lib/language.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/language.js b/lib/language.js index f75a36b353d..4a3ba93cbe7 100644 --- a/lib/language.js +++ b/lib/language.js @@ -952,7 +952,7 @@ function init() { ,dk: 'Period' ,fi: 'Aikaväli' ,nb: 'Periode' - ,he:, 'תקופה' + ,he: 'תקופה' } ,'High' : { cs: 'Vysoká' From 5a18725d34d1129e44c11dcb98314a7d911c2055 Mon Sep 17 00:00:00 2001 From: avielf Date: Thu, 17 Sep 2015 14:04:45 +0300 Subject: [PATCH 928/937] Log a Treatment screen Hebrew translastions --- lib/language.js | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/lib/language.js b/lib/language.js index b453ff7e1b5..03875e5023c 100644 --- a/lib/language.js +++ b/lib/language.js @@ -2328,6 +2328,7 @@ function init() { ,dk: 'Tid i minutter' ,fi: 'Aika minuuteissa' ,nb: 'Tid i minutter' + ,he: 'זמן בדקות' } ,'15 minutes later' : { cs: '15 min po' @@ -2343,6 +2344,7 @@ function init() { ,dk: '15 min senere' ,fi: '15 minuuttia myöhemmin' ,nb: '15 min senere' + ,he: 'רבע שעה מאוחר יותר' } ,'20 minutes later' : { cs: '20 min po' @@ -2358,6 +2360,7 @@ function init() { ,dk: '20 min senere' ,fi: '20 minuuttia myöhemmin' ,nb: '20 min senere' + ,he: 'עשרים דקות מאוחר יותר' } ,'30 minutes later' : { cs: '30 min po' @@ -2373,6 +2376,7 @@ function init() { ,dk: '30 min senere' ,fi: '30 minuuttia myöhemmin' ,nb: '30 min senere' + ,he: 'חצי שעה מאוחר יותר' } ,'45 minutes later' : { cs: '45 min po' @@ -2388,6 +2392,7 @@ function init() { ,dk: '45 min senere' ,fi: '45 minuuttia myöhemmin' ,nb: '45 min senere' + ,he: 'שלושת רבעי שעה מאוחר יותר' } ,'60 minutes later' : { cs: '60 min po' @@ -2403,6 +2408,7 @@ function init() { ,dk: '60 min senere' ,fi: '60 minuuttia myöhemmin' ,nb: '60 min senere' + ,he: 'שעה מאוחר יותר' } ,'Additional Notes, Comments' : { cs: 'Dalši poznámky, komentáře' @@ -2418,6 +2424,7 @@ function init() { ,dk: 'Ekstra noter, kommentarer' ,fi: 'Lisähuomiot, kommentit' ,nb: 'Ekstra notater, kommentarer' + ,he: 'הערות נוספות' } ,'RETRO MODE' : { cs: 'V MINULOSTI' @@ -2448,6 +2455,7 @@ function init() { ,dk: 'Nu' ,fi: 'Nyt' ,nb: 'Nå' + ,he: 'עכשיו' } ,'Other' : { cs: 'Jiný' @@ -2463,6 +2471,7 @@ function init() { ,dk: 'Øvrige' ,fi: 'Muu' ,nb: 'Annet' + ,he: 'אחר' } ,'Submit Form' : { cs: 'Odeslat formulář' @@ -2478,6 +2487,7 @@ function init() { ,dk: 'Gem hændelsen' ,fi: 'Lähetä tiedot' ,nb: 'Lagre' + ,he: 'שמור' } ,'Profile Editor' : { cs: 'Editor profilu' @@ -2493,6 +2503,7 @@ function init() { ,dk: 'Profil editor' ,fi: 'Profiilin muokkaus' ,nb: 'Profileditor' + ,he: 'ערוך פרופיל' } ,'Reports' : { cs: 'Výkazy' @@ -2688,6 +2699,7 @@ function init() { ,dk: 'Sensor' ,fi: 'Sensori' ,nb: 'Sensor' + ,he: 'חיישן סוכר' } ,'Finger' : { cs: 'Glukoměr' @@ -2703,6 +2715,7 @@ function init() { ,dk: 'Finger' ,fi: 'Sormi' ,nb: 'Finger' + ,he: 'אצבע' } ,'Manual' : { cs: 'Ručně' @@ -2718,6 +2731,7 @@ function init() { ,dk: 'Manuel' ,fi: 'Manuaalinen' ,nb: 'Manuell' + he:, 'ידני' } ,'Scale' : { cs: 'Měřítko' @@ -2748,6 +2762,7 @@ function init() { ,dk: 'Lineær' ,fi: 'Lineaarinen' ,nb: 'Lineær' + ,he: 'לינארי' } ,'Logarithmic' : { cs: 'logaritmické' @@ -2763,6 +2778,7 @@ function init() { ,dk: 'Logaritmisk' ,fi: 'Logaritminen' ,nb: 'Logaritmisk' + ,he: 'לוגריתמי' } ,'Silence for 30 minutes' : { cs: 'Ztlumit na 30 minut' @@ -2973,6 +2989,7 @@ function init() { ,dk: 'Log en hændelse' ,fi: 'Tallenna tapahtuma' ,nb: 'Logg en hendelse' + ,he: 'הזן רשומה' } ,'BG Check' : { cs: 'Kontrola glykémie' @@ -2988,6 +3005,7 @@ function init() { ,dk: 'BS kontrol' ,fi: 'Verensokerin tarkistus' ,nb: 'Blodsukkerkontroll' + ,he: 'בדיקת סוכר' } ,'Meal Bolus' : { cs: 'Bolus na jídlo' @@ -3003,6 +3021,7 @@ function init() { ,dk: 'Måltidsbolus' ,fi: 'Ruokailubolus' ,nb: 'Måltidsbolus' + ,he: 'בולוס ארוחה' } ,'Snack Bolus' : { cs: 'Bolus na svačinu' @@ -3018,6 +3037,7 @@ function init() { ,dk: 'Mellemmåltidsbolus' ,fi: 'Ruokakorjaus' ,nb: 'Mellommåltidsbolus' + ,he: 'בולוס ארוחת ביניים' } ,'Correction Bolus' : { cs: 'Bolus na glykémii' @@ -3033,6 +3053,7 @@ function init() { ,dk: 'Korrektionsbolus' ,fi: 'Korjausbolus' ,nb: 'Korreksjonsbolus' + ,he: 'בולוס תיקון' } ,'Carb Correction' : { cs: 'Přídavek sacharidů' @@ -3048,6 +3069,7 @@ function init() { ,dk: 'Kulhydratskorrektion' ,fi: 'Hiilihydraattikorjaus' ,nb: 'Karbohydratkorrigering' + ,he: 'בולוס פחמימות' } ,'Note' : { cs: 'Poznámka' @@ -3063,6 +3085,7 @@ function init() { ,dk: 'Note' ,fi: 'Merkintä' ,nb: 'Notat' + ,he: 'הערה' } ,'Question' : { cs: 'Otázka' @@ -3078,11 +3101,13 @@ function init() { ,dk: 'Spørgsmål' ,fi: 'Kysymys' ,nb: 'Spørsmål' + ,he: 'שאלה' } ,'Announcement' : { bg: 'Известяване' , fi: 'Tiedoitus' ,pt: 'Aviso' + ,he: 'הודעה' } ,'Exercise' : { cs: 'Cvičení' @@ -3098,6 +3123,7 @@ function init() { ,dk: 'Træning' ,fi: 'Fyysinen harjoitus' ,nb: 'Trening' + ,he: 'פעילות גופנית' } ,'Pump Site Change' : { cs: 'Výměna setu' @@ -3113,6 +3139,7 @@ function init() { ,dk: 'Skift insulin infusionssted' ,fi: 'Pumpun kanyylin vaihto' ,nb: 'Pumpebytte' + ,he: 'החלפת צינורית משאבה' } ,'Sensor Start' : { cs: 'Spuštění sensoru' @@ -3128,6 +3155,7 @@ function init() { ,dk: 'Sensorstart' ,fi: 'Sensorin aloitus' ,nb: 'Sensorstart' + ,he: 'אתחול חיישן סוכר' } ,'Sensor Change' : { cs: 'Výměna sensoru' @@ -3143,6 +3171,7 @@ function init() { ,dk: 'Sensor ombytning' ,fi: 'Sensorin vaihto' ,nb: 'Sensorbytte' + ,he: 'החלפת חיישן סוכר' } ,'Dexcom Sensor Start' : { cs: 'Spuštění sensoru' @@ -3158,6 +3187,7 @@ function init() { ,dk: 'Dexcom sensor start' ,fi: 'Sensorin aloitus' ,nb: 'Dexcom sensor start' + ,he: 'אתחול חיישן סוכר של דקסקום' } ,'Dexcom Sensor Change' : { cs: 'Výměna sensoru' @@ -3173,6 +3203,7 @@ function init() { ,dk: 'Dexcom sensor ombytning' ,fi: 'Sensorin vaihto' ,nb: 'Dexcom sensor bytte' + ,he: 'החלפת חיישן סוכר של דקסקום' } ,'Insulin Cartridge Change' : { cs: 'Výměna inzulínu' @@ -3188,6 +3219,7 @@ function init() { ,dk: 'Skift insulin beholder' ,fi: 'Insuliinisäiliön vaihto' ,nb: 'Skifte insulin beholder' + ,he: 'החלפת מחסנית אינסולין' } ,'D.A.D. Alert' : { cs: 'D.A.D. Alert' @@ -3203,6 +3235,7 @@ function init() { ,dk: 'Vuf Vuf! (Diabeteshundealarm!)' ,fi: 'Diabeteskoirahälytys' ,nb: 'Diabeteshundalarm' + ,he: 'ווף! ווף! התראת גשג' } ,'Glucose Reading' : { cs: 'Hodnota glykémie' @@ -3218,6 +3251,7 @@ function init() { ,dk: 'Glukose aflæsning' ,fi: 'Verensokeri' ,nb: 'Blodsukkermåling' + ,he: 'מדידת סוכר' } ,'Measurement Method' : { cs: 'Metoda měření' @@ -3233,6 +3267,7 @@ function init() { ,dk: 'Målemetode' ,fi: 'Mittaustapa' ,nb: 'Målemetode' + ,he: 'אמצעי מדידה' } ,'Meter' : { cs: 'Glukoměr' @@ -3248,6 +3283,7 @@ function init() { ,dk: 'Glukosemeter' ,fi: 'Sokerimittari' ,nb: 'Måleapparat' + ,he: 'מד סוכר' } ,'Insulin Given' : { cs: 'Inzulín' @@ -3263,6 +3299,7 @@ function init() { ,dk: 'Insulin dosis' ,fi: 'Insuliiniannos' ,nb: 'Insulin' + ,he: 'אינסולין שניתן' } ,'Amount in grams' : { cs: 'Množství v gramech' @@ -3278,6 +3315,7 @@ function init() { ,dk: 'Antal gram' ,fi: 'Määrä grammoissa' ,nb: 'Antall gram' + ,he: 'כמות בגרמים' } ,'Amount in units' : { cs: 'Množství v jednotkách' @@ -3293,6 +3331,7 @@ function init() { ,dk: 'Antal enheder' ,fi: 'Annos yksiköissä' ,nb: 'Antall enheter' + ,he: 'כמות ביחידות' } ,'View all treatments' : { cs: 'Zobraz všechny ošetření' @@ -3308,6 +3347,7 @@ function init() { ,dk: 'Vis behandlinger' ,fi: 'Katso kaikki hoitotoimenpiteet' ,nb: 'Vis behandlinger' + ,he: 'הצג את כל הטיפולים' } ,'Enable Alarms' : { cs: 'Povolit alarmy' @@ -3323,6 +3363,7 @@ function init() { ,dk: 'Aktivere alarmer' ,fi: 'Aktivoi hälytykset' ,nb: 'Aktiver alarmer' + ,he: 'הפעל התראות' } ,'When enabled an alarm may sound.' : { cs: 'Při povoleném alarmu zní zvuk' @@ -3338,6 +3379,7 @@ function init() { ,dk: 'Når aktiveret kan alarm lyde' ,fi: 'Aktivointi mahdollistaa äänihälytykset' ,nb: 'Når aktivert er alarmer aktive' + ,he: 'כשמופעל התראות יכולות להישמע.' } ,'Urgent High Alarm' : { cs: 'Urgentní vysoká glykémie' @@ -3353,6 +3395,7 @@ function init() { ,dk: 'Høj grænse overskredet' ,fi: 'Kriittinen korkea' ,nb: 'Kritisk høy alarm' + ,he: 'התראת גבוה דחופה' } ,'High Alarm' : { cs: 'Vysoká glykémie' @@ -3368,6 +3411,7 @@ function init() { ,dk: 'Høj grænse overskredet' ,fi: 'Korkea verensokeri' ,nb: 'Høy alarm' + ,he: 'התראת גבוה' } ,'Low Alarm' : { cs: 'Nízká glykémie' @@ -3383,6 +3427,7 @@ function init() { ,dk: 'Lav grænse overskredet' ,fi: 'Matala verensokeri' ,nb: 'Lav alarm' + ,he: 'התראת נמוך' } ,'Urgent Low Alarm' : { cs: 'Urgentní nízká glykémie' @@ -3398,6 +3443,7 @@ function init() { ,dk: 'Advarsel: Lav' ,fi: 'Kriittinen matala' ,nb: 'Kritisk lav alarm' + ,he: 'התראת נמוך דחופה' } ,'Stale Data: Warn' : { cs: 'Zastaralá data' @@ -4012,6 +4058,7 @@ function init() { ,dk: 'Kulhydratstid' ,fi: 'Syöntiaika' ,nb: 'Karbohydrattid' + ,he: 'זמן פחמימה' } ,'Language' : { cs: 'Jazyk' From 34a061a6d7c8972ea1a01a3c6e2c4a75eeffad01 Mon Sep 17 00:00:00 2001 From: avielf Date: Thu, 17 Sep 2015 14:10:44 +0300 Subject: [PATCH 929/937] Misplaced semicolon --- lib/language.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/language.js b/lib/language.js index 03875e5023c..d68a9ed66b7 100644 --- a/lib/language.js +++ b/lib/language.js @@ -2731,7 +2731,7 @@ function init() { ,dk: 'Manuel' ,fi: 'Manuaalinen' ,nb: 'Manuell' - he:, 'ידני' + ,he: 'ידני' } ,'Scale' : { cs: 'Měřítko' From 42da240567c5679e48f47eeaf440080c448f232a Mon Sep 17 00:00:00 2001 From: xpucuto Date: Thu, 17 Sep 2015 17:28:01 +0300 Subject: [PATCH 930/937] bulgarian language - 17.Sept.2015 --- lib/language.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/language.js b/lib/language.js index b453ff7e1b5..ef2b6a4fb31 100644 --- a/lib/language.js +++ b/lib/language.js @@ -1058,7 +1058,7 @@ function init() { ,pt: 'Normal' ,sv: 'Normal' ,ro: 'Normal' - ,bg: 'Нормално' + ,bg: 'Нормалнa' ,hr: 'Normalno' ,it: 'Normale' ,dk: 'Normal' @@ -4026,12 +4026,15 @@ function init() { } ,'Order' : { cs: 'Pořadí' + bg: 'Ред' } ,'oldest on top' : { cs: 'nejstarší nahoře' + ,bg: 'Старите най-отгоре' } ,'newest on top' : { cs: 'nejnovější nahoře' + ,bg: 'Новите най-отгоре' } }; From c6b06c8072e38c0e453cd7cfe66b5472615eedff Mon Sep 17 00:00:00 2001 From: xpucuto Date: Thu, 17 Sep 2015 17:47:42 +0300 Subject: [PATCH 931/937] Update language.js --- lib/language.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/language.js b/lib/language.js index ef2b6a4fb31..ba26e8b857a 100644 --- a/lib/language.js +++ b/lib/language.js @@ -4026,7 +4026,7 @@ function init() { } ,'Order' : { cs: 'Pořadí' - bg: 'Ред' + ,bg: 'Ред' } ,'oldest on top' : { cs: 'nejstarší nahoře' From 967c5c7b3cc1e6ab99c7a1913ad247f14de611aa Mon Sep 17 00:00:00 2001 From: Ben West Date: Thu, 17 Sep 2015 09:48:14 -0700 Subject: [PATCH 932/937] Make DELETE API more usable Thanks to @MilosKozak for these suggestions. * Don't crash if lookup by id does not yield any results * Report errors, if any * Allow DELETE by ID * Allow DELETE by wildcard, `*`, to delete all types. --- lib/api/entries/index.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/api/entries/index.js b/lib/api/entries/index.js index f56afc421d0..f08c96eef9c 100644 --- a/lib/api/entries/index.js +++ b/lib/api/entries/index.js @@ -246,10 +246,15 @@ function configure (app, wares, ctx) { api.get('/entries/:spec', function(req, res, next) { if (isId(req.params.spec)) { entries.getEntry(req.params.spec, function(err, entry) { + if (err) { return next(err); } res.entries = [entry]; res.entries_err = err; req.query.find = req.query.find || {}; - req.query.find.type = entry.type; + if (entry) { + req.query.find.type = entry.type; + } else { + res.entries_err = "No such id: '" + req.params.spec + "'"; + } next(); }); } else { @@ -581,12 +586,15 @@ curl -s -g 'http://localhost:1337/api/v1/times/20{14..15}/T{13..18}:{00..15}'.js */ api.delete('/entries/:spec', wares.verifyAuthorization, function (req, res, next) { // if ID, prepare to query for one record - if (isId(req.params.id)) { + if (isId(req.params.spec)) { prepReqModel(req, req.params.model); req.query = {find: {_id: req.params.spec}}; } else { req.params.model = req.params.spec; prepReqModel(req, req.params.model); + if (req.query.find.type == '*') { + delete req.query.find.type; + } } next( ); }, delete_records); From c0bd8c4ee9d1e52e005ce35eca712b1917f9d857 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Thu, 17 Sep 2015 20:21:51 +0300 Subject: [PATCH 933/937] When loading profiles from Mongo, sort by the actual field that tells when the profile was created. Load only one profile to speed up the process and consume less resources. Removed old check for dia field existence from iob-cob days. --- lib/data.js | 5 +---- lib/profile.js | 4 ++-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/data.js b/lib/data.js index b7e0dda50c9..b9681e11d4b 100644 --- a/lib/data.js +++ b/lib/data.js @@ -295,15 +295,12 @@ function loadTreatments (data, ctx, callback) { } function loadProfile (data, ctx, callback) { - ctx.profile.list(function (err, results) { + ctx.profile.last(function (err, results) { if (!err && results) { - // There should be only one document in the profile collection with a DIA. If there are multiple, use the last one. var profiles = []; results.forEach(function (element) { if (element) { - if (element.dia) { profiles[0] = element; - } } }); data.profiles = profiles; diff --git a/lib/profile.js b/lib/profile.js index 2131b088642..617eec33dba 100644 --- a/lib/profile.js +++ b/lib/profile.js @@ -22,11 +22,11 @@ function storage (collection, ctx) { } function list (fn) { - return api( ).find({ }).sort({validfrom: -1}).toArray(fn); + return api( ).find({ }).sort({startDate: -1}).toArray(fn); } function last (fn) { - return api().find().sort({validfrom: -1}).limit(1).toArray(fn); + return api().find().sort({startDate: -1}).limit(1).toArray(fn); } function api () { From e2e876d3728d790d7060ecd45760e37b29fc9d7f Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Fri, 18 Sep 2015 07:57:45 +0300 Subject: [PATCH 934/937] Change the Mongo index to also use the actual field (thanks Jason!) --- lib/profile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/profile.js b/lib/profile.js index 617eec33dba..31d2e75e57c 100644 --- a/lib/profile.js +++ b/lib/profile.js @@ -37,7 +37,7 @@ function storage (collection, ctx) { api.create = create; api.save = save; api.last = last; - api.indexedFields = ['validfrom']; + api.indexedFields = ['startDate']; return api; } From 9411a6321f5cead1e6cd81b7e6eebd82db0116df Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 17 Sep 2015 22:25:08 -0700 Subject: [PATCH 935/937] === vs == --- lib/api/entries/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/api/entries/index.js b/lib/api/entries/index.js index f08c96eef9c..3e5cbb94d80 100644 --- a/lib/api/entries/index.js +++ b/lib/api/entries/index.js @@ -592,7 +592,7 @@ curl -s -g 'http://localhost:1337/api/v1/times/20{14..15}/T{13..18}:{00..15}'.js } else { req.params.model = req.params.spec; prepReqModel(req, req.params.model); - if (req.query.find.type == '*') { + if (req.query.find.type === '*') { delete req.query.find.type; } } From a0906d04ded492ea5ef3157fb41cfc5f09a15c8e Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 17 Sep 2015 22:54:40 -0700 Subject: [PATCH 936/937] version bump for 0.8.1 --- bower.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index f2dc60ab9a9..0511964f203 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "nightscout", - "version": "0.8.1-beta2", + "version": "0.8.1", "dependencies": { "jquery": "2.1.0", "jQuery-Storage-API": "~1.7.2", diff --git a/package.json b/package.json index a6b20979b22..d906ea3ffd0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Nightscout", - "version": "0.8.1-beta2", + "version": "0.8.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", From 643a6a26f050e386c2e719eedb903fa7fa32d338 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 17 Sep 2015 23:10:38 -0700 Subject: [PATCH 937/937] remove drawer animation --- lib/client/browser-utils.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/client/browser-utils.js b/lib/client/browser-utils.js index fc7baf193a1..d39b501a2c3 100644 --- a/lib/client/browser-utils.js +++ b/lib/client/browser-utils.js @@ -66,11 +66,9 @@ function init ($) { function closeDrawer(id, callback) { lastOpenedDrawer = null; - $('html, body').animate({ scrollTop: 0 }); - $(id).animate({right: '-300px'}, 300, function () { - $(id).css('display', 'none'); - if (callback) { callback(); } - }); + $('html, body').css({ scrollTop: 0 }); + $(id).css({display: 'none', right: '-300px'}); + if (callback) { callback(); } } function openDrawer(id, prepare) { @@ -85,7 +83,7 @@ function init ($) { closeOpenDraw(function () { lastOpenedDrawer = id; if (prepare) { prepare(); } - $(id).css('display', 'block').animate({right: '0'}, 300); + $(id).css({display:'block', right: '0'}); }); }
    '+translate('Time')+''+translate('Readings')+'