From f58d8ec6b81606083ea85746df613a442ecadb4e Mon Sep 17 00:00:00 2001 From: Victor Sui Date: Wed, 8 Nov 2017 20:31:19 -0600 Subject: [PATCH 01/24] add stats --- api/v1/index.js | 2 +- api/v1/models/Stat.js | 58 +++++++++ api/v1/services/StatsService.js | 17 +++ .../migration/V20171011_2012__addStats.sql | 111 ++++++++++++++++++ test/stat.js | 65 ++++++++++ test/test.js | 1 + 6 files changed, 253 insertions(+), 1 deletion(-) create mode 100644 api/v1/models/Stat.js create mode 100644 database/migration/V20171011_2012__addStats.sql create mode 100644 test/stat.js diff --git a/api/v1/index.js b/api/v1/index.js index 2105023..c371475 100644 --- a/api/v1/index.js +++ b/api/v1/index.js @@ -30,10 +30,10 @@ v1.use('/health', controllers.HealthController.router); v1.use('/checkin', controllers.CheckInController.router); v1.use('/rsvp', controllers.RSVPController.router); v1.use('/announcement', controllers.AnnouncementController.router); -v1.use('/stats', controllers.StatsController.router); v1.use('/tracking', controllers.TrackingController.router); v1.use('/mail', controllers.MailController.router); v1.use('/event', controllers.EventController.router); +v1.use('/stats', controllers.StatsController.router); // logs resolved requests (the request once processed by various middleware) and outgoing responses v1.use((req, res, next) => { diff --git a/api/v1/models/Stat.js b/api/v1/models/Stat.js new file mode 100644 index 0000000..fe7ec62 --- /dev/null +++ b/api/v1/models/Stat.js @@ -0,0 +1,58 @@ +//const _Promise = require('bluebird'); +const _ = require('lodash'); + +const Model = require('./Model'); +const validators = require('../utils/validators'); + +const CATEGORIES = ['registration', 'rsvp', 'live-event']; + +const Stat = Model.extend({ + tableName: 'stats', + idAttribute: 'id', + validations: { + category: ['required', 'string', validators.in(CATEGORIES)], + stat: ['required', 'string'], + field: ['required', 'string'], + count: ['required', 'integer'] // Change to default 0? + } +}); + +/** + * Adds a row with category `category`, stat `stat`, and field `field`. + * Initializes count to 0 + * @param {String} category + * @param {String} stat + * @param {String} field + * @return {Promise} a Promise resolving to the newly-created Stat + */ +Stat.create = (category, stat, field) => { + const s = Stat.forge({ + category: category, + stat: stat, + field: field, + count: 0 + }); + + return s.save(); +}; + +/** + * Increments the specified stat by the amount + * @param {String} category + * @param {String} stat + * @param {String} field + * @param {Number} amount defaults to 1 + * @return {Promise} a Promise resolving to the updating Stat model + */ +Stat.increment = (category, stat, field, amount) => { + if (_.isUndefined(amount)) { + amount = 1; + } + return Stat.query().where({ + 'category': category, + 'stat': stat, + 'field': field + }).increment('count', amount); +}; + +module.exports = Stat; diff --git a/api/v1/services/StatsService.js b/api/v1/services/StatsService.js index c8d013d..dce65c5 100644 --- a/api/v1/services/StatsService.js +++ b/api/v1/services/StatsService.js @@ -4,6 +4,7 @@ const _ = require('lodash'); const database = require('../../database'); const knex = database.connection(); +const Stat = require('../models/Stat'); const Attendee = require('../models/Attendee'); const AttendeeRSVP = require('../models/AttendeeRSVP'); const CheckIn = require('../models/CheckIn'); @@ -18,6 +19,22 @@ const STATS_LIVE_HEADER = 'liveevent'; const STATS_RSVP_HEADER = 'rsvp'; const STATS_REG_HEADER = 'registration'; +module.exports.createStat = function (category, stat, field) { + return Stat.create(category, stat, field); +}; + +module.exports.find = function (category, stat, field) { + return Stat.where({ + category: category, + stat: stat, + field: field + }); +}; + +module.exports.incrementStat = function (category, stat, field) { + return () => Stat.increment(category, stat, field); +}; + /** * Returns a function that takes a query result and populates a stats object * @param {String} key the key to use to nest the stats diff --git a/database/migration/V20171011_2012__addStats.sql b/database/migration/V20171011_2012__addStats.sql new file mode 100644 index 0000000..d752771 --- /dev/null +++ b/database/migration/V20171011_2012__addStats.sql @@ -0,0 +1,111 @@ +CREATE TABLE `stats` ( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `category` VARCHAR(255) NOT NULL, + `stat` VARCHAR(255) NOT NULL, + `field` VARCHAR(255) NOT NULL, + `count` INT UNSIGNED NOT NULL DEFAULT 0, + PRIMARY KEY (`id`), + CONSTRAINT `fk_unique_stats` UNIQUE (`category`, `stat`, `field`) +); + +# ecosystems +# schools +# graduation year +# major + +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('registration', 'transportation', 'NOT_NEEDED'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('registration', 'transportation', 'BUS_REQUESTED'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('registration', 'transportation', 'IN_STATE'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('registration', 'transportation', 'OUT_OF_STATE'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('registration', 'transportation', 'INTERNATIONAL'); + +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('registration', 'diet', 'VEGETARIAN'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('registration', 'diet', 'VEGAN'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('registration', 'diet', 'NONE'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('registration', 'diet', 'GLUTEN_FREE'); + +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('registration', 'shirt_size', 'S'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('registration', 'shirt_size', 'M'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('registration', 'shirt_size', 'L'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('registration', 'shirt_size', 'XL'); + +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('registration', 'gender', 'MALE'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('registration', 'gender', 'FEMALE'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('registration', 'gender', 'NON_BINARY'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('registration', 'gender', 'OTHER'); + +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('registration', 'is_novice', '0'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('registration', 'is_novice', '1'); + +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('registration', 'attendees', 'count'); + +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('registration', 'status', 'ACCEPTED'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('registration', 'status', 'WAITLISTED'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('registration', 'status', 'REJECTED'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('registration', 'status', 'PENDING'); + + + +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', 'transportation', 'NOT_NEEDED'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', 'transportation', 'BUS_REQUESTED'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', 'transportation', 'IN_STATE'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', 'transportation', 'OUT_OF_STATE'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', 'transportation', 'INTERNATIONAL'); + +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', 'diet', 'VEGETARIAN'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', 'diet', 'VEGAN'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', 'diet', 'NONE'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', 'diet', 'GLUTEN_FREE'); + +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', 'shirt_size', 'S'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', 'shirt_size', 'M'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', 'shirt_size', 'L'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', 'shirt_size', 'XL'); + +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', 'gender', 'MALE'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', 'gender', 'FEMALE'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', 'gender', 'NON_BINARY'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', 'gender', 'OTHER'); + +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', 'is_novice', '0'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', 'is_novice', '1'); + +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', 'attendees', 'count'); + +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', 'status', 'ACCEPTED'); +#INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', `status`, `WAITLISTED`); +#INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', `status`, `REJECTED`); +#INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', `status`, `PENDING`); + + + +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', 'transportation', 'NOT_NEEDED'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', 'transportation', 'BUS_REQUESTED'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', 'transportation', 'IN_STATE'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', 'transportation', 'OUT_OF_STATE'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', 'transportation', 'INTERNATIONAL'); + +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', 'diet', 'VEGETARIAN'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', 'diet', 'VEGAN'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', 'diet', 'NONE'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', 'diet', 'GLUTEN_FREE'); + +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', 'shirt_size', 'S'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', 'shirt_size', 'M'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', 'shirt_size', 'L'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', 'shirt_size', 'XL'); + +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', 'gender', 'MALE'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', 'gender', 'FEMALE'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', 'gender', 'NON_BINARY'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', 'gender', 'OTHER'); + +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', 'is_novice', '0'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', 'is_novice', '1'); + +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', 'attendees', 'count'); + +#INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', `status`, `ACCEPTED`); +#INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', `status`, `WAITLISTED`); +#INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', `status`, `REJECTED`); +#INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', `status`, `PENDING`); diff --git a/test/stat.js b/test/stat.js new file mode 100644 index 0000000..8272a1b --- /dev/null +++ b/test/stat.js @@ -0,0 +1,65 @@ +//const _Promise = require('bluebird'); + +const chai = require('chai'); +const sinon = require('sinon'); +const _ = require('lodash'); + +//const UserService = require('../api/v1/models/UserService'); +const StatService = require('../api/v1/services/StatsService'); +const Stat = require('../api/v1/models/Stat'); + +const assert = chai.assert; +// const expect = chai.expect; +const tracker = require('mock-knex').getTracker(); + + +describe('StatService', () => { + describe('increment', () => { + let _createStat; + //let _find; + let testStat; + + before((done) => { + testStat = { category: 'registration', stat: 'testStat', field: 'testField' }; + _createStat = sinon.spy(Stat, 'create'); + //_find = sinon.spy(Stat, 'find'); + done(); + }); + + beforeEach((done) => { + tracker.install(); + done(); + }); + + afterEach((done) => { + tracker.uninstall(); + done(); + }); + + it('creates a new stat', (done) => { + const testStatClone = _.clone(testStat); + + tracker.on('query', (query) => { + query.response([ '1' ]); + }); + + const stat = StatService.createStat( + testStatClone.category, + testStatClone.stat, + testStatClone.field + ); + + // chai.expect(!_.isNull(stat)); + + stat.bind(this).then(() => { + assert(_createStat.calledOnce, 'Stat forge not called with right parameters'); + return done(); + }).catch((err) => done(err)); + }); + + after((done) => { + _createStat.restore(); + done(); + }); + }); +}); diff --git a/test/test.js b/test/test.js index efa6d6b..95624c3 100644 --- a/test/test.js +++ b/test/test.js @@ -25,5 +25,6 @@ require('./checkin.js'); require('./event.js'); require('./tracking.js'); require('./rsvp.js'); +require('./stat.js'); mockery.disable(); From c3cc66286bfae89e245b419dac94f1b0a59226a7 Mon Sep 17 00:00:00 2001 From: Arnav Sankaran Date: Tue, 23 Jan 2018 16:43:05 -0600 Subject: [PATCH 02/24] Fixed broken rsvp test --- test/rsvp.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/rsvp.js b/test/rsvp.js index 44658ce..fd17082 100644 --- a/test/rsvp.js +++ b/test/rsvp.js @@ -168,7 +168,7 @@ describe('RSVPService', () => { const RSVP = RSVPService.updateRSVP(testUser, testAttendeeRSVP, testRSVPClone); RSVP.bind(this).then(function() { - assert(_setRSVP.calledOnce, 'RSVP update not called with right parameters'); + assert(_setRSVP.calledThrice, 'RSVP update not called with right parameters'); assert(_saveRSVP.calledOnce, 'RSVP save not called'); _attendeeRole = testUser.getRole(utils.roles.ATTENDEE); @@ -177,8 +177,6 @@ describe('RSVPService', () => { return done(); }).catch((err) => done(err)); - - done(); }); afterEach((done) => { tracker.uninstall(); From 90725d6986b3cc84297db9996a3b833b5a64bf13 Mon Sep 17 00:00:00 2001 From: Arnav Sankaran Date: Wed, 24 Jan 2018 20:58:23 -0600 Subject: [PATCH 03/24] Add method to check if stat exists in database --- api/v1/models/Stat.js | 19 ++++++- api/v1/services/RegistrationService.js | 2 + api/v1/services/StatsService.js | 4 ++ .../migration/V20171011_2012__addStats.sql | 1 - .../V20171011_2012__addStats.revert.sql | 1 + test/stat.js | 51 +++++++++++++++++++ 6 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 database/revert/V20171011_2012__addStats.revert.sql diff --git a/api/v1/models/Stat.js b/api/v1/models/Stat.js index fe7ec62..436e862 100644 --- a/api/v1/models/Stat.js +++ b/api/v1/models/Stat.js @@ -1,4 +1,4 @@ -//const _Promise = require('bluebird'); +const _Promise = require('bluebird'); const _ = require('lodash'); const Model = require('./Model'); @@ -17,6 +17,23 @@ const Stat = Model.extend({ } }); +/** + * Return true if the stat exists, false otherwise. + * @param {String} category + * @param {String} stat + * @param {String} field + * @return {Promise} a Promise boolean, true if stat exists, false otherwise + */ +Stat.exists = (category, stat, field) => { + const s = Stat.where({ + category: category, + stat: stat, + field: field + }).query().count().then((count) => _Promise.resolve(count[0]['count(*)'] > 0)); + + return s; +}; + /** * Adds a row with category `category`, stat `stat`, and field `field`. * Initializes count to 0 diff --git a/api/v1/services/RegistrationService.js b/api/v1/services/RegistrationService.js index d64b1c4..e117943 100644 --- a/api/v1/services/RegistrationService.js +++ b/api/v1/services/RegistrationService.js @@ -339,6 +339,8 @@ module.exports.createAttendee = (user, attributes) => { throw new errors.InvalidParameterError(message, source); } + // TODO: UPDATE ALL REGISTRATION STATS HERE + return Attendee.transaction((t) => UserRole .addRole(user, utils.roles.ATTENDEE, true, t) .then(() => _saveWithRelated(attendee, attributes, t))); diff --git a/api/v1/services/StatsService.js b/api/v1/services/StatsService.js index 1d77a42..b836b07 100644 --- a/api/v1/services/StatsService.js +++ b/api/v1/services/StatsService.js @@ -23,6 +23,10 @@ module.exports.createStat = function (category, stat, field) { return Stat.create(category, stat, field); }; +module.exports.statExists = function (category, stat, field) { + return Stat.exists(category, stat, field); +}; + module.exports.find = function (category, stat, field) { return Stat.where({ category: category, diff --git a/database/migration/V20171011_2012__addStats.sql b/database/migration/V20171011_2012__addStats.sql index d752771..2a09dd7 100644 --- a/database/migration/V20171011_2012__addStats.sql +++ b/database/migration/V20171011_2012__addStats.sql @@ -8,7 +8,6 @@ CREATE TABLE `stats` ( CONSTRAINT `fk_unique_stats` UNIQUE (`category`, `stat`, `field`) ); -# ecosystems # schools # graduation year # major diff --git a/database/revert/V20171011_2012__addStats.revert.sql b/database/revert/V20171011_2012__addStats.revert.sql new file mode 100644 index 0000000..46d03b9 --- /dev/null +++ b/database/revert/V20171011_2012__addStats.revert.sql @@ -0,0 +1 @@ +DROP TABLE `stats`; \ No newline at end of file diff --git a/test/stat.js b/test/stat.js index 8272a1b..5508b8b 100644 --- a/test/stat.js +++ b/test/stat.js @@ -12,6 +12,7 @@ const assert = chai.assert; // const expect = chai.expect; const tracker = require('mock-knex').getTracker(); +const expect = chai.expect; describe('StatService', () => { describe('increment', () => { @@ -62,4 +63,54 @@ describe('StatService', () => { done(); }); }); + + describe('exists', () => { + let _existsStat; + let testStat; + + before((done) => { + testStat = { category: 'registration', stat: 'testStat', field: 'testField' }; + StatService.createStat( + testStat.category, + testStat.stat, + testStat.field + ); + _existsStat = sinon.spy(Stat, 'exists'); + done(); + }); + + beforeEach((done) => { + tracker.install(); + done(); + }); + + afterEach((done) => { + tracker.uninstall(); + done(); + }); + + it('checks if stat exists', (done) => { + const testStatClone = _.clone(testStat); + + tracker.on('query', (query) => { + query.response([ { 'count(*)': 1 } ]); + }); + + const stat = StatService.statExists( + testStatClone.category, + testStatClone.stat, + testStatClone.field + ); + + assert(_existsStat.calledOnce, 'Stat exists not called with right parameters'); + expect(stat) + .to.eventually.equal(true) + .and.notify(done); + }); + + after((done) => { + _existsStat.restore(); + done(); + }); + }); }); From 16489ae315f5180fb4694deda784b09b6fd8e50e Mon Sep 17 00:00:00 2001 From: Arnav Sankaran Date: Thu, 25 Jan 2018 10:20:38 -0600 Subject: [PATCH 04/24] Fixed stat increment and update stats on attendee registration --- api/v1/models/Stat.js | 16 +++++++++++----- api/v1/services/RegistrationService.js | 11 +++++++++-- api/v1/services/StatsService.js | 2 +- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/api/v1/models/Stat.js b/api/v1/models/Stat.js index 436e862..db5bb75 100644 --- a/api/v1/models/Stat.js +++ b/api/v1/models/Stat.js @@ -65,11 +65,17 @@ Stat.increment = (category, stat, field, amount) => { if (_.isUndefined(amount)) { amount = 1; } - return Stat.query().where({ - 'category': category, - 'stat': stat, - 'field': field - }).increment('count', amount); + + const s = Stat.query((qb) => { + qb.where('category', '=', category); + qb.where('stat', '=', stat); + qb.where('field', '=', field); + }).fetch(); + + return s.then((model) => { + model.set('count', model.get('count') + amount); + return model.save(); + }); }; module.exports = Stat; diff --git a/api/v1/services/RegistrationService.js b/api/v1/services/RegistrationService.js index e117943..5b8f770 100644 --- a/api/v1/services/RegistrationService.js +++ b/api/v1/services/RegistrationService.js @@ -13,6 +13,8 @@ const MailService = require('../services/MailService'); const errors = require('../errors'); const utils = require('../utils'); +const StatsService = require('../services/StatsService'); + /** * Persists (insert or update) a model instance and creates (insert only) any * related models as provided by the related mapping. Use #extractRelatedObjects @@ -324,6 +326,13 @@ module.exports.createAttendee = (user, attributes) => { return _Promise.reject(new errors.InvalidParameterError(message, source)); } + StatsService.incrementStat('registration', 'transportation', attributes.attendee.transportation); + StatsService.incrementStat('registration', 'diet', attributes.attendee.diet); + StatsService.incrementStat('registration', 'shirt_size', attributes.attendee.shirtSize); + StatsService.incrementStat('registration', 'gender', attributes.attendee.gender); + StatsService.incrementStat('registration', 'is_novice', attributes.attendee.isNovice); + StatsService.incrementStat('registration', 'attendees', 'count'); + const attendeeAttrs = attributes.attendee; delete attributes.attendee; @@ -339,8 +348,6 @@ module.exports.createAttendee = (user, attributes) => { throw new errors.InvalidParameterError(message, source); } - // TODO: UPDATE ALL REGISTRATION STATS HERE - return Attendee.transaction((t) => UserRole .addRole(user, utils.roles.ATTENDEE, true, t) .then(() => _saveWithRelated(attendee, attributes, t))); diff --git a/api/v1/services/StatsService.js b/api/v1/services/StatsService.js index b836b07..7e6dc4f 100644 --- a/api/v1/services/StatsService.js +++ b/api/v1/services/StatsService.js @@ -36,7 +36,7 @@ module.exports.find = function (category, stat, field) { }; module.exports.incrementStat = function (category, stat, field) { - return () => Stat.increment(category, stat, field); + return Stat.increment(category, stat, field); }; /** From c8eaa9dc7d28eb8eaaa36c96444e4982f02ec2cb Mon Sep 17 00:00:00 2001 From: Arnav Sankaran Date: Thu, 25 Jan 2018 10:29:19 -0600 Subject: [PATCH 05/24] Add missing catch when querying stat --- api/v1/models/Stat.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/v1/models/Stat.js b/api/v1/models/Stat.js index db5bb75..2a9ecae 100644 --- a/api/v1/models/Stat.js +++ b/api/v1/models/Stat.js @@ -75,7 +75,7 @@ Stat.increment = (category, stat, field, amount) => { return s.then((model) => { model.set('count', model.get('count') + amount); return model.save(); - }); + }).catch(() => null); }; module.exports = Stat; From 37cd7cf9efb7994b56b342ecda87ff5cfbe44979 Mon Sep 17 00:00:00 2001 From: Arnav Sankaran Date: Thu, 25 Jan 2018 17:41:17 -0600 Subject: [PATCH 06/24] Write all registration stats to db on signup --- api/v1/models/Stat.js | 7 +++++++ api/v1/services/RegistrationService.js | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/api/v1/models/Stat.js b/api/v1/models/Stat.js index 2a9ecae..6adb081 100644 --- a/api/v1/models/Stat.js +++ b/api/v1/models/Stat.js @@ -73,8 +73,15 @@ Stat.increment = (category, stat, field, amount) => { }).fetch(); return s.then((model) => { + if(model == null) { + return Stat.create(category, stat, String(field)).then((createdModel) => { + createdModel.set('count', createdModel.get('count') + amount); + return createdModel.save(); + }); + } model.set('count', model.get('count') + amount); return model.save(); + }).catch(() => null); }; diff --git a/api/v1/services/RegistrationService.js b/api/v1/services/RegistrationService.js index 5b8f770..5576bf6 100644 --- a/api/v1/services/RegistrationService.js +++ b/api/v1/services/RegistrationService.js @@ -331,7 +331,11 @@ module.exports.createAttendee = (user, attributes) => { StatsService.incrementStat('registration', 'shirt_size', attributes.attendee.shirtSize); StatsService.incrementStat('registration', 'gender', attributes.attendee.gender); StatsService.incrementStat('registration', 'is_novice', attributes.attendee.isNovice); + StatsService.incrementStat('registration', 'status', attributes.attendee.status); StatsService.incrementStat('registration', 'attendees', 'count'); + StatsService.incrementStat('registration', 'school', attributes.attendee.school); + StatsService.incrementStat('registration', 'graduation_year', attributes.attendee.graduationYear); + StatsService.incrementStat('registration', 'major', attributes.attendee.major); const attendeeAttrs = attributes.attendee; delete attributes.attendee; From ad7f1aac87a5dc1835571cc7d8483e4bee5b4e10 Mon Sep 17 00:00:00 2001 From: Arnav Sankaran Date: Thu, 25 Jan 2018 21:23:42 -0600 Subject: [PATCH 07/24] Fixed mail list bug in RSVPController --- api/v1/controllers/RSVPController.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/v1/controllers/RSVPController.js b/api/v1/controllers/RSVPController.js index 23c32bb..af0b116 100644 --- a/api/v1/controllers/RSVPController.js +++ b/api/v1/controllers/RSVPController.js @@ -4,7 +4,7 @@ const services = require('../services'); const middleware = require('../middleware'); const requests = require('../requests'); const roles = require('../utils/roles'); -const mail = require('../utils/mail'); +const config = require('../../config'); const router = require('express').Router(); function _isAuthenticated(req) { @@ -30,7 +30,7 @@ function createRSVP(req, res, next) { .createRSVP(attendee, req.user, req.body)) .then((rsvp) => { if (rsvp.get('isAttending')) { - services.MailService.addToList(req.user, mail.lists.attendees); + services.MailService.addToList(req.user, config.mail.lists.attendees); } res.body = rsvp.toJSON(); @@ -91,10 +91,10 @@ function _updateRSVPByAttendee(user, attendee, newRSVP) { .then((rsvp) => services.RSVPService.updateRSVP(user, rsvp, newRSVP) .then((updatedRSVP) => { if (_addToList(rsvp, newRSVP)) { - services.MailService.addToList(user, mail.lists.attendees); + services.MailService.addToList(user, config.mail.lists.attendees); } if (_removeFromList(rsvp, newRSVP)) { - services.MailService.removeFromList(user, mail.lists.attendees); + services.MailService.removeFromList(user, config.mail.lists.attendees); } return updatedRSVP; From 326b893c7eaec4bfa77981eac8debe71c7b56fb2 Mon Sep 17 00:00:00 2001 From: Arnav Sankaran Date: Thu, 25 Jan 2018 21:25:05 -0600 Subject: [PATCH 08/24] Update stats on attendee rsvp --- api/v1/services/RSVPService.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/api/v1/services/RSVPService.js b/api/v1/services/RSVPService.js index 97b45eb..ae76036 100644 --- a/api/v1/services/RSVPService.js +++ b/api/v1/services/RSVPService.js @@ -6,6 +6,9 @@ const RSVP = require('../models/AttendeeRSVP'); const UserRole = require('../models/UserRole'); const errors = require('../errors'); const utils = require('../utils'); + +const StatsService = require('../services/StatsService'); + /** * Gets an rsvp by its id * @param {integer} id the id of the RSVP to find @@ -22,6 +25,17 @@ module.exports.getRSVPById = (id) => RSVP.findById(id); * @throws {InvalidParameterError} thrown when an attendee already has an rsvp */ module.exports.createRSVP = (attendee, user, attributes) => { + + StatsService.incrementStat('rsvp', 'school', attendee.get('school')); + StatsService.incrementStat('rsvp', 'transportation', attendee.get('transportation')); + StatsService.incrementStat('rsvp', 'diet', attendee.get('diet')); + StatsService.incrementStat('rsvp', 'shirt_size', attendee.get('shirtSize')); + StatsService.incrementStat('rsvp', 'gender', attendee.get('gender')); + StatsService.incrementStat('rsvp', 'graduation_year', attendee.get('graduationYear')); + StatsService.incrementStat('rsvp', 'major', attendee.get('major')); + StatsService.incrementStat('rsvp', 'is_novice', attendee.get('isNovice')); + + attributes.attendeeId = attendee.get('id'); const rsvp = RSVP.forge(attributes); From 2ed2ce501e33719d7b3d5e659a9b58164e239a48 Mon Sep 17 00:00:00 2001 From: Arnav Sankaran Date: Thu, 25 Jan 2018 23:06:36 -0600 Subject: [PATCH 09/24] Update liveevent attendee count on checkin --- api/v1/models/Stat.js | 2 +- api/v1/services/CheckInService.js | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/api/v1/models/Stat.js b/api/v1/models/Stat.js index 6adb081..7eff82c 100644 --- a/api/v1/models/Stat.js +++ b/api/v1/models/Stat.js @@ -4,7 +4,7 @@ const _ = require('lodash'); const Model = require('./Model'); const validators = require('../utils/validators'); -const CATEGORIES = ['registration', 'rsvp', 'live-event']; +const CATEGORIES = ['registration', 'rsvp', 'liveevent']; const Stat = Model.extend({ tableName: 'stats', diff --git a/api/v1/services/CheckInService.js b/api/v1/services/CheckInService.js index a0d1d0f..d83de58 100644 --- a/api/v1/services/CheckInService.js +++ b/api/v1/services/CheckInService.js @@ -5,6 +5,7 @@ const NetworkCredential = require('../models/NetworkCredential'); const errors = require('../errors'); const utils = require('../utils'); +const StatsService = require('../services/StatsService'); /** * Finds a CheckIn by User ID @@ -61,6 +62,8 @@ module.exports.createCheckIn = (attributes) => { const credentialsRequested = attributes.credentialsRequested; delete attributes.credentialsRequested; + StatsService.incrementStat('liveevent', 'attendees', 'count'); + return CheckIn.transaction((t) => new CheckIn(attributes) .save(null, { transacting: t From c9896375cd58627cfb83b57380e445d35c121e36 Mon Sep 17 00:00:00 2001 From: Arnav Sankaran Date: Sun, 28 Jan 2018 17:51:30 -0600 Subject: [PATCH 10/24] Write stats into cache on increment --- api/v1/services/RSVPService.js | 1 + api/v1/services/StatsService.js | 51 +++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/api/v1/services/RSVPService.js b/api/v1/services/RSVPService.js index ae76036..14f35f6 100644 --- a/api/v1/services/RSVPService.js +++ b/api/v1/services/RSVPService.js @@ -34,6 +34,7 @@ module.exports.createRSVP = (attendee, user, attributes) => { StatsService.incrementStat('rsvp', 'graduation_year', attendee.get('graduationYear')); StatsService.incrementStat('rsvp', 'major', attendee.get('major')); StatsService.incrementStat('rsvp', 'is_novice', attendee.get('isNovice')); + StatsService.incrementStat('rsvp', 'attendees', 'count'); attributes.attendeeId = attendee.get('id'); diff --git a/api/v1/services/StatsService.js b/api/v1/services/StatsService.js index 7e6dc4f..cac57b0 100644 --- a/api/v1/services/StatsService.js +++ b/api/v1/services/StatsService.js @@ -36,9 +36,60 @@ module.exports.find = function (category, stat, field) { }; module.exports.incrementStat = function (category, stat, field) { + cache.hasKey(STATS_CACHE_KEY).then((hasKey) => { + if (!hasKey) { + _resetCachedStat().then(() => _incrementCachedStat(category, stat, field)); + } else { + _incrementCachedStat(category, stat, field); + } + }); return Stat.increment(category, stat, field); }; +function _resetCachedStat() { + stats = {}; + stats['registration'] = {}; + stats['registration']['school'] = {}; + stats['registration']['transportation'] = {}; + stats['registration']['diet'] = {}; + stats['registration']['shirtSize'] = {}; + stats['registration']['gender'] = {}; + stats['registration']['graduationYear'] = {}; + stats['registration']['isNovice'] = {}; + stats['registration']['status'] = {}; + stats['registration']['major'] = {}; + stats['registration']['attendees'] = {}; + stats['rsvp'] = {}; + stats['rsvp']['school'] = {}; + stats['rsvp']['transportation'] = {}; + stats['rsvp']['diet'] = {}; + stats['rsvp']['shirtSize'] = {}; + stats['rsvp']['gender'] = {}; + stats['rsvp']['graduationYear'] = {}; + stats['rsvp']['isNovice'] = {}; + stats['rsvp']['major'] = {}; + stats['rsvp']['attendees'] = {}; + stats['liveevent'] = {}; + stats['liveevent']['attendees'] = {}; + return cache.storeString(STATS_CACHE_KEY, JSON.stringify(stats)); +} + +function _incrementCachedStat(category, stat, field) { + cache.getString(STATS_CACHE_KEY).then((object) => JSON.parse(object)).then((stats) => { + if(stats[category] == null) { + stats[category] = {}; + } + if(stats[category][stat] == null) { + stats[category][stat] = {}; + } + if(stats[category][stat][field] == null) { + stats[category][stat][field] = 0; + } + stats[category][stat][field] += 1; + return cache.storeString(STATS_CACHE_KEY, JSON.stringify(stats)); + }); +} + /** * Returns a function that takes a query result and populates a stats object * @param {String} key the key to use to nest the stats From 58134faeb2ba85ebccbb70ef42905d5c8498577c Mon Sep 17 00:00:00 2001 From: Arnav Sankaran Date: Sun, 28 Jan 2018 21:14:19 -0600 Subject: [PATCH 11/24] Added function to stats back from database if needed --- api/v1/controllers/StatsController.js | 8 ++ api/v1/services/StatsService.js | 149 ++++++++++++++++++++++++++ 2 files changed, 157 insertions(+) diff --git a/api/v1/controllers/StatsController.js b/api/v1/controllers/StatsController.js index 3dcf68d..93afb5f 100644 --- a/api/v1/controllers/StatsController.js +++ b/api/v1/controllers/StatsController.js @@ -1,3 +1,5 @@ +const _ = require('lodash'); + const bodyParser = require('body-parser'); const middleware = require('../middleware'); const router = require('express').Router(); @@ -45,6 +47,11 @@ function getLiveEventStats(req, res, next) { .catch((error) => next(error)); } +function testStats(req, res, next) { + StatsService.testStats(); + return next(); +} + router.use(bodyParser.json()); router.use(middleware.auth); @@ -53,6 +60,7 @@ router.get('/all', middleware.permission(roles.ORGANIZERS), getAllStats); router.get('/registration', middleware.permission(roles.ORGANIZERS), getRegStats); router.get('/rsvp', middleware.permission(roles.ORGANIZERS), getRSVPStats); router.get('/live', middleware.permission(roles.ORGANIZERS), getLiveEventStats); +router.get('/test', middleware.permission(roles.ORGANIZERS), testStats); router.use(middleware.response); router.use(middleware.errors); diff --git a/api/v1/services/StatsService.js b/api/v1/services/StatsService.js index cac57b0..d3c742b 100644 --- a/api/v1/services/StatsService.js +++ b/api/v1/services/StatsService.js @@ -35,6 +35,15 @@ module.exports.find = function (category, stat, field) { }); }; +function _findAll(category, stat) { + return Stat.where({ + category: category, + stat: stat + }).fetchAll(); +}; + +module.exports.findAll = _findAll; + module.exports.incrementStat = function (category, stat, field) { cache.hasKey(STATS_CACHE_KEY).then((hasKey) => { if (!hasKey) { @@ -90,6 +99,142 @@ function _incrementCachedStat(category, stat, field) { }); } +function _readStatsFromDatabase() { + _resetCachedStat().then(() => { + cache.getString(STATS_CACHE_KEY).then((object) => JSON.parse(object)).then((stats) => { + + const queries = []; + + queries.push(_findAll('registration', 'school').then((collection) => { + collection.forEach((model) => { + stats['registration']['school'][model.get('field')] = model.get('count'); + }); + })); + + queries.push(_findAll('registration', 'transportation').then((collection) => { + collection.forEach((model) => { + stats['registration']['transportation'][model.get('field')] = model.get('count'); + }); + })); + + queries.push(_findAll('registration', 'diet').then((collection) => { + collection.forEach((model) => { + stats['registration']['diet'][model.get('field')] = model.get('count'); + }); + })); + + queries.push(_findAll('registration', 'shirt_size').then((collection) => { + collection.forEach((model) => { + stats['registration']['shirtSize'][model.get('field')] = model.get('count'); + }); + })); + + queries.push(_findAll('registration', 'gender').then((collection) => { + collection.forEach((model) => { + stats['registration']['gender'][model.get('field')] = model.get('count'); + }); + })); + + queries.push(_findAll('registration', 'graduation_year').then((collection) => { + collection.forEach((model) => { + stats['registration']['graduationYear'][model.get('field')] = model.get('count'); + }); + })); + + queries.push(_findAll('registration', 'is_novice').then((collection) => { + collection.forEach((model) => { + stats['registration']['isNovice'][model.get('field')] = model.get('count'); + }); + })); + + queries.push(_findAll('registration', 'status').then((collection) => { + collection.forEach((model) => { + stats['registration']['status'][model.get('field')] = model.get('count'); + }); + })); + + queries.push(_findAll('registration', 'major').then((collection) => { + collection.forEach((model) => { + stats['registration']['major'][model.get('field')] = model.get('count'); + }); + })); + + queries.push(_findAll('registration', 'attendees').then((collection) => { + collection.forEach((model) => { + stats['registration']['attendees'][model.get('field')] = model.get('count'); + }); + })); + + + queries.push(_findAll('rsvp', 'school').then((collection) => { + collection.forEach((model) => { + stats['rsvp']['school'][model.get('field')] = model.get('count'); + }); + })); + + queries.push(_findAll('rsvp', 'transportation').then((collection) => { + collection.forEach((model) => { + stats['rsvp']['transportation'][model.get('field')] = model.get('count'); + }); + })); + + queries.push(_findAll('rsvp', 'diet').then((collection) => { + collection.forEach((model) => { + stats['rsvp']['diet'][model.get('field')] = model.get('count'); + }); + })); + + queries.push(_findAll('rsvp', 'shirt_size').then((collection) => { + collection.forEach((model) => { + stats['rsvp']['shirtSize'][model.get('field')] = model.get('count'); + }); + })); + + queries.push(_findAll('rsvp', 'gender').then((collection) => { + collection.forEach((model) => { + stats['rsvp']['gender'][model.get('field')] = model.get('count'); + }); + })); + + queries.push(_findAll('rsvp', 'graduation_year').then((collection) => { + collection.forEach((model) => { + stats['rsvp']['graduationYear'][model.get('field')] = model.get('count'); + }); + })); + + queries.push(_findAll('rsvp', 'is_novice').then((collection) => { + collection.forEach((model) => { + stats['rsvp']['isNovice'][model.get('field')] = model.get('count'); + }); + })); + + queries.push(_findAll('rsvp', 'major').then((collection) => { + collection.forEach((model) => { + stats['rsvp']['major'][model.get('field')] = model.get('count'); + }); + })); + + queries.push(_findAll('rsvp', 'attendees').then((collection) => { + collection.forEach((model) => { + stats['rsvp']['attendees'][model.get('field')] = model.get('count'); + }); + })); + + + queries.push(_findAll('liveevent', 'attendees').then((collection) => { + collection.forEach((model) => { + stats['liveevent']['attendees'][model.get('field')] = model.get('count'); + }); + })); + + return _Promise.all(queries).then(() => { + return cache.storeString(STATS_CACHE_KEY, JSON.stringify(stats)); + }); + + }); + }); +} + /** * Returns a function that takes a query result and populates a stats object * @param {String} key the key to use to nest the stats @@ -377,3 +522,7 @@ module.exports.fetchLiveEventStats = () => cache.hasKey(STATS_LIVE_HEADER + STAT })); }); + +module.exports.testStats = function() { + _readStatsFromDatabase(); +}; \ No newline at end of file From 614c8bd19db6eae3aa711e32bb662ab877681221 Mon Sep 17 00:00:00 2001 From: Arnav Sankaran Date: Sun, 28 Jan 2018 21:41:01 -0600 Subject: [PATCH 12/24] Fetch stats from cache when possible, falling back to database when needed --- api/v1/controllers/StatsController.js | 6 - api/v1/services/StatsService.js | 177 +++++--------------------- 2 files changed, 33 insertions(+), 150 deletions(-) diff --git a/api/v1/controllers/StatsController.js b/api/v1/controllers/StatsController.js index 93afb5f..31b03fb 100644 --- a/api/v1/controllers/StatsController.js +++ b/api/v1/controllers/StatsController.js @@ -47,11 +47,6 @@ function getLiveEventStats(req, res, next) { .catch((error) => next(error)); } -function testStats(req, res, next) { - StatsService.testStats(); - return next(); -} - router.use(bodyParser.json()); router.use(middleware.auth); @@ -60,7 +55,6 @@ router.get('/all', middleware.permission(roles.ORGANIZERS), getAllStats); router.get('/registration', middleware.permission(roles.ORGANIZERS), getRegStats); router.get('/rsvp', middleware.permission(roles.ORGANIZERS), getRSVPStats); router.get('/live', middleware.permission(roles.ORGANIZERS), getLiveEventStats); -router.get('/test', middleware.permission(roles.ORGANIZERS), testStats); router.use(middleware.response); router.use(middleware.errors); diff --git a/api/v1/services/StatsService.js b/api/v1/services/StatsService.js index d3c742b..3fb0c56 100644 --- a/api/v1/services/StatsService.js +++ b/api/v1/services/StatsService.js @@ -100,8 +100,8 @@ function _incrementCachedStat(category, stat, field) { } function _readStatsFromDatabase() { - _resetCachedStat().then(() => { - cache.getString(STATS_CACHE_KEY).then((object) => JSON.parse(object)).then((stats) => { + return _resetCachedStat().then(() => { + return cache.getString(STATS_CACHE_KEY).then((object) => JSON.parse(object)).then((stats) => { const queries = []; @@ -379,150 +379,39 @@ function _populateTrackedEvents(cb) { * Fetches the current stats, requerying them if not cached * @return {Promise} resolving to key-value pairs of stats */ -module.exports.fetchAllStats = () => { - const stats = {}; - stats.registrationStats = {}; - stats.rsvpStats = {}; - stats.liveEventStats = {}; - - return module.exports.fetchRegistrationStats() - .then((regstats) => { - stats.registrationStats = regstats; - return module.exports.fetchRSVPStats(); - }) - .then((rsvpstats) => { - stats.rsvpStats = rsvpstats; - return module.exports.fetchLiveEventStats(); - }) - .then((livestats) => { - stats.liveEventStats = livestats; - return stats; - }); -}; - -module.exports.fetchRegistrationStats = () => cache.hasKey(STATS_REG_HEADER + STATS_CACHE_KEY) - .then((hasKey) => { - if (hasKey) { - return cache.getString(STATS_REG_HEADER + STATS_CACHE_KEY) - .then((object) => JSON.parse(object)); - } - const stats = {}; - const queries = []; - - const schoolQuery = _populateAttendeeAttribute('school', _populateStats('school', stats)); - queries.push(schoolQuery); - - const transportationQuery = _populateAttendeeAttribute('transportation', _populateStats('transportation', stats)); - queries.push(transportationQuery); - - const dietQuery = _populateAttendeeAttribute('diet', _populateStats('diet', stats)); - queries.push(dietQuery); - - const shirtSizeQuery = _populateAttendeeAttribute('shirt_size', _populateStats('shirtSize', stats)); - queries.push(shirtSizeQuery); - - const genderQuery = _populateAttendeeAttribute('gender', _populateStats('gender', stats)); - queries.push(genderQuery); - - const graduationYearQuery = _populateAttendeeAttribute('graduation_year', _populateStats('graduationYear', stats)); - queries.push(graduationYearQuery); - - const majorQuery = _populateAttendeeAttribute('major', _populateStats('major', stats)); - queries.push(majorQuery); - - const isNoviceQuery = _populateAttendeeAttribute('is_novice', _populateStats('isNovice', stats)); - queries.push(isNoviceQuery); - - const attendeeQuery = _populateAttendees(_populateStatsField('attendees', stats)); - queries.push(attendeeQuery); - - const statusQuery = _populateAttendeeAttribute('status', _populateStats('status', stats)); - queries.push(statusQuery); - - return _Promise.all(queries) - .then(() => cache.storeString(STATS_REG_HEADER + STATS_CACHE_KEY, JSON.stringify(stats)) - .then(() => { - const tenMinutesFromNow = (10 * 60); - return cache.expireKey(STATS_REG_HEADER + STATS_CACHE_KEY, tenMinutesFromNow) - .then(() => stats); - })); - - }); - -module.exports.fetchRSVPStats = () => cache.hasKey(STATS_RSVP_HEADER + STATS_CACHE_KEY) - .then((hasKey) => { - if (hasKey) { - return cache.getString(STATS_RSVP_HEADER + STATS_CACHE_KEY) - .then((object) => JSON.parse(object)); - } - const stats = {}; - const queries = []; - - const attendingSchoolQuery = _populateAttendingAttendeeAttribute('school', _populateStats('school', stats)); - queries.push(attendingSchoolQuery); - - const attendingTransportationQuery = _populateAttendingAttendeeAttribute('transportation', _populateStats('transportation', stats)); - queries.push(attendingTransportationQuery); - - const attendingDietQuery = _populateAttendingAttendeeAttribute('diet', _populateStats('diet', stats)); - queries.push(attendingDietQuery); - - const attendingShirtSizeQuery = _populateAttendingAttendeeAttribute('shirt_size', _populateStats('shirtSize', stats)); - queries.push(attendingShirtSizeQuery); - - const attendingGenderQuery = _populateAttendingAttendeeAttribute('gender', _populateStats('gender', stats)); - queries.push(attendingGenderQuery); +function _fetchAllStats() { - const attendingGraduationYearQuery = _populateAttendingAttendeeAttribute('graduation_year', _populateStats('graduationYear', stats)); - queries.push(attendingGraduationYearQuery); - - const attendingMajorQuery = _populateAttendingAttendeeAttribute('major', _populateStats('major', stats)); - queries.push(attendingMajorQuery); - - const attendingIsNoviceQuery = _populateAttendingAttendeeAttribute('is_novice', _populateStats('isNovice', stats)); - queries.push(attendingIsNoviceQuery); - - const RSVPsQuery = _populateRSVPs(_populateStats('rsvps', stats)); - queries.push(RSVPsQuery); - - const RSVPTypesQuery = _populateRSVPTypes(_populateStats('type', stats)); - queries.push(RSVPTypesQuery); - - return _Promise.all(queries) - .then(() => cache.storeString(STATS_RSVP_HEADER + STATS_CACHE_KEY, JSON.stringify(stats)) - .then(() => { - const tenMinutesFromNow = (10 * 60); - return cache.expireKey(STATS_RSVP_HEADER + STATS_CACHE_KEY, tenMinutesFromNow) - .then(() => stats); - })); - - }); - -module.exports.fetchLiveEventStats = () => cache.hasKey(STATS_LIVE_HEADER + STATS_CACHE_KEY) - .then((hasKey) => { - if (hasKey) { - return cache.getString(STATS_LIVE_HEADER + STATS_CACHE_KEY) - .then((object) => JSON.parse(object)); - } - const stats = {}; - const queries = []; - - const checkIns = _populateCheckins(_populateStatsField('checkins', stats)); - queries.push(checkIns); + return cache.hasKey(STATS_CACHE_KEY).then((hasKey) => { + if (!hasKey) { + return _readStatsFromDatabase().then(() => { + return cache.getString(STATS_CACHE_KEY).then((object) => JSON.parse(object)).then((stats) => { + return stats; + }); + }) + } else { + return cache.getString(STATS_CACHE_KEY).then((object) => JSON.parse(object)).then((stats) => { + return stats; + }); + } + }); +}; - const trackedEventQuery = _populateTrackedEvents(_populateStats('trackedEvents', stats)); - queries.push(trackedEventQuery); +module.exports.fetchAllStats = _fetchAllStats; - return _Promise.all(queries) - .then(() => cache.storeString(STATS_LIVE_HEADER + STATS_CACHE_KEY, JSON.stringify(stats)) - .then(() => { - const oneMinuteFromNow = 60; - return cache.expireKey(STATS_LIVE_HEADER + STATS_CACHE_KEY, oneMinuteFromNow) - .then(() => stats); - })); +module.exports.fetchRegistrationStats = function() { + return _fetchAllStats().then((stats) => { + return stats['registration']; + }); +}; - }); +module.exports.fetchRSVPStats = function() { + return _fetchAllStats().then((stats) => { + return stats['rsvp']; + }); +}; -module.exports.testStats = function() { - _readStatsFromDatabase(); -}; \ No newline at end of file +module.exports.fetchLiveEventStats = function() { + return _fetchAllStats().then((stats) => { + return stats['liveevent']; + }); +}; From 66a5a1d97071df38e5d2efa2286434ae79b8b6b7 Mon Sep 17 00:00:00 2001 From: Arnav Sankaran Date: Sun, 28 Jan 2018 21:54:28 -0600 Subject: [PATCH 13/24] Ran linter and removed old stats code --- api/v1/controllers/StatsController.js | 2 - api/v1/services/StatsService.js | 421 ++++++++------------------ 2 files changed, 128 insertions(+), 295 deletions(-) diff --git a/api/v1/controllers/StatsController.js b/api/v1/controllers/StatsController.js index 31b03fb..3dcf68d 100644 --- a/api/v1/controllers/StatsController.js +++ b/api/v1/controllers/StatsController.js @@ -1,5 +1,3 @@ -const _ = require('lodash'); - const bodyParser = require('body-parser'); const middleware = require('../middleware'); const router = require('express').Router(); diff --git a/api/v1/services/StatsService.js b/api/v1/services/StatsService.js index 3fb0c56..859e51f 100644 --- a/api/v1/services/StatsService.js +++ b/api/v1/services/StatsService.js @@ -1,23 +1,11 @@ const _Promise = require('bluebird'); -const _ = require('lodash'); - -const ctx = require('ctx'); -const database = ctx.database(); -const knex = database.connection(); const Stat = require('../models/Stat'); -const Attendee = require('../models/Attendee'); -const AttendeeRSVP = require('../models/AttendeeRSVP'); -const CheckIn = require('../models/CheckIn'); -const TrackedEvent = require('../models/TrackingEvent'); const utils = require('../utils'); const cache = utils.cache; const STATS_CACHE_KEY = 'stats'; -const STATS_LIVE_HEADER = 'liveevent'; -const STATS_RSVP_HEADER = 'rsvp'; -const STATS_REG_HEADER = 'registration'; module.exports.createStat = function (category, stat, field) { return Stat.create(category, stat, field); @@ -40,7 +28,7 @@ function _findAll(category, stat) { category: category, stat: stat }).fetchAll(); -}; +} module.exports.findAll = _findAll; @@ -56,7 +44,7 @@ module.exports.incrementStat = function (category, stat, field) { }; function _resetCachedStat() { - stats = {}; + const stats = {}; stats['registration'] = {}; stats['registration']['school'] = {}; stats['registration']['transportation'] = {}; @@ -85,6 +73,9 @@ function _resetCachedStat() { function _incrementCachedStat(category, stat, field) { cache.getString(STATS_CACHE_KEY).then((object) => JSON.parse(object)).then((stats) => { + if(stats == null) { + stats = {}; + } if(stats[category] == null) { stats[category] = {}; } @@ -100,279 +91,135 @@ function _incrementCachedStat(category, stat, field) { } function _readStatsFromDatabase() { - return _resetCachedStat().then(() => { - return cache.getString(STATS_CACHE_KEY).then((object) => JSON.parse(object)).then((stats) => { + return _resetCachedStat().then(() => cache.getString(STATS_CACHE_KEY).then((object) => JSON.parse(object)).then((stats) => { - const queries = []; + const queries = []; - queries.push(_findAll('registration', 'school').then((collection) => { - collection.forEach((model) => { - stats['registration']['school'][model.get('field')] = model.get('count'); - }); - })); - - queries.push(_findAll('registration', 'transportation').then((collection) => { - collection.forEach((model) => { - stats['registration']['transportation'][model.get('field')] = model.get('count'); - }); - })); - - queries.push(_findAll('registration', 'diet').then((collection) => { - collection.forEach((model) => { - stats['registration']['diet'][model.get('field')] = model.get('count'); - }); - })); - - queries.push(_findAll('registration', 'shirt_size').then((collection) => { - collection.forEach((model) => { - stats['registration']['shirtSize'][model.get('field')] = model.get('count'); - }); - })); - - queries.push(_findAll('registration', 'gender').then((collection) => { - collection.forEach((model) => { - stats['registration']['gender'][model.get('field')] = model.get('count'); - }); - })); - - queries.push(_findAll('registration', 'graduation_year').then((collection) => { - collection.forEach((model) => { - stats['registration']['graduationYear'][model.get('field')] = model.get('count'); - }); - })); - - queries.push(_findAll('registration', 'is_novice').then((collection) => { - collection.forEach((model) => { - stats['registration']['isNovice'][model.get('field')] = model.get('count'); - }); - })); - - queries.push(_findAll('registration', 'status').then((collection) => { - collection.forEach((model) => { - stats['registration']['status'][model.get('field')] = model.get('count'); - }); - })); - - queries.push(_findAll('registration', 'major').then((collection) => { - collection.forEach((model) => { - stats['registration']['major'][model.get('field')] = model.get('count'); - }); - })); - - queries.push(_findAll('registration', 'attendees').then((collection) => { - collection.forEach((model) => { - stats['registration']['attendees'][model.get('field')] = model.get('count'); - }); - })); - - - queries.push(_findAll('rsvp', 'school').then((collection) => { - collection.forEach((model) => { - stats['rsvp']['school'][model.get('field')] = model.get('count'); - }); - })); - - queries.push(_findAll('rsvp', 'transportation').then((collection) => { - collection.forEach((model) => { - stats['rsvp']['transportation'][model.get('field')] = model.get('count'); - }); - })); - - queries.push(_findAll('rsvp', 'diet').then((collection) => { - collection.forEach((model) => { - stats['rsvp']['diet'][model.get('field')] = model.get('count'); - }); - })); - - queries.push(_findAll('rsvp', 'shirt_size').then((collection) => { - collection.forEach((model) => { - stats['rsvp']['shirtSize'][model.get('field')] = model.get('count'); - }); - })); - - queries.push(_findAll('rsvp', 'gender').then((collection) => { - collection.forEach((model) => { - stats['rsvp']['gender'][model.get('field')] = model.get('count'); - }); - })); - - queries.push(_findAll('rsvp', 'graduation_year').then((collection) => { - collection.forEach((model) => { - stats['rsvp']['graduationYear'][model.get('field')] = model.get('count'); - }); - })); - - queries.push(_findAll('rsvp', 'is_novice').then((collection) => { - collection.forEach((model) => { - stats['rsvp']['isNovice'][model.get('field')] = model.get('count'); - }); - })); - - queries.push(_findAll('rsvp', 'major').then((collection) => { - collection.forEach((model) => { - stats['rsvp']['major'][model.get('field')] = model.get('count'); - }); - })); - - queries.push(_findAll('rsvp', 'attendees').then((collection) => { - collection.forEach((model) => { - stats['rsvp']['attendees'][model.get('field')] = model.get('count'); - }); - })); - - - queries.push(_findAll('liveevent', 'attendees').then((collection) => { - collection.forEach((model) => { - stats['liveevent']['attendees'][model.get('field')] = model.get('count'); - }); - })); - - return _Promise.all(queries).then(() => { - return cache.storeString(STATS_CACHE_KEY, JSON.stringify(stats)); + queries.push(_findAll('registration', 'school').then((collection) => { + collection.forEach((model) => { + stats['registration']['school'][model.get('field')] = model.get('count'); }); - - }); - }); -} + })); -/** - * Returns a function that takes a query result and populates a stats object - * @param {String} key the key to use to nest the stats - * @param {Object} stats the stats object to be populated - * @return {Function} The generated function - */ -function _populateStats(key, stats) { - return function(result) { - stats[key] = {}; - _.forEach(result.models, (model) => { - stats[key][model.attributes.name] = model.attributes.count; - }); - }; -} + queries.push(_findAll('registration', 'transportation').then((collection) => { + collection.forEach((model) => { + stats['registration']['transportation'][model.get('field')] = model.get('count'); + }); + })); -/** - * Returns a function that takes a query result and populates a stats object - * Differs from above in that it doesn't process a collection - * @param {String} key the key to use to map a count result - * @param {Object} stats the stats object to be populated - * @return {Function} The generated function - */ -function _populateStatsField(key, stats) { - return function(result) { - stats[key] = result.attributes.count; - }; -} + queries.push(_findAll('registration', 'diet').then((collection) => { + collection.forEach((model) => { + stats['registration']['diet'][model.get('field')] = model.get('count'); + }); + })); -function _populateCheckins(cb) { - return CheckIn.query((qb) => { - qb.count('id as count'); - }) - .fetch() - .then(cb); -} + queries.push(_findAll('registration', 'shirt_size').then((collection) => { + collection.forEach((model) => { + stats['registration']['shirtSize'][model.get('field')] = model.get('count'); + }); + })); + queries.push(_findAll('registration', 'gender').then((collection) => { + collection.forEach((model) => { + stats['registration']['gender'][model.get('field')] = model.get('count'); + }); + })); -/** - * Queries Attendee rsvps and performs a callback on the results - * @param {Function} cb the function to process the query results with - * @return {Promise} resolving to the return value of the callback - */ -function _populateRSVPs(cb) { - return AttendeeRSVP.query((qb) => { - qb.select('is_attending as name') - .count('is_attending as count') - .from('attendee_rsvps') - .groupBy('is_attending'); - }) - .fetchAll() - .then(cb); -} + queries.push(_findAll('registration', 'graduation_year').then((collection) => { + collection.forEach((model) => { + stats['registration']['graduationYear'][model.get('field')] = model.get('count'); + }); + })); + queries.push(_findAll('registration', 'is_novice').then((collection) => { + collection.forEach((model) => { + stats['registration']['isNovice'][model.get('field')] = model.get('count'); + }); + })); -/** - * Queries Attendee rsvp types interests and performs a callback on the results - * @param {Function} cb the function to process the query results with - * @return {Promise} resolving to the return value of the callback - */ -function _populateRSVPTypes(cb) { - return AttendeeRSVP.query((qb) => { - qb.select('type as name') - .count('is_attending as count') - .from('attendee_rsvps') - .groupBy('type'); - }) - .fetchAll() - .then(cb); -} + queries.push(_findAll('registration', 'status').then((collection) => { + collection.forEach((model) => { + stats['registration']['status'][model.get('field')] = model.get('count'); + }); + })); -/** - * Queries an attendee attribute and counts the unique entries - * @param {String} attribute the attribute to query for - * @param {Function} cb the function to process the query results with - * @return {Promise} resolving to the return value of the callback - */ -function _populateAttendeeAttribute(attribute, cb) { - return Attendee.query((qb) => { - qb.select(attribute + ' as name') - .count(attribute + ' as count') - .from('attendees') - .groupBy(attribute); - }) - .fetchAll() - .then(cb); -} + queries.push(_findAll('registration', 'major').then((collection) => { + collection.forEach((model) => { + stats['registration']['major'][model.get('field')] = model.get('count'); + }); + })); -/** - * Queries an (attending) attendee attribute and counts the unique entries - * Attending is defined as a ACCEPTED status and is_attending RSVP - * @param {String} attribute the attribute to query for - * @param {Function} cb the function to process the query results with - * @return {Promise} resolving to the return value of the callback - */ -function _populateAttendingAttendeeAttribute(attribute, cb) { - return Attendee.query((qb) => { - qb.select(attribute + ' as name') - .count(attribute + ' as count') - .innerJoin('attendee_rsvps as ar', function() { - this.on('attendees.status', '=', knex.raw('?', [ 'ACCEPTED' ])) - .andOn('ar.is_attending', '=', knex.raw('?', [ '1' ])); - }) - .groupBy(attribute); - }) - .fetchAll() - .then(cb); -} + queries.push(_findAll('registration', 'attendees').then((collection) => { + collection.forEach((model) => { + stats['registration']['attendees'][model.get('field')] = model.get('count'); + }); + })); -/** - * Queries the total number of attendees - * @param {Function} cb the function to process the query results with - * @return {Promise} resolving to the return value of the callback - */ -function _populateAttendees(cb) { - return Attendee.query((qb) => { - qb.count('a.id as attending') - .from('attendees as a') - .innerJoin('attendee_rsvps as ar', function() { - this.on('a.status', '=', knex.raw('?', [ 'ACCEPTED' ])) - .andOn('ar.is_attending', '=', knex.raw('?', [ '1' ])); - }); - }) - .fetch() - .then(cb); -} -/** - * Queries the current stats for tracked events - * @param {Function} cb the function to process the query results with - * @return {Promise} resolving to the return value of the callback - */ -function _populateTrackedEvents(cb) { - return TrackedEvent.query((qb) => { - qb.select('name', 'count') - .groupBy('name'); - }) - .fetchAll() - .then(cb); + queries.push(_findAll('rsvp', 'school').then((collection) => { + collection.forEach((model) => { + stats['rsvp']['school'][model.get('field')] = model.get('count'); + }); + })); + + queries.push(_findAll('rsvp', 'transportation').then((collection) => { + collection.forEach((model) => { + stats['rsvp']['transportation'][model.get('field')] = model.get('count'); + }); + })); + + queries.push(_findAll('rsvp', 'diet').then((collection) => { + collection.forEach((model) => { + stats['rsvp']['diet'][model.get('field')] = model.get('count'); + }); + })); + + queries.push(_findAll('rsvp', 'shirt_size').then((collection) => { + collection.forEach((model) => { + stats['rsvp']['shirtSize'][model.get('field')] = model.get('count'); + }); + })); + + queries.push(_findAll('rsvp', 'gender').then((collection) => { + collection.forEach((model) => { + stats['rsvp']['gender'][model.get('field')] = model.get('count'); + }); + })); + + queries.push(_findAll('rsvp', 'graduation_year').then((collection) => { + collection.forEach((model) => { + stats['rsvp']['graduationYear'][model.get('field')] = model.get('count'); + }); + })); + + queries.push(_findAll('rsvp', 'is_novice').then((collection) => { + collection.forEach((model) => { + stats['rsvp']['isNovice'][model.get('field')] = model.get('count'); + }); + })); + + queries.push(_findAll('rsvp', 'major').then((collection) => { + collection.forEach((model) => { + stats['rsvp']['major'][model.get('field')] = model.get('count'); + }); + })); + + queries.push(_findAll('rsvp', 'attendees').then((collection) => { + collection.forEach((model) => { + stats['rsvp']['attendees'][model.get('field')] = model.get('count'); + }); + })); + + + queries.push(_findAll('liveevent', 'attendees').then((collection) => { + collection.forEach((model) => { + stats['liveevent']['attendees'][model.get('field')] = model.get('count'); + }); + })); + + return _Promise.all(queries).then(() => cache.storeString(STATS_CACHE_KEY, JSON.stringify(stats))); + + })); } /** @@ -383,35 +230,23 @@ function _fetchAllStats() { return cache.hasKey(STATS_CACHE_KEY).then((hasKey) => { if (!hasKey) { - return _readStatsFromDatabase().then(() => { - return cache.getString(STATS_CACHE_KEY).then((object) => JSON.parse(object)).then((stats) => { - return stats; - }); - }) - } else { - return cache.getString(STATS_CACHE_KEY).then((object) => JSON.parse(object)).then((stats) => { - return stats; - }); - } + return _readStatsFromDatabase().then(() => cache.getString(STATS_CACHE_KEY).then((object) => JSON.parse(object)).then((stats) => stats)); + } + return cache.getString(STATS_CACHE_KEY).then((object) => JSON.parse(object)).then((stats) => stats); + }); -}; +} module.exports.fetchAllStats = _fetchAllStats; module.exports.fetchRegistrationStats = function() { - return _fetchAllStats().then((stats) => { - return stats['registration']; - }); + return _fetchAllStats().then((stats) => stats['registration']); }; module.exports.fetchRSVPStats = function() { - return _fetchAllStats().then((stats) => { - return stats['rsvp']; - }); + return _fetchAllStats().then((stats) => stats['rsvp']); }; module.exports.fetchLiveEventStats = function() { - return _fetchAllStats().then((stats) => { - return stats['liveevent']; - }); + return _fetchAllStats().then((stats) => stats['liveevent']); }; From 29e1fcb3b518b9424f0f671a4dc2aa9c80eca146 Mon Sep 17 00:00:00 2001 From: Arnav Sankaran Date: Sun, 28 Jan 2018 22:20:54 -0600 Subject: [PATCH 14/24] Added stats for tracked live events --- api/v1/models/TrackingEvent.js | 6 ++++++ api/v1/services/StatsService.js | 14 +++++++++++++- api/v1/services/TrackingService.js | 5 +++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/api/v1/models/TrackingEvent.js b/api/v1/models/TrackingEvent.js index 7ed8d2c..23986c0 100644 --- a/api/v1/models/TrackingEvent.js +++ b/api/v1/models/TrackingEvent.js @@ -17,4 +17,10 @@ TrackingEvent.findByName = function(searchName) { .fetch(); }; +TrackingEvent.findAll = function() { + return TrackingEvent.where({ + + }).fetchAll(); +} + module.exports = TrackingEvent; diff --git a/api/v1/services/StatsService.js b/api/v1/services/StatsService.js index 859e51f..bae1ce4 100644 --- a/api/v1/services/StatsService.js +++ b/api/v1/services/StatsService.js @@ -1,6 +1,7 @@ const _Promise = require('bluebird'); const Stat = require('../models/Stat'); +const TrackingEvent = require('../models/TrackingEvent'); const utils = require('../utils'); const cache = utils.cache; @@ -40,7 +41,11 @@ module.exports.incrementStat = function (category, stat, field) { _incrementCachedStat(category, stat, field); } }); - return Stat.increment(category, stat, field); + if(category == 'liveevent' && category == 'events') { + return new _Promise(); + } else { + return Stat.increment(category, stat, field); + } }; function _resetCachedStat() { @@ -68,6 +73,7 @@ function _resetCachedStat() { stats['rsvp']['attendees'] = {}; stats['liveevent'] = {}; stats['liveevent']['attendees'] = {}; + stats['liveevent']['events'] = {}; return cache.storeString(STATS_CACHE_KEY, JSON.stringify(stats)); } @@ -217,6 +223,12 @@ function _readStatsFromDatabase() { }); })); + queries.push(TrackingEvent.findAll().then((collection) => { + collection.forEach((model) => { + stats['liveevent']['events'][model.get('name')] = model.get('count'); + }); + })); + return _Promise.all(queries).then(() => cache.storeString(STATS_CACHE_KEY, JSON.stringify(stats))); })); diff --git a/api/v1/services/TrackingService.js b/api/v1/services/TrackingService.js index 18d0e9e..30e5f52 100644 --- a/api/v1/services/TrackingService.js +++ b/api/v1/services/TrackingService.js @@ -8,6 +8,8 @@ const TrackingItem = require('../models/TrackingEvent'); const errors = require('../errors'); const utils = require('../utils'); +const StatsService = require('../services/StatsService'); + const TRACKING_NAMESPACE = 'utracking_'; const TRACKED_EVENT = 'trackedEvent'; @@ -55,6 +57,7 @@ module.exports.createTrackingEvent = (attributes) => { * @throws {InvalidParameterError} when an attendee has already participated in an event */ module.exports.addEventParticipant = (participantId) => { + let currentEvent; return cache.getAsync(TRACKED_EVENT) .then((result) => { @@ -66,6 +69,8 @@ module.exports.addEventParticipant = (participantId) => { currentEvent = result; + StatsService.incrementStat('liveevent', 'events', currentEvent); + return cache.getAsync(TRACKING_NAMESPACE + participantId); }) .then((result) => { From c37fa6e3ff2a1853289dc154f73e074ca6364e45 Mon Sep 17 00:00:00 2001 From: Arnav Sankaran Date: Sun, 28 Jan 2018 22:21:23 -0600 Subject: [PATCH 15/24] Ran linter --- api/v1/models/TrackingEvent.js | 2 +- api/v1/services/StatsService.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/api/v1/models/TrackingEvent.js b/api/v1/models/TrackingEvent.js index 23986c0..d710fa3 100644 --- a/api/v1/models/TrackingEvent.js +++ b/api/v1/models/TrackingEvent.js @@ -21,6 +21,6 @@ TrackingEvent.findAll = function() { return TrackingEvent.where({ }).fetchAll(); -} +}; module.exports = TrackingEvent; diff --git a/api/v1/services/StatsService.js b/api/v1/services/StatsService.js index bae1ce4..0d882eb 100644 --- a/api/v1/services/StatsService.js +++ b/api/v1/services/StatsService.js @@ -43,9 +43,9 @@ module.exports.incrementStat = function (category, stat, field) { }); if(category == 'liveevent' && category == 'events') { return new _Promise(); - } else { - return Stat.increment(category, stat, field); - } + } + return Stat.increment(category, stat, field); + }; function _resetCachedStat() { From 0c2b6792560f7f4c7ce6e30ef102ffdf76f96181 Mon Sep 17 00:00:00 2001 From: Arnav Sankaran Date: Wed, 31 Jan 2018 20:28:55 -0600 Subject: [PATCH 16/24] Updated Docs --- docs/Attendee-Statistics-Documentation.md | 218 +++++++++++++++++++++- 1 file changed, 215 insertions(+), 3 deletions(-) diff --git a/docs/Attendee-Statistics-Documentation.md b/docs/Attendee-Statistics-Documentation.md index a869036..6c9a92b 100644 --- a/docs/Attendee-Statistics-Documentation.md +++ b/docs/Attendee-Statistics-Documentation.md @@ -1,6 +1,6 @@ ### Purpose -Provides functionality for statistics. Registration and RSVP stats are cached for 10 minutes before being requeried, live event stats are cached for two minutes. +Provides functionality for statistics. Registration, RSVP, and live event stats are computed in realtime. --- @@ -21,6 +21,111 @@ None Response ``` +{ + "meta": null, + "data": { + "registration": { + "school": { + "University of Illinois at Urbana-Champaign": 4 + }, + "transportation": { + "BUS_REQUESTED": 0, + "INTERNATIONAL": 0, + "IN_STATE": 0, + "NOT_NEEDED": 9, + "OUT_OF_STATE": 0 + }, + "diet": { + "GLUTEN_FREE": 0, + "NONE": 9, + "VEGAN": 0, + "VEGETARIAN": 0 + }, + "shirtSize": { + "L": 0, + "M": 8, + "S": 0, + "XL": 0 + }, + "gender": { + "FEMALE": 0, + "MALE": 9, + "NON_BINARY": 0, + "OTHER": 0 + }, + "graduationYear": { + "2019": 3 + }, + "isNovice": { + "0": 0, + "1": 9 + }, + "status": { + "ACCEPTED": 0, + "PENDING": 0, + "REJECTED": 0, + "WAITLISTED": 0 + }, + "major": { + "Computer Science": 4 + }, + "attendees": { + "count": 9 + } + }, + "rsvp": { + "school": { + "University of Illinois at Urbana-Champaign": 1 + }, + "transportation": { + "BUS_REQUESTED": 0, + "INTERNATIONAL": 0, + "IN_STATE": 0, + "NOT_NEEDED": 1, + "OUT_OF_STATE": 0 + }, + "diet": { + "GLUTEN_FREE": 0, + "NONE": 1, + "VEGAN": 0, + "VEGETARIAN": 0 + }, + "shirtSize": { + "L": 0, + "M": 1, + "S": 0, + "XL": 0 + }, + "gender": { + "FEMALE": 0, + "MALE": 1, + "NON_BINARY": 0, + "OTHER": 0 + }, + "graduationYear": { + "2019": 1 + }, + "isNovice": { + "0": 0, + "1": 1 + }, + "major": { + "Computer Science": 1 + }, + "attendees": { + "count": 0 + } + }, + "liveevent": { + "attendees": { + "count": 17 + }, + "events": { + "Example Event": 1 + } + } + } +} ``` @@ -49,6 +154,58 @@ None Response ``` +{ + "meta": null, + "data": { + "school": { + "University of Illinois at Urbana-Champaign": 4 + }, + "transportation": { + "BUS_REQUESTED": 0, + "INTERNATIONAL": 0, + "IN_STATE": 0, + "NOT_NEEDED": 9, + "OUT_OF_STATE": 0 + }, + "diet": { + "GLUTEN_FREE": 0, + "NONE": 9, + "VEGAN": 0, + "VEGETARIAN": 0 + }, + "shirtSize": { + "L": 0, + "M": 8, + "S": 0, + "XL": 0 + }, + "gender": { + "FEMALE": 0, + "MALE": 9, + "NON_BINARY": 0, + "OTHER": 0 + }, + "graduationYear": { + "2019": 3 + }, + "isNovice": { + "0": 0, + "1": 9 + }, + "status": { + "ACCEPTED": 0, + "PENDING": 0, + "REJECTED": 0, + "WAITLISTED": 0 + }, + "major": { + "Computer Science": 4 + }, + "attendees": { + "count": 9 + } + } +} ``` @@ -77,7 +234,52 @@ None Response ``` - +{ + "meta": null, + "data": { + "school": { + "University of Illinois at Urbana-Champaign": 1 + }, + "transportation": { + "BUS_REQUESTED": 0, + "INTERNATIONAL": 0, + "IN_STATE": 0, + "NOT_NEEDED": 1, + "OUT_OF_STATE": 0 + }, + "diet": { + "GLUTEN_FREE": 0, + "NONE": 1, + "VEGAN": 0, + "VEGETARIAN": 0 + }, + "shirtSize": { + "L": 0, + "M": 1, + "S": 0, + "XL": 0 + }, + "gender": { + "FEMALE": 0, + "MALE": 1, + "NON_BINARY": 0, + "OTHER": 0 + }, + "graduationYear": { + "2019": 1 + }, + "isNovice": { + "0": 0, + "1": 1 + }, + "major": { + "Computer Science": 1 + }, + "attendees": { + "count": 0 + } + } +} ``` Errors:
@@ -105,7 +307,17 @@ None Response ``` - +{ + "metad null, + "data": { + "attendees": { + "count": 17 + }, + "events": { + "Example Event": 1 + } + } +} ``` Errors:
From 856039292ed3949b2508f8609a3f62766f97c34c Mon Sep 17 00:00:00 2001 From: Arnav Sankaran Date: Sun, 11 Feb 2018 23:44:48 -0600 Subject: [PATCH 17/24] Rename liveevent to live_event --- api/v1/models/Stat.js | 2 +- api/v1/services/CheckInService.js | 2 +- api/v1/services/StatsService.js | 18 +++---- api/v1/services/TrackingService.js | 2 +- .../migration/V20171011_2012__addStats.sql | 53 +++++++------------ 5 files changed, 32 insertions(+), 45 deletions(-) diff --git a/api/v1/models/Stat.js b/api/v1/models/Stat.js index 7eff82c..6ed93e3 100644 --- a/api/v1/models/Stat.js +++ b/api/v1/models/Stat.js @@ -4,7 +4,7 @@ const _ = require('lodash'); const Model = require('./Model'); const validators = require('../utils/validators'); -const CATEGORIES = ['registration', 'rsvp', 'liveevent']; +const CATEGORIES = ['registration', 'rsvp', 'live_event']; const Stat = Model.extend({ tableName: 'stats', diff --git a/api/v1/services/CheckInService.js b/api/v1/services/CheckInService.js index d83de58..73a10de 100644 --- a/api/v1/services/CheckInService.js +++ b/api/v1/services/CheckInService.js @@ -62,7 +62,7 @@ module.exports.createCheckIn = (attributes) => { const credentialsRequested = attributes.credentialsRequested; delete attributes.credentialsRequested; - StatsService.incrementStat('liveevent', 'attendees', 'count'); + StatsService.incrementStat('live_event', 'attendees', 'count'); return CheckIn.transaction((t) => new CheckIn(attributes) .save(null, { diff --git a/api/v1/services/StatsService.js b/api/v1/services/StatsService.js index 0d882eb..5fb9e5c 100644 --- a/api/v1/services/StatsService.js +++ b/api/v1/services/StatsService.js @@ -41,7 +41,7 @@ module.exports.incrementStat = function (category, stat, field) { _incrementCachedStat(category, stat, field); } }); - if(category == 'liveevent' && category == 'events') { + if(category == 'live_event' && category == 'events') { return new _Promise(); } return Stat.increment(category, stat, field); @@ -71,9 +71,9 @@ function _resetCachedStat() { stats['rsvp']['isNovice'] = {}; stats['rsvp']['major'] = {}; stats['rsvp']['attendees'] = {}; - stats['liveevent'] = {}; - stats['liveevent']['attendees'] = {}; - stats['liveevent']['events'] = {}; + stats['live_event'] = {}; + stats['live_event']['attendees'] = {}; + stats['live_event']['events'] = {}; return cache.storeString(STATS_CACHE_KEY, JSON.stringify(stats)); } @@ -217,15 +217,15 @@ function _readStatsFromDatabase() { })); - queries.push(_findAll('liveevent', 'attendees').then((collection) => { + queries.push(_findAll('live_event', 'attendees').then((collection) => { collection.forEach((model) => { - stats['liveevent']['attendees'][model.get('field')] = model.get('count'); + stats['live_event']['attendees'][model.get('field')] = model.get('count'); }); })); queries.push(TrackingEvent.findAll().then((collection) => { collection.forEach((model) => { - stats['liveevent']['events'][model.get('name')] = model.get('count'); + stats['live_event']['events'][model.get('name')] = model.get('count'); }); })); @@ -259,6 +259,6 @@ module.exports.fetchRSVPStats = function() { return _fetchAllStats().then((stats) => stats['rsvp']); }; -module.exports.fetchLiveEventStats = function() { - return _fetchAllStats().then((stats) => stats['liveevent']); +module.exports.fetchLive_EventStats = function() { + return _fetchAllStats().then((stats) => stats['live_event']); }; diff --git a/api/v1/services/TrackingService.js b/api/v1/services/TrackingService.js index 30e5f52..0c6463a 100644 --- a/api/v1/services/TrackingService.js +++ b/api/v1/services/TrackingService.js @@ -69,7 +69,7 @@ module.exports.addEventParticipant = (participantId) => { currentEvent = result; - StatsService.incrementStat('liveevent', 'events', currentEvent); + StatsService.incrementStat('live_event', 'events', currentEvent); return cache.getAsync(TRACKING_NAMESPACE + participantId); }) diff --git a/database/migration/V20171011_2012__addStats.sql b/database/migration/V20171011_2012__addStats.sql index 2a09dd7..194c75a 100644 --- a/database/migration/V20171011_2012__addStats.sql +++ b/database/migration/V20171011_2012__addStats.sql @@ -8,10 +8,6 @@ CREATE TABLE `stats` ( CONSTRAINT `fk_unique_stats` UNIQUE (`category`, `stat`, `field`) ); -# schools -# graduation year -# major - INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('registration', 'transportation', 'NOT_NEEDED'); INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('registration', 'transportation', 'BUS_REQUESTED'); INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('registration', 'transportation', 'IN_STATE'); @@ -72,39 +68,30 @@ INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', 'is_novice', ' INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', 'attendees', 'count'); INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', 'status', 'ACCEPTED'); -#INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', `status`, `WAITLISTED`); -#INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', `status`, `REJECTED`); -#INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', `status`, `PENDING`); - - -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', 'transportation', 'NOT_NEEDED'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', 'transportation', 'BUS_REQUESTED'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', 'transportation', 'IN_STATE'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', 'transportation', 'OUT_OF_STATE'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', 'transportation', 'INTERNATIONAL'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', 'diet', 'VEGETARIAN'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', 'diet', 'VEGAN'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', 'diet', 'NONE'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', 'diet', 'GLUTEN_FREE'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('live_event', 'transportation', 'NOT_NEEDED'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('live_event', 'transportation', 'BUS_REQUESTED'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('live_event', 'transportation', 'IN_STATE'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('live_event', 'transportation', 'OUT_OF_STATE'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('live_event', 'transportation', 'INTERNATIONAL'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', 'shirt_size', 'S'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', 'shirt_size', 'M'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', 'shirt_size', 'L'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', 'shirt_size', 'XL'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('live_event', 'diet', 'VEGETARIAN'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('live_event', 'diet', 'VEGAN'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('live_event', 'diet', 'NONE'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('live_event', 'diet', 'GLUTEN_FREE'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', 'gender', 'MALE'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', 'gender', 'FEMALE'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', 'gender', 'NON_BINARY'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', 'gender', 'OTHER'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('live_event', 'shirt_size', 'S'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('live_event', 'shirt_size', 'M'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('live_event', 'shirt_size', 'L'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('live_event', 'shirt_size', 'XL'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', 'is_novice', '0'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', 'is_novice', '1'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('live_event', 'gender', 'MALE'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('live_event', 'gender', 'FEMALE'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('live_event', 'gender', 'NON_BINARY'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('live_event', 'gender', 'OTHER'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', 'attendees', 'count'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('live_event', 'is_novice', '0'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('live_event', 'is_novice', '1'); -#INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', `status`, `ACCEPTED`); -#INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', `status`, `WAITLISTED`); -#INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', `status`, `REJECTED`); -#INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveevent', `status`, `PENDING`); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('live_event', 'attendees', 'count'); From eee93e3129a4b78e5fbf6ecf0ef242bd3e48f4cd Mon Sep 17 00:00:00 2001 From: Arnav Sankaran Date: Wed, 21 Feb 2018 21:12:14 -0600 Subject: [PATCH 18/24] Write stats to cache in series --- api/v1/models/Stat.js | 8 +-- api/v1/services/CheckInService.js | 2 +- api/v1/services/RSVPService.js | 20 +++--- api/v1/services/RegistrationService.js | 23 ++++--- api/v1/services/StatsService.js | 32 +++++----- api/v1/services/TrackingService.js | 2 +- .../migration/V20171011_2012__addStats.sql | 64 +++++++++---------- 7 files changed, 77 insertions(+), 74 deletions(-) diff --git a/api/v1/models/Stat.js b/api/v1/models/Stat.js index 6ed93e3..fca65cc 100644 --- a/api/v1/models/Stat.js +++ b/api/v1/models/Stat.js @@ -66,10 +66,10 @@ Stat.increment = (category, stat, field, amount) => { amount = 1; } - const s = Stat.query((qb) => { - qb.where('category', '=', category); - qb.where('stat', '=', stat); - qb.where('field', '=', field); + const s = Stat.where({ + category: category, + stat: stat, + field: field }).fetch(); return s.then((model) => { diff --git a/api/v1/services/CheckInService.js b/api/v1/services/CheckInService.js index 73a10de..e109738 100644 --- a/api/v1/services/CheckInService.js +++ b/api/v1/services/CheckInService.js @@ -62,7 +62,7 @@ module.exports.createCheckIn = (attributes) => { const credentialsRequested = attributes.credentialsRequested; delete attributes.credentialsRequested; - StatsService.incrementStat('live_event', 'attendees', 'count'); + StatsService.incrementStat('liveEvent', 'attendees', 'count'); return CheckIn.transaction((t) => new CheckIn(attributes) .save(null, { diff --git a/api/v1/services/RSVPService.js b/api/v1/services/RSVPService.js index c1914e0..3488634 100644 --- a/api/v1/services/RSVPService.js +++ b/api/v1/services/RSVPService.js @@ -26,16 +26,16 @@ module.exports.getRSVPById = (id) => RSVP.findById(id); */ module.exports.createRSVP = (attendee, user, attributes) => { - StatsService.incrementStat('rsvp', 'school', attendee.get('school')); - StatsService.incrementStat('rsvp', 'transportation', attendee.get('transportation')); - StatsService.incrementStat('rsvp', 'diet', attendee.get('diet')); - StatsService.incrementStat('rsvp', 'shirt_size', attendee.get('shirtSize')); - StatsService.incrementStat('rsvp', 'gender', attendee.get('gender')); - StatsService.incrementStat('rsvp', 'graduation_year', attendee.get('graduationYear')); - StatsService.incrementStat('rsvp', 'major', attendee.get('major')); - StatsService.incrementStat('rsvp', 'is_novice', attendee.get('isNovice')); - StatsService.incrementStat('rsvp', 'attendees', 'count'); - + StatsService.incrementStat('rsvp', 'school', attendee.get('school')).then(() => + StatsService.incrementStat('rsvp', 'transportation', attendee.get('transportation')).then(() => + StatsService.incrementStat('rsvp', 'diet', attendee.get('diet')).then(() => + StatsService.incrementStat('rsvp', 'shirtSize', attendee.get('shirtSize')).then(() => + StatsService.incrementStat('rsvp', 'gender', attendee.get('gender')).then(() => + StatsService.incrementStat('rsvp', 'graduationYear', attendee.get('graduationYear')).then(() => + StatsService.incrementStat('rsvp', 'major', attendee.get('major')).then(() => + StatsService.incrementStat('rsvp', 'isNovice', attendee.get('isNovice') ? 1 : 0).then(() => + StatsService.incrementStat('rsvp', 'attendees', 'count'))))))))); + attributes.attendeeId = attendee.get('id'); const rsvp = RSVP.forge(attributes); diff --git a/api/v1/services/RegistrationService.js b/api/v1/services/RegistrationService.js index ee53645..9d72f98 100644 --- a/api/v1/services/RegistrationService.js +++ b/api/v1/services/RegistrationService.js @@ -327,16 +327,19 @@ module.exports.createAttendee = (user, attributes) => { return _Promise.reject(new errors.InvalidParameterError(message, source)); } - StatsService.incrementStat('registration', 'transportation', attributes.attendee.transportation); - StatsService.incrementStat('registration', 'diet', attributes.attendee.diet); - StatsService.incrementStat('registration', 'shirt_size', attributes.attendee.shirtSize); - StatsService.incrementStat('registration', 'gender', attributes.attendee.gender); - StatsService.incrementStat('registration', 'is_novice', attributes.attendee.isNovice); - StatsService.incrementStat('registration', 'status', attributes.attendee.status); - StatsService.incrementStat('registration', 'attendees', 'count'); - StatsService.incrementStat('registration', 'school', attributes.attendee.school); - StatsService.incrementStat('registration', 'graduation_year', attributes.attendee.graduationYear); - StatsService.incrementStat('registration', 'major', attributes.attendee.major); + const statAttributes = attributes.attendee; + + StatsService.incrementStat('registration', 'school', statAttributes.school).then(() => + StatsService.incrementStat('registration', 'transportation', statAttributes.transportation).then(() => + StatsService.incrementStat('registration', 'diet', statAttributes.diet).then(() => + StatsService.incrementStat('registration', 'shirtSize', statAttributes.shirtSize).then(() => + StatsService.incrementStat('registration', 'gender', statAttributes.gender).then(() => + StatsService.incrementStat('registration', 'graduationYear', statAttributes.graduationYear).then(() => + StatsService.incrementStat('registration', 'major', statAttributes.major).then(() => + StatsService.incrementStat('registration', 'isNovice', statAttributes.isNovice ? 1 : 0).then(() => + StatsService.incrementStat('registration', 'attendees', 'count').then(() => + StatsService.incrementStat('registration', 'status', 'PENDING')))))))))); + const attendeeAttrs = attributes.attendee; delete attributes.attendee; diff --git a/api/v1/services/StatsService.js b/api/v1/services/StatsService.js index 5fb9e5c..c33ea2c 100644 --- a/api/v1/services/StatsService.js +++ b/api/v1/services/StatsService.js @@ -41,8 +41,8 @@ module.exports.incrementStat = function (category, stat, field) { _incrementCachedStat(category, stat, field); } }); - if(category == 'live_event' && category == 'events') { - return new _Promise(); + if(category == 'liveEvent' && category == 'events') { + return _Promise.resolve(true); } return Stat.increment(category, stat, field); @@ -71,9 +71,9 @@ function _resetCachedStat() { stats['rsvp']['isNovice'] = {}; stats['rsvp']['major'] = {}; stats['rsvp']['attendees'] = {}; - stats['live_event'] = {}; - stats['live_event']['attendees'] = {}; - stats['live_event']['events'] = {}; + stats['liveEvent'] = {}; + stats['liveEvent']['attendees'] = {}; + stats['liveEvent']['events'] = {}; return cache.storeString(STATS_CACHE_KEY, JSON.stringify(stats)); } @@ -119,7 +119,7 @@ function _readStatsFromDatabase() { }); })); - queries.push(_findAll('registration', 'shirt_size').then((collection) => { + queries.push(_findAll('registration', 'shirtSize').then((collection) => { collection.forEach((model) => { stats['registration']['shirtSize'][model.get('field')] = model.get('count'); }); @@ -131,13 +131,13 @@ function _readStatsFromDatabase() { }); })); - queries.push(_findAll('registration', 'graduation_year').then((collection) => { + queries.push(_findAll('registration', 'graduationYear').then((collection) => { collection.forEach((model) => { stats['registration']['graduationYear'][model.get('field')] = model.get('count'); }); })); - queries.push(_findAll('registration', 'is_novice').then((collection) => { + queries.push(_findAll('registration', 'isNovice').then((collection) => { collection.forEach((model) => { stats['registration']['isNovice'][model.get('field')] = model.get('count'); }); @@ -180,7 +180,7 @@ function _readStatsFromDatabase() { }); })); - queries.push(_findAll('rsvp', 'shirt_size').then((collection) => { + queries.push(_findAll('rsvp', 'shirtSize').then((collection) => { collection.forEach((model) => { stats['rsvp']['shirtSize'][model.get('field')] = model.get('count'); }); @@ -192,13 +192,13 @@ function _readStatsFromDatabase() { }); })); - queries.push(_findAll('rsvp', 'graduation_year').then((collection) => { + queries.push(_findAll('rsvp', 'graduationYear').then((collection) => { collection.forEach((model) => { stats['rsvp']['graduationYear'][model.get('field')] = model.get('count'); }); })); - queries.push(_findAll('rsvp', 'is_novice').then((collection) => { + queries.push(_findAll('rsvp', 'isNovice').then((collection) => { collection.forEach((model) => { stats['rsvp']['isNovice'][model.get('field')] = model.get('count'); }); @@ -217,15 +217,15 @@ function _readStatsFromDatabase() { })); - queries.push(_findAll('live_event', 'attendees').then((collection) => { + queries.push(_findAll('liveEvent', 'attendees').then((collection) => { collection.forEach((model) => { - stats['live_event']['attendees'][model.get('field')] = model.get('count'); + stats['liveEvent']['attendees'][model.get('field')] = model.get('count'); }); })); queries.push(TrackingEvent.findAll().then((collection) => { collection.forEach((model) => { - stats['live_event']['events'][model.get('name')] = model.get('count'); + stats['liveEvent']['events'][model.get('name')] = model.get('count'); }); })); @@ -259,6 +259,6 @@ module.exports.fetchRSVPStats = function() { return _fetchAllStats().then((stats) => stats['rsvp']); }; -module.exports.fetchLive_EventStats = function() { - return _fetchAllStats().then((stats) => stats['live_event']); +module.exports.fetchLiveEventStats = function() { + return _fetchAllStats().then((stats) => stats['liveEvent']); }; diff --git a/api/v1/services/TrackingService.js b/api/v1/services/TrackingService.js index 0c6463a..f14b69d 100644 --- a/api/v1/services/TrackingService.js +++ b/api/v1/services/TrackingService.js @@ -69,7 +69,7 @@ module.exports.addEventParticipant = (participantId) => { currentEvent = result; - StatsService.incrementStat('live_event', 'events', currentEvent); + StatsService.incrementStat('liveEvent', 'events', currentEvent); return cache.getAsync(TRACKING_NAMESPACE + participantId); }) diff --git a/database/migration/V20171011_2012__addStats.sql b/database/migration/V20171011_2012__addStats.sql index 194c75a..54e27ac 100644 --- a/database/migration/V20171011_2012__addStats.sql +++ b/database/migration/V20171011_2012__addStats.sql @@ -19,18 +19,18 @@ INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('registration', 'diet' INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('registration', 'diet', 'NONE'); INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('registration', 'diet', 'GLUTEN_FREE'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('registration', 'shirt_size', 'S'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('registration', 'shirt_size', 'M'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('registration', 'shirt_size', 'L'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('registration', 'shirt_size', 'XL'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('registration', 'shirtSize', 'S'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('registration', 'shirtSize', 'M'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('registration', 'shirtSize', 'L'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('registration', 'shirtSize', 'XL'); INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('registration', 'gender', 'MALE'); INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('registration', 'gender', 'FEMALE'); INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('registration', 'gender', 'NON_BINARY'); INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('registration', 'gender', 'OTHER'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('registration', 'is_novice', '0'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('registration', 'is_novice', '1'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('registration', 'isNovice', '0'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('registration', 'isNovice', '1'); INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('registration', 'attendees', 'count'); @@ -52,46 +52,46 @@ INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', 'diet', 'VEGAN INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', 'diet', 'NONE'); INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', 'diet', 'GLUTEN_FREE'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', 'shirt_size', 'S'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', 'shirt_size', 'M'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', 'shirt_size', 'L'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', 'shirt_size', 'XL'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', 'shirtSize', 'S'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', 'shirtSize', 'M'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', 'shirtSize', 'L'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', 'shirtSize', 'XL'); INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', 'gender', 'MALE'); INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', 'gender', 'FEMALE'); INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', 'gender', 'NON_BINARY'); INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', 'gender', 'OTHER'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', 'is_novice', '0'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', 'is_novice', '1'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', 'isNovice', '0'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', 'isNovice', '1'); INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', 'attendees', 'count'); INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('rsvp', 'status', 'ACCEPTED'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('live_event', 'transportation', 'NOT_NEEDED'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('live_event', 'transportation', 'BUS_REQUESTED'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('live_event', 'transportation', 'IN_STATE'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('live_event', 'transportation', 'OUT_OF_STATE'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('live_event', 'transportation', 'INTERNATIONAL'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveEvent', 'transportation', 'NOT_NEEDED'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveEvent', 'transportation', 'BUS_REQUESTED'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveEvent', 'transportation', 'IN_STATE'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveEvent', 'transportation', 'OUT_OF_STATE'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveEvent', 'transportation', 'INTERNATIONAL'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('live_event', 'diet', 'VEGETARIAN'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('live_event', 'diet', 'VEGAN'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('live_event', 'diet', 'NONE'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('live_event', 'diet', 'GLUTEN_FREE'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveEvent', 'diet', 'VEGETARIAN'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveEvent', 'diet', 'VEGAN'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveEvent', 'diet', 'NONE'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveEvent', 'diet', 'GLUTEN_FREE'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('live_event', 'shirt_size', 'S'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('live_event', 'shirt_size', 'M'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('live_event', 'shirt_size', 'L'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('live_event', 'shirt_size', 'XL'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveEvent', 'shirtSize', 'S'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveEvent', 'shirtSize', 'M'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveEvent', 'shirtSize', 'L'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveEvent', 'shirtSize', 'XL'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('live_event', 'gender', 'MALE'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('live_event', 'gender', 'FEMALE'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('live_event', 'gender', 'NON_BINARY'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('live_event', 'gender', 'OTHER'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveEvent', 'gender', 'MALE'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveEvent', 'gender', 'FEMALE'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveEvent', 'gender', 'NON_BINARY'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveEvent', 'gender', 'OTHER'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('live_event', 'is_novice', '0'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('live_event', 'is_novice', '1'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveEvent', 'isNovice', '0'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveEvent', 'isNovice', '1'); -INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('live_event', 'attendees', 'count'); +INSERT INTO `stats` (`category`, `stat`, `field`) VALUES ('liveEvent', 'attendees', 'count'); From 2c7d0647acb25c4a950a2fcb0349a8764e5dc519 Mon Sep 17 00:00:00 2001 From: Arnav Sankaran Date: Wed, 21 Feb 2018 21:39:35 -0600 Subject: [PATCH 19/24] Decrement RSVP stats on changing accept to decline --- api/v1/services/RSVPService.js | 48 ++++++++++++++++++++++++++------- api/v1/services/StatsService.js | 34 +++++++++++++++++++++++ 2 files changed, 72 insertions(+), 10 deletions(-) diff --git a/api/v1/services/RSVPService.js b/api/v1/services/RSVPService.js index 3488634..37f3eaa 100644 --- a/api/v1/services/RSVPService.js +++ b/api/v1/services/RSVPService.js @@ -8,6 +8,7 @@ const errors = require('../errors'); const utils = require('../utils'); const StatsService = require('../services/StatsService'); +const RegistrationService = require('../services/RegistrationService'); /** * Gets an rsvp by its id @@ -26,16 +27,17 @@ module.exports.getRSVPById = (id) => RSVP.findById(id); */ module.exports.createRSVP = (attendee, user, attributes) => { - StatsService.incrementStat('rsvp', 'school', attendee.get('school')).then(() => - StatsService.incrementStat('rsvp', 'transportation', attendee.get('transportation')).then(() => - StatsService.incrementStat('rsvp', 'diet', attendee.get('diet')).then(() => - StatsService.incrementStat('rsvp', 'shirtSize', attendee.get('shirtSize')).then(() => - StatsService.incrementStat('rsvp', 'gender', attendee.get('gender')).then(() => - StatsService.incrementStat('rsvp', 'graduationYear', attendee.get('graduationYear')).then(() => - StatsService.incrementStat('rsvp', 'major', attendee.get('major')).then(() => - StatsService.incrementStat('rsvp', 'isNovice', attendee.get('isNovice') ? 1 : 0).then(() => - StatsService.incrementStat('rsvp', 'attendees', 'count'))))))))); - + if(attributes.isAttending) { + StatsService.incrementStat('rsvp', 'school', attendee.get('school')).then(() => + StatsService.incrementStat('rsvp', 'transportation', attendee.get('transportation')).then(() => + StatsService.incrementStat('rsvp', 'diet', attendee.get('diet')).then(() => + StatsService.incrementStat('rsvp', 'shirtSize', attendee.get('shirtSize')).then(() => + StatsService.incrementStat('rsvp', 'gender', attendee.get('gender')).then(() => + StatsService.incrementStat('rsvp', 'graduationYear', attendee.get('graduationYear')).then(() => + StatsService.incrementStat('rsvp', 'major', attendee.get('major')).then(() => + StatsService.incrementStat('rsvp', 'isNovice', attendee.get('isNovice') ? 1 : 0).then(() => + StatsService.incrementStat('rsvp', 'attendees', 'count'))))))))); + } attributes.attendeeId = attendee.get('id'); const rsvp = RSVP.forge(attributes); @@ -81,6 +83,32 @@ module.exports.findRSVPByAttendee = (attendee) => RSVP * @returns {Promise} the resolved RSVP */ module.exports.updateRSVP = (user, rsvp, attributes) => { + const oldAttending = rsvp.get('isAttending'); + const newAttending = attributes.isAttending; + RegistrationService.findAttendeeByUser(user).then((attendee) => { + if(!oldAttending && newAttending) { + StatsService.incrementStat('rsvp', 'school', attendee.get('school')).then(() => + StatsService.incrementStat('rsvp', 'transportation', attendee.get('transportation')).then(() => + StatsService.incrementStat('rsvp', 'diet', attendee.get('diet')).then(() => + StatsService.incrementStat('rsvp', 'shirtSize', attendee.get('shirtSize')).then(() => + StatsService.incrementStat('rsvp', 'gender', attendee.get('gender')).then(() => + StatsService.incrementStat('rsvp', 'graduationYear', attendee.get('graduationYear')).then(() => + StatsService.incrementStat('rsvp', 'major', attendee.get('major')).then(() => + StatsService.incrementStat('rsvp', 'isNovice', attendee.get('isNovice') ? 1 : 0).then(() => + StatsService.incrementStat('rsvp', 'attendees', 'count'))))))))); + } else if(oldAttending && !newAttending) { + StatsService.decrementStat('rsvp', 'school', attendee.get('school')).then(() => + StatsService.decrementStat('rsvp', 'transportation', attendee.get('transportation')).then(() => + StatsService.decrementStat('rsvp', 'diet', attendee.get('diet')).then(() => + StatsService.decrementStat('rsvp', 'shirtSize', attendee.get('shirtSize')).then(() => + StatsService.decrementStat('rsvp', 'gender', attendee.get('gender')).then(() => + StatsService.decrementStat('rsvp', 'graduationYear', attendee.get('graduationYear')).then(() => + StatsService.decrementStat('rsvp', 'major', attendee.get('major')).then(() => + StatsService.decrementStat('rsvp', 'isNovice', attendee.get('isNovice') ? 1 : 0).then(() => + StatsService.decrementStat('rsvp', 'attendees', 'count'))))))))); + } + }); + rsvp.set(attributes); return rsvp diff --git a/api/v1/services/StatsService.js b/api/v1/services/StatsService.js index c33ea2c..6375c31 100644 --- a/api/v1/services/StatsService.js +++ b/api/v1/services/StatsService.js @@ -48,6 +48,21 @@ module.exports.incrementStat = function (category, stat, field) { }; +module.exports.decrementStat = function (category, stat, field) { + cache.hasKey(STATS_CACHE_KEY).then((hasKey) => { + if (!hasKey) { + _resetCachedStat().then(() => _decrementCachedStat(category, stat, field)); + } else { + _decrementCachedStat(category, stat, field); + } + }); + if(category == 'liveEvent' && category == 'events') { + return _Promise.resolve(true); + } + return Stat.increment(category, stat, field, -1); + +}; + function _resetCachedStat() { const stats = {}; stats['registration'] = {}; @@ -96,6 +111,25 @@ function _incrementCachedStat(category, stat, field) { }); } +function _decrementCachedStat(category, stat, field) { + cache.getString(STATS_CACHE_KEY).then((object) => JSON.parse(object)).then((stats) => { + if(stats == null) { + stats = {}; + } + if(stats[category] == null) { + stats[category] = {}; + } + if(stats[category][stat] == null) { + stats[category][stat] = {}; + } + if(stats[category][stat][field] == null) { + stats[category][stat][field] = 0; + } + stats[category][stat][field] -= 1; + return cache.storeString(STATS_CACHE_KEY, JSON.stringify(stats)); + }); +} + function _readStatsFromDatabase() { return _resetCachedStat().then(() => cache.getString(STATS_CACHE_KEY).then((object) => JSON.parse(object)).then((stats) => { From 6b4b68cf5c2d3e69ba11d70d871778301efde86a Mon Sep 17 00:00:00 2001 From: Arnav Sankaran Date: Wed, 21 Feb 2018 22:01:36 -0600 Subject: [PATCH 20/24] Removed stats test --- test/stat.js | 116 --------------------------------------------------- test/test.js | 1 - 2 files changed, 117 deletions(-) delete mode 100644 test/stat.js diff --git a/test/stat.js b/test/stat.js deleted file mode 100644 index 5508b8b..0000000 --- a/test/stat.js +++ /dev/null @@ -1,116 +0,0 @@ -//const _Promise = require('bluebird'); - -const chai = require('chai'); -const sinon = require('sinon'); -const _ = require('lodash'); - -//const UserService = require('../api/v1/models/UserService'); -const StatService = require('../api/v1/services/StatsService'); -const Stat = require('../api/v1/models/Stat'); - -const assert = chai.assert; -// const expect = chai.expect; -const tracker = require('mock-knex').getTracker(); - -const expect = chai.expect; - -describe('StatService', () => { - describe('increment', () => { - let _createStat; - //let _find; - let testStat; - - before((done) => { - testStat = { category: 'registration', stat: 'testStat', field: 'testField' }; - _createStat = sinon.spy(Stat, 'create'); - //_find = sinon.spy(Stat, 'find'); - done(); - }); - - beforeEach((done) => { - tracker.install(); - done(); - }); - - afterEach((done) => { - tracker.uninstall(); - done(); - }); - - it('creates a new stat', (done) => { - const testStatClone = _.clone(testStat); - - tracker.on('query', (query) => { - query.response([ '1' ]); - }); - - const stat = StatService.createStat( - testStatClone.category, - testStatClone.stat, - testStatClone.field - ); - - // chai.expect(!_.isNull(stat)); - - stat.bind(this).then(() => { - assert(_createStat.calledOnce, 'Stat forge not called with right parameters'); - return done(); - }).catch((err) => done(err)); - }); - - after((done) => { - _createStat.restore(); - done(); - }); - }); - - describe('exists', () => { - let _existsStat; - let testStat; - - before((done) => { - testStat = { category: 'registration', stat: 'testStat', field: 'testField' }; - StatService.createStat( - testStat.category, - testStat.stat, - testStat.field - ); - _existsStat = sinon.spy(Stat, 'exists'); - done(); - }); - - beforeEach((done) => { - tracker.install(); - done(); - }); - - afterEach((done) => { - tracker.uninstall(); - done(); - }); - - it('checks if stat exists', (done) => { - const testStatClone = _.clone(testStat); - - tracker.on('query', (query) => { - query.response([ { 'count(*)': 1 } ]); - }); - - const stat = StatService.statExists( - testStatClone.category, - testStatClone.stat, - testStatClone.field - ); - - assert(_existsStat.calledOnce, 'Stat exists not called with right parameters'); - expect(stat) - .to.eventually.equal(true) - .and.notify(done); - }); - - after((done) => { - _existsStat.restore(); - done(); - }); - }); -}); diff --git a/test/test.js b/test/test.js index 95624c3..efa6d6b 100644 --- a/test/test.js +++ b/test/test.js @@ -25,6 +25,5 @@ require('./checkin.js'); require('./event.js'); require('./tracking.js'); require('./rsvp.js'); -require('./stat.js'); mockery.disable(); From d54acb45ea6a46726b68a524a810d9ac3f5e3a0a Mon Sep 17 00:00:00 2001 From: Arnav Sankaran Date: Wed, 21 Feb 2018 22:07:23 -0600 Subject: [PATCH 21/24] Reverted rsvp tests --- test/rsvp.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/rsvp.js b/test/rsvp.js index 02671e2..13669f8 100644 --- a/test/rsvp.js +++ b/test/rsvp.js @@ -158,7 +158,7 @@ describe('RSVPService', () => { const RSVP = RSVPService.updateRSVP(testUser, testAttendeeRSVP, testRSVPClone); RSVP.bind(this).then(() => { - assert(_setRSVP.calledTwice, 'RSVP update not called with right parameters'); + assert(_setRSVP.calledOnce, 'RSVP update not called with right parameters'); assert(_saveRSVP.calledOnce, 'RSVP save not called'); _attendeeRole = testUser.getRole(utils.roles.ATTENDEE); @@ -166,6 +166,8 @@ describe('RSVPService', () => { return done(); }).catch((err) => done(err)); + + done(); }); afterEach((done) => { tracker.uninstall(); From 7144f39c3261915558fca276042c04605fba3136 Mon Sep 17 00:00:00 2001 From: Arnav Sankaran Date: Wed, 21 Feb 2018 22:39:04 -0600 Subject: [PATCH 22/24] Removed unused stat function --- api/v1/models/Stat.js | 18 ------------------ api/v1/services/StatsService.js | 4 ---- 2 files changed, 22 deletions(-) diff --git a/api/v1/models/Stat.js b/api/v1/models/Stat.js index fca65cc..55bf923 100644 --- a/api/v1/models/Stat.js +++ b/api/v1/models/Stat.js @@ -1,4 +1,3 @@ -const _Promise = require('bluebird'); const _ = require('lodash'); const Model = require('./Model'); @@ -17,23 +16,6 @@ const Stat = Model.extend({ } }); -/** - * Return true if the stat exists, false otherwise. - * @param {String} category - * @param {String} stat - * @param {String} field - * @return {Promise} a Promise boolean, true if stat exists, false otherwise - */ -Stat.exists = (category, stat, field) => { - const s = Stat.where({ - category: category, - stat: stat, - field: field - }).query().count().then((count) => _Promise.resolve(count[0]['count(*)'] > 0)); - - return s; -}; - /** * Adds a row with category `category`, stat `stat`, and field `field`. * Initializes count to 0 diff --git a/api/v1/services/StatsService.js b/api/v1/services/StatsService.js index 6375c31..26034ed 100644 --- a/api/v1/services/StatsService.js +++ b/api/v1/services/StatsService.js @@ -12,10 +12,6 @@ module.exports.createStat = function (category, stat, field) { return Stat.create(category, stat, field); }; -module.exports.statExists = function (category, stat, field) { - return Stat.exists(category, stat, field); -}; - module.exports.find = function (category, stat, field) { return Stat.where({ category: category, From 14ed003bc543227ff0d78f17c9a292d3877716c9 Mon Sep 17 00:00:00 2001 From: Arnav Sankaran Date: Wed, 21 Feb 2018 22:45:43 -0600 Subject: [PATCH 23/24] Refactored stats increment calls --- api/v1/services/RSVPService.js | 54 +++++++++++++------------- api/v1/services/RegistrationService.js | 20 +++++----- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/api/v1/services/RSVPService.js b/api/v1/services/RSVPService.js index 37f3eaa..5f7d4a7 100644 --- a/api/v1/services/RSVPService.js +++ b/api/v1/services/RSVPService.js @@ -28,15 +28,15 @@ module.exports.getRSVPById = (id) => RSVP.findById(id); module.exports.createRSVP = (attendee, user, attributes) => { if(attributes.isAttending) { - StatsService.incrementStat('rsvp', 'school', attendee.get('school')).then(() => - StatsService.incrementStat('rsvp', 'transportation', attendee.get('transportation')).then(() => - StatsService.incrementStat('rsvp', 'diet', attendee.get('diet')).then(() => - StatsService.incrementStat('rsvp', 'shirtSize', attendee.get('shirtSize')).then(() => - StatsService.incrementStat('rsvp', 'gender', attendee.get('gender')).then(() => - StatsService.incrementStat('rsvp', 'graduationYear', attendee.get('graduationYear')).then(() => - StatsService.incrementStat('rsvp', 'major', attendee.get('major')).then(() => - StatsService.incrementStat('rsvp', 'isNovice', attendee.get('isNovice') ? 1 : 0).then(() => - StatsService.incrementStat('rsvp', 'attendees', 'count'))))))))); + StatsService.incrementStat('rsvp', 'school', attendee.get('school')) + .then(() => StatsService.incrementStat('rsvp', 'transportation', attendee.get('transportation')) + .then(() => StatsService.incrementStat('rsvp', 'diet', attendee.get('diet')) + .then(() => StatsService.incrementStat('rsvp', 'shirtSize', attendee.get('shirtSize')) + .then(() => StatsService.incrementStat('rsvp', 'gender', attendee.get('gender')) + .then(() => StatsService.incrementStat('rsvp', 'graduationYear', attendee.get('graduationYear')) + .then(() => StatsService.incrementStat('rsvp', 'major', attendee.get('major')) + .then(() => StatsService.incrementStat('rsvp', 'isNovice', attendee.get('isNovice') ? 1 : 0) + .then(() => StatsService.incrementStat('rsvp', 'attendees', 'count'))))))))); } attributes.attendeeId = attendee.get('id'); @@ -87,25 +87,25 @@ module.exports.updateRSVP = (user, rsvp, attributes) => { const newAttending = attributes.isAttending; RegistrationService.findAttendeeByUser(user).then((attendee) => { if(!oldAttending && newAttending) { - StatsService.incrementStat('rsvp', 'school', attendee.get('school')).then(() => - StatsService.incrementStat('rsvp', 'transportation', attendee.get('transportation')).then(() => - StatsService.incrementStat('rsvp', 'diet', attendee.get('diet')).then(() => - StatsService.incrementStat('rsvp', 'shirtSize', attendee.get('shirtSize')).then(() => - StatsService.incrementStat('rsvp', 'gender', attendee.get('gender')).then(() => - StatsService.incrementStat('rsvp', 'graduationYear', attendee.get('graduationYear')).then(() => - StatsService.incrementStat('rsvp', 'major', attendee.get('major')).then(() => - StatsService.incrementStat('rsvp', 'isNovice', attendee.get('isNovice') ? 1 : 0).then(() => - StatsService.incrementStat('rsvp', 'attendees', 'count'))))))))); + StatsService.incrementStat('rsvp', 'school', attendee.get('school')) + .then(() => StatsService.incrementStat('rsvp', 'transportation', attendee.get('transportation')) + .then(() => StatsService.incrementStat('rsvp', 'diet', attendee.get('diet')) + .then(() => StatsService.incrementStat('rsvp', 'shirtSize', attendee.get('shirtSize')) + .then(() => StatsService.incrementStat('rsvp', 'gender', attendee.get('gender')) + .then(() => StatsService.incrementStat('rsvp', 'graduationYear', attendee.get('graduationYear')) + .then(() => StatsService.incrementStat('rsvp', 'major', attendee.get('major')) + .then(() => StatsService.incrementStat('rsvp', 'isNovice', attendee.get('isNovice') ? 1 : 0) + .then(() => StatsService.incrementStat('rsvp', 'attendees', 'count'))))))))); } else if(oldAttending && !newAttending) { - StatsService.decrementStat('rsvp', 'school', attendee.get('school')).then(() => - StatsService.decrementStat('rsvp', 'transportation', attendee.get('transportation')).then(() => - StatsService.decrementStat('rsvp', 'diet', attendee.get('diet')).then(() => - StatsService.decrementStat('rsvp', 'shirtSize', attendee.get('shirtSize')).then(() => - StatsService.decrementStat('rsvp', 'gender', attendee.get('gender')).then(() => - StatsService.decrementStat('rsvp', 'graduationYear', attendee.get('graduationYear')).then(() => - StatsService.decrementStat('rsvp', 'major', attendee.get('major')).then(() => - StatsService.decrementStat('rsvp', 'isNovice', attendee.get('isNovice') ? 1 : 0).then(() => - StatsService.decrementStat('rsvp', 'attendees', 'count'))))))))); + StatsService.decrementStat('rsvp', 'school', attendee.get('school')) + .then(() => StatsService.decrementStat('rsvp', 'transportation', attendee.get('transportation')) + .then(() => StatsService.decrementStat('rsvp', 'diet', attendee.get('diet')) + .then(() => StatsService.decrementStat('rsvp', 'shirtSize', attendee.get('shirtSize')) + .then(() => StatsService.decrementStat('rsvp', 'gender', attendee.get('gender')) + .then(() => StatsService.decrementStat('rsvp', 'graduationYear', attendee.get('graduationYear')) + .then(() => StatsService.decrementStat('rsvp', 'major', attendee.get('major')) + .then(() => StatsService.decrementStat('rsvp', 'isNovice', attendee.get('isNovice') ? 1 : 0) + .then(() => StatsService.decrementStat('rsvp', 'attendees', 'count'))))))))); } }); diff --git a/api/v1/services/RegistrationService.js b/api/v1/services/RegistrationService.js index 9d72f98..503ea4f 100644 --- a/api/v1/services/RegistrationService.js +++ b/api/v1/services/RegistrationService.js @@ -329,16 +329,16 @@ module.exports.createAttendee = (user, attributes) => { const statAttributes = attributes.attendee; - StatsService.incrementStat('registration', 'school', statAttributes.school).then(() => - StatsService.incrementStat('registration', 'transportation', statAttributes.transportation).then(() => - StatsService.incrementStat('registration', 'diet', statAttributes.diet).then(() => - StatsService.incrementStat('registration', 'shirtSize', statAttributes.shirtSize).then(() => - StatsService.incrementStat('registration', 'gender', statAttributes.gender).then(() => - StatsService.incrementStat('registration', 'graduationYear', statAttributes.graduationYear).then(() => - StatsService.incrementStat('registration', 'major', statAttributes.major).then(() => - StatsService.incrementStat('registration', 'isNovice', statAttributes.isNovice ? 1 : 0).then(() => - StatsService.incrementStat('registration', 'attendees', 'count').then(() => - StatsService.incrementStat('registration', 'status', 'PENDING')))))))))); + StatsService.incrementStat('registration', 'school', statAttributes.school) + .then(() => StatsService.incrementStat('registration', 'transportation', statAttributes.transportation) + .then(() => StatsService.incrementStat('registration', 'diet', statAttributes.diet) + .then(() => StatsService.incrementStat('registration', 'shirtSize', statAttributes.shirtSize) + .then(() => StatsService.incrementStat('registration', 'gender', statAttributes.gender) + .then(() => StatsService.incrementStat('registration', 'graduationYear', statAttributes.graduationYear) + .then(() => StatsService.incrementStat('registration', 'major', statAttributes.major) + .then(() => StatsService.incrementStat('registration', 'isNovice', statAttributes.isNovice ? 1 : 0) + .then(() => StatsService.incrementStat('registration', 'attendees', 'count') + .then(() => StatsService.incrementStat('registration', 'status', 'PENDING')))))))))); const attendeeAttrs = attributes.attendee; From 9ca2538ad5f366583802f32e5c60ea53b5c40035 Mon Sep 17 00:00:00 2001 From: Arnav Sankaran Date: Wed, 21 Feb 2018 23:37:07 -0600 Subject: [PATCH 24/24] Removed promise nesting --- api/v1/services/RSVPService.js | 48 +++++++++++++------------- api/v1/services/RegistrationService.js | 18 +++++----- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/api/v1/services/RSVPService.js b/api/v1/services/RSVPService.js index 5f7d4a7..cf5501f 100644 --- a/api/v1/services/RSVPService.js +++ b/api/v1/services/RSVPService.js @@ -29,14 +29,14 @@ module.exports.createRSVP = (attendee, user, attributes) => { if(attributes.isAttending) { StatsService.incrementStat('rsvp', 'school', attendee.get('school')) - .then(() => StatsService.incrementStat('rsvp', 'transportation', attendee.get('transportation')) - .then(() => StatsService.incrementStat('rsvp', 'diet', attendee.get('diet')) - .then(() => StatsService.incrementStat('rsvp', 'shirtSize', attendee.get('shirtSize')) - .then(() => StatsService.incrementStat('rsvp', 'gender', attendee.get('gender')) - .then(() => StatsService.incrementStat('rsvp', 'graduationYear', attendee.get('graduationYear')) - .then(() => StatsService.incrementStat('rsvp', 'major', attendee.get('major')) - .then(() => StatsService.incrementStat('rsvp', 'isNovice', attendee.get('isNovice') ? 1 : 0) - .then(() => StatsService.incrementStat('rsvp', 'attendees', 'count'))))))))); + .then(() => StatsService.incrementStat('rsvp', 'transportation', attendee.get('transportation'))) + .then(() => StatsService.incrementStat('rsvp', 'diet', attendee.get('diet'))) + .then(() => StatsService.incrementStat('rsvp', 'shirtSize', attendee.get('shirtSize'))) + .then(() => StatsService.incrementStat('rsvp', 'gender', attendee.get('gender'))) + .then(() => StatsService.incrementStat('rsvp', 'graduationYear', attendee.get('graduationYear'))) + .then(() => StatsService.incrementStat('rsvp', 'major', attendee.get('major'))) + .then(() => StatsService.incrementStat('rsvp', 'isNovice', attendee.get('isNovice') ? 1 : 0)) + .then(() => StatsService.incrementStat('rsvp', 'attendees', 'count')); } attributes.attendeeId = attendee.get('id'); @@ -88,24 +88,24 @@ module.exports.updateRSVP = (user, rsvp, attributes) => { RegistrationService.findAttendeeByUser(user).then((attendee) => { if(!oldAttending && newAttending) { StatsService.incrementStat('rsvp', 'school', attendee.get('school')) - .then(() => StatsService.incrementStat('rsvp', 'transportation', attendee.get('transportation')) - .then(() => StatsService.incrementStat('rsvp', 'diet', attendee.get('diet')) - .then(() => StatsService.incrementStat('rsvp', 'shirtSize', attendee.get('shirtSize')) - .then(() => StatsService.incrementStat('rsvp', 'gender', attendee.get('gender')) - .then(() => StatsService.incrementStat('rsvp', 'graduationYear', attendee.get('graduationYear')) - .then(() => StatsService.incrementStat('rsvp', 'major', attendee.get('major')) - .then(() => StatsService.incrementStat('rsvp', 'isNovice', attendee.get('isNovice') ? 1 : 0) - .then(() => StatsService.incrementStat('rsvp', 'attendees', 'count'))))))))); + .then(() => StatsService.incrementStat('rsvp', 'transportation', attendee.get('transportation'))) + .then(() => StatsService.incrementStat('rsvp', 'diet', attendee.get('diet'))) + .then(() => StatsService.incrementStat('rsvp', 'shirtSize', attendee.get('shirtSize'))) + .then(() => StatsService.incrementStat('rsvp', 'gender', attendee.get('gender'))) + .then(() => StatsService.incrementStat('rsvp', 'graduationYear', attendee.get('graduationYear'))) + .then(() => StatsService.incrementStat('rsvp', 'major', attendee.get('major'))) + .then(() => StatsService.incrementStat('rsvp', 'isNovice', attendee.get('isNovice') ? 1 : 0)) + .then(() => StatsService.incrementStat('rsvp', 'attendees', 'count')); } else if(oldAttending && !newAttending) { StatsService.decrementStat('rsvp', 'school', attendee.get('school')) - .then(() => StatsService.decrementStat('rsvp', 'transportation', attendee.get('transportation')) - .then(() => StatsService.decrementStat('rsvp', 'diet', attendee.get('diet')) - .then(() => StatsService.decrementStat('rsvp', 'shirtSize', attendee.get('shirtSize')) - .then(() => StatsService.decrementStat('rsvp', 'gender', attendee.get('gender')) - .then(() => StatsService.decrementStat('rsvp', 'graduationYear', attendee.get('graduationYear')) - .then(() => StatsService.decrementStat('rsvp', 'major', attendee.get('major')) - .then(() => StatsService.decrementStat('rsvp', 'isNovice', attendee.get('isNovice') ? 1 : 0) - .then(() => StatsService.decrementStat('rsvp', 'attendees', 'count'))))))))); + .then(() => StatsService.decrementStat('rsvp', 'transportation', attendee.get('transportation'))) + .then(() => StatsService.decrementStat('rsvp', 'diet', attendee.get('diet'))) + .then(() => StatsService.decrementStat('rsvp', 'shirtSize', attendee.get('shirtSize'))) + .then(() => StatsService.decrementStat('rsvp', 'gender', attendee.get('gender'))) + .then(() => StatsService.decrementStat('rsvp', 'graduationYear', attendee.get('graduationYear'))) + .then(() => StatsService.decrementStat('rsvp', 'major', attendee.get('major'))) + .then(() => StatsService.decrementStat('rsvp', 'isNovice', attendee.get('isNovice') ? 1 : 0)) + .then(() => StatsService.decrementStat('rsvp', 'attendees', 'count')); } }); diff --git a/api/v1/services/RegistrationService.js b/api/v1/services/RegistrationService.js index 503ea4f..8ca0ec4 100644 --- a/api/v1/services/RegistrationService.js +++ b/api/v1/services/RegistrationService.js @@ -330,15 +330,15 @@ module.exports.createAttendee = (user, attributes) => { const statAttributes = attributes.attendee; StatsService.incrementStat('registration', 'school', statAttributes.school) - .then(() => StatsService.incrementStat('registration', 'transportation', statAttributes.transportation) - .then(() => StatsService.incrementStat('registration', 'diet', statAttributes.diet) - .then(() => StatsService.incrementStat('registration', 'shirtSize', statAttributes.shirtSize) - .then(() => StatsService.incrementStat('registration', 'gender', statAttributes.gender) - .then(() => StatsService.incrementStat('registration', 'graduationYear', statAttributes.graduationYear) - .then(() => StatsService.incrementStat('registration', 'major', statAttributes.major) - .then(() => StatsService.incrementStat('registration', 'isNovice', statAttributes.isNovice ? 1 : 0) - .then(() => StatsService.incrementStat('registration', 'attendees', 'count') - .then(() => StatsService.incrementStat('registration', 'status', 'PENDING')))))))))); + .then(() => StatsService.incrementStat('registration', 'transportation', statAttributes.transportation)) + .then(() => StatsService.incrementStat('registration', 'diet', statAttributes.diet)) + .then(() => StatsService.incrementStat('registration', 'shirtSize', statAttributes.shirtSize)) + .then(() => StatsService.incrementStat('registration', 'gender', statAttributes.gender)) + .then(() => StatsService.incrementStat('registration', 'graduationYear', statAttributes.graduationYear)) + .then(() => StatsService.incrementStat('registration', 'major', statAttributes.major)) + .then(() => StatsService.incrementStat('registration', 'isNovice', statAttributes.isNovice ? 1 : 0)) + .then(() => StatsService.incrementStat('registration', 'attendees', 'count')) + .then(() => StatsService.incrementStat('registration', 'status', 'PENDING')); const attendeeAttrs = attributes.attendee;