UNPKG

@universis/evaluations

Version:

Universis evaluations library

966 lines (337 loc) 34.8 kB
"use strict";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