src/routes/stats.js
const { inject } = require('@forestadmin/context');
const _ = require('lodash');
const auth = require('../services/auth');
const logger = require('../services/logger');
const error = require('../utils/error');
const path = require('../services/path');
const StatSerializer = require('../serializers/stat');
const PermissionMiddlewareCreator = require('../middlewares/permissions');
const Schemas = require('../generators/schemas');
const CHART_TYPE_VALUE = 'Value';
const CHART_TYPE_PIE = 'Pie';
const CHART_TYPE_LINE = 'Line';
const CHART_TYPE_LEADERBOARD = 'Leaderboard';
const CHART_TYPE_OBJECTIVE = 'Objective';
module.exports = function Stats(app, model, Implementation, opts) {
const { chartHandler, modelsManager } = inject();
const modelName = Implementation.getModelName(model);
const permissionMiddlewareCreator = new PermissionMiddlewareCreator(modelName);
this.get = async (request, response, next) => {
let promise = null;
function getAssociationModel(schema, associationName) {
const field = _.find(schema.fields, { field: associationName });
let relatedModelName;
if (field && field.reference) {
[relatedModelName] = field.reference.split('.');
}
const models = modelsManager.getModels();
return _.find(
models,
(currentModel) => Implementation.getModelName(currentModel) === relatedModelName,
);
}
const chart = await chartHandler.getChartWithContextInjected({
userId: request.user.id,
renderingId: request.user.renderingId,
chartRequest: request.body,
});
const params = { timezone: request.query.timezone, ...request.body, ...chart };
const { type } = request.body;
if (type === CHART_TYPE_LEADERBOARD) {
const schema = Schemas.schemas[model.name];
const modelRelationship = getAssociationModel(schema, request.body.relationshipFieldName);
promise = new Implementation
.LeaderboardStatGetter(model, modelRelationship, params, request.user).perform();
} else {
// Objective chart uses a value stat getter to retrieve the value.
const statGetterType = type === CHART_TYPE_OBJECTIVE ? CHART_TYPE_VALUE : type;
promise = new Implementation[`${statGetterType}StatGetter`](model, params, opts, request.user).perform();
}
if (!promise) {
return response.status(400).send({ error: 'Chart type not found.' });
}
return promise
.then((stat) => {
if (type === CHART_TYPE_OBJECTIVE) {
stat.value.value = stat.value.countCurrent;
delete stat.value.countCurrent;
delete stat.value.countPrevious;
}
return new StatSerializer(stat).perform();
})
.then((stat) => { response.send(stat); })
.catch(next);
};
function getErrorQueryColumnsName(result, keyNames) {
const message = `The result columns must be named ${keyNames} instead of '${
Object.keys(result).join('\', \'')}'.`;
logger.error(`Live Query error: ${message}`);
return new error.UnprocessableEntity(message);
}
this.getWithLiveQuery = async (request, response, next) => {
try {
const { query, contextVariables } = await chartHandler.getQueryForChart({
userId: request.user.id,
renderingId: request.user.renderingId,
chartRequest: request.body,
});
let result = await new Implementation.QueryStatGetter({
...request.body,
query,
contextVariables: {
...(request.body.contextVariables || {}),
...contextVariables,
},
}, opts).perform();
switch (request.body.type) {
case CHART_TYPE_VALUE:
if (result.length) {
const resultLine = result[0];
if (resultLine.value === undefined) {
throw getErrorQueryColumnsName(resultLine, '\'value\'');
} else {
result = {
countCurrent: resultLine.value,
countPrevious: resultLine.previous,
};
}
}
break;
case CHART_TYPE_PIE:
case CHART_TYPE_LEADERBOARD:
if (result.length) {
result.forEach((resultLine) => {
if (resultLine.value === undefined || resultLine.key === undefined) {
throw getErrorQueryColumnsName(resultLine, '\'key\', \'value\'');
}
});
}
break;
case CHART_TYPE_LINE:
if (result.length) {
result.forEach((resultLine) => {
if (resultLine.value === undefined || resultLine.key === undefined) {
throw getErrorQueryColumnsName(resultLine, '\'key\', \'value\'');
}
});
}
result = result.map((resultLine) => ({
label: resultLine.key,
values: {
value: resultLine.value,
},
}));
break;
case CHART_TYPE_OBJECTIVE:
if (result.length) {
const resultLine = result[0];
if (resultLine.value === undefined || resultLine.objective === undefined) {
throw getErrorQueryColumnsName(resultLine, '\'value\', \'objective\'');
} else {
result = {
objective: resultLine.objective,
value: resultLine.value,
};
}
}
break;
default:
throw new Error('Unknown Chart type');
}
const data = new StatSerializer({ value: result }).perform();
response.send(data);
} catch (caughtError) {
next(caughtError);
}
};
this.perform = () => {
app.post(path.generate(`stats/${modelName}`, opts), auth.ensureAuthenticated, permissionMiddlewareCreator.stats(), this.get);
app.post(path.generate('stats', opts), auth.ensureAuthenticated, permissionMiddlewareCreator.stats(), this.getWithLiveQuery);
};
};