@universis/evaluations
Version:
Universis evaluations library
341 lines (200 loc) • 15.3 kB
JavaScript
;var _data = require("@themost/data");
var _evaluationEventModel = _interopRequireDefault(require("./evaluation-event-model"));
var _common = require("@themost/common");
var _moment = _interopRequireDefault(require("moment"));var _dec, _dec2, _dec3, _dec4, _dec5, _class, _class2;function _interopRequireDefault(obj) {return obj && obj.__esModule ? obj : { default: obj };}function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) {var desc = {};Object.keys(descriptor).forEach(function (key) {desc[key] = descriptor[key];});desc.enumerable = !!desc.enumerable;desc.configurable = !!desc.configurable;if ('value' in desc || desc.initializer) {desc.writable = true;}desc = decorators.slice().reverse().reduce(function (desc, decorator) {return decorator(target, property, desc) || desc;}, desc);if (context && desc.initializer !== void 0) {desc.value = desc.initializer ? desc.initializer.call(context) : void 0;desc.initializer = undefined;}if (desc.initializer === void 0) {Object.defineProperty(target, property, desc);desc = null;}return desc;}
/**
* @class
*/let
CourseClassEvaluationEvent = (_dec = _data.EdmMapping.entityType('CourseClassEvaluationEvent'), _dec2 =
_data.EdmMapping.func('Results', _data.EdmType.CollectionOf('Object')), _dec3 =
_data.EdmMapping.action('GenerateEvaluationTokens', 'GenerateCourseClassTokenAction'), _dec4 =
_data.EdmMapping.func('TokenInfo', _data.EdmType.CollectionOf('Object')), _dec5 =
_data.EdmMapping.action('SendEvaluationTokens', 'SendCourseClassTokenAction'), _dec(_class = (_class2 = class CourseClassEvaluationEvent extends _evaluationEventModel.default {/**
* @constructor
*/constructor() {super();}async getResults() {// check access to ClassInstructorEvaluationEvent and get results silently
const event = await this.context.model('CourseClassEvaluationEvent').where('id').equal(this.getId());if (event) {const form = await this.getForm();return this.context.model(form.properties.name).where('evaluationEvent').equal(this.id).silent().prepare();}}async generateEvaluationTokens() {const context = this.context;const action = { event: this.id, numberOfStudents: 0, total: 0, actionStatus: { alternateName: 'PotentialActionStatus' } }; // get courseClass event
const event = await context.model('CourseClassEvaluationEvent').where('id').equal(this.id).expand('courseClass').getItem();if (event) {//check if a GenerateCourseClassTokenAction exists
const prevAction = await context.model('GenerateCourseClassTokenAction').where('event').equal(this.id).and('actionStatus/alternateName').notEqual('FailedActionStatus').silent().getItem();if (prevAction) {throw new Error(context.__('A previous action for generating evaluation tokens has been found.'));}const courseClass = event.courseClass;action.numberOfStudents = await context.model('StudentCourseClass').where('courseClass').equal(courseClass.id).silent().count(); // save action and then generateTokens
const generateAction = await context.model('GenerateCourseClassTokenAction').save(action);const generateOptions = { evaluationEvent: event.id, n: action.numberOfStudents, expires: event.endDate };try {// save number of students to event
event.numberOfStudents = action.numberOfStudents;await context.model('CourseClassEvaluationEvent').save(event);if (event.numberOfStudents > 0) {const studentMetadata = context.getConfiguration().getSourceAt('settings/evaluation/useStudentInfoAtMetadata') || false;await _evaluationEventModel.default.generateTokens(context, generateOptions); // get all generated tokens and generate tokens for child events
const tokens = await context.model('EvaluationAccessToken').where('evaluationEvent').equal(event.id).silent().getAllItems();const students = await this.getStudentEvaluationEvents(context, event, courseClass);for (const student of students) {const access_token = tokens.pop(); // student.id
const metadata = { id: studentMetadata ? student.id : null, student: studentMetadata ? student.student : null, semester: student.semester, registrationType: student.registrationType };for (let i = 0; i < student.events.length; i++) {const studentEvent = student.events[i];if (event.id === studentEvent.id) {await context.model('EvaluationAccessToken').silent().save({ access_token: access_token.access_token, metadata });} else {await context.model('EvaluationAccessToken').silent().save({ evaluationEvent: studentEvent, expires: generateOptions.expires, parentToken: access_token, metadata });}}}}generateAction.total = action.numberOfStudents;generateAction.actionStatus = { alternateName: 'CompletedActionStatus' };return await context.model('GenerateCourseClassTokenAction').save(generateAction);} catch (err) {_common.TraceUtils.error(err); // update also action status
generateAction.total = 0;generateAction.actionStatus = { alternateName: 'FailedActionStatus' };generateAction.description = err.message;return await context.model('GenerateCourseClassTokenAction').save(generateAction);}}}async getTokenStatistics() {const tokenInfo = { total: 0, used: 0, sent: 0 }; // get number of tokens
const tokens = await this.context.model('EvaluationAccessToken').where('evaluationEvent').equal(this.getId()).select('count(evaluationEvent) as total', 'used', 'sent').groupBy('used', 'sent', 'evaluationEvent').silent().getItems();tokens.map((item) => {tokenInfo.total += item.total;tokenInfo.used += item.used ? item.total : 0;tokenInfo.sent += item.sent ? item.total : 0;});return tokenInfo;}async sendEvaluationTokens() {const context = this.context;let sendAction = {};let subject; // get event and check if event endDate has passed
try {// get current date
const currentDate = (0, _moment.default)(new Date()).startOf('day').toDate(); // get only date format
const event = await context.model('CourseClassEvaluationEvent').where('id').equal(this.getId()).and('date(endDate)').greaterOrEqual(currentDate).expand({ name: 'courseClass', options: {
'$expand': 'course' } },
{
name: 'actions',
options: {
'$expand': 'actionStatus' } }).
getItem();
// validate event
if (event == null) {
throw new _common.DataNotFoundError('The event cannot be found or is inaccessible.');
}
// validate status
if (!(event.eventStatus && event.eventStatus.alternateName === 'EventOpened')) {
throw new _common.DataError('E_INVALID_PERIOD', context.__('Send tokens action may only be executed for Open/Active events'));
}
// validate event actions
if (!(event.actions && event.actions.length)) {
throw new _common.DataNotFoundError('E_NO_TOKENS', context.__('Token actions were not found for the specified event or they are inaccessible.'));
}
// find completed token generation action (can only be one)
const successfulTokenAction = event.actions.find((generateTokenAction) => generateTokenAction.actionStatus.alternateName === 'CompletedActionStatus');
if (successfulTokenAction == null) {
throw new _common.DataError('E_NO_TOKENS', 'The specified event does not have a completed token generation action.');
}
// get tokenInfo for this event
const tokenInfo = await this.getTokenStatistics();
// validate sent-used separately for descriptive errors
if (tokenInfo.sent) {
throw new _common.DataError('E_ALREADY_SENT', `${context.__('The bulk sending of the tokens could not be completed because some of them had already been sent')} (${tokenInfo.sent})`);
}
if (tokenInfo.used) {
throw new _common.DataError('E_ALREADY_USED', `${context.__('The bulk sending of the tokens could not be completed because some of them had already been used')} (${tokenInfo.used})`);
}
// start gathering students
const courseClass = event.courseClass;
const students = await this.getStudentEvaluationEvents(context, event, courseClass);
if (!(Array.isArray(students) && students.length)) {
throw new _common.DataError('E_STUDENTS_FOUND', 'No recipients/students found.');
}
// check total of students
if (students.length !== tokenInfo.total) {
throw new _common.DataError('E_TOTAL_STUDENTS',
`${this.context.__("The number of students in the class differs from the generated tokens")}` +
`(${this.context.__("Students")}: ${students.length}, ${this.context.__("Number of tokens")}:${tokenInfo.total}.`);
}
// validate student emails, must all exist and have a valid format
// https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address
const mailRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
const invalidEmail = students.find((student) => {
return !(student.email && mailRegex.test(student.email));
});
if (invalidEmail) {
throw new _common.DataError('E_INVALID_EMAIL', `${context.__('At least one student\'s email is null or invalid')} (email:${invalidEmail.email || ' '} ${context.__('Student')}:${invalidEmail.student})`);
}
// get tokens
let tokens = await this.context.model('EvaluationAccessToken').where('evaluationEvent').equal(this.getId()).
silent().
getAllItems();
// get mail template
const mailTemplate = await context.model('MailConfiguration').
where('target').equal('SendCourseClassTokenAction').
silent().
getItem();
if (mailTemplate == null) {
throw new _common.DataError('E_MISSING_TEMPLATE', 'Cannot execute send tokens action because template is missing for SendCourseClassTokenAction.');
}
// check if there is already an active sendTokensAction
const alreadySendingTokens = await context.model('SendCourseClassTokenAction').
where('event').equal(event.id).
and('actionStatus/alternateName').equal('ActiveActionStatus').
silent().count();
if (alreadySendingTokens) {
throw new _common.DataError('E_ALREADY_SENDING', `${context.__('The bulk sending process of tokens is already in progress')}`);
}
// save SendInstructorTokenAction
const sendTokenAction = {
event: this.getId(),
numberOfStudents: students.length,
total: tokenInfo.total,
sent: 0,
failed: 0,
actionStatus: {
alternateName: 'ActiveActionStatus' } };
sendAction = await context.model('SendCourseClassTokenAction').silent().save(sendTokenAction);
// format mail subject expected params ${course}
const exp = new RegExp('\\$\\{\\w+\\}+', 'ig');
subject = mailTemplate.subject;
const params = subject.match(exp);
if (params && params.length > 0) {
// parse only custom parameters ${instructor},${course}
params.forEach((param) => {
if (param === '${course}') {
// get courseClass code and title
subject = subject.replace(param, courseClass.course ? `${courseClass.course.displayCode} ${courseClass.title}` : '');
} else {
subject = subject.replace(param, '');
}
});
}
_evaluationEventModel.default.sendMessages(context, students, tokens, tokenInfo, mailTemplate, subject, sendAction, event);
} catch (err) {
_common.TraceUtils.error(err);
// create send tokens action
const sendTokenAction = {
event: this.getId(),
numberOfStudents: 0,
total: 0,
sent: 0,
failed: 0,
actionStatus: {
alternateName: 'FailedActionStatus' },
description: err.message };
// and save
if (sendAction && sendAction.id) {
sendTokenAction.id = sendAction.id;
}
if (!subject) {
// load courseClass
const item = await context.model('CourseClassEvaluationEvent').where('id').equal(this.getId()).
expand({
name: 'courseClass',
options: {
'$expand': 'course' } }).
getItem();
const courseClass = item && item.courseClass;
subject = courseClass ? `${courseClass.course.displayCode} ${courseClass.title}` : '';
}
_evaluationEventModel.default.sendSse(context.user, context, this.getId(), subject, err);
return await context.model('SendCourseClassTokenAction').silent().save(sendTokenAction);
}
}
async getStudentEvaluationEvents(context, event, courseClass) {
// get child events (classInstructorEvaluationEvents)
const classInstructorEvaluationEvents = await context.model('ClassInstructorEvaluationEvents').
where('superEvent').
equal(event.id).
expand({
name: 'courseClassInstructor',
options: {
'$expand': 'courseClass($expand=sections)' } }).
silent().
getAllItems();
// get course class sections' instructors
const instructorSections = await context.model('CourseClassSectionInstructor').where('courseClass').equal(courseClass.id).
select('section/section as section', 'instructor').flatten().silent().getAllItems();
const students = await context.model('StudentCourseClass').where('courseClass').
equal(courseClass.id).silent().getAllItems();
for (const student of students) {
student.events = [];
student.events.push(event);
if (classInstructorEvaluationEvents && classInstructorEvaluationEvents.length > 0) {
let instructorEvents = [];
if (instructorSections && instructorSections.length && _common.LangUtils.parseInt(courseClass.mustRegisterSection) === 1) {
instructorEvents = classInstructorEvaluationEvents.filter((x) => {
return x.courseClassInstructor && instructorSections.filter((x) => {
return student.section === x.section;
}).map((x) => {
return x.instructor;
}).includes(x.courseClassInstructor.instructor);
});
}
if (instructorEvents && instructorEvents.length) {
instructorEvents.forEach((x) => {
student.events.push(x);
});
} else
{
student.events.push.apply(student.events, classInstructorEvaluationEvents);
}
}
}
return students;
}}, (_applyDecoratedDescriptor(_class2.prototype, "getResults", [_dec2], Object.getOwnPropertyDescriptor(_class2.prototype, "getResults"), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, "generateEvaluationTokens", [_dec3], Object.getOwnPropertyDescriptor(_class2.prototype, "generateEvaluationTokens"), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, "getTokenStatistics", [_dec4], Object.getOwnPropertyDescriptor(_class2.prototype, "getTokenStatistics"), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, "sendEvaluationTokens", [_dec5], Object.getOwnPropertyDescriptor(_class2.prototype, "sendEvaluationTokens"), _class2.prototype)), _class2)) || _class);
module.exports = CourseClassEvaluationEvent;
//# sourceMappingURL=course-class-evaluation-event-model.js.map