@universis/evaluations
Version:
Universis evaluations library
966 lines (337 loc) • 34.8 kB
JavaScript
;var _common = require("@themost/common");
var _data = require("@themost/data");
var _web = require("@themost/web");
var _formInspector = require("@themost/form-inspector");
var _evaluationDocumentModel = _interopRequireDefault(require("./evaluation-document-model"));
var _semver = require("semver");
var _util = _interopRequireDefault(require("util"));
var _fs = _interopRequireDefault(require("fs"));
var _moment = _interopRequireDefault(require("moment"));
var _mailer = require("@themost/mailer");
var _EvaluationService = require("../EvaluationService");var _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _dec7, _dec8, _dec9, _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;}const EvaluationAccessToken = require('./evaluation-access-token-model');
const math = require('mathjs');
/**
* @class
*/let
EvaluationEvent = (_dec = _data.EdmMapping.entityType('EvaluationEvent'), _dec2 =
_data.EdmMapping.func('Current', 'EvaluationEvent'), _dec3 =
_data.EdmMapping.func('form', 'Object'), _dec4 =
_data.EdmMapping.func('Results', _data.EdmType.CollectionOf('Object')), _dec5 =
_data.EdmMapping.func('Tokens', _data.EdmType.CollectionOf('EvaluationAccessToken')), _dec6 =
_data.EdmMapping.func('FormAttributes', _data.EdmType.CollectionOf('Object')), _dec7 =
_data.EdmMapping.func('FormAttributesSummary', _data.EdmType.CollectionOf('Object')), _dec8 =
_data.EdmMapping.param('data', 'Object', false, true), _dec9 =
_data.EdmMapping.action('Evaluate', 'Object'), _dec(_class = (_class2 = class EvaluationEvent extends _data.DataObject {/**
* @constructor
*/constructor() {super();} /**
* Get current evaluation based on the evaluation key contained in Evaluation-Key HTTP header
* @param {HttpContext} context
*/static async getCurrent(context) {// get evaluation key
const token = await EvaluationAccessToken.inspect(context, context.user && context.user.authenticationToken);if (token.active === false) {throw new _common.HttpTokenExpiredError(context.__('Evaluation access token has been expired or is invalid.'));} // get evaluation event
// get current date
let evaluationEvent = await context.model('EvaluationEvent').where('id').equal(token.sub).expand('eventStatus', 'subEvents').silent().getTypedItem();if (evaluationEvent == null) {throw new _common.HttpNotFoundError('The specified evaluation cannot be found');}if (evaluationEvent.eventStatus) {if (evaluationEvent.eventStatus.alternateName !== 'EventOpened') {throw new _common.DataError('E_CLOSED_EVENT', context.__('The course evaluation has been closed and is no longer available'));}} // check dates
const currentDate = new Date().setHours(0, 0, 0, 0);const startDate = evaluationEvent.startDate.setHours(0, 0, 0, 0);const endDate = evaluationEvent.endDate.setHours(0, 0, 0, 0);if (currentDate < startDate) {throw new _common.DataError('E_NOT_STARTED', context.__('Evaluation period for this course has not been started yet'));}if (currentDate > endDate) {throw new _common.DataError('E_EXPIRED', context.__('Evaluation period for this course has been expired'));}let courseClass;let courseClassTitle;switch (evaluationEvent.additionalType) {case 'ClassInstructorEvaluationEvent':const event = await context.model('ClassInstructorEvaluationEvent').where('id').equal(token.sub).expand({ name: 'courseClassInstructor', options: { $expand: 'courseClass($expand=course,year,period,locale),instructor' } }).silent().getTypedItem();courseClass = event.courseClassInstructor.courseClass;const instructor = event.courseClassInstructor.instructor;courseClassTitle = courseClass.locale && courseClass.locale.title || courseClass.title;evaluationEvent.courseClass = `${courseClass.course.displayCode}-${courseClassTitle} (${courseClass.year.alternateName}-${courseClass.period.name}) `;evaluationEvent.instructor = `${instructor.familyName} ${instructor.givenName}`;break;case 'CourseClassEvaluationEvent':const courseEvent = await context.model('CourseClassEvaluationEvent').where('id').equal(token.sub).expand({ name: 'courseClass', options: { $expand: 'course,year,period,locale,instructors($expand=instructor)' } }).silent().getTypedItem();courseClass = courseEvent.courseClass;courseClassTitle = courseClass.locale && courseClass.locale.title || courseClass.title;evaluationEvent.courseClass = `${courseClass.course.displayCode}-${courseClassTitle} (${courseClass.year.alternateName}-${courseClass.period.name}) `; // check if child evaluation tokens
const instructorTokens = await context.model('EvaluationAccessToken').where('parentToken').equal(token.access_token).silent().getAllItems();if (instructorTokens && instructorTokens.length > 0) {evaluationEvent.subEvents = [];for (let i = 0; i < instructorTokens.length; i++) {const instructorToken = await EvaluationAccessToken.inspect(context, instructorTokens[i].access_token);if (instructorToken.active) {// load evaluation event for getting instructor name
const subEvent = await context.model('ClassInstructorEvaluationEvent').where('id').equal(instructorTokens[i].evaluationEvent).and('superEvent').equal(evaluationEvent.id).and('eventStatus/alternateName').equal('EventOpened').select('id', 'courseClassInstructor/instructor/familyName as familyName', 'courseClassInstructor/instructor/givenName as givenName').silent().getItem();if (subEvent) {subEvent.instructor = `${subEvent.familyName} ${subEvent.givenName}`;subEvent.access_token = instructorTokens[i].access_token;evaluationEvent.subEvents.push(subEvent);}}}}break;case 'default':evaluationEvent = await evaluationEvent.silent().getAdditionalObject();break;}return evaluationEvent;}async getForm() {const form = await this.property('evaluationDocument').getItem();const service = this.context.getApplication().getService(function PrivateContentService() {}); /**
* @type {string}
*/const formPath = await new Promise((resolve, reject) => {service.resolvePhysicalPath(this.context, form, (err, physicalPath) => {if (err) {return reject(err);}return resolve(physicalPath);});});const readFileAsync = _util.default.promisify(_fs.default.readFile);let content = await readFileAsync(formPath);if (content == null) {throw new _common.HttpNotFoundError('Evaluation form cannot be found');}let buffer = Buffer.from(content, 'base64');let result;result = JSON.parse(buffer.toString('utf8'));try {result.properties = result.properties || {};Object.assign(result.properties, { name: form.resultType, _id: form.id, _event: this.id }); // post-processing
let shouldUpgrade = true;let model = this.context.model(form.resultType);if (model != null) {// try to convert model and form versions to a valid semver format
const modelVersion = (0, _semver.valid)((0, _semver.coerce)(model.version || '1.0.0'));const formVersion = (0, _semver.valid)((0, _semver.coerce)(form.version || '1.0.0')); // and upgrade evaluation document only if its form has been upgraded
shouldUpgrade = (0, _semver.lt)(modelVersion, formVersion);}if (shouldUpgrade) {// load for inspector
const inspector = new _formInspector.JsonFormInspector();model = inspector.inspect(result); // post processing steps
Object.assign(model, { source: form.resultType, // add source from evaluation document e.g. Evaluation1
view: form.resultType, // add view from evaluation document e.g. Evaluation1
version: model.version, // set version (follow updates)
hidden: true }); // add evaluation event and evaluation document associations
// for further processing of evaluation results
model.fields.push.apply(model.fields, [{ name: 'evaluationEvent', type: this.getModel().name, editable: false, nullable: false, indexed: true }]); // reset privileges
/**
* @type {DataConfigurationStrategy|*}
*/const dataConfiguration = this.context.getApplication().getConfiguration().getStrategy(_data.DataConfigurationStrategy);model.privileges.splice(0); // get resultPrivileges
const thisModel = dataConfiguration.model(this.getModel().name);model.privileges.push.apply(model.privileges, [{ "mask": 15, "type": "global" }, { "mask": 1, "type": "global", "account": "Administrators" }]); // add extra privileges
if (Array.isArray(thisModel.resultPrivileges)) {model.privileges.push.apply(model.privileges, thisModel.resultPrivileges);}dataConfiguration.setModelDefinition(model); // add entity set
const builder = this.context.getApplication().getService(_data.ODataModelBuilder);builder.addEntitySet(model.name, model.name);if (model.hidden) {builder.removeEntitySet(model.name);} // try to convert model and form versions to a valid semver format
const modelVersion = (0, _semver.valid)((0, _semver.coerce)(model.version || '1.0.0'));const formVersion = (0, _semver.valid)((0, _semver.coerce)(form.version || '1.0.0')); // and if they are different
if ((0, _semver.neq)(modelVersion, formVersion)) {// update evaluation document version
await this.context.model('EvaluationDocument').silent().save({ id: form.id, // note: save semver version to satisfy the pattern of the field
// see "pattern": "^\\d+.\\d+.\\d+$"
version: modelVersion });}} // check if event has sub events and append form to result
if (this.subEvents && this.subEvents.length) {// get first subEvent for
const subEvent = this.context.model('EvaluationEvent').convert(this.subEvents[0]);const subResult = await subEvent.getForm(); // add select boxes for each instructor
if (this.subEvents.length > 1) {const label = this.context.__('Select at least one instructor to evaluate');const selectInstructorsComponent = { "label": label, "optionsLabelPosition": "right", "tableView": false, "key": "instructors", "type": "selectboxes", "input": true, "inputType": "checkbox", "validate": { "required": true } };this.subEvents.sort((a, b) => a.instructor < b.instructor ? -1 : 1);selectInstructorsComponent.values = this.subEvents.map((x) => {x.label = x.instructor;x.value = x.id;return x;});result.components.push(selectInstructorsComponent);const headerComponent = subResult.components[0];headerComponent.customConditional = "show =(data && data.instructors[row.id])";headerComponent.refreshOn = "instructors";} // create panel
const repeaterComponent = { "label": " ", "disableAddingRemovingRows": true, "reorder": false, "customClass": "border-0", "addAnotherPosition": "bottom", "layoutFixed": false, "enableRowGroups": false, "initEmpty": false, "hideLabel": true, "tableView": false, "key": "subEvents", "type": "datagrid", "input": true, "components": [] };repeaterComponent.components.push.apply(repeaterComponent.components, subResult.components);result.components.push(repeaterComponent);}return result;} catch (err) {_common.TraceUtils.error(`Error while decoding evaluation form ${form.id} content.`);_common.TraceUtils.error(err);throw new _common.HttpServerError('An error occurred while decoding evaluation form.');}}async getResults() {const form = await this.getForm();return this.context.model(form.properties.name).where('evaluationEvent').equal(this.id).prepare();}async getAccessTokens() {return this.context.model('EvaluationAccessToken').where('evaluationEvent').equal(this.id).prepare();}async getFormAttributes() {/**
* @type {DataContext|*}
*/const context = this.context;const results = await this.getFormResults(context, this.getId(), null);return results.responses;}async getFormAttributesSummary() {/**
* @type {DataContext|*}
*/const context = this.context;const eventModel = this.getModel().name;const results = await this.getFormResults(context, this.getId(), null);let q = this.context.model(eventModel).where('id').equal(this.getId()).expand({ 'name': 'tokens', 'options': { '$select': 'evaluationEvent,count(evaluationEvent) as total,used', '$groupby': 'evaluationEvent,used' } }, 'organizer');if (eventModel === 'ClassInstructorEvaluationEvent') {q = this.context.model(eventModel).where('id').equal(this.getId()).expand({ 'name': 'tokens', 'options': { '$select': 'evaluationEvent,count(evaluationEvent) as total,used', '$groupby': 'evaluationEvent,used' } }, { 'name': 'courseClassInstructor', 'options': { '$expand': 'courseClass($expand=year, period, course($expand=department($expand=locale), locale), locale), instructor' } });} // check if target event model has a courseClass field
const courseClassField = (this.context.model(eventModel).fields || []).find((field) => field.type === 'CourseClass');if (courseClassField) {// and expand it
q.expand({ name: courseClassField.name, options: { $expand: 'year, period, course($expand=department($expand=locale), locale), locale' } });}results.event = await q.getItem();if (results.event == null) {throw new _common.DataNotFoundError('Evaluation event not found.');} // count used tokens of event
if (results.event.tokens) {const usedTokens = results.event.tokens && results.event.tokens.find((x) => {return x.used === true;});results.event.tokensUsed = usedTokens ? usedTokens.total : 0;} // get avg of questions avg
const avg = this.getAvgValues(context, results);results.event.responsesAvg = avg.responsesAvg;results.event.questionsAvg = avg.questionsAvg;return results;}async getFormResults(context, id, events) {const result = [];const evaluationEvents = [];if (id) {evaluationEvents.push(id);} else {if (Array.isArray(events) && events.length) {evaluationEvents.push.apply(evaluationEvents, events.map((x) => {return x.id || x;}));}}if (evaluationEvents.length === 0) {throw new _common.DataError('Evaluation events not supplied.');}const form = await this.getForm();this.setParentLegend(form); // get model attributes
/**
* @type {DataConfigurationStrategy|*}
*/const dataConfiguration = context.getApplication().getConfiguration().getStrategy(_data.DataConfigurationStrategy);const dataTypes = dataConfiguration.dataTypes;const q = context.model(form.properties.name).asQueryable();const selectArguments = [];const model = dataConfiguration.model(form.properties.name);const attributes = model.fields;let totalResponses = 0;const summaryValues = [];const currentLocale = context.locale;const settings = form.settings && form.settings.i18n;const translations = settings && settings[currentLocale] || {};attributes.forEach((attribute) => {const key = this.findByKey(form.components, attribute.name);if (key) {attribute = Object.assign(attribute, { name: attribute.name, label: key.label });attribute.isNumber = false;attribute.legend = key.customProperties && key.customProperties.legend;if (Object.prototype.hasOwnProperty.call(dataTypes, attribute.type)) {attribute.isNumber = dataTypes[attribute.type].type === 'number';}selectArguments.push(`${attribute.name}`); // clear std,avg and responses from model attribute
attribute.std = 0;attribute.avg = 0;delete attribute.responses; // prepare a values mapper
const valuesMapper = (keyValue) => {return { value: keyValue.value, // use localized label, if it exists
label: translations[keyValue.label] || keyValue.label };};if (key.type === 'radio') {if (attribute.isNumber) {if (summaryValues.length === 0) {// add radio values from first key
summaryValues.push.apply(summaryValues, key.values);} else {//add values only if not exist
summaryValues.push.apply(summaryValues, key.values.filter((x) => {return summaryValues.findIndex((y) => {return y.value === x.value;}) < 0;}));}} else {// prepare the attribute's values for post-processing (e.g. count)
attribute.values = (key.values || []).map(valuesMapper);}}if (key.type === 'selectboxes' && !attribute.isNumber) {// prepare the attribute's values for post-processing (e.g. count)
attribute.values = (key.values || []).map(valuesMapper);} // clone attribute
let attr = { ...attribute };attr.legend = translations[attr.legend] || attr.legend;attr.title = translations[attr.title] || attr.title;attr.label = translations[attr.label] || attr.label;result.push(attr);}}); // add total = 0 to summaryValues
summaryValues.map((x) => {x.total = 0;return x;});const showResults = await this.showResults(context); // check if zero values should be excluded
const ignoreZeroValues = context.getConfiguration().getSourceAt('settings/evaluation/ignoreZeroValues') || false;if (selectArguments.length && showResults === true) {const responses = await q.select.apply(q, selectArguments).where('evaluationEvent').in(evaluationEvents).silent().getAllItems();for (let i = 0; i < result.length; i++) {const qElement = result[i];qElement.responses = [];if (qElement.isNumber) {// remove zero values from responses avg and std
const sample = responses.filter((x) => {if (ignoreZeroValues) {return x[qElement.name] != 0;}return true;}); // add mean value and standard deviation
qElement.std = sample.length ? math.std(sample.map((x) => {return x[qElement.name];})) : 0;qElement.avg = sample.length ? math.mean(sample.map((x) => {return x[qElement.name];})) : 0;qElement.numberOfResponses = sample.length;summaryValues.forEach((radioValue) => {const result = responses.filter((y) => {return y[qElement.name] == radioValue.value;}).length;radioValue.total += result;totalResponses += result;});qElement.responses = responses.map((x) => {if (x[qElement.name] > 0 && ignoreZeroValues || !ignoreZeroValues) {return x[qElement.name];}});} else {qElement.responses = responses.map((x) => {const value = x[qElement.name]; // if the field type is Json
if (qElement.type === 'Json') {// enumerate value keys and consider only truthy values
return Object.keys(value).filter((item) => {return !!value[item];});} // else, just return the value
return value;}); // check if the element contains values (post-processing)
if (Array.isArray(qElement.values) && qElement.values.length > 0) {// enumerate them
qElement.values = qElement.values.map((keyValue) => {// and append the count
keyValue.count = qElement.responses.filter((qResponse) => {// if the field's type is Json
if (qElement.type === 'Json') {// count responses which include the item
return (qResponse || []).includes(keyValue.value);} // else, just count flat items
return keyValue.value === qResponse;}).length;return keyValue;});}}}}const results = {};results.responses = result;results.summaryValues = [];results.summaryValues.push.apply(results.summaryValues, summaryValues.map((x) => {x.totalResponses = totalResponses;return x;}));return results;}async showResults(context) {// this is a custom way to get permissions based on eventStatus and user groups
let showResults = true;let found = false;const eventStatusesResults = context.getConfiguration().getSourceAt('settings/evaluation/results/statuses') || [];const groupsAllowed = context.getConfiguration().getSourceAt('settings/evaluation/results/groupsAllowed') || [];if (eventStatusesResults.length > 0) {// check if eventStatus is allowed
if (groupsAllowed.length > 0) {// but first check if user belongs to allowed groups
const userName = context.interactiveUser ? context.interactiveUser.name : context.user.name;const user = await context.model('User').where('name').equal(userName).expand('groups').getItem();found = user.groups.some((r) => groupsAllowed.indexOf(r.name) >= 0);}if (!found) {const evaluationEvent = await context.model('EvaluationEvent').where('id').equal(this.getId()).select('id', 'eventStatus/alternateName as eventStatus').getItem();if (evaluationEvent == null) {throw new _common.HttpNotFoundError('The specified evaluation cannot be found');}showResults = eventStatusesResults.findIndex((x) => {return x === evaluationEvent.eventStatus;}) >= 0;}}return showResults;}findByKey(array, key) {for (let index = 0; index < array.length; index++) {const element = array[index];if (element.key === key) {return element;} else {if (element.components) {const found = this.findByKey(element.components, key);if (found) {return found;}}}}}setParentLegend(component) {if (Array.isArray(component.components)) {component.components.forEach((item) => {if (component.legend) {item.customProperties = item.customProperties || {};item.customProperties.legend = component.legend;}this.setParentLegend(item);});}}async evaluate(data) {const token = await EvaluationAccessToken.inspect(this.context, this.context.user.authenticationToken);if (token.active === false) {throw new _common.HttpTokenExpiredError('Evaluation access token has been expired or is invalid.');}if (this.id !== token.sub) {throw new _common.HttpBadRequestError('Evaluation access token is invalid');} // get evaluation form
const form = await this.getForm(); // get evaluation document
if (form == null) {throw new _common.HttpNotFoundError('Evaluation form cannot be found');} // get evaluation document
const evaluationDocument = await this.context.model(_evaluationDocumentModel.default).where('id').equal(form.properties._id).silent().getItem();if (evaluationDocument == null) {throw new _common.HttpNotFoundError('Evaluation document cannot be found');} // assign defaults
Object.assign(data, { evaluationEvent: this.id, evaluationDocument: evaluationDocument.id }); // and finally append evaluation
await this.context.model(evaluationDocument.resultType).on('after.save', (event, callback) => {const context = event.model.context;context.model(EvaluationAccessToken).where('access_token').equal(context.user && context.user.authenticationToken).silent().getItem().then(async (token) => {if (token == null) {throw new _common.HttpTokenExpiredError('Evaluation token is required');} // check if subEvents have been evaluated
const subEvents = event.target.subEvents;if (subEvents && subEvents.length) {// evaluate each sub event
for (let i = 0; i < subEvents.length; i++) {context.user.authenticationToken = subEvents[i].access_token;const subEvent = context.model('EvaluationEvent').convert(subEvents[i].id);delete subEvents[i].id;if (subEvents.length > 1 && event.target.instructors) {// check if instructor has been evaluated
if (event.target.instructors[subEvent.id] === true) {await subEvent.evaluate(subEvents[i]);}} else {await subEvent.evaluate(subEvents[i]);}}}return context.model(EvaluationAccessToken).silent().save({ access_token: token.access_token, expires: new Date(0), // set epoch date
used: true });}).then(() => {return callback();}).catch((err) => {_common.TraceUtils.error('An error occurred while submitting evaluation');return callback(err);});}).silent().save(data);return data;}static async generateTokens(context, generateOptions) {let items = [];if (Array.isArray(generateOptions)) {let n = 0;let newItems; // eslint-disable-next-line no-unused-vars
for (let itemGenerateOptions of generateOptions) {newItems = await EvaluationEvent.generateManyTokens(context, itemGenerateOptions);n += newItems.length;items.push.apply(items, newItems);}await context.model('EvaluationAccessToken').silent().save(items);return n;
} else {
items = await EvaluationEvent.generateManyTokens(context, generateOptions);
await context.model('EvaluationAccessToken').silent().save(items);
return items.length;
}
}
/**
* Generates a number of access tokens for the given event
* @param {DataContext} context
* @param {{evaluationEvent: number, n: number, expires?: DateTime}} generateOptions
*/
static async generateManyTokens(context, generateOptions) {
const n = generateOptions.n;
_common.Args.check(typeof n === 'number', 'Expected a number greater than zero');
_common.Args.check(n > 0, 'The number of access tokens must be greater than zero');
let maximumGenerateTokens = _common.LangUtils.parseInt(context.getConfiguration().getSourceAt('settings/evaluation/maximumGenerateTokens'));
if (maximumGenerateTokens === 0) {
maximumGenerateTokens = 1000;
}
_common.Args.check(n < maximumGenerateTokens, `The specified number of tokens exceeded the maximum allowed (${maximumGenerateTokens})`);
const items = [];
// generate tokens
for (let index = 0; index < n; index++) {
items.push({
evaluationEvent: generateOptions.evaluationEvent,
expires: generateOptions.expires });
}
return items;
}
/**
*
* @param {import('@themost/express').ExpressDataContext} appContext
* @param students
* @param tokens
* @param tokenInfo
* @param mailTemplate
* @param subject
* @param action
* @param evaluationEvent
*/
static sendMessages(appContext, students, tokens, tokenInfo, mailTemplate, subject, action, evaluationEvent) {
const app = appContext.getApplication();
const context = app.createContext();
const user = appContext.user;
context.user = user;
let failed = [];
let succeeded = [];
let message = subject;
const transporter = appContext.getApplication().getService(_EvaluationService.EvaluationService).getMailTransporter();
// use an async function to send emails
(async () => {
for (const student of students) {
// refresh mailer
const mailer = (0, _mailer.getMailer)(context); // maybe investigate why this has to be refreshed.
// pop an access token
let access_token;
const studentMetadata = context.getConfiguration().getSourceAt('settings/evaluation/useStudentInfoAtMetadata') || false;
// student metadata contains student identifiers only if setting useStudentInfoAtMetadata is true
// otherwise only semester and registrationType are stored
const metadata = {
id: studentMetadata ? student.id : null,
student: studentMetadata ? student.student : null,
semester: student.semester,
registrationType: student.registrationType };
if (studentMetadata) {
// try to find access token for specific student
access_token = tokens.find((x) => {
return x.metadata && x.metadata.student === student.id;
});
}
if (!access_token) {
access_token = tokens.pop();
// update metadata
} else
{
//
_common.TraceUtils.log('test');
}
access_token.metadata = metadata;
// send message
await new Promise((resolve) => {
mailer.transporter(transporter).
template(mailTemplate.template).
subject(subject).
to(student.email).
send(Object.assign(evaluationEvent, {
access_token: access_token.access_token }),
(err) => {
if (err) {
try {
_common.TraceUtils.error(`SendEvaluationTokensAction', 'An error occurred while trying to send token to student email ${student.email} for evaluation event ${evaluationEvent.id}`);
_common.TraceUtils.error(err);
} catch (err1) {
// do nothing
}
failed.push(access_token);
return resolve();
}
succeeded.push(access_token);
// update successfully sent token
return context.model('EvaluationAccessToken').silent().save({
access_token: access_token.access_token, // access_token is primary
sent: true,
metadata: access_token.metadata,
$state: 2 }).
then((χ) => {
return resolve();
}).catch((err) => {
_common.TraceUtils.error(err);
return resolve();
});
});
});
}
})().then(() => {
context.finalize(() => {
// create send tokens action
const sendTokensAction = {
id: action && action.id || null,
event: evaluationEvent,
numberOfStudents: students.length,
total: tokenInfo.total,
sent: succeeded.length,
failed: failed.length,
actionStatus: {
alternateName: tokenInfo.total === failed.length ? 'FailedActionStatus' : 'CompletedActionStatus' },
description: tokenInfo.total === failed.length ? context.__('Sending tokens failed for all students') : null };
// and save
return context.model(action.additionalType).silent().save(sendTokensAction).then((item) => {
EvaluationEvent.sendSse(user, context, evaluationEvent.id, message, sendTokensAction.description ? { message: sendTokensAction.description } : null);
}).catch((err) => {
_common.TraceUtils.error(err);
});
});
}).catch((err) => {
context.finalize(() => {
// log error
_common.TraceUtils.error(err);
// update successfully sent tokens
const succeededTokens = succeeded.map((succeededToken) => {
return {
access_token: succeededToken,
sent: true,
$state: 2 };
});
return context.model('EvaluationAccessToken').silent().save(succeededTokens).then((evaluationTokens) => {
// create send tokens action
const sendTokensAction = {
id: action && action.id || null,
event: evaluationEvent,
numberOfStudents: students.length,
total: tokenInfo.total,
sent: succeeded.length,
failed: failed.length,
actionStatus: {
alternateName: tokenInfo.total === failed.length ? 'FailedActionStatus' : 'CompletedActionStatus' },
description: tokenInfo.total === failed.length ? context.__('Sending tokens failed for all students') : null };
// and save
return context.model(action.additionalType).silent().save(sendTokensAction).then((tokenAction) => {
EvaluationEvent.sendSse(user, context, evaluationEvent.id, message, err);
}).catch((err) => {
_common.TraceUtils.error(err);
});
});
});
});
}
static sendSse(user, context, evaluationEvent, title, error) {
// try to find ServerSentEventService
const sse = context.getApplication().getService(function ServerSentEventService() {});
// only if it exists - it is not required
if (sse != null) {
if (user != null) {
try {
// create an event
const event = {
entitySet: 'EvaluationEvents',
entityType: "EvaluationEvent",
target: {
id: evaluationEvent,
message: error ? `${title} ${error.message}` : title },
status: {
success: !error },
dateCreated: new Date() };
// and send it to the user that is performing the approval (registrar scope)
sse.emit(user.name, user.authenticationScope || "qa", event);
}
catch (err) {
_common.TraceUtils.error(`An error occurred while trying to send sse event`);
_common.TraceUtils.error(err);
}
}
}
}
getAvgValues(context, results)
{
const avg = {
responsesAvg: 0,
questionsAvg: 0 };
// get avg of questions avg
const ignoreZeroValues = context.getConfiguration().getSourceAt('settings/evaluation/ignoreZeroValues') || false;
// filter only numbers
const filteredResults = results.responses.filter((x) => {
return !!x.isNumber;
});
// filter avg values
const filteredByValueResults = filteredResults.
filter((x) => {
return ignoreZeroValues && x.avg > 0 || !ignoreZeroValues;
});
avg.responsesAvg = filteredByValueResults.length ? math.mean(filteredByValueResults.
map((x) => {
return x.avg;
})) : 0;
// get all answers
const questionResults = filteredResults.length ? filteredResults.reduce((prev, curr) => {
if (curr.responses) {
prev.push(...curr.responses.filter((y) => {
return y !== undefined;
}).map((y) => {
return y;
}));
}
return prev;
}, []) : [];
avg.questionsAvg = questionResults.length ? math.mean(questionResults) : 0;
return avg;
}}, (_applyDecoratedDescriptor(_class2, "getCurrent", [_dec2], Object.getOwnPropertyDescriptor(_class2, "getCurrent"), _class2), _applyDecoratedDescriptor(_class2.prototype, "getForm", [_dec3], Object.getOwnPropertyDescriptor(_class2.prototype, "getForm"), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, "getResults", [_dec4], Object.getOwnPropertyDescriptor(_class2.prototype, "getResults"), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, "getAccessTokens", [_dec5], Object.getOwnPropertyDescriptor(_class2.prototype, "getAccessTokens"), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, "getFormAttributes", [_dec6], Object.getOwnPropertyDescriptor(_class2.prototype, "getFormAttributes"), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, "getFormAttributesSummary", [_dec7], Object.getOwnPropertyDescriptor(_class2.prototype, "getFormAttributesSummary"), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, "evaluate", [_dec8, _dec9], Object.getOwnPropertyDescriptor(_class2.prototype, "evaluate"), _class2.prototype)), _class2)) || _class);
module.exports = EvaluationEvent;
//# sourceMappingURL=evaluation-event-model.js.map