Skip to content
This repository has been archived by the owner on Jun 21, 2019. It is now read-only.

Update Statistics To Realtime w/ Caching #194

Open
wants to merge 27 commits into
base: staging
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
f58d8ec
add stats
vsui Nov 9, 2017
68891bc
Merge branch 'master' into add-stats
ASankaran Jan 23, 2018
c3cc662
Fixed broken rsvp test
ASankaran Jan 23, 2018
90725d6
Add method to check if stat exists in database
ASankaran Jan 25, 2018
16489ae
Fixed stat increment and update stats on attendee registration
ASankaran Jan 25, 2018
c8eaa9d
Add missing catch when querying stat
ASankaran Jan 25, 2018
37cd7cf
Write all registration stats to db on signup
ASankaran Jan 25, 2018
ad7f1aa
Fixed mail list bug in RSVPController
ASankaran Jan 26, 2018
326b893
Update stats on attendee rsvp
ASankaran Jan 26, 2018
2ed2ce5
Update liveevent attendee count on checkin
ASankaran Jan 26, 2018
c989637
Write stats into cache on increment
ASankaran Jan 28, 2018
58134fa
Added function to stats back from database if needed
ASankaran Jan 29, 2018
614c8bd
Fetch stats from cache when possible, falling back to database when n…
ASankaran Jan 29, 2018
66a5a1d
Ran linter and removed old stats code
ASankaran Jan 29, 2018
29e1fcb
Added stats for tracked live events
ASankaran Jan 29, 2018
c37fa6e
Ran linter
ASankaran Jan 29, 2018
0c2b679
Updated Docs
ASankaran Feb 1, 2018
320f523
Merge pull request #214 from HackIllinois/staging
YashoSharma Feb 8, 2018
212c4cc
Merged master
ASankaran Feb 12, 2018
8560392
Rename liveevent to live_event
ASankaran Feb 12, 2018
eee93e3
Write stats to cache in series
ASankaran Feb 22, 2018
2c7d064
Decrement RSVP stats on changing accept to decline
ASankaran Feb 22, 2018
6b4b68c
Removed stats test
ASankaran Feb 22, 2018
d54acb4
Reverted rsvp tests
ASankaran Feb 22, 2018
7144f39
Removed unused stat function
ASankaran Feb 22, 2018
14ed003
Refactored stats increment calls
ASankaran Feb 22, 2018
9ca2538
Removed promise nesting
ASankaran Feb 22, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api/v1/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,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) => {
Expand Down
70 changes: 70 additions & 0 deletions api/v1/models/Stat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
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<Stat>} 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<Stat>} a Promise resolving to the updating Stat model
*/
Stat.increment = (category, stat, field, amount) => {
if (_.isUndefined(amount)) {
amount = 1;
}

const s = Stat.where({
category: category,
stat: stat,
field: field
}).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);
};

module.exports = Stat;
6 changes: 6 additions & 0 deletions api/v1/models/TrackingEvent.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,10 @@ TrackingEvent.findByName = function(searchName) {
.fetch();
};

TrackingEvent.findAll = function() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't believe this is necessary

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stats Service needs it to query all the tracked events and report the number of people who checked in.

return TrackingEvent.where({

}).fetchAll();
};

module.exports = TrackingEvent;
3 changes: 3 additions & 0 deletions api/v1/services/CheckInService.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
43 changes: 43 additions & 0 deletions api/v1/services/RSVPService.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ const RSVP = require('../models/AttendeeRSVP');
const UserRole = require('../models/UserRole');
const errors = require('../errors');
const utils = require('../utils');

const StatsService = require('../services/StatsService');
const RegistrationService = require('../services/RegistrationService');

/**
* Gets an rsvp by its id
* @param {integer} id the id of the RSVP to find
Expand All @@ -22,6 +26,19 @@ module.exports.getRSVPById = (id) => RSVP.findById(id);
* @throws {InvalidParameterError} thrown when an attendee already has an rsvp
*/
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'));
}

attributes.attendeeId = attendee.get('id');
const rsvp = RSVP.forge(attributes);

Expand Down Expand Up @@ -66,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
Expand Down
16 changes: 16 additions & 0 deletions api/v1/services/RegistrationService.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ const errors = require('../errors');
const utils = require('../utils');
const config = require('ctx').config();

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
Expand Down Expand Up @@ -325,6 +327,20 @@ module.exports.createAttendee = (user, attributes) => {
return _Promise.reject(new errors.InvalidParameterError(message, source));
}

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;

Expand Down
Loading