e-ucm/rage-analytics-backend

View on GitHub
lib/traces.js

Summary

Maintainability
C
1 day
Test Coverage
'use strict';

/**
 * Manages the 'consumers' of the data received.
 *
 * There are two main data consumers.
 *      1) OpenLRS consumer which stores the data in an LRS.
 *      2) Kafka consumer which queues the data to be analyzed by a Storm cluster.
 */

module.exports = (function () {
    var Q = require('q');
    var tracesConverter = require('./tracesConverter');
    // Consumers interested in the incoming traces
    var consumers = [];

    /**
     * Used as a key in the 'extensions' attribute of an object
     * in a statement, to store they ID of a given gameplay.
     */
    var gameplayIdKey = 'gameplayId';

    /**
     * Used as a key in the 'extensions' attribute of an object
     * in a statement, to store they ID of a given game version.
     */
    var versionIdKey = 'versionId';

    /*
    * Used as a key in the 'extensions' attribute of an object
    * in a statement, to store they ID of a given activity.
    */
    var activityIdKey = 'activityId';
    /**
     * Used as a key in the 'extensions' attribute of an object
     * in a statement, to store the attempts of a player playing a game.
     */
    var sessionKey = 'session';

    /**
     * Used as a key in the 'extensions' attribute of an object
     * in a statement, to store the moment the session 1 has started
     */
    var firstSessionStartedKey = 'firstSessionStarted';

    /**
     * Used as a key in the 'extensions' attribute of an object
     * in a statement, to store the moment the current session has started.
     * Note that if the current session equals 1 then the 'firstSessionStartedKey' and
     * 'currentSessionStartedKey' are equal.
     */
    var currentSessionStartedKey = 'currentSessionStarted';

    var entries = {};

    var buildEntries = function (gameplayId, versionId, activityId, session, firstSessionStarted, currentSessionStarted) {
        entries[gameplayIdKey] = gameplayId;
        entries[versionIdKey] = versionId;
        entries[activityIdKey] = activityId;
        entries[sessionKey] = session;
        entries[firstSessionStartedKey] = firstSessionStarted;
        entries[currentSessionStartedKey] = currentSessionStarted;
        return entries;
    };

    /**
     * Adds to a given statement different attributes defined by the entries.
     * For instance, an attribute could be pointing to the gameplay id that statement belongs to.
     * The attribute will be added as an extension inside the object definition attribute.
     * If there isn't one, this method will create a new one, resulting in following statement:
     *
     * {
     *      ...
     *      'object': {
     *          ...
     *          'definition': {
     *              ...
     *              'extensions': {
     *                  'gameplayId': <gameplayId>,
     *                  'versionId': <versionId>
     *              }
     *          }
     *      }
     * }
     *
     *
     * @param statement
     * @param entries an object with the entries (<key, value>) you want to add.
     */
    var addEntriesToStatement = function (statement, entries) {
        if (!statement.object) {
            statement.object = {
                definition: {
                    extensions: {}
                }
            };
        } else if (!statement.object.definition) {
            statement.object.definition = {
                extensions: {}
            };
        } else if (!statement.object.definition.extensions) {
            statement.object.definition.extensions = {};
        }
        for (var property in entries) {
            if (entries.hasOwnProperty(property)) {
                statement.object.definition.extensions[property] = entries[property];
            }
        }
    };

    var wrongFormatError = function (statement, err) {
        var message = 'Statement ' + statement + ' doesn\'t have a valid format. ';
        if (err) {
            message += err;
        }
        throw {
            status: 400,
            message: message
        };
    };

    /**
     * Adds a reference with the gameplayId and also versionId to the data.
     * The gameplay and version id reference is added as an extension, more info. at 'addGameplayIdToStatement' function.
     *
     * @param playerId
     * @param versionId
     * @param gameplayId
     * @param data - An array of statements.
     * @param start
     * @returns {*}
     */
    var processTraces = function (playerId, versionId, gameplayId, activityId, session, firstSessionStarted, currentSessionStarted, data, start) {
        if (!data || !data.length) {
            return Q.fcall(function () {
                throw {
                    status: 400,
                    message: 'Statements must be an array!'
                };
            });
        }

        var convertedData = [];
        var i = start;
        while (i < data.length) {
            var statement = data[i];
            addEntriesToStatement(statement, buildEntries(gameplayId, versionId, activityId, session,
            firstSessionStarted, currentSessionStarted));

            var result = tracesConverter(statement);
            if (result.error || !result.trace) {
                return Q.fcall(wrongFormatError(statement, result.error));
            }

            convertedData.push(result.trace);
            i++;
        }

        return Q.fcall(function () {
            return convertedData;
        });
    };

    return {
        /**
         * Registers a new consumer for the received data.
         *
         * @param consumer - A consumer is an object with an 'addTraces' method as described below:
         *
         *
         *      @param playerId - String, the ID of the player created in the authentication process. More info. at
         *                        'collector.js'
         *      @param versionId - String, the ID of the version of the game this data belongs to.
         *      @param gameplayId - String, the ID of the gameplay created for the anonymous player. When starting to
         *                          collect data a new gameplay is created if there isn't an existing one already for
         *                          the anonymous player.
         *      @param data - Array[Statements], where a Statement must be xAPI v1.0.1 compliant
         *                    (https://github.com/adlnet/xAPI-Spec/blob/master/xAPI.md#statement).
         *
         *      function addTraces(playerId, versionId, gameplayId, data) {
         *          ...
         *      }
         */
        addConsumer: function (consumer) {
            consumers.push(consumer);
        },

        clearConsumers: function () {
            consumers = [];
        },

        /**
         * Processes the data (see 'processTraces' function) and passes the processed data to the available consumers.
         * @param authToken
         * @param data
         * @param activity
         * @returns { message: 'Success.'} or an array of errors from the consumers that failed while consuming the data.
         */
        add: function (authToken, data, activity) {

            return processTraces(authToken.playerId, authToken.versionId, authToken.gameplayId, authToken.activityId,
                authToken.session, authToken.firstSessionStarted, authToken.currentSessionStarted, data, 0).then(function (convertedData) {
                var promises = [];
                for (var i = 0; i < consumers.length; i++) {
                    promises.push(consumers[i].addTraces(authToken.playerId, authToken.versionId, authToken.gameplayId, activity, data, convertedData));
                }
                return Q.all(promises)
                    .then(function () {
                        return {
                            message: 'Success.'
                        };
                    }).fail(function (err) {
                        return Q.fcall(function () {
                            if (!err.status) {
                                err.status = 400;
                            }
                            throw err;
                        });
                    });
            });
        }
    };
})();