[IMPROVE] lib/Statistics improved and metrics collector (#24177)

* statistics property get to promise

* E2EE Metrics

* Accounts 2fA Metrics

* SMTP Metrics

* General Apps Metrics

* NPS Survey Metrics

* Update Metrics

* Live Stream and Broadcasting Metrics

* Message and Search Metrics

* OTR, Push, and Threads Metrics

* Video Conference Metrics

* WebRTC Metrics

* [IMPROVE] Settings Statistics

* [FIX] No absolut path to imports

* [FIX] Redundant use of `await`

* Canned Responses Statistics

* Add Canned Responses on ISetting

* [FIX] Canned response logic decision

* Typo

* Remove legacy Promise.await

* Refactored map

* Call Cursor.prototype.toArray() after map and filter

* Move misaligned comment

Co-authored-by: Leonardo Ostjen Couto <leonardoostjen@gmail.com>
Co-authored-by: Tasso Evangelista <tasso.evangelista@rocket.chat>
pull/24276/head
Fábio Albuquerque 3 years ago committed by GitHub
parent 36aa6f1084
commit 328fbebdee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      app/cloud/server/functions/buildRegistrationData.js
  2. 89
      app/statistics/server/lib/statistics.js
  3. 100
      definition/ISetting.ts
  4. 161
      server/lib/statistics/getSettingsStatistics.ts

@ -5,7 +5,7 @@ import { statistics } from '../../../statistics';
import { LICENSE_VERSION } from '../license'; import { LICENSE_VERSION } from '../license';
export async function buildWorkspaceRegistrationData() { export async function buildWorkspaceRegistrationData() {
const stats = (await Statistics.findLast()) || statistics.get(); const stats = (await Statistics.findLast()) || (await statistics.get());
const address = settings.get('Site_Url'); const address = settings.get('Site_Url');
const siteName = settings.get('Site_Name'); const siteName = settings.get('Site_Name');

@ -24,11 +24,12 @@ import { getAppsStatistics } from './getAppsStatistics';
import { getServicesStatistics } from './getServicesStatistics'; import { getServicesStatistics } from './getServicesStatistics';
import { getStatistics as getEnterpriseStatistics } from '../../../../ee/app/license/server'; import { getStatistics as getEnterpriseStatistics } from '../../../../ee/app/license/server';
import { Team, Analytics } from '../../../../server/sdk'; import { Team, Analytics } from '../../../../server/sdk';
import { getSettingsStatistics } from '../../../../server/lib/statistics/getSettingsStatistics';
const wizardFields = ['Organization_Type', 'Industry', 'Size', 'Country', 'Language', 'Server_Type', 'Register_Server']; const wizardFields = ['Organization_Type', 'Industry', 'Size', 'Country', 'Language', 'Server_Type', 'Register_Server'];
const getUserLanguages = (totalUsers) => { const getUserLanguages = async (totalUsers) => {
const result = Promise.await(UsersRaw.getUserLanguages()); const result = await UsersRaw.getUserLanguages();
const languages = { const languages = {
none: totalUsers, none: totalUsers,
@ -48,7 +49,7 @@ const getUserLanguages = (totalUsers) => {
const { db } = MongoInternals.defaultRemoteCollectionDriver().mongo; const { db } = MongoInternals.defaultRemoteCollectionDriver().mongo;
export const statistics = { export const statistics = {
get: function _getStatistics() { get: async () => {
const readPreference = readSecondaryPreferred(db); const readPreference = readSecondaryPreferred(db);
const statistics = {}; const statistics = {};
@ -86,7 +87,7 @@ export const statistics = {
statistics.busyUsers = Meteor.users.find({ status: 'busy' }).count(); statistics.busyUsers = Meteor.users.find({ status: 'busy' }).count();
statistics.totalConnectedUsers = statistics.onlineUsers + statistics.awayUsers; statistics.totalConnectedUsers = statistics.onlineUsers + statistics.awayUsers;
statistics.offlineUsers = statistics.totalUsers - statistics.onlineUsers - statistics.awayUsers - statistics.busyUsers; statistics.offlineUsers = statistics.totalUsers - statistics.onlineUsers - statistics.awayUsers - statistics.busyUsers;
statistics.userLanguages = getUserLanguages(statistics.totalUsers); statistics.userLanguages = await getUserLanguages(statistics.totalUsers);
// Room statistics // Room statistics
statistics.totalRooms = Rooms.find().count(); statistics.totalRooms = Rooms.find().count();
@ -98,7 +99,7 @@ export const statistics = {
statistics.totalThreads = Messages.countThreads(); statistics.totalThreads = Messages.countThreads();
// Teams statistics // Teams statistics
statistics.teams = Promise.await(Team.getStatistics()); statistics.teams = await Team.getStatistics();
// livechat visitors // livechat visitors
statistics.totalLivechatVisitors = LivechatVisitors.find().count(); statistics.totalLivechatVisitors = LivechatVisitors.find().count();
@ -110,7 +111,7 @@ export const statistics = {
statistics.livechatEnabled = settings.get('Livechat_enabled'); statistics.livechatEnabled = settings.get('Livechat_enabled');
// Count and types of omnichannel rooms // Count and types of omnichannel rooms
statistics.omnichannelSources = Promise.await(RoomsRaw.allRoomSourcesCount().toArray()).map(({ _id: { id, alias, type }, count }) => ({ statistics.omnichannelSources = (await RoomsRaw.allRoomSourcesCount().toArray()).map(({ _id: { id, alias, type }, count }) => ({
id, id,
alias, alias,
type, type,
@ -190,19 +191,17 @@ export const statistics = {
statistics.enterpriseReady = true; statistics.enterpriseReady = true;
statistics.uploadsTotal = Promise.await(Uploads.find().count()); statistics.uploadsTotal = await Uploads.find().count();
const [result] = Promise.await( const [result] = await Uploads.col
Uploads.col .aggregate(
.aggregate( [
[ {
{ $group: { _id: 'total', total: { $sum: '$size' } },
$group: { _id: 'total', total: { $sum: '$size' } }, },
}, ],
], { readPreference },
{ readPreference }, )
) .toArray();
.toArray(),
);
statistics.uploadsTotalSize = result ? result.total : 0; statistics.uploadsTotalSize = result ? result.total : 0;
statistics.migration = getControl(); statistics.migration = getControl();
@ -215,33 +214,35 @@ export const statistics = {
statistics.mongoVersion = mongoVersion; statistics.mongoVersion = mongoVersion;
statistics.mongoStorageEngine = mongoStorageEngine; statistics.mongoStorageEngine = mongoStorageEngine;
statistics.uniqueUsersOfYesterday = Promise.await(Sessions.getUniqueUsersOfYesterday()); statistics.uniqueUsersOfYesterday = await Sessions.getUniqueUsersOfYesterday();
statistics.uniqueUsersOfLastWeek = Promise.await(Sessions.getUniqueUsersOfLastWeek()); statistics.uniqueUsersOfLastWeek = await Sessions.getUniqueUsersOfLastWeek();
statistics.uniqueUsersOfLastMonth = Promise.await(Sessions.getUniqueUsersOfLastMonth()); statistics.uniqueUsersOfLastMonth = await Sessions.getUniqueUsersOfLastMonth();
statistics.uniqueDevicesOfYesterday = Promise.await(Sessions.getUniqueDevicesOfYesterday()); statistics.uniqueDevicesOfYesterday = await Sessions.getUniqueDevicesOfYesterday();
statistics.uniqueDevicesOfLastWeek = Promise.await(Sessions.getUniqueDevicesOfLastWeek()); statistics.uniqueDevicesOfLastWeek = await Sessions.getUniqueDevicesOfLastWeek();
statistics.uniqueDevicesOfLastMonth = Promise.await(Sessions.getUniqueDevicesOfLastMonth()); statistics.uniqueDevicesOfLastMonth = await Sessions.getUniqueDevicesOfLastMonth();
statistics.uniqueOSOfYesterday = Promise.await(Sessions.getUniqueOSOfYesterday()); statistics.uniqueOSOfYesterday = await Sessions.getUniqueOSOfYesterday();
statistics.uniqueOSOfLastWeek = Promise.await(Sessions.getUniqueOSOfLastWeek()); statistics.uniqueOSOfLastWeek = await Sessions.getUniqueOSOfLastWeek();
statistics.uniqueOSOfLastMonth = Promise.await(Sessions.getUniqueOSOfLastMonth()); statistics.uniqueOSOfLastMonth = await Sessions.getUniqueOSOfLastMonth();
statistics.apps = getAppsStatistics(); statistics.apps = getAppsStatistics();
statistics.services = getServicesStatistics(); statistics.services = getServicesStatistics();
const integrations = Promise.await( // If getSettingsStatistics() returns an error, save as empty object.
Integrations.find( const settingsStatisticsObject = (await getSettingsStatistics()) || {};
{}, statistics.settings = settingsStatisticsObject;
{
projection: { const integrations = await Integrations.find(
_id: 0, {},
type: 1, {
enabled: 1, projection: {
scriptEnabled: 1, _id: 0,
}, type: 1,
readPreference, enabled: 1,
scriptEnabled: 1,
}, },
).toArray(), readPreference,
); },
).toArray();
statistics.integrations = { statistics.integrations = {
totalIntegrations: integrations.length, totalIntegrations: integrations.length,
@ -254,15 +255,15 @@ export const statistics = {
totalWithScriptEnabled: integrations.filter((integration) => integration.scriptEnabled === true).length, totalWithScriptEnabled: integrations.filter((integration) => integration.scriptEnabled === true).length,
}; };
statistics.pushQueue = Promise.await(NotificationQueue.col.estimatedDocumentCount()); statistics.pushQueue = await NotificationQueue.col.estimatedDocumentCount();
statistics.enterprise = getEnterpriseStatistics(); statistics.enterprise = getEnterpriseStatistics();
Promise.await(Analytics.resetSeatRequestCount()); await Analytics.resetSeatRequestCount();
return statistics; return statistics;
}, },
async save() { async save() {
const rcStatistics = statistics.get(); const rcStatistics = await statistics.get();
rcStatistics.createdAt = new Date(); rcStatistics.createdAt = new Date();
await Statistics.insertOne(rcStatistics); await Statistics.insertOne(rcStatistics);
return rcStatistics; return rcStatistics;

@ -137,3 +137,103 @@ export const isSettingCode = (setting: ISettingBase): setting is ISettingCode =>
export const isSettingAction = (setting: ISettingBase): setting is ISettingAction => setting.type === 'action'; export const isSettingAction = (setting: ISettingBase): setting is ISettingAction => setting.type === 'action';
export const isSettingAsset = (setting: ISettingBase): setting is ISettingAsset => setting.type === 'asset'; export const isSettingAsset = (setting: ISettingBase): setting is ISettingAsset => setting.type === 'asset';
export interface ISettingStatistics {
account2fa?: boolean;
cannedResponsesEnabled?: boolean;
e2e?: boolean;
e2eDefaultDirectRoom?: boolean;
e2eDefaultPrivateRoom?: boolean;
smtpHost?: string;
smtpPort?: string;
fromEmail?: string;
frameworkDevMode?: boolean;
frameworkEnable?: boolean;
surveyEnabled?: boolean;
updateChecker?: boolean;
liveStream?: boolean;
broadcasting?: boolean;
allowEditing?: boolean;
allowDeleting?: boolean;
allowUnrecognizedSlashCommand?: boolean;
allowBadWordsFilter?: boolean;
readReceiptEnabled?: boolean;
readReceiptStoreUsers?: boolean;
otrEnable?: boolean;
pushEnable?: boolean;
globalSearchEnabled?: boolean;
threadsEnabled?: boolean;
bigBlueButton?: boolean;
jitsiEnabled?: boolean;
webRTCEnableChannel?: boolean;
webRTCEnablePrivate?: boolean;
webRTCEnableDirect?: boolean;
}
export interface ISettingStatisticsObject {
accounts?: {
account2fa?: boolean;
};
cannedResponses?: {
cannedResponsesEnabled?: boolean;
};
e2ee?: {
e2e?: boolean;
e2eDefaultDirectRoom?: boolean;
e2eDefaultPrivateRoom?: boolean;
};
email?: {
smtp?: {
smtpHost?: string;
smtpPort?: string;
fromEmail?: string;
};
};
general?: {
apps?: {
frameworkDevMode?: boolean;
frameworkEnable?: boolean;
};
nps?: {
surveyEnabled?: boolean;
};
update?: {
updateChecker?: boolean;
};
};
liveStreamAndBroadcasting?: {
liveStream?: boolean;
broadcasting?: boolean;
};
message?: {
allowEditing?: boolean;
allowDeleting?: boolean;
allowUnrecognizedSlashCommand?: boolean;
allowBadWordsFilter?: boolean;
readReceiptEnabled?: boolean;
readReceiptStoreUsers?: boolean;
};
otr?: {
otrEnable?: boolean;
};
push?: {
pushEnable?: boolean;
};
search?: {
defaultProvider?: {
globalSearchEnabled?: boolean;
};
};
threads?: {
threadsEnabled?: boolean;
};
videoConference?: {
bigBlueButton?: boolean;
jitsiEnabled?: boolean;
};
webRTC?: {
webRTCEnableChannel?: boolean;
webRTCEnablePrivate?: boolean;
webRTCEnableDirect?: boolean;
};
}

@ -0,0 +1,161 @@
import { Settings } from '../../../app/models/server/raw';
import { ISettingStatistics, ISettingStatisticsObject } from '../../../definition/ISetting';
const setSettingsStatistics = async (settings: ISettingStatistics): Promise<ISettingStatisticsObject> => {
const {
account2fa,
cannedResponsesEnabled,
e2e,
e2eDefaultDirectRoom,
e2eDefaultPrivateRoom,
smtpHost,
smtpPort,
fromEmail,
frameworkDevMode,
frameworkEnable,
surveyEnabled,
updateChecker,
liveStream,
broadcasting,
allowEditing,
allowDeleting,
allowUnrecognizedSlashCommand,
allowBadWordsFilter,
readReceiptEnabled,
readReceiptStoreUsers,
globalSearchEnabled,
otrEnable,
pushEnable,
threadsEnabled,
bigBlueButton,
jitsiEnabled,
webRTCEnableChannel,
webRTCEnablePrivate,
webRTCEnableDirect,
} = settings;
// If Canned Response does not exist add blank object to the statistic else add canned response object
const cannedRes = !cannedResponsesEnabled ? {} : { cannedResponses: { cannedResponsesEnabled } };
const statisticObject = {
accounts: {
account2fa,
},
...cannedRes,
e2ee: {
e2e,
e2eDefaultDirectRoom,
e2eDefaultPrivateRoom,
},
email: {
smtp: {
smtpHost,
smtpPort,
fromEmail,
},
},
general: {
apps: {
frameworkDevMode,
frameworkEnable,
},
nps: {
surveyEnabled,
},
update: {
updateChecker,
},
},
liveStreamAndBroadcasting: {
liveStream,
broadcasting,
},
message: {
allowEditing,
allowDeleting,
allowUnrecognizedSlashCommand,
allowBadWordsFilter,
readReceiptEnabled,
readReceiptStoreUsers,
},
otr: {
otrEnable,
},
push: {
pushEnable,
},
search: {
defaultProvider: {
globalSearchEnabled,
},
},
threads: {
threadsEnabled,
},
videoConference: {
bigBlueButton,
jitsiEnabled,
},
webRTC: {
webRTCEnableChannel,
webRTCEnablePrivate,
webRTCEnableDirect,
},
};
return statisticObject;
};
export const getSettingsStatistics = async (): Promise<ISettingStatisticsObject> => {
try {
const settingsBase = [
{ key: 'Accounts_TwoFactorAuthentication_Enabled', alias: 'account2fa' },
{ key: 'E2E_Enable', alias: 'e2e' },
{ key: 'E2E_Enabled_Default_DirectRooms', alias: 'e2eDefaultDirectRoom' },
{ key: 'E2E_Enabled_Default_PrivateRooms', alias: 'e2eDefaultPrivateRoom' },
{ key: 'SMTP_Host', alias: 'smtpHost' },
{ key: 'SMTP_Port', alias: 'smtpPort' },
{ key: 'From_Email', alias: 'fromEmail' },
{ key: 'Apps_Framework_Development_Mode', alias: 'frameworkDevMode' },
{ key: 'Apps_Framework_enabled', alias: 'frameworkEnable' },
{ key: 'NPS_survey_enabled', alias: 'surveyEnabled' },
{ key: 'Update_EnableChecker', alias: 'updateChecker' },
{ key: 'Livestream_enabled', alias: 'liveStream' },
{ key: 'Broadcasting_enabled', alias: 'broadcasting' },
{ key: 'Message_AllowEditing', alias: 'allowEditing' },
{ key: 'Message_AllowDeleting', alias: 'allowDeleting' },
{ key: 'Message_AllowUnrecognizedSlashCommand', alias: 'allowUnrecognizedSlashCommand' },
{ key: 'Message_AllowBadWordsFilter', alias: 'allowBadWordsFilter' },
{ key: 'Message_Read_Receipt_Enabled', alias: 'readReceiptEnabled' },
{ key: 'Message_Read_Receipt_Store_Users', alias: 'readReceiptStoreUsers' },
{ key: 'Search.defaultProvider.GlobalSearchEnabled', alias: 'globalSearchEnabled' },
{ key: 'OTR_Enable', alias: 'otrEnable' },
{ key: 'Push_enable', alias: 'pushEnable' },
{ key: 'Threads_enabled', alias: 'threadsEnabled' },
{ key: 'bigbluebutton_Enabled', alias: 'bigBlueButton' },
{ key: 'Jitsi_Enabled', alias: 'jitsiEnabled' },
{ key: 'WebRTC_Enable_Channel', alias: 'webRTCEnableChannel' },
{ key: 'WebRTC_Enable_Private', alias: 'webRTCEnablePrivate' },
{ key: 'WebRTC_Enable_Direct', alias: 'webRTCEnableDirect' },
{ key: 'Canned_Responses_Enable', alias: 'cannedResponses' },
];
// Mapping only _id values
const settingsIDs = settingsBase.map((el) => el.key);
const settingsStatistics = (
await Settings.findByIds(settingsIDs)
.map((el): ISettingStatistics => {
const alias = settingsBase.find((obj) => obj.key === el._id)?.alias || {};
if (!!alias && Object.keys(el).length) return { [String(alias)]: el.value };
return alias;
})
.filter((el: ISettingStatistics) => Object.keys(el).length) // Filter to remove all empty objects
.toArray()
).reduce((a, b) => Object.assign(a, b), {}); // Convert array to objects
const staticticObject = await setSettingsStatistics(settingsStatistics);
return staticticObject;
} catch (error: any) {
return error;
}
};
Loading…
Cancel
Save