diff --git a/apps/meteor/app/importer-csv/server/importer.js b/apps/meteor/app/importer-csv/server/importer.js index ce92a08c235d..d4d3d014dbfa 100644 --- a/apps/meteor/app/importer-csv/server/importer.js +++ b/apps/meteor/app/importer-csv/server/importer.js @@ -1,7 +1,7 @@ import { Random } from 'meteor/random'; import { Base, ProgressStep, ImporterWebsocket } from '../../importer/server'; -import { Users } from '../../models/server'; +import { Users, Settings as SettingsRaw } from '../../models/server'; export class CsvImporter extends Base { constructor(info, importRecord) { @@ -119,7 +119,8 @@ export class CsvImporter extends Base { }); } - super.updateRecord({ 'count.users': parsedUsers.length }); + SettingsRaw.incrementValueById('CSV_Importer_Count', usersCount); + super.updateRecord({ 'count.users': usersCount }); return increaseProgressCount(); } diff --git a/apps/meteor/app/importer-hipchat-enterprise/server/importer.js b/apps/meteor/app/importer-hipchat-enterprise/server/importer.js index c2c05c34032f..0e1fed9db759 100644 --- a/apps/meteor/app/importer-hipchat-enterprise/server/importer.js +++ b/apps/meteor/app/importer-hipchat-enterprise/server/importer.js @@ -5,6 +5,7 @@ import fs from 'fs'; import { Meteor } from 'meteor/meteor'; import { Base, ProgressStep } from '../../importer/server'; +import { Settings as SettingsRaw } from '../../models/server'; export class HipChatEnterpriseImporter extends Base { constructor(info, importRecord) { @@ -52,6 +53,7 @@ export class HipChatEnterpriseImporter extends Base { this.converter.addUser(newUser); } + SettingsRaw.incrementValueById('Hipchat_Enterprise_Importer_Count', count); super.updateRecord({ 'count.users': count }); super.addCountToTotal(count); } diff --git a/apps/meteor/app/importer-slack-users/server/importer.js b/apps/meteor/app/importer-slack-users/server/importer.js index 08c2992c766c..5fcf3924499b 100644 --- a/apps/meteor/app/importer-slack-users/server/importer.js +++ b/apps/meteor/app/importer-slack-users/server/importer.js @@ -5,6 +5,7 @@ import { Random } from 'meteor/random'; import { RawImports, Base, ProgressStep, Selection, SelectionUser } from '../../importer/server'; import { RocketChatFile } from '../../file'; import { Users } from '../../models'; +import { Settings as SettingsRaw } from '../../models/server'; export class SlackUsersImporter extends Base { constructor(info, importRecord) { @@ -164,6 +165,7 @@ export class SlackUsersImporter extends Base { }); } + SettingsRaw.incrementValueById('Slack_Users_Importer_Count', this.users.users.length); super.updateProgress(ProgressStep.FINISHING); super.updateProgress(ProgressStep.DONE); } catch (e) { diff --git a/apps/meteor/app/importer-slack/server/importer.js b/apps/meteor/app/importer-slack/server/importer.js index e2270576fcc5..8a9caa1775fe 100644 --- a/apps/meteor/app/importer-slack/server/importer.js +++ b/apps/meteor/app/importer-slack/server/importer.js @@ -1,7 +1,7 @@ import _ from 'underscore'; import { Base, ProgressStep, ImporterWebsocket } from '../../importer/server'; -import { Messages, ImportData } from '../../models/server'; +import { Messages, ImportData, Settings as SettingsRaw } from '../../models/server'; import { settings } from '../../settings/server'; import { MentionsParser } from '../../mentions/lib/MentionsParser'; import { getUserAvatarURL } from '../../utils/lib/getUserAvatarURL'; @@ -155,6 +155,7 @@ export class SlackImporter extends Base { } this.converter.addUser(newUser); + SettingsRaw.incrementValueById('Slack_Importer_Count'); } } diff --git a/apps/meteor/app/lib/server/methods/sendInvitationEmail.js b/apps/meteor/app/lib/server/methods/sendInvitationEmail.js index f5927e8c5a0e..89cbdce9eb97 100644 --- a/apps/meteor/app/lib/server/methods/sendInvitationEmail.js +++ b/apps/meteor/app/lib/server/methods/sendInvitationEmail.js @@ -4,6 +4,7 @@ import { check } from 'meteor/check'; import * as Mailer from '../../../mailer'; import { hasPermission } from '../../../authorization'; import { settings } from '../../../settings'; +import { Settings as SettingsRaw } from '../../../models/server'; let html = ''; Meteor.startup(() => { @@ -37,7 +38,7 @@ Meteor.methods({ return validEmails.filter((email) => { try { - return Mailer.send({ + const mailerResult = Mailer.send({ to: email, from: settings.get('From_Email'), subject, @@ -46,6 +47,9 @@ Meteor.methods({ email, }, }); + + SettingsRaw.incrementValueById('Invitation_Email_Count'); + return mailerResult; } catch ({ message }) { throw new Meteor.Error('error-email-send-failed', `Error trying to send email: ${message}`, { method: 'sendInvitationEmail', diff --git a/apps/meteor/app/lib/server/startup/email.ts b/apps/meteor/app/lib/server/startup/email.ts index b5e1d2f938c0..470599a41d52 100644 --- a/apps/meteor/app/lib/server/startup/email.ts +++ b/apps/meteor/app/lib/server/startup/email.ts @@ -474,6 +474,11 @@ settingsRegistry.addGroup('Email', function () { ); }); + this.add('Invitation_Email_Count', 0, { + type: 'int', + hidden: true, + }); + this.section('Forgot_password_section', function () { this.add('Forgot_Password_Email_Subject', '{Forgot_Password_Email_Subject}', { type: 'string', diff --git a/apps/meteor/app/lib/server/startup/settings.ts b/apps/meteor/app/lib/server/startup/settings.ts index b81c4e52d237..8683597ef833 100644 --- a/apps/meteor/app/lib/server/startup/settings.ts +++ b/apps/meteor/app/lib/server/startup/settings.ts @@ -180,6 +180,26 @@ settingsRegistry.addGroup('Accounts', function () { type: 'string', hidden: true, }); + this.add('Manual_Entry_User_Count', 0, { + type: 'int', + hidden: true, + }); + this.add('CSV_Importer_Count', 0, { + type: 'int', + hidden: true, + }); + this.add('Hipchat_Enterprise_Importer_Count', 0, { + type: 'int', + hidden: true, + }); + this.add('Slack_Importer_Count', 0, { + type: 'int', + hidden: true, + }); + this.add('Slack_Users_Importer_Count', 0, { + type: 'int', + hidden: true, + }); this.add('Accounts_UseDefaultBlockedDomainsList', true, { type: 'boolean', }); diff --git a/apps/meteor/app/models/server/models/Rooms.js b/apps/meteor/app/models/server/models/Rooms.js index bc8932afe570..593fdab626da 100644 --- a/apps/meteor/app/models/server/models/Rooms.js +++ b/apps/meteor/app/models/server/models/Rooms.js @@ -26,6 +26,7 @@ export class Rooms extends Base { // field used for DMs only this.tryEnsureIndex({ uids: 1 }, { sparse: true }); this.tryEnsureIndex({ createdOTR: 1 }, { sparse: true }); + this.tryEnsureIndex({ encrypted: 1 }, { sparse: true }); // used on statistics this.tryEnsureIndex( { diff --git a/apps/meteor/app/models/server/models/Settings.js b/apps/meteor/app/models/server/models/Settings.js index 19d7059e76a7..c2ef98d35476 100644 --- a/apps/meteor/app/models/server/models/Settings.js +++ b/apps/meteor/app/models/server/models/Settings.js @@ -145,7 +145,7 @@ export class Settings extends Base { return this.update(query, update); } - incrementValueById(_id) { + incrementValueById(_id, value = 1) { const query = { blocked: { $ne: true }, _id, @@ -153,7 +153,7 @@ export class Settings extends Base { const update = { $inc: { - value: 1, + value, }, }; diff --git a/apps/meteor/app/models/server/models/Users.js b/apps/meteor/app/models/server/models/Users.js index 0a794af7e32f..3e8eb705d3d3 100644 --- a/apps/meteor/app/models/server/models/Users.js +++ b/apps/meteor/app/models/server/models/Users.js @@ -60,6 +60,9 @@ export class Users extends Base { this.tryEnsureIndex({ extension: 1 }, { sparse: true, unique: true }); this.tryEnsureIndex({ language: 1 }, { sparse: true }); + this.tryEnsureIndex({ 'active': 1, 'services.email2fa.enabled': 1 }, { sparse: true }); // used by statistics + this.tryEnsureIndex({ 'active': 1, 'services.totp.enabled': 1 }, { sparse: true }); // used by statistics + const collectionObj = this.model.rawCollection(); this.findAndModify = Meteor.wrapAsync(collectionObj.findAndModify, collectionObj); } diff --git a/apps/meteor/app/models/server/raw/Invites.ts b/apps/meteor/app/models/server/raw/Invites.ts index 2f3e9d835a92..ef55e009ca8e 100644 --- a/apps/meteor/app/models/server/raw/Invites.ts +++ b/apps/meteor/app/models/server/raw/Invites.ts @@ -27,4 +27,12 @@ export class InvitesRaw extends BaseRaw { }, ); } + + async countUses(): Promise { + const [result] = await this.col + .aggregate<{ totalUses: number } | undefined>([{ $group: { _id: null, totalUses: { $sum: '$uses' } } }]) + .toArray(); + + return result?.totalUses || 0; + } } diff --git a/apps/meteor/app/models/server/raw/Messages.js b/apps/meteor/app/models/server/raw/Messages.js index ce0343ded065..7f84cac25420 100644 --- a/apps/meteor/app/models/server/raw/Messages.js +++ b/apps/meteor/app/models/server/raw/Messages.js @@ -188,4 +188,46 @@ export class MessagesRaw extends BaseRaw { options, ); } + + async countRoomsWithStarredMessages(options) { + const [queryResult] = await this.col + .aggregate( + [{ $match: { 'starred._id': { $exists: true } } }, { $group: { _id: '$rid' } }, { $group: { _id: null, total: { $sum: 1 } } }], + options, + ) + .toArray(); + + return queryResult?.total || 0; + } + + async countRoomsWithPinnedMessages(options) { + const [queryResult] = await this.col + .aggregate([{ $match: { pinned: true } }, { $group: { _id: '$rid' } }, { $group: { _id: null, total: { $sum: 1 } } }], options) + .toArray(); + + return queryResult?.total || 0; + } + + async countE2EEMessages(options) { + return this.find({ t: 'e2e' }, options).count(); + } + + findPinned(options) { + const query = { + t: { $ne: 'rm' }, + _hidden: { $ne: true }, + pinned: true, + }; + + return this.find(query, options); + } + + findStarred(options) { + const query = { + '_hidden': { $ne: true }, + 'starred._id': { $exists: true }, + }; + + return this.find(query, options); + } } diff --git a/apps/meteor/app/models/server/raw/Rooms.js b/apps/meteor/app/models/server/raw/Rooms.js index a7e338db5f68..1f147c87966f 100644 --- a/apps/meteor/app/models/server/raw/Rooms.js +++ b/apps/meteor/app/models/server/raw/Rooms.js @@ -460,4 +460,21 @@ export class RoomsRaw extends BaseRaw { }, ]); } + + findByE2E(options) { + return this.find( + { + encrypted: true, + }, + options, + ); + } + + findRoomsInsideTeams(autoJoin = false) { + return this.find({ + teamId: { $exists: true }, + teamMain: { $exists: false }, + ...(autoJoin && { teamDefault: true }), + }); + } } diff --git a/apps/meteor/app/models/server/raw/Users.js b/apps/meteor/app/models/server/raw/Users.js index 29dfd1c3a49f..c6b4f0809d22 100644 --- a/apps/meteor/app/models/server/raw/Users.js +++ b/apps/meteor/app/models/server/raw/Users.js @@ -999,4 +999,20 @@ export class UsersRaw extends BaseRaw { return this.find(query, options); } + + findActiveUsersTOTPEnable(options) { + const query = { + 'active': true, + 'services.totp.enabled': true, + }; + return this.find(query, options); + } + + findActiveUsersEmail2faEnable(options) { + const query = { + 'active': true, + 'services.email2fa.enabled': true, + }; + return this.find(query, options); + } } diff --git a/apps/meteor/app/statistics/server/lib/getImporterStatistics.ts b/apps/meteor/app/statistics/server/lib/getImporterStatistics.ts new file mode 100644 index 000000000000..0f8fc5beccc5 --- /dev/null +++ b/apps/meteor/app/statistics/server/lib/getImporterStatistics.ts @@ -0,0 +1,10 @@ +import { settings } from '../../../settings/server'; + +export function getImporterStatistics(): Record { + return { + totalCSVImportedUsers: settings.get('CSV_Importer_Count'), + totalHipchatEnterpriseImportedUsers: settings.get('Hipchat_Enterprise_Importer_Count'), + totalSlackImportedUsers: settings.get('Slack_Importer_Count'), + totalSlackUsersImportedUsers: settings.get('Slack_Users_Importer_Count'), + }; +} diff --git a/apps/meteor/app/statistics/server/lib/getServicesStatistics.ts b/apps/meteor/app/statistics/server/lib/getServicesStatistics.ts index b41cbb2d8385..b0aec1438019 100644 --- a/apps/meteor/app/statistics/server/lib/getServicesStatistics.ts +++ b/apps/meteor/app/statistics/server/lib/getServicesStatistics.ts @@ -1,6 +1,11 @@ +import { MongoInternals } from 'meteor/mongo'; + +import { readSecondaryPreferred } from '../../../../server/database/readSecondaryPreferred'; import { settings } from '../../../settings/server'; import { Users } from '../../../models/server'; +const { db } = MongoInternals.defaultRemoteCollectionDriver().mongo; + function getCustomOAuthServices(): Record< string, { @@ -9,6 +14,8 @@ function getCustomOAuthServices(): Record< users: number; } > { + const readPreference = readSecondaryPreferred(db); + const customOauth = settings.getByRegexp(/Accounts_OAuth_Custom-[^-]+$/im); return Object.fromEntries( Object.entries(customOauth).map(([key, value]) => { @@ -18,7 +25,7 @@ function getCustomOAuthServices(): Record< { enabled: Boolean(value), mergeRoles: settings.get(`Accounts_OAuth_Custom-${name}-merge_roles`), - users: Users.countActiveUsersByService(name), + users: Users.countActiveUsersByService(name, { readPreference }), }, ]; }), @@ -26,9 +33,11 @@ function getCustomOAuthServices(): Record< } export function getServicesStatistics(): Record { + const readPreference = readSecondaryPreferred(db); + return { ldap: { - users: Users.countActiveUsersByService('ldap'), + users: Users.countActiveUsersByService('ldap', { readPreference }), enabled: settings.get('LDAP_Enable'), loginFallback: settings.get('LDAP_Login_Fallback'), encryption: settings.get('LDAP_Encryption'), @@ -53,7 +62,7 @@ export function getServicesStatistics(): Record { }, saml: { enabled: settings.get('SAML_Custom_Default'), - users: Users.countActiveUsersByService('saml'), + users: Users.countActiveUsersByService('saml', { readPreference }), signatureValidationType: settings.get('SAML_Custom_Default_signature_validation_type'), generateUsername: settings.get('SAML_Custom_Default_generate_username'), updateSubscriptionsOnLogin: settings.get('SAML_Custom_Default_channels_update'), @@ -61,66 +70,66 @@ export function getServicesStatistics(): Record { }, cas: { enabled: settings.get('CAS_enabled'), - users: Users.countActiveUsersByService('cas'), + users: Users.countActiveUsersByService('cas', { readPreference }), allowUserCreation: settings.get('CAS_Creation_User_Enabled'), alwaysSyncUserData: settings.get('CAS_Sync_User_Data_Enabled'), }, oauth: { apple: { enabled: settings.get('Accounts_OAuth_Apple'), - users: Users.countActiveUsersByService('apple'), + users: Users.countActiveUsersByService('apple', { readPreference }), }, dolphin: { enabled: settings.get('Accounts_OAuth_Dolphin'), - users: Users.countActiveUsersByService('dolphin'), + users: Users.countActiveUsersByService('dolphin', { readPreference }), }, drupal: { enabled: settings.get('Accounts_OAuth_Drupal'), - users: Users.countActiveUsersByService('drupal'), + users: Users.countActiveUsersByService('drupal', { readPreference }), }, facebook: { enabled: settings.get('Accounts_OAuth_Facebook'), - users: Users.countActiveUsersByService('facebook'), + users: Users.countActiveUsersByService('facebook', { readPreference }), }, github: { enabled: settings.get('Accounts_OAuth_Github'), - users: Users.countActiveUsersByService('github'), + users: Users.countActiveUsersByService('github', { readPreference }), }, githubEnterprise: { enabled: settings.get('Accounts_OAuth_GitHub_Enterprise'), - users: Users.countActiveUsersByService('github_enterprise'), + users: Users.countActiveUsersByService('github_enterprise', { readPreference }), }, gitlab: { enabled: settings.get('Accounts_OAuth_Gitlab'), - users: Users.countActiveUsersByService('gitlab'), + users: Users.countActiveUsersByService('gitlab', { readPreference }), }, google: { enabled: settings.get('Accounts_OAuth_Google'), - users: Users.countActiveUsersByService('google'), + users: Users.countActiveUsersByService('google', { readPreference }), }, linkedin: { enabled: settings.get('Accounts_OAuth_Linkedin'), - users: Users.countActiveUsersByService('linkedin'), + users: Users.countActiveUsersByService('linkedin', { readPreference }), }, meteor: { enabled: settings.get('Accounts_OAuth_Meteor'), - users: Users.countActiveUsersByService('meteor'), + users: Users.countActiveUsersByService('meteor', { readPreference }), }, nextcloud: { enabled: settings.get('Accounts_OAuth_Nextcloud'), - users: Users.countActiveUsersByService('nextcloud'), + users: Users.countActiveUsersByService('nextcloud', { readPreference }), }, tokenpass: { enabled: settings.get('Accounts_OAuth_Tokenpass'), - users: Users.countActiveUsersByService('tokenpass'), + users: Users.countActiveUsersByService('tokenpass', { readPreference }), }, twitter: { enabled: settings.get('Accounts_OAuth_Twitter'), - users: Users.countActiveUsersByService('twitter'), + users: Users.countActiveUsersByService('twitter', { readPreference }), }, wordpress: { enabled: settings.get('Accounts_OAuth_Wordpress'), - users: Users.countActiveUsersByService('wordpress'), + users: Users.countActiveUsersByService('wordpress', { readPreference }), }, custom: getCustomOAuthServices(), }, diff --git a/apps/meteor/app/statistics/server/lib/statistics.ts b/apps/meteor/app/statistics/server/lib/statistics.ts index 9471d1d1c1eb..051456c55ae7 100644 --- a/apps/meteor/app/statistics/server/lib/statistics.ts +++ b/apps/meteor/app/statistics/server/lib/statistics.ts @@ -18,6 +18,7 @@ import { Statistics, Sessions, Integrations, + Invites, Uploads, LivechatDepartment, EmailInbox, @@ -27,9 +28,10 @@ import { } from '../../../models/server/raw'; import { readSecondaryPreferred } from '../../../../server/database/readSecondaryPreferred'; import { getAppsStatistics } from './getAppsStatistics'; +import { getImporterStatistics } from './getImporterStatistics'; import { getServicesStatistics } from './getServicesStatistics'; import { getStatistics as getEnterpriseStatistics } from '../../../../ee/app/license/server'; -import { Analytics } from '../../../../server/sdk'; +import { Analytics, Team } from '../../../../server/sdk'; import { getSettingsStatistics } from '../../../../server/lib/statistics/getSettingsStatistics'; const wizardFields = ['Organization_Type', 'Industry', 'Size', 'Country', 'Language', 'Server_Type', 'Register_Server']; @@ -395,6 +397,7 @@ export const statistics = { statistics.apps = getAppsStatistics(); statistics.services = getServicesStatistics(); + statistics.importer = getImporterStatistics(); // If getSettingsStatistics() returns an error, save as empty object. statsPms.push( @@ -448,6 +451,12 @@ export const statistics = { }), ); + statsPms.push( + Team.getStatistics().then((result) => { + statistics.teams = result; + }), + ); + statsPms.push(Analytics.resetSeatRequestCount()); statistics.dashboardCount = settings.get('Engagement_Dashboard_Load_Count'); @@ -457,6 +466,21 @@ export const statistics = { statistics.slashCommandsJitsi = settings.get('Jitsi_Start_SlashCommands_Count'); statistics.totalOTRRooms = Rooms.findByCreatedOTR().count(); statistics.totalOTR = settings.get('OTR_Count'); + statistics.totalRoomsWithStarred = await MessagesRaw.countRoomsWithStarredMessages({ readPreference }); + statistics.totalRoomsWithPinned = await MessagesRaw.countRoomsWithPinnedMessages({ readPreference }); + statistics.totalUserTOTP = await UsersRaw.findActiveUsersTOTPEnable({ readPreference }).count(); + statistics.totalUserEmail2fa = await UsersRaw.findActiveUsersEmail2faEnable({ readPreference }).count(); + statistics.totalPinned = await MessagesRaw.findPinned({ readPreference }).count(); + statistics.totalStarred = await MessagesRaw.findStarred({ readPreference }).count(); + statistics.totalLinkInvitation = await Invites.find().count(); + statistics.totalLinkInvitationUses = await Invites.countUses(); + statistics.totalEmailInvitation = settings.get('Invitation_Email_Count'); + statistics.totalE2ERooms = await RoomsRaw.findByE2E({ readPreference }).count(); + statistics.logoChange = Object.keys(settings.get('Assets_logo')).includes('url'); + statistics.homeTitleChanged = settings.get('Layout_Home_Title') !== 'Home'; + statistics.showHomeButton = settings.get('Layout_Show_Home_Button'); + statistics.totalEncryptedMessages = await MessagesRaw.countE2EEMessages({ readPreference }); + statistics.totalManuallyAddedUsers = settings.get('Manual_Entry_User_Count'); await Promise.all(statsPms).catch(log); diff --git a/apps/meteor/client/views/admin/info/DeploymentCard.stories.tsx b/apps/meteor/client/views/admin/info/DeploymentCard.stories.tsx index 3e37793b83d8..aa8eb90a6b03 100644 --- a/apps/meteor/client/views/admin/info/DeploymentCard.stories.tsx +++ b/apps/meteor/client/views/admin/info/DeploymentCard.stories.tsx @@ -73,18 +73,8 @@ export default { userLanguages: {}, teams: { totalTeams: 0, - teamStats: [ - { - teamId: '', - mainRoom: '', - totalRooms: 0, - totalMessages: 0, - totalPublicRooms: 0, - totalPrivateRooms: 0, - totalDefaultRooms: 0, - totalMembers: 0, - }, - ], + totalRoomsInsideTeams: 0, + totalDefaultRoomsInsideTeams: 0, }, totalLivechatVisitors: 0, totalLivechatAgents: 0, @@ -179,6 +169,7 @@ export default { totalFailed: 0, }, services: {}, + importer: {}, settings: {}, integrations: { totalIntegrations: 0, @@ -199,8 +190,7 @@ export default { }, createdAt: new Date(), showHomeButton: false, - homeTitle: '', - homeBody: '', + homeTitleChanged: false, logoChange: false, customCSS: 0, customScript: 0, @@ -228,6 +218,13 @@ export default { messageAuditLoad: 0, dashboardCount: 0, joinJitsiButton: 0, + totalLinkInvitation: 0, + roomsWithPinnedMessages: 0, + roomsWithStarredMessages: 0, + roomsInsideTeams: 0, + totalEncryptedMessages: 0, + totalLinkInvitationUses: 0, + totalManuallyAddedUsers: 0, }, instances: [], }, diff --git a/apps/meteor/client/views/admin/info/InformationPage.stories.tsx b/apps/meteor/client/views/admin/info/InformationPage.stories.tsx index 4a018a36da80..b969929cab1d 100644 --- a/apps/meteor/client/views/admin/info/InformationPage.stories.tsx +++ b/apps/meteor/client/views/admin/info/InformationPage.stories.tsx @@ -103,18 +103,8 @@ export default { userLanguages: {}, teams: { totalTeams: 0, - teamStats: [ - { - teamId: '', - mainRoom: '', - totalRooms: 0, - totalMessages: 0, - totalPublicRooms: 0, - totalPrivateRooms: 0, - totalDefaultRooms: 0, - totalMembers: 0, - }, - ], + totalRoomsInsideTeams: 0, + totalDefaultRoomsInsideTeams: 0, }, totalLivechatVisitors: 0, totalLivechatAgents: 0, @@ -209,6 +199,7 @@ export default { totalFailed: 0, }, services: {}, + importer: {}, settings: {}, integrations: { totalIntegrations: 0, @@ -229,8 +220,7 @@ export default { }, createdAt: new Date(), showHomeButton: false, - homeTitle: '', - homeBody: '', + homeTitleChanged: false, logoChange: false, customCSS: 0, customScript: 0, @@ -258,6 +248,13 @@ export default { messageAuditLoad: 0, dashboardCount: 0, joinJitsiButton: 0, + totalLinkInvitation: 0, + roomsWithPinnedMessages: 0, + roomsWithStarredMessages: 0, + roomsInsideTeams: 0, + totalEncryptedMessages: 0, + totalLinkInvitationUses: 0, + totalManuallyAddedUsers: 0, }, instances: [], }, diff --git a/apps/meteor/client/views/admin/info/UsageCard.stories.tsx b/apps/meteor/client/views/admin/info/UsageCard.stories.tsx index 90fe35cc3570..5775962fd5f5 100644 --- a/apps/meteor/client/views/admin/info/UsageCard.stories.tsx +++ b/apps/meteor/client/views/admin/info/UsageCard.stories.tsx @@ -51,18 +51,8 @@ export default { userLanguages: {}, teams: { totalTeams: 0, - teamStats: [ - { - teamId: '', - mainRoom: '', - totalRooms: 0, - totalMessages: 0, - totalPublicRooms: 0, - totalPrivateRooms: 0, - totalDefaultRooms: 0, - totalMembers: 0, - }, - ], + totalRoomsInsideTeams: 0, + totalDefaultRoomsInsideTeams: 0, }, totalLivechatVisitors: 0, totalLivechatAgents: 0, @@ -157,6 +147,7 @@ export default { totalFailed: 0, }, services: {}, + importer: {}, settings: {}, integrations: { totalIntegrations: 0, @@ -177,8 +168,7 @@ export default { }, createdAt: new Date(), showHomeButton: false, - homeTitle: '', - homeBody: '', + homeTitleChanged: false, logoChange: false, customCSS: 0, customScript: 0, @@ -206,6 +196,13 @@ export default { messageAuditLoad: 0, dashboardCount: 0, joinJitsiButton: 0, + totalLinkInvitation: 0, + roomsWithPinnedMessages: 0, + roomsWithStarredMessages: 0, + roomsInsideTeams: 0, + totalEncryptedMessages: 0, + totalLinkInvitationUses: 0, + totalManuallyAddedUsers: 0, }, }, } as ComponentMeta; diff --git a/apps/meteor/client/views/admin/users/AddUser.js b/apps/meteor/client/views/admin/users/AddUser.js index 8c644b6a55ae..010d20659b81 100644 --- a/apps/meteor/client/views/admin/users/AddUser.js +++ b/apps/meteor/client/views/admin/users/AddUser.js @@ -78,6 +78,9 @@ export function AddUser({ roles, onReload, ...props }) { ); const saveAction = useEndpointAction('POST', 'users.create', values, t('User_created_successfully!')); + const eventStats = useEndpointAction('POST', 'statistics.telemetry', { + params: [{ eventName: 'updateCounter', settingsId: 'Manual_Entry_User_Count' }], + }); const handleSave = useMutableCallback(async () => { Object.entries(values).forEach(([key, value]) => { @@ -94,6 +97,7 @@ export function AddUser({ roles, onReload, ...props }) { const result = await saveAction(); if (result.success) { + eventStats(); goToUser(result.user._id); onReload(); } diff --git a/apps/meteor/server/sdk/types/ITeamService.ts b/apps/meteor/server/sdk/types/ITeamService.ts index f18437e7af1d..1564ae5c6cdd 100644 --- a/apps/meteor/server/sdk/types/ITeamService.ts +++ b/apps/meteor/server/sdk/types/ITeamService.ts @@ -1,5 +1,5 @@ +import { IPaginationOptions, IQueryOptions, IRecordsWithTotal, ITeam, ITeamMember, ITeamStats, TEAM_TYPE } from '@rocket.chat/core-typings'; import { FilterQuery, FindOneOptions, WithoutProjection } from 'mongodb'; -import { ITeam, IRecordsWithTotal, IPaginationOptions, IQueryOptions, ITeamMember, TEAM_TYPE } from '@rocket.chat/core-typings'; import type { IRoom, IUser, IRole } from '@rocket.chat/core-typings'; import { ICreateRoomParams } from './IRoomService'; @@ -111,4 +111,5 @@ export interface ITeamService { removeMemberFromTeams(userId: string, teamIds: Array): Promise; removeAllMembersFromTeam(teamId: string): Promise; removeRolesFromMember(teamId: string, userId: string, roles: Array): Promise; + getStatistics(): Promise; } diff --git a/apps/meteor/server/services/team/service.ts b/apps/meteor/server/services/team/service.ts index e17951c04731..7df0e05958e7 100644 --- a/apps/meteor/server/services/team/service.ts +++ b/apps/meteor/server/services/team/service.ts @@ -8,7 +8,6 @@ import { addUserToRoom } from '../../../app/lib/server/functions/addUserToRoom'; import { removeUserFromRoom } from '../../../app/lib/server/functions/removeUserFromRoom'; import { getSubscribedRoomsForUserWithDetails } from '../../../app/lib/server/functions/getRoomsWithSingleOwner'; import type { InsertionModel } from '../../../app/models/server/raw/BaseRaw'; -import { MessagesRaw } from '../../../app/models/server/raw/Messages'; import { RoomsRaw } from '../../../app/models/server/raw/Rooms'; import { SubscriptionsRaw } from '../../../app/models/server/raw/Subscriptions'; import { TeamRaw } from '../../../app/models/server/raw/Team'; @@ -43,8 +42,6 @@ export class TeamService extends ServiceClassInternal implements ITeamService { private TeamMembersModel: TeamMemberRaw; - private MessagesModel: MessagesRaw; - constructor(db: Db) { super(); @@ -55,7 +52,6 @@ export class TeamService extends ServiceClassInternal implements ITeamService { }); this.TeamModel = new TeamRaw(db.collection('rocketchat_team')); this.TeamMembersModel = new TeamMemberRaw(db.collection('rocketchat_team_member')); - this.MessagesModel = new MessagesRaw(db.collection('rocketchat_message')); } async create(uid: string, { team, room = { name: team.name, extraData: {} }, members, owner }: ITeamCreateParams): Promise { @@ -958,41 +954,11 @@ export class TeamService extends ServiceClassInternal implements ITeamService { } async getStatistics(): Promise { - const stats = {} as ITeamStats; - const teams = this.TeamModel.find({}); - const teamsArray = await teams.toArray(); - - stats.totalTeams = await teams.count(); - stats.teamStats = []; - - for await (const team of teamsArray) { - // exclude the main room from the stats - const teamRooms = await this.RoomsModel.find({ - teamId: team._id, - teamMain: { $exists: false }, - }).toArray(); - const roomIds = teamRooms.map((r) => r._id); - const [totalMessagesInTeam, defaultRooms, totalMembers] = await Promise.all([ - this.MessagesModel.find({ rid: { $in: roomIds } }).count(), - this.RoomsModel.findDefaultRoomsForTeam(team._id).count(), - this.TeamMembersModel.findByTeamId(team._id).count(), - ]); - - const teamData = { - teamId: team._id, - mainRoom: team.roomId, - totalRooms: teamRooms.length, - totalMessages: totalMessagesInTeam, - totalPublicRooms: teamRooms.filter((r) => r.t === 'c').length, - totalPrivateRooms: teamRooms.filter((r) => r.t !== 'c').length, - totalDefaultRooms: defaultRooms, - totalMembers, - }; - - stats.teamStats.push(teamData); - } - - return stats; + return { + totalTeams: await this.TeamModel.find({}).count(), + totalRoomsInsideTeams: await this.RoomsModel.findRoomsInsideTeams().count(), + totalDefaultRoomsInsideTeams: await this.RoomsModel.findRoomsInsideTeams(true).count(), + }; } async autocomplete(uid: string, name: string): Promise { diff --git a/packages/core-typings/src/IStats.ts b/packages/core-typings/src/IStats.ts index 01ce617c140c..fd13428438c8 100644 --- a/packages/core-typings/src/IStats.ts +++ b/packages/core-typings/src/IStats.ts @@ -119,6 +119,7 @@ export interface IStats { totalFailed: number | false; }; services: Record; + importer: Record; settings: ISettingStatisticsObject; integrations: { totalIntegrations: number; @@ -145,4 +146,22 @@ export interface IStats { messageAuditLoad: number; dashboardCount: number; joinJitsiButton: number; + totalRoomsWithStarred: number; + totalRoomsWithPinned: number; + totalUserEmail2fa: number; + totalUserTOTP: number; + totalStarred: number; + totalPinned: number; + totalLinkInvitation: number; + totalEmailInvitation: number; + roomsWithPinnedMessages: number; + roomsWithStarredMessages: number; + totalE2ERooms: number; + logoChange: boolean; + homeTitleChanged: boolean; + roomsInsideTeams: number; + showHomeButton: boolean; + totalEncryptedMessages: number; + totalLinkInvitationUses: number; + totalManuallyAddedUsers: number; } diff --git a/packages/core-typings/src/ITeam.ts b/packages/core-typings/src/ITeam.ts index 6616af53213d..a5e4c3de31b2 100644 --- a/packages/core-typings/src/ITeam.ts +++ b/packages/core-typings/src/ITeam.ts @@ -46,19 +46,10 @@ export interface IRecordsWithTotal { total: number; } -export interface ITeamStatData { - teamId: string; - mainRoom: string; - totalRooms: number; - totalMessages: number; - totalPublicRooms: number; - totalPrivateRooms: number; - totalDefaultRooms: number; - totalMembers: number; -} export interface ITeamStats { totalTeams: number; - teamStats: Array; + totalRoomsInsideTeams: number; + totalDefaultRoomsInsideTeams: number; } // TODO: move to service sdk package