UNPKG

@universis/evaluations

Version:

Universis evaluations library

319 lines (194 loc) 15.7 kB
"use strict";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 ClassInstructorEvaluationEvent = (_dec = _data.EdmMapping.entityType('ClassInstructorEvaluationEvent'), _dec2 = _data.EdmMapping.func('Results', _data.EdmType.CollectionOf('Object')), _dec3 = _data.EdmMapping.action('GenerateEvaluationTokens', 'GenerateInstructorTokenAction'), _dec4 = _data.EdmMapping.func('TokenInfo', _data.EdmType.CollectionOf('Object')), _dec5 = _data.EdmMapping.action('SendEvaluationTokens', 'SendInstructorTokenAction'), _dec(_class = (_class2 = class ClassInstructorEvaluationEvent extends _evaluationEventModel.default {/** * @constructor */constructor() {super();}async getResults() {// check access to ClassInstructorEvaluationEvent and get results silently const event = await this.context.model('ClassInstructorEvaluationEvent').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 instructor const event = await context.model('ClassInstructorEvaluationEvent').where('id').equal(this.id).expand({ name: 'courseClassInstructor', options: { '$expand': 'courseClass' } }).getItem();if (event) {//check if a GenerateInstructorTokenAction exists const prevAction = await context.model('GenerateInstructorTokenAction').where('event').equal(this.id).and('actionStatus/alternateName').notEqual('FailedActionStatus').silent().getItem();if (prevAction) {throw new Error('A previous action for generating evaluation tokens has been found.');}const courseClass = event.courseClassInstructor && event.courseClassInstructor.courseClass;const eventInstructor = event.courseClassInstructor && event.courseClassInstructor.instructor; // get class sections const sections = await context.model('CourseClassSection').where('courseClass').equal(courseClass.id).getItems(); // check if course class has sections if (sections && sections.length > 0 && courseClass.mustRegisterSection !== 0) {// get only number of students registered at specific section const instructorSections = await context.model('CourseClassSectionInstructor').where('courseClass').equal(courseClass.id).and('instructor').equal(eventInstructor).select('section/section as sectionId').silent().getItems();if (instructorSections && instructorSections.length === 0) {action.actionStatus.alternateName = 'FailedActionStatus';action.description = 'Instructor is not related with any of sections';return await context.model('GenerateInstructorTokenAction').save(action);} else {const sectionIds = instructorSections.map((section) => {return section.sectionId;}); // get number of students for specific course class sections action.numberOfStudents = await context.model('StudentCourseClass').where('courseClass').equal(courseClass.id).and('section').in(sectionIds).silent().count();}} else {action.numberOfStudents = await context.model('StudentCourseClass').where('courseClass').equal(courseClass.id).silent().count();} // save action and then generateTokens const generateAction = await context.model('GenerateInstructorTokenAction').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('ClassInstructorEvaluationEvent').save(event);if (event.numberOfStudents > 0) {await _evaluationEventModel.default.generateTokens(context, generateOptions);}generateAction.total = action.numberOfStudents;generateAction.actionStatus = { alternateName: 'CompletedActionStatus' };return await context.model('GenerateInstructorTokenAction').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('GenerateInstructorTokenAction').save(generateAction);}}}async getTokenStatistics() {const tokenInfo = { total: 0, used: 0, sent: 0 }; // get number if 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('ClassInstructorEvaluationEvent').where('id').equal(this.getId()).and('date(endDate)').greaterOrEqual(currentDate).expand({ name: 'courseClassInstructor', options: { '$expand': 'courseClass($expand=course), instructor' // expand course and instructor so their attributes are available in the mail template } }, { 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 let students; const courseClass = event.courseClassInstructor && event.courseClassInstructor.courseClass; const eventInstructor = event.courseClassInstructor && event.courseClassInstructor.instructor; // get class sections const sections = await context.model('CourseClassSection').where('courseClass').equal(courseClass.id).getAllItems(); // check if course class has sections if (sections && sections.length > 0 && courseClass.mustRegisterSection !== 0) { // get only number of students registered at specific section const instructorSections = await context.model('CourseClassSectionInstructor').where('courseClass').equal(courseClass.id). and('instructor').equal(eventInstructor).select('section/section as sectionId').silent().getAllItems(); if (instructorSections && instructorSections.length === 0) { throw new _common.DataError('E_INVALID_SECTION', 'Instructor is not related with any of sections'); } const sectionIds = instructorSections.map((section) => { return section.sectionId; }); // get students for specific course class sections students = await context.model('StudentCourseClass'). where('courseClass').equal(courseClass.id). and('section').in(sectionIds). select('email'). silent(). getAllItems(); } else { students = await context.model('StudentCourseClass'). where('courseClass').equal(courseClass.id). select('email', 'student'). silent(). getAllItems(); } 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()). select('access_token'). silent(). getAllItems(); // and map them to an array tokens = tokens.map((token) => token.access_token); // get mail template const mailTemplate = await context.model('MailConfiguration'). where('target').equal('SendInstructorTokenAction'). silent(). getItem(); if (mailTemplate == null) { throw new _common.DataError('E_MISSING_TEMPLATE', 'Cannot execute send tokens action because template is missing for SendInstructorTokenAction.'); } // check if there is already an active sendTokensAction const alreadySendingTokens = await context.model('SendInstructorTokenAction'). 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('SendInstructorTokenAction').silent().save(sendTokenAction); // format mail subject expected params ${course}, ${instructor} const exp = new RegExp('\\$\\{\\w+\\}+', 'ig'); subject = mailTemplate.subject; const params = subject.match(exp); if (params.length > 0) { // parse only custom parameters ${instructor},${course} params.forEach((param) => { if (param === '${instructor}') { // get instructor full name subject = subject.replace(param, eventInstructor ? `${eventInstructor.familyName} ${eventInstructor.givenName}` : ''); } else 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('ClassInstructorEvaluationEvent').where('id').equal(this.getId()). expand({ name: 'courseClassInstructor', options: { '$expand': 'courseClass($expand=course)' } }). getItem(); const courseClass = item && item.courseClassInstructor && item.courseClassInstructor.courseClass; subject = courseClass ? `${courseClass.course.displayCode} ${courseClass.title}` : ''; } _evaluationEventModel.default.sendSse(context.user, context, this.getId(), subject, err); return await context.model('SendInstructorTokenAction').silent().save(sendTokenAction); } }}, (_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 = ClassInstructorEvaluationEvent; //# sourceMappingURL=class-instructor-evaluation-event-model.js.map