From 2c37f3a06cb43ec13d77784a1d572435939be22f Mon Sep 17 00:00:00 2001 From: rjrodger Date: Thu, 8 Aug 2019 18:37:56 -0700 Subject: [PATCH] v0.2.0 --- README.md | 31 +++++++++ dest/segment.js | 2 + package-lock.json | 106 +++++++++++++++-------------- package.json | 6 +- test/user-telemetry.test.js | 10 ++- user-telemetry-docs.js | 128 ------------------------------------ user-telemetry.js | 28 ++++++-- 7 files changed, 123 insertions(+), 188 deletions(-) delete mode 100644 user-telemetry-docs.js diff --git a/README.md b/README.md index ffe4e6a..6460a82 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ $ npm install @seneca/user-telemetry ## Action Patterns +* [get:stats,sys:user-telemetry](#-getstatssysusertelemetry-) @@ -30,6 +31,36 @@ $ npm install @seneca/user-telemetry ## Action Descriptions +### « `get:stats,sys:user-telemetry` » + +Get event collection statistics. + + + + +#### Examples + + + +* `get:stats,sys:user-telemetry,get:stats` + * + + + +#### Replies With + + +``` +{ + _start: 'Start time', + _last: 'Last event time', + _instance: 'Seneca instance id', + user_cmd: 'Count of _user_cmd_ (e.g. register, login, ...) events seen' +} +``` + + +---------- diff --git a/dest/segment.js b/dest/segment.js index ebb8761..5eab2fe 100644 --- a/dest/segment.js +++ b/dest/segment.js @@ -29,6 +29,8 @@ var d = null const intern = (segment.intern) = { handle: { register: function(seneca, options, analytics, msg, res, meta) { + // if res.ok!!! + analytics.identify({ userId: res.user.id, timestamp: (d=new Date(),d.setTime(meta.start),d), diff --git a/package-lock.json b/package-lock.json index ea3cf73..35cd393 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@seneca/user-telemetry", - "version": "0.0.1", + "version": "0.1.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -40,14 +40,22 @@ } }, "@hapi/bossy": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@hapi/bossy/-/bossy-4.1.1.tgz", - "integrity": "sha512-SLmqA4f6b+u9Cqp3+0c1gC58WZqUkkHymgoI68osfreyBCgMwErHnHKWDF+YMnLm4RB9ZRIpSosw+DGDNKlZ8A==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@hapi/bossy/-/bossy-4.1.2.tgz", + "integrity": "sha512-6DfWr60rMkXggtYU5mcnGsUeDFRyMu5SFlDN+OLEPB7Ye34j92vYrD2kuR7HPdi/WGqW2K2mPa34g4rN4gmeMQ==", "dev": true, "requires": { "@hapi/boom": "7.x.x", - "@hapi/hoek": "6.x.x", + "@hapi/hoek": "8.x.x", "@hapi/joi": "15.x.x" + }, + "dependencies": { + "@hapi/hoek": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-8.2.0.tgz", + "integrity": "sha512-pR2ZgiP562aiaQvQ98WgfqfTrm+xG+7hwHRPEiYZ+7U1OHAAb4OVZJIalCP03bMqYSioQzflzVTVrybSwDBn1Q==", + "dev": true + } } }, "@hapi/code": { @@ -60,23 +68,23 @@ }, "dependencies": { "@hapi/hoek": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-8.1.0.tgz", - "integrity": "sha512-b1J4jxYnW+n6lC91V6Pqg9imP9BZq0HNCeM+3sbXg05rQsE9cGYrKFpZjyztVesGmNRE6R+QaEoWGATeIiUVjA==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-8.2.0.tgz", + "integrity": "sha512-pR2ZgiP562aiaQvQ98WgfqfTrm+xG+7hwHRPEiYZ+7U1OHAAb4OVZJIalCP03bMqYSioQzflzVTVrybSwDBn1Q==", "dev": true } } }, "@hapi/eslint-config-hapi": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/@hapi/eslint-config-hapi/-/eslint-config-hapi-12.1.0.tgz", - "integrity": "sha512-1s/kAcMEn/h2hEQgXr8d8DE9Ajgu/QUz1HFuMWDJnzR7nJG4RR5p3Ees03STuTq9lbLTZ99MI43F40scXtBxPg==", + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/@hapi/eslint-config-hapi/-/eslint-config-hapi-12.2.0.tgz", + "integrity": "sha512-tByj7aMwzpG1bTxiVkDlNWbEcdm7AYNp8HSFys8COO5Vxg7FwTqyU44Ke/Jdm3M13vGSD+OEyF0LB3aw4khjlQ==", "dev": true }, "@hapi/eslint-plugin-hapi": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@hapi/eslint-plugin-hapi/-/eslint-plugin-hapi-4.3.3.tgz", - "integrity": "sha512-LrUA7XgCw12/GQ1UjC94rtFW5hW+VMq17j1OIsMXaCEaUgNoqkzwLqxY8A/vx5mMTK660LoD+fQZz4lh6PfX6A==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@hapi/eslint-plugin-hapi/-/eslint-plugin-hapi-4.3.4.tgz", + "integrity": "sha512-sm7dg6m6dPjRCx1DDaTK7Ij7YjZMJsdscZOn4dAPvWekhEPYSRoQRXId64iVVjxubuZELzcIyUoNj9koyVx4Sg==", "dev": true, "requires": { "@hapi/rule-capitalize-modules": "1.x.x", @@ -136,27 +144,27 @@ "dev": true }, "@hapi/rule-capitalize-modules": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@hapi/rule-capitalize-modules/-/rule-capitalize-modules-1.2.0.tgz", - "integrity": "sha512-qOn+1LI3OVJCtMHZc1lvF4sQ9d1SOC7ksgTj0dXHp1+Ay7AmKD1EDk63BptZDYN/sVq7ds0i4SLdr9BIeRKncQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@hapi/rule-capitalize-modules/-/rule-capitalize-modules-1.2.1.tgz", + "integrity": "sha512-XduBSQQehgY/PJX/Ud2H5UdVyNVEC3QiU00vOHWvpn+kbH1co2dmzpu2JEGGxKmdGHjm8jdDkrlqVxGFXHAuhQ==", "dev": true }, "@hapi/rule-for-loop": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@hapi/rule-for-loop/-/rule-for-loop-1.2.0.tgz", - "integrity": "sha512-lV7l9DwWH+eTDJjUK7dRsY1yTiGPBh/dhl5MEPBaSNfDP8EwUlf1WXe+SBO9IRcoQdgGy9t4jDx/eSRaTRlVNQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@hapi/rule-for-loop/-/rule-for-loop-1.2.1.tgz", + "integrity": "sha512-9Y2yjNpmhntryViPTb6JlTCqma9fF+H0lCtjvlWA0La/ckxPSzXfwh9kgroyjQ3mJiwKDUYboqC4/BK6L5DFUg==", "dev": true }, "@hapi/rule-no-arrowception": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@hapi/rule-no-arrowception/-/rule-no-arrowception-1.1.1.tgz", - "integrity": "sha512-PMRSFTXpJeyuQx2wTqGv9FPcN6wlv/aue1FZhX/71WKdA49JURQAFp2JJ0hMTC/MWL18qshMETQ4mWJKO5KHqw==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@hapi/rule-no-arrowception/-/rule-no-arrowception-1.1.2.tgz", + "integrity": "sha512-NV6IpfcUpI6w/6oR2oBFaBUoOGC3j3xzfXq7ZciBnmOyReqwuSiyvwLb9SeSomei/n1LHaVdnIXJnMD9IAma2Q==", "dev": true }, "@hapi/rule-no-var": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@hapi/rule-no-var/-/rule-no-var-1.1.3.tgz", - "integrity": "sha512-tDhf8q5d5+7UqnYfCPX6NHi4mWtWbTrqHt7lDN1GWAGtdXyr49BJZoC5YiauVF/53VHz0P6GAiwTQ1SDm/wNiA==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@hapi/rule-no-var/-/rule-no-var-1.1.4.tgz", + "integrity": "sha512-u0gtMRd9uxlmmew3H5pBZJe1D64dTz5yhPWU3UcurOwZGTbGYU2PAUpjxE0TOaeCRDW+tL5Y/9f7637P2vqQSA==", "dev": true }, "@hapi/rule-scope-start": { @@ -166,18 +174,18 @@ "dev": true }, "@hapi/topo": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-3.1.2.tgz", - "integrity": "sha512-r+aumOqJ5QbD6aLPJWqVjMAPsx5pZKz+F5yPqXZ/WWG9JTtHbQqlzrJoknJ0iJxLj9vlXtmpSdjlkszseeG8OA==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-3.1.3.tgz", + "integrity": "sha512-JmS9/vQK6dcUYn7wc2YZTqzIKubAQcJKu2KCKAru6es482U5RT5fP1EXCPtlXpiK7PR0On/kpQKI4fRKkzpZBQ==", "dev": true, "requires": { "@hapi/hoek": "8.x.x" }, "dependencies": { "@hapi/hoek": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-8.1.0.tgz", - "integrity": "sha512-b1J4jxYnW+n6lC91V6Pqg9imP9BZq0HNCeM+3sbXg05rQsE9cGYrKFpZjyztVesGmNRE6R+QaEoWGATeIiUVjA==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-8.2.0.tgz", + "integrity": "sha512-pR2ZgiP562aiaQvQ98WgfqfTrm+xG+7hwHRPEiYZ+7U1OHAAb4OVZJIalCP03bMqYSioQzflzVTVrybSwDBn1Q==", "dev": true } } @@ -208,9 +216,9 @@ } }, "@seneca/user": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@seneca/user/-/user-3.0.1.tgz", - "integrity": "sha512-kLLxyd4ebw8Y83ARb2fhf9bHaHLwx0ABtLIi0rOJyLhrtTVthyGZWGRzRmw3HDA9ObRk1Hq7zOZ6LXfbczE9wA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@seneca/user/-/user-3.1.0.tgz", + "integrity": "sha512-uddTUhgp4zeJ7yBmBiDUE5VY2axukZvBjrc1iVUnIcGgbYJOoFw0hsapbeP+kenQDjfsNAAj1NB0L8Btn0nErg==", "dev": true, "requires": { "eraro": "1.1", @@ -242,9 +250,9 @@ "dev": true }, "@types/node": { - "version": "12.6.8", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.6.8.tgz", - "integrity": "sha512-aX+gFgA5GHcDi89KG5keey2zf0WfZk/HAQotEamsK2kbey+8yGKcson0hbK8E+v0NArlCJQCqMP161YhV6ZXLg==", + "version": "12.7.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.1.tgz", + "integrity": "sha512-aK9jxMypeSrhiYofWWBf/T7O+KwaiAHzM4sveCdWPn71lzUSMimRnKzhXDKfKwV1kWoBo2P1aGgaIYGLf9/ljw==", "dev": true }, "acorn": { @@ -742,9 +750,9 @@ "dev": true }, "coveralls": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.0.5.tgz", - "integrity": "sha512-/KD7PGfZv/tjKB6LoW97jzIgFqem0Tu9tZL9/iwBnBd8zkIZp7vT1ZSHNvnr0GSQMV/LTMxUstWg8WcDDUVQKg==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.0.6.tgz", + "integrity": "sha512-Pgh4v3gCI4T/9VijVrm8Ym5v0OgjvGLKj3zTUwkvsCiwqae/p6VLzpsFNjQS2i6ewV7ef+DjFJ5TSKxYt/mCrA==", "dev": true, "requires": { "growl": "~> 1.10.0", @@ -2603,12 +2611,12 @@ "dev": true }, "seneca": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/seneca/-/seneca-3.13.0.tgz", - "integrity": "sha512-D7gQ9vlWHCSJx6ESj3SG0fqZ+qFThqRlH8vi5Fyco7li05/Qw5nR0jXGf90T8GPRgsMeD8/xATYjTwdljWwdJg==", + "version": "3.13.2", + "resolved": "https://registry.npmjs.org/seneca/-/seneca-3.13.2.tgz", + "integrity": "sha512-KVMPXE7K9XbdJSq12tGfvQDjllH+QWhqpDGdsl9w5wVcIsKoYStfRV73Exr8jQWAgcKDiv6rvC8+8mNwcxZmKw==", "dev": true, "requires": { - "@hapi/joi": "^15.0.3", + "@hapi/joi": "^15.1.0", "eraro": "^1.1.0", "gate-executor": "^2.0.1", "gex": "^0.3.0", @@ -2640,12 +2648,12 @@ } }, "seneca-doc": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/seneca-doc/-/seneca-doc-0.2.2.tgz", - "integrity": "sha512-lVjitzr0Aq9q7muyF6ugA2Qy6CNUSh6/FJ7Pd3WQIuJWkhv0fv8+E1Lh3moFATzf1/v1P2ugjAFOqh+C/BYOXQ==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/seneca-doc/-/seneca-doc-1.0.0.tgz", + "integrity": "sha512-mG0Cn1dju5i5n/RCyR1ZDU2im5xCAx0wWZz4aCMAJBN9ppXoLXY5FO8Xt8j4j5diY7pLHBBwAzv/1MgPwSVOtA==", "dev": true, "requires": { - "seneca": "^3.13.0", + "seneca": "^3.13.2", "seneca-promisify": "^0.8.0" } }, diff --git a/package.json b/package.json index 9e07b45..92d57e9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@seneca/user-telemetry", - "version": "0.1.1", + "version": "0.2.0", "description": "A Seneca plugin that provides basic user telemetry operations.", "main": "user-telemetry.js", "scripts": { @@ -41,10 +41,10 @@ "devDependencies": { "seneca": "plugin", "seneca-basic": "^0.6.0", - "seneca-doc": "^0.2.2", + "seneca-doc": "^1.0.0", "seneca-entity": "^4.1.0", "seneca-plugin-validator": "^0.1.1", "seneca-promisify": "^0.8.0", - "@seneca/user": "^3.0.1" + "@seneca/user": "^3.1.0" } } diff --git a/test/user-telemetry.test.js b/test/user-telemetry.test.js index b7b3ace..0d4187f 100644 --- a/test/user-telemetry.test.js +++ b/test/user-telemetry.test.js @@ -26,11 +26,17 @@ lab.test('happy', async () => { }).ready() await si.post( - 'role:user,cmd:register,name:Joe\x20Bloggs,email:joe.bloggs@example.com' + 'sys:user,cmd:register,user:{name:Joe\x20Bloggs,email:joe.bloggs@example.com}' + ) + await si.post( + 'sys:user,cmd:login,user:{email:joe.bloggs@example.com,auto:true}' ) - await si.post('role:user,cmd:login,email:joe.bloggs@example.com,auto:true') expect(log).equal(['init', 'event/register', 'event/login']) + + var stats = await si.post('sys:user-telemetry,get:stats') + expect(stats).contains({ register: 1, login: 1 }) + expect(stats._start).below(stats._last) }) function seneca_instance(config, plugin_options) { diff --git a/user-telemetry-docs.js b/user-telemetry-docs.js deleted file mode 100644 index bd70837..0000000 --- a/user-telemetry-docs.js +++ /dev/null @@ -1,128 +0,0 @@ -const Joi = require('@hapi/joi') - -module.exports = { - add_rel: { - desc: - 'Add a directed relation between two nodes in a given user-telemetry.', - examples: { - 'user-telemetry:number,rel:lessthan,add:rel,from:,to:': - 'Idempotently add a pair of nodes to _number_ user-telemetry connected by directed relation _lessthan_ with `from-id` and `to-id` referencing external entity identifiers as per user-telemetry definition in options.' - }, - validate: { - 'user-telemetry': Joi.string() - .required() - .description('The name of the user-telemetry.'), - rel: Joi.string() - .required() - .description('The name of the relation.'), - from: Joi.string() - .required() - .description('From-side entity identifier.'), - to: Joi.string() - .required() - .description('To-side entity identifier.') - }, - reply_desc: { - from: '_from_ parameter, as provided', - to: '_to_ parameter, as provided', - rel: '_rel_ parameter, as provided', - 'user-telemetry': '_user-telemetry_ parameter, as provided', - node: { - f: 'from-side entity identifier', - t: 'to-side entity identifier', - r: 'relation name', - id: 'internal user-telemetry node identifier' - } - } - }, - - list_rel: { - desc: 'List nodes connected by a given relation.', - examples: { - 'user-telemetry:number,rel:lessthan,list:rel': - 'List all nodes in _number_ user-telemetry connected by directed relation _lessthan_', - 'user-telemetry:number,rel:lessthan,list:rel,from:': - 'List all nodes in _number_ user-telemetry connected by directed relation _lessthan_ with given `from-id`', - 'user-telemetry:number,rel:lessthan,list:rel,to:': - 'List all nodes in _number_ user-telemetry connected by directed relation _lessthan_ with given `to-id`', - 'user-telemetry:number,rel:lessthan,list:rel,to:,entity:from': - 'List all nodes in _number_ user-telemetry connected by directed relation _lessthan_ with given `to-id`, loading and including referenced from-side entities', - 'user-telemetry:number,rel:lessthan,list:rel,entity:to': - 'List all nodes in _number_ user-telemetry connected by directed relation _lessthan_, loading and including referenced to-side entities' - }, - validate: { - 'user-telemetry': Joi.string() - .required() - .description('The name of the user-telemetry.'), - rel: Joi.string() - .required() - .description('The name of the relation.'), - from: Joi.string().description('From-side entity identifier.'), - to: Joi.string().description('To-side entity identifier.'), - entity: Joi.string() - .valid('from', 'to', 'both') - .description('Entity side to load. Values: _from_, _to_, _both_.') - }, - reply_desc: { - from: '_from_ parameter, as provided', - to: '_to_ parameter, as provided', - rel: '_rel_ parameter, as provided', - 'user-telemetry': '_user-telemetry_ parameter, as provided', - list: [ - { - f: 'from-side entity identifier', - t: 'to-side entity identifier', - r: 'relation name', - id: 'internal user-telemetry node identifier', - fe: 'from-side entity', - te: 'to-side entity' - }, - '...' - ] - } - }, - - load_tree: { - desc: 'Load tree of nodes connected by given relation.', - examples: { - 'user-telemetry:number,rel:lessthan,from:': - 'Load tree of nodes from _number_ user-telemetry connected by directed relation _lessthan_ with `from-id` nodes at first level, to default depth of 1.', - 'user-telemetry:number,rel:lessthan,from:,depth:2,entity:to': - 'Load tree of nodes from _number_ user-telemetry connected by directed relation _lessthan_ with `from-id` nodes at first level, to depth 2, loading and returning referenced to-side entities.' - }, - validate: { - 'user-telemetry': Joi.string() - .required() - .description('The name of the user-telemetry.'), - rel: Joi.string() - .required() - .description('The name of the relation.'), - from: Joi.string().description('From-side entity identifier.'), - to: Joi.string().description('To-side entity identifier.'), - depth: Joi.number() - .default(1) - .description('Depth of user-telemetry to traverse.'), - entity: Joi.string() - .valid('from', 'to', 'both') - .description('Entity side to load. Values: _from_, _to_, _both_.') - }, - reply_desc: { - from: '_from_ parameter, as provided', - to: '_to_ parameter, as provided', - rel: '_rel_ parameter, as provided', - 'user-telemetry': '_user-telemetry_ parameter, as provided', - c: [ - { - f: 'from-side entity identifier', - t: 'to-side entity identifier', - r: 'relation name', - id: 'internal user-telemetry node identifier', - fe: 'from-side entity', - te: 'to-side entity', - c: ['{ ...connected-nodes... }'] - }, - '...connected-nodes...' - ] - } - } -} diff --git a/user-telemetry.js b/user-telemetry.js index 9f9e598..7aabe2a 100644 --- a/user-telemetry.js +++ b/user-telemetry.js @@ -1,8 +1,6 @@ /* Copyright (c) 2019 voxgig and other contributors, MIT License */ 'use strict' -// const Docs = require('./user-telemetry-docs.js') - module.exports = user_telemetry module.exports.defaults = { dest: [] @@ -14,10 +12,17 @@ module.exports.errors = {} function user_telemetry(options) { var dests = null - this.sub('out$:true,role:user,cmd:register', handle_event).sub( - 'out$:true,role:user,cmd:login', - handle_event - ) + var stats = { + _start: Date.now(), + _instance: this.id, + register: 0, + login: 0 + } + stats._last = stats._start + + this.message('sys:user-telemetry,get:stats', get_stats) + .sub('out$:true,sys:user,cmd:register', handle_event) + .sub('out$:true,sys:user,cmd:login', handle_event) this.prepare(async function() { dests = await intern.resolve_dest(this, options) @@ -35,7 +40,18 @@ function user_telemetry(options) { } }) + async function get_stats(msg) { + return this.util.deep(stats) + } + function handle_event(msg, res, meta) { + var cmd = msg.cmd + + if (null != stats[cmd]) { + stats[cmd]++ + stats._last = Date.now() + } + for (var i = 0; i < dests.length; i++) { // NOTE: called synchronously! try {