feat(metrics KPI): Added some metrics KPI Datas

This commit is contained in:
Emile Ndagijimana 2022-09-21 14:33:17 +02:00
parent 344fa07316
commit d323c1e51e
3 changed files with 335 additions and 73 deletions

145
models/server/metrics.js Normal file
View file

@ -0,0 +1,145 @@
import { Meteor } from 'meteor/meteor';
import Users from '../users';
function acceptedIpAdress(ipAdress) {
//return true if a given ipAdress was setted by an admin user
console.log('idpAdress', ipAdress);
//Check if ipAdress is accepted
console.log(
'process.env.WEKAN_METRICS_ACCEPTED_IP_ADRESS',
process.env.WEKAN_METRICS_ACCEPTED_IP_ADRESS,
);
//console.log("process.env", process.env);
const trustedIpAdress = process.env.WEKAN_METRICS_ACCEPTED_IP_ADRESS;
//console.log("trustedIpAdress", trustedIpAdress);
//console.log("trustedIpAdress !== undefined && trustedIpAdress.split(",").includes(ipAdress)", trustedIpAdress !== undefined && trustedIpAdress.split(",").includes(ipAdress));
return (
trustedIpAdress !== undefined &&
trustedIpAdress.split(',').includes(ipAdress)
);
}
Meteor.startup(() => {
WebApp.connectHandlers.use('/metrics', (req, res, next) => {
try {
const ipAdress =
req.headers['x-forwarded-for'] || req.socket.remoteAddress;
// if(process.env.TRUST_PROXY_FORXARD)
// {
// const ipAdress = req.headers['x-forwarded-for'] || req.socket.remoteAddress
// }else{
// const ipAdress = req.socket.remoteAddress
// }
// List of trusted ip adress will be found in environment variable "WEKAN_METRICS_ACCEPTED_IP_ADRESS" (separeted with commas)
if (acceptedIpAdress(ipAdress)) {
let metricsRes = '';
let resCount = 0;
//connected users
metricsRes += '# Number of connected users\n';
// To Do: Get number of connected user by using meteor socketJs
const allOpenedSockets = Meteor.server.stream_server.open_sockets;
let connectedUserIds = [];
allOpenedSockets.forEach(
(socket) =>
//console.log('meteor session', socket._meteorSession.userId)
socket._meteorSession.userId !== null &&
connectedUserIds.push(socket._meteorSession.userId),
);
resCount = connectedUserIds.length; // KPI 1
metricsRes += 'connectedUsers ' + resCount + '\n';
//registered users
metricsRes += '# Number of registered users\n';
// To Do: Get number of registered user
resCount = Users.find({}).count(); // KPI 2
metricsRes += 'registeredUsers ' + resCount + '\n';
resCount = 0;
//board numbers
metricsRes += '# Number of registered boards\n';
// To Do: Get number of registered boards
resCount = Boards.find({ archived: false, type: 'board' }).count(); // KPI 3
metricsRes += 'registeredboards ' + resCount + '\n';
resCount = 0;
//board numbers by registered users
metricsRes += '# Number of registered boards by registered users\n';
// To Do: Get number of registered boards by registered users
resCount =
Boards.find({ archived: false, type: 'board' }).count() /
Users.find({}).count(); // KPI 4
metricsRes += 'registeredboardsBysRegisteredUsers ' + resCount + '\n';
resCount = 0;
//board numbers with only one member
metricsRes += '# Number of registered boards\n';
// To Do: Get board numbers with only one member
resCount = Boards.find({
archived: false,
type: 'board',
members: { $size: 1 },
}).count(); // KPI 5
metricsRes += 'registeredboardsWithOnlyOneMember ' + resCount + '\n';
resCount = 0;
// KPI 6 : - stocker la date de dernière connexion
// KPI 6 = count where date de dernière connexion > x jours
// Découpe en label since 5 jours / 10 jours / 20 Jours / 30 jours
//Number of users with last connection dated 5 days ago
metricsRes +=
'# Number of users with last connection dated 5 days ago\n';
// To Do: Get number of users with last connection dated 5 days ago
let xdays = 5;
let dateWithXdaysAgo = new Date(
new Date() - xdays * 24 * 60 * 60 * 1000,
);
console.log({ dateWithXdaysAgo });
resCount = Users.find({
lastConnectionDate: { $gte: dateWithXdaysAgo },
}).count(); // KPI 5
metricsRes += 'usersWithLastConnectionDated5DaysAgo ' + resCount + '\n';
resCount = 0;
metricsRes +=
'# Number of users with last connection dated 10 days ago\n';
// To Do: Get number of users with last connection dated 10 days ago
xdays = 10;
dateWithXdaysAgo = new Date(new Date() - xdays * 24 * 60 * 60 * 1000);
console.log({ dateWithXdaysAgo });
resCount = Users.find({
lastConnectionDate: { $gte: dateWithXdaysAgo },
}).count(); // KPI 5
metricsRes +=
'usersWithLastConnectionDated10DaysAgo ' + resCount + '\n';
resCount = 0;
// TO DO:
// moyenne de connexion : ((date de déconnexion - date de dernière connexion) + (dernière moyenne)) / 2
// KPI 7 : somme des moyenne de connexion / nombre d'utilisateur (à ignore les utilisateur avec 0 moyenne)
res.writeHead(200); // HTTP status
res.end(metricsRes);
} else {
res.writeHead(401); // HTTP status
res.end(
'IpAdress: ' +
ipAdress +
' is not authorized to perform this action !!\n',
);
}
} catch (e) {
res.writeHead(500); // HTTP status
res.end(e.toString());
}
});
});

View file

@ -2,7 +2,7 @@
import { SyncedCron } from 'meteor/percolate:synced-cron';
import { TAPi18n } from '/imports/i18n';
import ImpersonatedUsers from './impersonatedUsers';
import { Index, MongoDBEngine } from 'meteor/easy:search'
import { Index, MongoDBEngine } from 'meteor/easy:search';
// Sandstorm context is detected using the METEOR_SETTINGS environment variable
// in the package definition.
@ -45,39 +45,39 @@ Users.attachSchema(
/**
* the list of organizations that a user belongs to
*/
type: [Object],
optional: true,
type: [Object],
optional: true,
},
'orgs.$.orgId':{
'orgs.$.orgId': {
/**
* The uniq ID of the organization
*/
type: String,
type: String,
},
'orgs.$.orgDisplayName':{
'orgs.$.orgDisplayName': {
/**
* The display name of the organization
*/
type: String,
type: String,
},
teams: {
/**
* the list of teams that a user belongs to
*/
type: [Object],
optional: true,
type: [Object],
optional: true,
},
'teams.$.teamId':{
'teams.$.teamId': {
/**
* The uniq ID of the team
*/
type: String,
type: String,
},
'teams.$.teamDisplayName':{
'teams.$.teamDisplayName': {
/**
* The display name of the team
*/
type: String,
type: String,
},
emails: {
/**
@ -228,7 +228,7 @@ Users.attachSchema(
type: String,
optional: true,
},
'profile.moveAndCopyDialog' : {
'profile.moveAndCopyDialog': {
/**
* move and copy card dialog
*/
@ -254,7 +254,7 @@ Users.attachSchema(
*/
type: String,
},
'profile.moveChecklistDialog' : {
'profile.moveChecklistDialog': {
/**
* move checklist dialog
*/
@ -286,7 +286,7 @@ Users.attachSchema(
*/
type: String,
},
'profile.copyChecklistDialog' : {
'profile.copyChecklistDialog': {
/**
* copy checklist dialog
*/
@ -494,6 +494,10 @@ Users.attachSchema(
type: [String],
optional: true,
},
lastConnectionDate: {
type: Date,
optional: true,
},
}),
);
@ -542,13 +546,13 @@ UserSearchIndex = new Index({
fields: ['username', 'profile.fullname', 'profile.avatarUrl'],
allowedFields: ['username', 'profile.fullname', 'profile.avatarUrl'],
engine: new MongoDBEngine({
fields: function(searchObject, options) {
fields: function (searchObject, options) {
return {
'username': 1,
username: 1,
'profile.fullname': 1,
'profile.avatarUrl': 1
'profile.avatarUrl': 1,
};
}
},
}),
});
@ -561,6 +565,7 @@ Users.safeFields = {
orgs: 1,
teams: 1,
authenticationMethod: 1,
lastConnectionDate: 1,
};
if (Meteor.isClient) {
@ -630,43 +635,65 @@ Users.helpers({
teamIds() {
if (this.teams) {
// TODO: Should the Team collection be queried to determine if the team isActive?
return this.teams.map(team => { return team.teamId });
return this.teams.map((team) => {
return team.teamId;
});
}
return [];
},
orgIds() {
if (this.orgs) {
// TODO: Should the Org collection be queried to determine if the organization isActive?
return this.orgs.map(org => { return org.orgId });
return this.orgs.map((org) => {
return org.orgId;
});
}
return [];
},
orgsUserBelongs() {
if (this.orgs) {
return this.orgs.map(function(org){return org.orgDisplayName}).sort().join(',');
return this.orgs
.map(function (org) {
return org.orgDisplayName;
})
.sort()
.join(',');
}
return '';
},
orgIdsUserBelongs() {
if (this.orgs) {
return this.orgs.map(function(org){return org.orgId}).join(',');
return this.orgs
.map(function (org) {
return org.orgId;
})
.join(',');
}
return '';
},
teamsUserBelongs() {
if (this.teams) {
return this.teams.map(function(team){ return team.teamDisplayName}).sort().join(',');
return this.teams
.map(function (team) {
return team.teamDisplayName;
})
.sort()
.join(',');
}
return '';
},
teamIdsUserBelongs() {
if (this.teams) {
return this.teams.map(function(team){ return team.teamId}).join(',');
return this.teams
.map(function (team) {
return team.teamId;
})
.join(',');
}
return '';
},
boards() {
return Boards.userBoards(this._id, null, {}, { sort: { sort: 1 } })
return Boards.userBoards(this._id, null, {}, { sort: { sort: 1 } });
},
starredBoards() {
@ -675,7 +702,7 @@ Users.helpers({
this._id,
false,
{ _id: { $in: starredBoards } },
{ sort: { sort: 1 } }
{ sort: { sort: 1 } },
);
},
@ -690,7 +717,7 @@ Users.helpers({
this._id,
false,
{ _id: { $in: invitedBoards } },
{ sort: { sort: 1 } }
{ sort: { sort: 1 } },
);
},
@ -728,7 +755,7 @@ Users.helpers({
* <li> the board, swimlane and list id is stored for each board
*/
getMoveAndCopyDialogOptions() {
let _ret = {}
let _ret = {};
if (this.profile && this.profile.moveAndCopyDialog) {
_ret = this.profile.moveAndCopyDialog;
}
@ -739,7 +766,7 @@ Users.helpers({
* <li> the board, swimlane, list and card id is stored for each board
*/
getMoveChecklistDialogOptions() {
let _ret = {}
let _ret = {};
if (this.profile && this.profile.moveChecklistDialog) {
_ret = this.profile.moveChecklistDialog;
}
@ -750,7 +777,7 @@ Users.helpers({
* <li> the board, swimlane, list and card id is stored for each board
*/
getCopyChecklistDialogOptions() {
let _ret = {}
let _ret = {};
if (this.profile && this.profile.copyChecklistDialog) {
_ret = this.profile.copyChecklistDialog;
}
@ -811,7 +838,7 @@ Users.helpers({
return profile.hiddenMinicardLabelText || false;
},
hasRescuedCardDescription(){
hasRescuedCardDescription() {
const profile = this.profile || {};
return profile.rescueCardDescription || false;
},
@ -1430,17 +1457,30 @@ if (Meteor.isServer) {
}
try {
const fullName = inviter.profile !== undefined && inviter.profile.fullname !== undefined ? inviter.profile.fullname : "";
const userFullName = user.profile !== undefined && user.profile.fullname !== undefined ? user.profile.fullname : "";
const fullName =
inviter.profile !== undefined &&
inviter.profile.fullname !== undefined
? inviter.profile.fullname
: '';
const userFullName =
user.profile !== undefined && user.profile.fullname !== undefined
? user.profile.fullname
: '';
const params = {
user: userFullName != "" ? userFullName + " (" + user.username + " )" : user.username,
inviter: fullName != "" ? fullName + " (" + inviter.username + " )" : inviter.username,
user:
userFullName != ''
? userFullName + ' (' + user.username + ' )'
: user.username,
inviter:
fullName != ''
? fullName + ' (' + inviter.username + ' )'
: inviter.username,
board: board.title,
url: board.absoluteUrl(),
};
const lang = user.getLanguage();
/*
/*
if (process.env.MAIL_SERVICE !== '') {
let transporter = nodemailer.createTransport({
service: process.env.MAIL_SERVICE,
@ -1486,7 +1526,11 @@ if (Meteor.isServer) {
if (!Meteor.user().isAdmin)
throw new Meteor.Error(403, 'Permission denied');
ImpersonatedUsers.insert({ adminId: Meteor.user()._id, userId: userId, reason: 'clickedImpersonate' });
ImpersonatedUsers.insert({
adminId: Meteor.user()._id,
userId: userId,
reason: 'clickedImpersonate',
});
this.setUserId(userId);
},
isImpersonated(userId) {
@ -1502,19 +1546,22 @@ if (Meteor.isServer) {
if (Meteor.user() && Meteor.user().isAdmin) {
Users.find({
teams: {
$elemMatch: {teamId: teamId}
}
}).forEach(user => {
Users.update({
_id: user._id,
teams: {
$elemMatch: {teamId: teamId}
}
}, {
$set: {
'teams.$.teamDisplayName': teamDisplayName
}
});
$elemMatch: { teamId: teamId },
},
}).forEach((user) => {
Users.update(
{
_id: user._id,
teams: {
$elemMatch: { teamId: teamId },
},
},
{
$set: {
'teams.$.teamDisplayName': teamDisplayName,
},
},
);
});
}
},
@ -1524,19 +1571,22 @@ if (Meteor.isServer) {
if (Meteor.user() && Meteor.user().isAdmin) {
Users.find({
orgs: {
$elemMatch: {orgId: orgId}
}
}).forEach(user => {
Users.update({
_id: user._id,
orgs: {
$elemMatch: {orgId: orgId}
}
}, {
$set: {
'orgs.$.orgDisplayName': orgDisplayName
}
});
$elemMatch: { orgId: orgId },
},
}).forEach((user) => {
Users.update(
{
_id: user._id,
orgs: {
$elemMatch: { orgId: orgId },
},
},
{
$set: {
'orgs.$.orgDisplayName': orgDisplayName,
},
},
);
});
}
},
@ -1699,7 +1749,7 @@ if (Meteor.isServer) {
Users._collection.createIndex({
modifiedAt: -1,
});
/* Commented out extra index because of IndexOptionsConflict.
/* Commented out extra index because of IndexOptionsConflict.
Users._collection.createIndex(
{
username: 1,
@ -1918,14 +1968,13 @@ if (Meteor.isServer) {
// TODO : pay attention if ldap field in the user model change to another content ex : ldap field to connection_type
if (doc.authenticationMethod !== 'ldap' && disableRegistration) {
let invitationCode = null;
if(doc.authenticationMethod.toLowerCase() == 'oauth2')
{ // OIDC authentication mode
if (doc.authenticationMethod.toLowerCase() == 'oauth2') {
// OIDC authentication mode
invitationCode = InvitationCodes.findOne({
email: doc.emails[0].address.toLowerCase(),
valid: true,
});
}
else{
} else {
invitationCode = InvitationCodes.findOne({
code: doc.profile.icode,
valid: true,

View file

@ -1,4 +1,4 @@
Meteor.publish('user-miniprofile', function(usernames) {
Meteor.publish('user-miniprofile', function (usernames) {
check(usernames, Array);
// eslint-disable-next-line no-console
@ -19,7 +19,7 @@ Meteor.publish('user-miniprofile', function(usernames) {
);
});
Meteor.publish('user-admin', function() {
Meteor.publish('user-admin', function () {
return Meteor.users.find(this.userId, {
fields: {
isAdmin: 1,
@ -30,7 +30,7 @@ Meteor.publish('user-admin', function() {
});
});
Meteor.publish('user-authenticationMethod', function(match) {
Meteor.publish('user-authenticationMethod', function (match) {
check(match, String);
return Users.find(
{ $or: [{ _id: match }, { email: match }, { username: match }] },
@ -43,3 +43,71 @@ Meteor.publish('user-authenticationMethod', function(match) {
},
);
});
// update last connection date and last connection average time (in seconds) for a user
// function UpdateLastConnectionDateAndLastConnectionAverageTime(lstUsers) {
// let lastConnectionAverageTime;
// lstUsers.forEach((currentUser) => {
// lastConnectionAverageTime =
// currentUser.lastConnectionAverageTimeInSecs !== undefined
// ? currentUser.lastConnectionAverageTimeInSecs
// : 0;
// lastConnectionAverageTime =
// currentUser.lastConnectionDate !== undefined
// ? ((new Date().getTime() - currentUser.lastConnectionDate.getTime()) /
// 1000 +
// lastConnectionAverageTime) /
// 2
// : 0;
// Users.update(currentUser._id, {
// $set: {
// lastConnectionDate: new Date(),
// lastConnectionAverageTimeInSecs: parseInt(lastConnectionAverageTime),
// },
// });
// });
// }
if (Meteor.isServer) {
Meteor.onConnection(function (connection) {
// console.log(
// 'Meteor.server.stream_server.open_sockets',
// Meteor.server.stream_server.open_sockets,
// );
//console.log('connection.Id on connection...', connection.id);
// connection.onClose(() => {
// console.log('connection.Id on close...', connection.id);
// // Get all user that were connected to this socket
// // And update last connection date and last connection average time (in seconds) for each user
// let lstOfUserThatWasConnectedToThisSocket = Users.find({
// lastconnectedSocketId: connection.id,
// }).fetch();
// if (
// lstOfUserThatWasConnectedToThisSocket !== undefined &&
// lstOfUserThatWasConnectedToThisSocket.length > 0
// ) {
// console.log({ lstOfUserThatWasConnectedToThisSocket });
// UpdateLastConnectionDateAndLastConnectionAverageTime(
// lstOfUserThatWasConnectedToThisSocket,
// );
// }
// });
// Meteor.server.stream_server.open_sockets.forEach((socket) =>
// console.log('meteor session', socket._meteorSession.userId),
// );
// update last connected user date (neddeed for one of the KPI)
Meteor.server.stream_server.open_sockets.forEach(
(socket) =>
//console.log('meteor session', socket._meteorSession.userId),
socket._meteorSession?.userId !== null &&
Users.update(socket._meteorSession.userId, {
$set: {
lastConnectionDate: new Date(),
},
}),
);
});
}