Skip to content

Commit

Permalink
Implement autoregistration
Browse files Browse the repository at this point in the history
  • Loading branch information
petschekr committed Apr 17, 2016
1 parent 3e318a1 commit a646519
Show file tree
Hide file tree
Showing 2 changed files with 157 additions and 1 deletion.
30 changes: 29 additions & 1 deletion World Perspectives/public/components/admin/admin-emails.html
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ <h2>Registration Settings</h2>
<iron-ajax id="get-emails-ajax" auto url="/admin/registration/email" handle-as="json" last-response="{{emailStatus}}" on-error="handleError"></iron-ajax>
<iron-ajax id="send-registration-emails-ajax" url="/admin/registration/email/registration" method="POST" handle-as="json" on-response="sendRegistrationEmailsResponse" on-error="handleError"></iron-ajax>
<iron-ajax id="send-schedule-emails-ajax" url="/admin/registration/email/schedule" method="POST" handle-as="json" on-response="sendScheduleEmailsResponse" on-error="handleError"></iron-ajax>
<iron-ajax auto url="/admin/registration/stats" handle-as="json" last-response="{{registrationStats}}" on-error="handleError"></iron-ajax>
<iron-ajax auto id="stats-ajax" url="/admin/registration/stats" handle-as="json" last-response="{{registrationStats}}" on-error="handleError"></iron-ajax>
<iron-ajax id="autoregister-ajax" url="/admin/registration/auto" method="POST" handle-as="json" on-response="autoregisterResponse" on-error="handleError"></iron-ajax>
<paper-toggle-button id="open" checked="[[registrationOpen.open]]" on-change="updateRegistrationOpen" style="width: 184px; margin: 20px auto;">
<template is="dom-if" if="[[registrationOpen.open]]">
Registration open
Expand Down Expand Up @@ -118,6 +119,15 @@ <h2>Send Emails</h2>
<span class="flex"></span>
</div>
</content-section>
<content-section>
<h2>Autoregister</h2>
<p>This will automatically register all unregistered users into available sessions from least full to most full. This will only affect unregistered users and cannot be undone.</p>
<div class="flex-container">
<span class="flex"></span>
<paper-button raised on-click="autoregister">Autoregister</paper-button>
<span class="flex"></span>
</div>
</content-section>
<paper-toast id="loading" text="Loading..." duration="0"></paper-toast>
<paper-toast id="done" text="Something happened" duration="5000"></paper-toast>
<paper-toast id="error" error text="Something happened" duration="5000"></paper-toast>
Expand Down Expand Up @@ -181,6 +191,24 @@ <h2>Send Emails</h2>
}
this.$["registration-open-ajax"].generateRequest();
},
autoregister: function () {
var message = "Are you sure that you want to autoregister all unregistered users?";
if (!confirm(message))
return;
this.$.loading.open();
this.$["autoregister-ajax"].generateRequest();
},
autoregisterResponse: function () {
this.$.loading.close();
var response = this.$["autoregister-ajax"].lastResponse;
if (!response.success) {
this.$.error.show(response.message);
}
else {
this.$.done.show(response.message);
}
this.$["stats-ajax"].generateRequest();
},
handleError: function (e) {
this.$.loading.close();
this.$.error.show(e.target.lastError.error.message || "An error occurred!");
Expand Down
128 changes: 128 additions & 0 deletions World Perspectives/routes/admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1362,5 +1362,133 @@ router.route("/registration/stats")
});
}).catch(common.handleError.bind(response));
});
router.route("/registration/auto").post(function (request, response) {
var totalUsers = 0;
// Get all unregistered users and all sessions first
Promise.all([
db.cypherAsync({
query: "MATCH (u:User {registered: false}) RETURN u.username AS username"
}),
db.cypherAsync({
query: "MATCH(item:ScheduleItem {editable: true }) RETURN item.title AS title, item.start AS startTime, item.end AS endTime"
}),
db.cypherAsync({
query: "MATCH (s:Session) RETURN s.slug AS slug, s.attendees AS attendees, s.capacity AS capacity, s.startTime AS startTime, s.endTime AS endTime"
})
]).spread(function (users: User[], editablePeriods: any[], sessions: any[]) {
totalUsers = users.length;
Promise.each(editablePeriods, function (period) {
// Shuffle users for each period
function shuffle(array) {
let counter = array.length;

// While there are elements in the array
while (counter > 0) {
// Pick a random index
let index = Math.floor(Math.random() * counter);

// Decrease counter by 1
counter--;

// And swap the last element with it
let temp = array[counter];
array[counter] = array[index];
array[index] = temp;
}

return array;
}
users = shuffle(users);
// Find non-full sessions that occur at this time
var availableSessions = [];
function updateAvailableSessions() {
availableSessions = sessions.filter(function (session) {
if (session.attendees < session.capacity && moment(session.startTime).isSame(moment(period.startTime))) {
return true;
}
return false;
});
if (availableSessions.length < 1) {
// Not enough sessions
console.warn(`Not enough capacity at ${moment(period.startTime).format(timeFormat)} to autoregister`);
}
function sortSessions(sessions: any[]): any[] {
return sessions.sort(function (a, b) {
return a.attendees - b.attendees;
});
}
availableSessions = sortSessions(availableSessions);
}
updateAvailableSessions();
// Find the unregistered users that have registered for this period already (partially completed registration) to filter them out of needing to be autoregistered
return db.cypherAsync({
query: "MATCH (u:User {registered: false})-[r:ATTENDS]->(s:Session {startTime: {startTime}}) RETURN u.username AS username",
params: {
startTime: period.startTime
}
}).then(function (attendingUsers: User[]) {
var attendingUsernames = attendingUsers.map(function (attendingUser) {
return attendingUser.username;
});
var remainingUsers = users.filter(function (user) {
return attendingUsernames.indexOf(user.username) === -1;
});
Promise.each(remainingUsers, function (user: User) {
updateAvailableSessions();
// Check if this user moderates or presents a session at this time period
return Promise.all([
db.cypherAsync({
query: "MATCH (u:User {username: {username}})-[r:PRESENTS]->(s:Session {startTime: {startTime}}) RETURN s.slug AS slug",
params: {
username: user.username,
startTime: period.startTime
}
}),
db.cypherAsync({
query: "MATCH (u:User {username: {username}})-[r:MODERATES]->(s:Session {startTime: {startTime}}) RETURN s.slug AS slug",
params: {
username: user.username,
startTime: period.startTime
}
})
]).spread(function (presenting: any[], moderating: any[]) {
var registerSlug = null;
if (presenting.length > 0) {
registerSlug = presenting[0].slug;
}
else if (moderating.length > 0) {
registerSlug = moderating[0].slug;
}
else {
registerSlug = availableSessions[0].slug;
availableSessions[0].attendees++;
}
// Register this user
return db.cypherAsync({
query: `
MATCH (user:User {username: {username}})
MATCH (session:Session {slug: {slug}})
CREATE (user)-[r:ATTENDS]->(session)
SET session.attendees = session.attendees + 1`,
params: {
username: user.username,
slug: registerSlug
}
});
});
});
});
});
}).then(function () {
// Set all users as registered
return db.cypherAsync({
query: "MATCH (u:User {registered: false}) SET u.registered = true"
});
}).then(function () {
response.json({ "success": true, "message": `Successfully autoregistered ${totalUsers} users` });
}).catch(IgnoreError, function () {
// Response has already been handled if this error is thrown
}).catch(common.handleError.bind(response));
});

export = router;

0 comments on commit a646519

Please sign in to comment.