lib/kibana/kibana.js
'use strict';
module.exports = (function () {
var Q = require('q');
var request = require('request');
var kibana = {};
var async = require('async');
var db = require('../db');
var Collection = require('easy-collections'),
activities = new Collection(db, 'activities'),
games = new Collection(db, 'games'),
versions = new Collection(db, 'versions'),
classes = new Collection(db, 'classes'),
groups = new Collection(db, 'groups'),
groupings = new Collection(db, 'groupings');
// jscs:disable requireCamelCaseOrUpperCaseIdentifiers
var defaultObject = {
out: {
name: '',
gameplayId: '',
type_hashCode: 0,
score: 0.01,
response: '',
type: '',
event_hashcode: 0,
target: '',
versionId: '',
success: false,
gameplayId_hashCode: 0,
event: '',
timestamp: '2000-01-19T11:05:27.772Z',
target_hashCode: 0,
stored: '2000-01-19T11:05:27.772Z',
progress: 0.01,
time: 0.01,
ext: {
progress: 0.01,
time: 0.01,
location: {
lat: 0.01,
lon: 0.01
}
}
},
activityName: '',
activityId: '',
classId: ''
};
var defaultObjectClassIndexObject = defaultObject;
var classIndexTemplate = require('../../lib/kibana/defaultIndex');
kibana.defaultObject = defaultObject;
var rootAuthToken;
// jscs:enable requireCamelCaseOrUpperCaseIdentifiers
var getParticipants = function (objectId) {
var deferred = Q.defer();
objectId = objectId.toString();
console.log('kibana.updateActivityPermissions -> Obtaining participants for: ' + objectId);
activities.findById(objectId)
.then(function (activityObj) {
if (activityObj) {
console.log('kibana.updateActivityPermissions -> Activity Found');
resolveParticipants(activityObj.groups, activityObj.groupings, activityObj.classId)
.then(function (participants) {
var users = participants.students.concat(participants.assistants.concat(participants.teachers));
deferred.resolve(users);
})
.fail(deferred.reject);
} else {
console.log('kibana.updateActivityPermissions -> Activity not found. Trying to find participants in class');
return classes.findById(objectId)
.then(function (classObj) {
if (classObj) {
console.log('kibana.updateActivityPermissions -> Class Found');
resolveParticipants(classObj.groups, classObj.groupings, classObj._id)
.then(function (participants) {
var users = participants.students.concat(participants.assistants.concat(participants.teachers));
deferred.resolve(users);
})
.fail(deferred.reject);
} else {
console.log('kibana.updateActivityPermissions -> Trying to find participants in version');
return versions.findById(objectId)
.then(function (versionObj) {
if (versionObj) {
console.log('kibana.updateActivityPermissions -> Version found');
return games.findById(versionObj.gameId);
}
deferred.reject(new Error('Version not found', 404));
})
.then(function (gameObj) {
if (gameObj) {
console.log('kibana.updateActivityPermissions -> Game found');
var participants = gameObj.developers ? gameObj.developers : [];
if (gameObj.authors && gameObj.authors.length) {
participants = gameObj.authors.concat(participants);
}
deferred.resolve(participants);
} else {
deferred.reject(new Error('Game not found', 404));
}
});
}
});
}
})
.fail(deferred.reject);
return deferred.promise;
};
/**
* Get all participants of activity. If the activity use groupings, return the participants of the grouping, if use groups
* return the participants of the groups. Else, return the participants of the parent class.
* @param activityObj
*/
var resolveParticipants = function (groupsObj, groupingsObj, classId, callback) {
var deferred = Q.defer();
var participants = {students: [], teachers: [], assistants: []};
if (groupingsObj.length > 0) {
console.log('using groupings');
groupings.find({classId: classId}).then(function (groupingsArray) {
groups.find({classId: classId}).then(function (groupsArray) {
groupingsObj.forEach(function (groupingId) {
addParticipantsFromGroupingId(participants, groupingsArray, groupsArray, groupingId);
});
deferred.resolve(participants);
}).fail(deferred.reject);
}).fail(deferred.reject);
} else if (groupsObj.length > 0) {
console.log('using groups');
groups.find({classId: classId}).then(function (groupsArray) {
groupsObj.forEach(function (groupId) {
addParticipantsFromGroupId(participants, groupsArray, groupId);
});
deferred.resolve(participants);
}).fail(deferred.reject);
} else {
console.log('using class');
classes.findById(classId).then(function (classObj) {
deferred.resolve(classObj.participants);
}).fail(deferred.reject);
}
return deferred.promise;
};
var addParticipantsFromGroupingId = function (participants, groupingsArray, groupsArray, groupingId) {
for (var i = 0; i < groupingsArray.length; i++) {
if (groupingId === groupingsArray[i]._id) {
for (var j = 0; j < groupingsArray[i].groups.length; j++) {
addParticipantsFromGroupId(participants, groupsArray, groupingsArray[i].groups[j]);
}
break;
}
}
};
var addParticipantsFromGroupId = function (participants, groupsArray, groupId) {
for (var i = 0; i < groupsArray.length; i++) {
if (groupId === groupsArray[i]._id) {
pushUsrFromGroupToParticipants(participants, groupsArray[i], 'teachers');
pushUsrFromGroupToParticipants(participants, groupsArray[i], 'assistants');
pushUsrFromGroupToParticipants(participants, groupsArray[i], 'students');
return;
}
}
};
var pushUsrFromGroupToParticipants = function (participants, group, role) {
group.participants[role].forEach(function (usr) {
if (participants[role].indexOf(usr) === -1) {
participants[role].push(usr);
}
});
};
/**
* Creates a new analysis for the given versionId.
* @Returns a promise with the analysis created
*/
kibana.getVisualizations = function (user, gameId, esClient) {
var deferred = Q.defer();
esClient.search({
index: '.games' + gameId,
type: 'list',
q: '_id:' + gameId
}, function (error, response) {
if (!error) {
if (response.hits.hits[0]) {
if (user === 'dev') {
deferred.resolve(response.hits.hits[0]._source.visualizationsDev ? response.hits.hits[0]._source.visualizationsDev : []);
} else if ('tch') {
deferred.resolve(response.hits.hits[0]._source.visualizationsTch ? response.hits.hits[0]._source.visualizationsTch : []);
} else if ('all') {
var c = response.hits.hits[0]._source.visualizationsTch.concat(
response.hits.hits[0]._source.visualizationsDev.filter(function (item) {
return response.hits.hits[0]._source.visualizationsTch.indexOf(item) < 0;
}));
deferred.resolve(c);
}
} else {
console.log('kibana.getVisualizations -> Visualizations not found for game: ', gameId);
deferred.resolve([]);
}
} else {
deferred.reject(error);
}
});
return deferred.promise;
};
kibana.getIndexTemplate = function (indexTemplateId, esClient) {
var deferred = Q.defer();
esClient.search({
index: '.template',
q: '_id:' + indexTemplateId
}, function (error, response) {
if (!error) {
if (response.hits.hits[0]) {
deferred.resolve(response.hits.hits[0]._source);
} else {
deferred.reject(new Error('Template not found with id: ' + indexTemplateId, 404));
}
} else {
deferred.reject(error);
}
});
return deferred.promise;
};
kibana.createIndexPatternWithTemplate = function (indexNames, indexTemplate, esClient) {
if (!Array.isArray(indexNames)) {
indexNames = [indexNames];
}
var promises = [];
indexNames.forEach(function (indexName) {
var indexPromise = Q.defer();
indexTemplate.title = indexName;
console.log('kibana.createIndexPatternWithTemplate -> Started, id: ' + indexName);
promises.push(indexPromise.promise);
esClient.index({
index: '.kibana',
type: 'index-pattern',
id: indexName,
body: indexTemplate
}, function (error, response) {
if (error) {
indexPromise.reject(error);
} else {
indexPromise.resolve(response);
}
});
});
return Q.all(promises);
};
kibana.createKibanaIndices = function (indexName, indexTemplateId, user, config, esClient) {
var deferred = Q.defer();
var dataIndices = [indexName, 'thomaskilmann-' + indexName];
console.log('kibana.createKibanaIndices -> Started!');
kibana.addIndexObject(indexName, indexTemplateId, esClient)
.then(function () {
console.log('kibana.createKibanaIndices -> Added intex object!');
kibana.getIndexTemplate(indexTemplateId, esClient)
.then(function (indexTemplate) {
console.log('kibana.createKibanaIndices -> Index template obtained!');
kibana.createIndexPatternWithTemplate(dataIndices, indexTemplate, esClient)
.then(function () {
console.log('kibana.createKibanaIndices -> Building kibana resources!');
return kibana.buildKibanaResources(dataIndices, config, esClient);
})
.then(function (kibanaResources) {
console.log('kibana.createKibanaIndices -> Updating permissions!');
return kibana.updateActivityPermissions(indexName, kibanaResources, config, user);
})
.then(function () {
console.log('kibana.createKibanaIndices -> Done!');
deferred.resolve();
})
.catch(function (err) {
deferred.reject(err);
});
})
.fail(function (error) {
deferred.reject(error);
});
})
.fail(function (error) {
deferred.reject(error);
});
return deferred.promise;
};
kibana.indexVisualizationList = function (id, list, esClient) {
var deferred = Q.defer();
esClient.index({
index: '.games' + id,
type: 'list',
id: id,
body: list
}, function (error, response) {
if (!error) {
deferred.resolve(response);
} else {
deferred.reject(error);
}
});
return deferred.promise;
};
kibana.getKibanaBaseVisualizations = function(role, config, id, esClient) {
var deferred = Q.defer();
console.log('Kibana.getKibanaBaseVisualizations() -> Started');
kibana.getVisualizations(role, id, esClient)
.then(function(visualizations) {
deferred.resolve(visualizations);
})
.fail(function(error) {
console.log('Kibana.getKibanaBaseVisualizations() -> ERROR!');
deferred.reject(error);
});
return deferred.promise;
};
kibana.getTemplatesByAuthor = function(idAuthor, esClient) {
var deferred = Q.defer();
esClient.search({
size: 100,
from: 0,
index: '.template',
q: 'author:' + idAuthor
}, function (error, response) {
if (!error) {
if (response.hits && response.hits.hits) {
deferred.resolve(response.hits.hits);
} else {
deferred.resolve([]);
}
} else {
deferred.reject(error);
}
});
return deferred.promise;
};
kibana.indexTemplate = function (type, id, body, esClient) {
var deferred = Q.defer();
esClient.index({
index: '.template',
type: type,
id: id,
body: body
}, function (error, response) {
if (!error) {
deferred.resolve(response);
} else {
deferred.reject(error);
}
});
return deferred.promise;
};
kibana.setGameVisualizationByTemplate = function (gameId, id, visualization, fields, esClient) {
var deferred = Q.defer();
Object.keys(fields).forEach(function (key) {
visualization.visState = visualization.visState.replace(new RegExp(key, 'g'), fields[key]);
});
esClient.index({
index: '.games' + gameId,
type: 'visualization',
id: id,
body: visualization
}, function (error, response) {
if (!error) {
return deferred.resolve(response);
}
deferred.reject(error);
});
return deferred.promise;
};
kibana.createIndex = function(config, indexName, indexTemplateId, user, esClient) {
var deferred = Q.defer();
console.log('kibana.createIndex() -> Started');
kibana.createKibanaIndices(indexName, indexTemplateId, user, config, esClient)
.then(function(ret) {
console.log('kibana.createIndex() -> Success, resolving!');
return deferred.resolve(ret);
})
.fail(function(error) {
if (error) {
console.log('kibana.createIndex() -> ERROR!');
console.log(JSON.stringify(error, null, 2));
console.log(indexName);
console.log(indexTemplateId);
return deferred.reject(error);
}
});
return deferred.promise;
};
kibana.createVisualizationsAndDashboard = function(config, id, gameId, visualizations, user, esClient, extra) {
var deferred = Q.defer();
console.log('kibana.createVisualizationsAndDashboard() -> Started');
var numPan = 1;
var panels = [];
var uiStates = {};
var Completed = function() {
numPan++;
console.log('kibana.createVisualizationsAndDashboard() -> Vis created ' + numPan + '/' + visualizations.length);
if (numPan > visualizations.length) {
var dashboard = kibana.generateDashboard(id, panels, uiStates, extra);
kibana.createDashboard(dashboard, id, gameId, esClient, config, user)
.then(function(res) {
console.log('kibana.createVisualizationsAndDashboard() -> Success, resolving!');
deferred.resolve(res);
})
.fail(function(err) {
console.log('kibana.createVisualizationsAndDashboard() -> ERROR on creating the dashboard!');
console.log(JSON.stringify(err, null, 2));
deferred.reject(err);
});
}
};
var createVis = function(position) {
kibana.createVisualization(config, gameId, id, visualizations[position], position, esClient)
.then(function(panel) {
panels.push(panel);
uiStates['P-' + position] = {vis: {legendOpen: false}};
console.log('kibana.createVisualizationsAndDashboard() -> Completing');
Completed();
})
.fail(function(error) {
console.log('kibana.createVisualizationsAndDashboard() -> ERROR on creating visualization!');
console.log(JSON.stringify(error, null, 2));
return deferred.reject(error);
});
};
if (visualizations.length > 0) {
for (var i = 0; i < visualizations.length; i++) {
createVis(i);
}
} else {
deferred.resolve();
}
return deferred.promise;
};
kibana.generateDashboard = function(id, panels, uiStates, extra) {
console.log('Kibana -> generateDashboard');
var timeFrom = 'now-1h';
var refreshInterval = {
display: '5 seconds',
pause: false,
section: 1,
value: 5000
};
if (extra) {
if (extra.timeFrom) {
timeFrom = extra.timeFrom;
}
if (extra.refreshInterval) {
refreshInterval = extra.refreshInterval;
}
}
return {
title: 'dashboard_' + id,
hits: 0,
description: '',
panelsJSON: '[' + panels.toString() + ']',
optionsJSON: '{"darkTheme":false}',
uiStateJSON: JSON.stringify(uiStates),
version: 1,
timeRestore: true,
timeTo: 'now',
timeFrom: timeFrom,
refreshInterval: refreshInterval,
kibanaSavedObjectMeta: {
searchSourceJSON: '{"filter":[{"query":{"query_string":{"query":"*","analyze_wildcard":true}}}]}'
}
};
};
kibana.createVisualization = function(config, gameId, id, visualizationId, position, esClient) {
var deferred = Q.defer();
console.log('Kibana.createVisualization() -> Started!');
var cloneVisualizationPromise = {};
if (gameId) {
cloneVisualizationPromise = kibana.cloneVisualizationForId(gameId, visualizationId, id, esClient);
} else {
cloneVisualizationPromise = kibana.cloneVisualizationForClass(visualizationId, id, esClient);
visualizationId = visualizationId.title;
}
cloneVisualizationPromise.then(function (result) {
console.log('Activities.kibana.createVisualization() -> Success, resolving!');
deferred.resolve('{\"id\":\"' + visualizationId + '_' + id +
'\",\"type\":\"visualization\",\"panelIndex\":' + position + ',' +
'\"size_x\":6,\"size_y\":4,\"col\":' + (1 + (position - 1 % 2)) + ',\"row\":' +
(position + 1 / 2) + '}');
})
.fail(function(err) {
console.log('Kibana.createVisualization() -> ERROR on creating visualization!');
deferred.reject(err);
});
return deferred.promise;
};
kibana.createRequiredIndexesForClass = function (classId, user, config, esClient) {
var deferred = Q.defer();
esClient.indices.exists({index: classId}, function (err, exists) {
console.log('Including object in new activity');
if (err || !exists) {
kibana.getIndexObject(classId, esClient)
.then(function (indexObject) {
return kibana.insertIndexObject(classId, indexObject, esClient);
})
.then(function () {
return kibana.createIndexPatternWithTemplate(classId, classIndexTemplate, esClient);
})
.then(function () {
deferred.resolve();
})
.catch(function (error) {
deferred.reject(error);
});
} else {
deferred.resolve();
}
});
return deferred.promise;
};
kibana.cloneVisualizationForId = function (gameId, visualizationId, id, esClient) {
var deferred = Q.defer();
esClient.search({
index: '.games' + gameId,
q: '_id:' + visualizationId
}, function (error, response) {
if (!error) {
if (response.hits.hits[0] && response.hits.hits[0]._source.kibanaSavedObjectMeta) {
var re = /"index":"(\w+-)?(\w+.?\w+)"/;
var obj = response.hits.hits[0]._source;
// Replace template and save it
var m = re.exec(obj.kibanaSavedObjectMeta.searchSourceJSON);
if (m && m.length > 1) {
obj.kibanaSavedObjectMeta.searchSourceJSON = obj.kibanaSavedObjectMeta.searchSourceJSON.replace(m[2], id);
}
esClient.index({
index: '.kibana',
type: 'visualization',
id: response.hits.hits[0]._id + '_' + id,
body: obj
}, function (error, result) {
if (!error) {
deferred.resolve(result);
} else {
deferred.reject(error);
}
});
} else {
deferred.reject(new Error('Template not found', 404));
}
} else {
deferred.reject(error);
}
});
return deferred.promise;
};
kibana.cloneVisualizationForClass = function (visualization, classId, esClient) {
var deferred = Q.defer();
if (visualization && visualization.kibanaSavedObjectMeta) {
var re = /"index":"(\w+-)?(\w+.?\w+)"/;
var obj = visualization;
delete obj.isClass;
// Replace template and save it
var m = re.exec(obj.kibanaSavedObjectMeta.searchSourceJSON);
if (m && m.length > 1) {
obj.kibanaSavedObjectMeta.searchSourceJSON = obj.kibanaSavedObjectMeta.searchSourceJSON.replace(m[2], classId);
}
esClient.index({
index: '.kibana',
type: 'visualization',
id: obj.title + '_' + classId,
body: obj
}, function (error, result) {
if (!error) {
deferred.resolve(result);
} else {
deferred.reject(error);
}
});
} else {
deferred.reject(new Error('Visualization not provided correctly', 404));
}
return deferred.promise;
};
kibana.createDashboard = function (dashboard, id, parentId, esClient, config, user) {
var deferred = Q.defer();
kibana.addIndexObject(id, parentId, esClient)
.then(function () {
esClient.index({
index: '.kibana',
type: 'dashboard',
id: 'dashboard_' + id,
body: dashboard
}, function (error, response) {
if (!error) {
var visualizations = JSON.parse(dashboard.panelsJSON);
var kibanaResources = ['dashboard_' + id];
for (var i = 0; i < visualizations.length; i++) {
kibanaResources.push(visualizations[i].id);
}
getParticipants(id)
.then(function (participants) {
kibana.auth(config, function (error, token) {
if (error) {
return deferred.reject(error);
}
async.every(participants, function (user, cb) {
kibana.updateKibanaPermission(config, user, kibanaResources, cb);
}, function (err, result) {
rootAuthToken = null;
deferred.resolve(result);
});
});
})
.fail(function (error) {
deferred.reject(error);
});
} else {
deferred.reject(error);
}
});
})
.fail(function (error) {
deferred.reject(error);
});
return deferred.promise;
};
kibana.addIndexObject = function (id, parentId, esClient) {
var deferred = Q.defer();
esClient.indices.exists({index: id}, function (err, exists) {
console.log('Including object in new activity');
if (err || !exists) {
kibana.getIndexObject(parentId, esClient)
.then(function (indexObject) {
return kibana.insertIndexObject(id, indexObject, esClient);
})
.then(function () {
deferred.resolve();
})
.fail(function (error) {
deferred.reject(error);
});
} else {
deferred.resolve();
}
});
return deferred.promise;
};
kibana.getIndexObject = function (id, esClient) {
var deferred = Q.defer();
var indexObject = defaultObject;
esClient.search({
index: '.objectfields',
type: 'object_fields',
q: '_id:' + 'object_fields' + id
}, function (error, response) {
if (!error && response.hits && response.hits.hits && response.hits.hits.length > 0) {
indexObject = response.hits.hits[0]._source;
}
deferred.resolve(indexObject);
});
return deferred.promise;
};
kibana.getIndexObjectForClass = function (classId, esClient) {
var deferred = Q.defer();
classes.findById(classId)
.then(function (classObj) {
if (classObj) {
deferred.resolve(defaultObjectClassIndexObject);
}
})
.fail(function (error) {
deferred.reject(error);
});
return deferred.promise;
};
kibana.getIndexObjectFromClass = function (classId, esClient) {
var deferred = Q.defer();
var classes = require('../classes');
var indexObject = defaultObject;
classes.findById(classId)
.then(function (classObj) {
if (classObj) {
esClient.search({
index: '.objectfields',
type: 'object_fields',
q: '_id:' + 'object_fields' + classObj._id
}, function (error, response) {
if (!error && response.hits && response.hits.hits && response.hits.hits.length > 0) {
indexObject = response.hits.hits[0]._source;
}
deferred.resolve(indexObject);
});
} else {
deferred.reject({message: 'Class not found for id: ' + classId});
}
})
.fail(function (error) {
deferred.reject(error);
});
return deferred.promise;
};
kibana.insertIndexObject = function (id, indexObject, esClient) {
var deferred = Q.defer();
esClient.index({
index: id.toString(),
type: 'traces',
body: indexObject
}, function (error, created) {
if (error) {
deferred.reject(error);
} else {
deferred.resolve();
}
});
return deferred.promise;
};
kibana.updateActivityPermissions = function (indexName, kibanaResources, config) {
var deferred = Q.defer();
getParticipants(indexName)
.then(function (participants) {
kibana.auth(config, function (error, token) {
if (error) {
return deferred.reject(error);
}
async.every(participants, function (user, cb) {
kibana.updateKibanaPermission(config, user, kibanaResources, cb);
}, function (err, result) {
rootAuthToken = null;
deferred.resolve(result);
});
});
});
return deferred.promise;
};
/**
* Logs in as an admin and tries to set the permissions for the user that
* performed the request
* @param config
* @param data The Lookup permissions, e.g.
* {
* "key":"_id",
* "user": "dev"
* "resource":"id1",
* "methods":["post","put"],
* "url":"/url/*"
* }
* @param callback
*/
kibana.updateKibanaPermission = function (config, user, resources, callback) {
var updateKP = function () {
var baseUsersAPI = config.a2.a2ApiPath;
request({
uri: baseUsersAPI + 'applications/look/kibana',
method: 'PUT',
body: {
key: 'docs._id',
user: user,
resources: resources,
methods: ['post', 'put'],
url: '/elasticsearch/_mget'
},
json: true,
headers: {
Authorization: 'Bearer ' + rootAuthToken
}
}, function (err, httpResponse, body) {
if (err) {
return callback(err);
}
callback();
});
};
if (!rootAuthToken) {
kibana.auth(config, function () {
updateKP();
});
} else {
updateKP();
}
};
kibana.auth = function (config, callback) {
console.log('Refreshing auth token');
var baseUsersAPI = config.a2.a2ApiPath;
request.post(baseUsersAPI + 'login', {
form: {
username: config.a2.a2AdminUsername,
password: config.a2.a2AdminPassword
},
json: true
},
function (err, httpResponse, body) {
if (err) {
console.log('Error logging in AUTH');
return callback(err);
}
if (!body.user || !body.user.token) {
var tokenErr = new Error('No user token (Wrong admin credentials)');
tokenErr.status = 403;
return callback(tokenErr);
}
rootAuthToken = body.user.token;
return callback(null, rootAuthToken);
});
};
/**
* Creates a resources array to define the correct permissions
* @param indexName
* @param config
* @param esClient
*/
kibana.buildKibanaResources = function (indexNames, config, esClient) {
var deferred = Q.defer();
console.log('kibana.buildKibanaResources -> Started!');
esClient.search({
size: 1,
from: 0,
index: '.kibana',
type: 'config'
}, function (error, response) {
if (error) {
return deferred.reject(error);
}
var resources = [];
if (response.hits && response.hits.hits.length > 0) {
response.hits.hits.forEach(function (hit) {
resources.push(hit._id);
});
resources.push(config.kibana.defaultIndex);
indexNames.forEach(function (indexName) {
resources.push(indexName);
});
console.log('kibana.buildKibanaResources -> SUCCESS!');
deferred.resolve(resources);
} else {
console.log('kibana.buildKibanaResources -> ERROR!');
deferred.reject(new Error('No Kibana version found!'));
}
});
return deferred.promise;
};
return kibana;
})();