Skip to content

Commit

Permalink
Merge pull request #19 from ExtensionEngine/feature/graded-events-end…
Browse files Browse the repository at this point in the history
…points

List Graded Events Endpoint
  • Loading branch information
Frano Sinčić authored Jun 6, 2019
2 parents bf522ba + a1ac862 commit 1061fd1
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 20 deletions.
3 changes: 2 additions & 1 deletion server/common/database/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ const sqlFunctions = {
max: 'MAX',
average: 'AVG',
count: 'COUNT',
distinct: 'DISTINCT'
distinct: 'DISTINCT',
sum: 'SUM'
};

module.exports = {
Expand Down
68 changes: 50 additions & 18 deletions server/event/event.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,54 @@ const db = require('../common/database');
const HttpStatus = require('http-status');
const map = require('lodash/map');
const pick = require('lodash/pick');
const set = require('lodash/set');

const { GradedEvent, LearnerProfile, UngradedEvent, Sequelize, utils } = db;
const fn = utils.build(UngradedEvent);
const { CREATED } = HttpStatus;
const { Op } = Sequelize;
const commonAttrs = ['userId', 'activityId', 'interactionStart', 'interactionEnd'];
const ungradedAttrs = ['progress'].concat(commonAttrs);
const gradedAttrs = ['questionId', 'isCorrect', 'answer'].concat(commonAttrs);

const parseResult = it => ({
...it,
avgDuration: parseFloat(it.avgDuration),
views: parseInt(it.views, 10)
});
const ungradedFilterAttrs = ['interactionStart', 'interactionEnd', 'activityIds'];
const gradedFilterAttrs = ungradedFilterAttrs.concat('questionIds');

function listUngradedEvents({ cohortId, query, options }, res) {
const { activityIds, uniqueViews, fromDate, toDate } = query;
const fn = utils.build(UngradedEvent);
const group = [fn.column('activityId')];
const views = uniqueViews ? fn.distinct('userId') : fn.column('userId');
const views = query.uniqueViews ? fn.distinct('userId') : fn.column('userId');
const attributes = [
[...group, 'activityId'],
[fn.count(views), 'views'],
[fn.average('duration'), 'avgDuration'],
[fn.max('interactionEnd'), 'lastViewed']
];
const where = { cohortId };
if (fromDate) where.interactionStart = { [Op.gte]: fromDate };
if (toDate) where.interactionEnd = { [Op.lte]: toDate };
if (activityIds) where.activityId = { [Op.in]: activityIds };
const where = getFilters({ cohortId, ...pick(query, ungradedFilterAttrs) });
const opts = { where, ...options, group, attributes, raw: true };
return UngradedEvent.findAndCountAll(opts).then(({ rows, count }) => {
const items = map(rows, parseResult);
return res.jsend.success(({ items, total: count.length }));
});
}

function listGradedEvents({ cohortId, query, options }, res) {
const fn = utils.build(GradedEvent);
const group = [fn.column('questionId'), fn.column('activityId')];
const attributes = [
[fn.column('questionId'), 'questionId'],
[fn.column('activityId'), 'activityId'],
[fn.sum(Sequelize.cast(fn.column('isCorrect'), 'integer')), 'correct'],
[fn.count(fn.column('userId')), 'submissions'],
[fn.average('duration'), 'avgDuration'],
[fn.max('interactionEnd'), 'lastSubmitted']
];
const where = getFilters({ cohortId, ...pick(query, gradedFilterAttrs) });
const opts = { where, ...options, group, attributes, raw: true };
return GradedEvent.findAndCountAll(opts).then(({ rows, count }) => {
const items = map(rows, parseResult);
return res.jsend.success(({ items, total: count.length }));
});
}

async function reportUngradedEvent({ cohortId, body }, res) {
const data = { cohortId, ...pick(body, ungradedAttrs) };
const profileCond = { cohortId, userId: body.userId };
Expand All @@ -54,13 +66,33 @@ async function reportGradedEvent({ cohortId, body }, res) {
return res.status(CREATED).end();
}

function calculateDuration({ interactionStart, interactionEnd }) {
if (!interactionStart || !interactionEnd) return null;
return Math.ceil((interactionEnd - interactionStart) / 1000);
}

module.exports = {
listUngradedEvents,
listGradedEvents,
reportUngradedEvent,
reportGradedEvent
};

function parseResult(it) {
const intAttributes = ['views', 'submissions', 'correct'];
return Object.keys(it).reduce((acc, key) => {
if (key === 'avgDuration') return set(acc, key, parseFloat(it[key]));
if (intAttributes.includes(key)) return set(acc, key, parseInt(it[key], 10));
return set(acc, key, it[key]);
}, {});
}

function getFilters(query) {
const { activityIds, cohortId, fromDate, toDate, questionIds } = query;
const where = { cohortId };
if (fromDate) where.interactionStart = { [Op.gte]: fromDate };
if (toDate) where.interactionEnd = { [Op.lte]: toDate };
if (activityIds) where.activityId = { [Op.in]: activityIds };
if (questionIds) where.questionId = { [Op.in]: questionIds };
return where;
}

function calculateDuration({ interactionStart, interactionEnd }) {
if (!interactionStart || !interactionEnd) return null;
return Math.ceil((interactionEnd - interactionStart) / 1000);
}
3 changes: 2 additions & 1 deletion server/event/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
const ctrl = require('./event.controller');
const { processPagination } = require('../common/pagination');
const router = require('express').Router();
const { UngradedEvent } = require('../common/database');
const { GradedEvent, UngradedEvent } = require('../common/database');

router
.get('/ungraded', processPagination(UngradedEvent), ctrl.listUngradedEvents)
.get('/graded', processPagination(GradedEvent), ctrl.listGradedEvents)
.post('/ungraded', ctrl.reportUngradedEvent)
.post('/graded', ctrl.reportGradedEvent);

Expand Down

0 comments on commit 1061fd1

Please sign in to comment.