From 0165786f65ad6bdc3a05e975b3948c28050508d0 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 23 Feb 2024 14:52:35 +0100 Subject: [PATCH] Add total moderators/admins in stats --- config/default.yaml | 6 +++ config/production.yaml.example | 6 +++ .../models/src/server/server-stats.model.ts | 3 ++ packages/tests/src/api/server/stats.ts | 19 ++++++- server/core/initializers/config.ts | 6 +++ server/core/lib/stat-manager.ts | 17 +++++- server/core/models/user/user.ts | 54 +++++++++---------- support/doc/api/openapi.yaml | 6 +++ 8 files changed, 88 insertions(+), 29 deletions(-) diff --git a/config/default.yaml b/config/default.yaml index 5a337341c..d4e773d46 100644 --- a/config/default.yaml +++ b/config/default.yaml @@ -456,6 +456,12 @@ stats: abuses: enabled: true + total_moderators: + enabled: true + + total_admins: + enabled: true + cache: previews: size: 500 # Max number of previews you want to cache diff --git a/config/production.yaml.example b/config/production.yaml.example index 2cfe4e215..5e9b62af6 100644 --- a/config/production.yaml.example +++ b/config/production.yaml.example @@ -454,6 +454,12 @@ stats: abuses: enabled: true + total_moderators: + enabled: true + + total_admins: + enabled: true + ############################################################################### # # From this point, almost all following keys can be overridden by the web interface diff --git a/packages/models/src/server/server-stats.model.ts b/packages/models/src/server/server-stats.model.ts index 42f04ea94..1f02a0d03 100644 --- a/packages/models/src/server/server-stats.model.ts +++ b/packages/models/src/server/server-stats.model.ts @@ -10,6 +10,9 @@ export interface ServerStats extends ActivityPubMessagesSuccess, ActivityPubMess totalWeeklyActiveUsers: number totalMonthlyActiveUsers: number + totalModerators: number + totalAdmins: number + totalLocalVideos: number totalLocalVideoViews: number totalLocalVideoComments: number diff --git a/packages/tests/src/api/server/stats.ts b/packages/tests/src/api/server/stats.ts index 248100fef..604664611 100644 --- a/packages/tests/src/api/server/stats.ts +++ b/packages/tests/src/api/server/stats.ts @@ -56,7 +56,11 @@ describe('Test stats (excluding redundancy)', function () { expect(data.totalLocalVideos).to.equal(1) expect(data.totalLocalVideoViews).to.equal(1) expect(data.totalLocalVideoFilesSize).to.equal(218910) + expect(data.totalUsers).to.equal(2) + expect(data.totalModerators).to.equal(0) + expect(data.totalAdmins).to.equal(1) + expect(data.totalVideoComments).to.equal(1) expect(data.totalVideos).to.equal(1) expect(data.totalInstanceFollowers).to.equal(2) @@ -71,7 +75,11 @@ describe('Test stats (excluding redundancy)', function () { expect(data.totalLocalVideos).to.equal(0) expect(data.totalLocalVideoViews).to.equal(0) expect(data.totalLocalVideoFilesSize).to.equal(0) + expect(data.totalUsers).to.equal(1) + expect(data.totalModerators).to.equal(0) + expect(data.totalAdmins).to.equal(1) + expect(data.totalVideoComments).to.equal(1) expect(data.totalVideos).to.equal(1) expect(data.totalInstanceFollowers).to.equal(1) @@ -85,7 +93,11 @@ describe('Test stats (excluding redundancy)', function () { expect(data.totalLocalVideoComments).to.equal(0) expect(data.totalLocalVideos).to.equal(0) expect(data.totalLocalVideoViews).to.equal(0) + expect(data.totalUsers).to.equal(1) + expect(data.totalModerators).to.equal(0) + expect(data.totalAdmins).to.equal(1) + expect(data.totalVideoComments).to.equal(1) expect(data.totalVideos).to.equal(1) expect(data.totalInstanceFollowing).to.equal(1) @@ -420,7 +432,9 @@ describe('Test stats (excluding redundancy)', function () { await servers[0].run({ stats: { registration_requests: { enabled: false }, - abuses: { enabled: false } + abuses: { enabled: false }, + total_admins: { enabled: false }, + total_moderators: { enabled: false } } }) @@ -433,6 +447,9 @@ describe('Test stats (excluding redundancy)', function () { expect(data.totalAbuses).to.be.null expect(data.totalAbusesProcessed).to.be.null expect(data.averageAbuseResponseTimeMs).to.be.null + + expect(data.totalAdmins).to.be.null + expect(data.totalModerators).to.be.null }) }) diff --git a/server/core/initializers/config.ts b/server/core/initializers/config.ts index b13de5e97..8410afc95 100644 --- a/server/core/initializers/config.ts +++ b/server/core/initializers/config.ts @@ -369,6 +369,12 @@ const CONFIG = { }, ABUSES: { ENABLED: config.get('stats.abuses.enabled') + }, + TOTAL_MODERATORS: { + ENABLED: config.get('stats.total_moderators.enabled') + }, + TOTAL_ADMINS: { + ENABLED: config.get('stats.total_admins.enabled') } }, ADMIN: { diff --git a/server/core/lib/stat-manager.ts b/server/core/lib/stat-manager.ts index 2d9176b24..3a44a5f70 100644 --- a/server/core/lib/stat-manager.ts +++ b/server/core/lib/stat-manager.ts @@ -49,7 +49,14 @@ class StatsManager { async getStats () { const { totalLocalVideos, totalLocalVideoViews, totalVideos } = await VideoModel.getStats() const { totalLocalVideoComments, totalVideoComments } = await VideoCommentModel.getStats() - const { totalUsers, totalDailyActiveUsers, totalWeeklyActiveUsers, totalMonthlyActiveUsers } = await UserModel.getStats() + const { + totalUsers, + totalDailyActiveUsers, + totalWeeklyActiveUsers, + totalMonthlyActiveUsers, + totalAdmins, + totalModerators + } = await UserModel.getStats() const { totalInstanceFollowers, totalInstanceFollowing } = await ActorFollowModel.getStats() const { totalLocalVideoFilesSize } = await VideoFileModel.getStats() const { @@ -68,6 +75,14 @@ class StatsManager { totalWeeklyActiveUsers, totalMonthlyActiveUsers, + totalModerators: CONFIG.STATS.TOTAL_MODERATORS.ENABLED + ? totalModerators + : null, + + totalAdmins: CONFIG.STATS.TOTAL_ADMINS.ENABLED + ? totalAdmins + : null, + totalLocalVideos, totalLocalVideoViews, totalLocalVideoComments, diff --git a/server/core/models/user/user.ts b/server/core/models/user/user.ts index 87cd31e0b..c2f8f81e0 100644 --- a/server/core/models/user/user.ts +++ b/server/core/models/user/user.ts @@ -8,7 +8,8 @@ import { VideoPlaylistType, type NSFWPolicyType, type UserAdminFlagType, - type UserRoleType + type UserRoleType, + UserRole } from '@peertube/peertube-models' import { TokensCache } from '@server/lib/auth/tokens-cache.js' import { LiveQuotaStore } from '@server/lib/live/index.js' @@ -67,7 +68,7 @@ import { ActorFollowModel } from '../actor/actor-follow.js' import { ActorImageModel } from '../actor/actor-image.js' import { ActorModel } from '../actor/actor.js' import { OAuthTokenModel } from '../oauth/oauth-token.js' -import { getAdminUsersSort, SequelizeModel, throwIfNotValid } from '../shared/index.js' +import { getAdminUsersSort, parseAggregateResult, SequelizeModel, throwIfNotValid } from '../shared/index.js' import { VideoChannelModel } from '../video/video-channel.js' import { VideoImportModel } from '../video/video-import.js' import { VideoLiveModel } from '../video/video-live.js' @@ -875,32 +876,31 @@ export class UserModel extends SequelizeModel { return parseInt(total, 10) } - static async getStats () { - function getActiveUsers (days: number) { - const query = { - where: { - [Op.and]: [ - literal(`"lastLoginDate" > NOW() - INTERVAL '${days}d'`) - ] - } + static getStats () { + const query = `SELECT ` + + `COUNT(*) AS "totalUsers", ` + + `COUNT(*) FILTER (WHERE "lastLoginDate" > NOW() - INTERVAL '1d') AS "totalDailyActiveUsers", ` + + `COUNT(*) FILTER (WHERE "lastLoginDate" > NOW() - INTERVAL '7d') AS "totalWeeklyActiveUsers", ` + + `COUNT(*) FILTER (WHERE "lastLoginDate" > NOW() - INTERVAL '30d') AS "totalMonthlyActiveUsers", ` + + `COUNT(*) FILTER (WHERE "lastLoginDate" > NOW() - INTERVAL '180d') AS "totalHalfYearActiveUsers", ` + + `COUNT(*) FILTER (WHERE "role" = ${UserRole.MODERATOR}) AS "totalModerators", ` + + `COUNT(*) FILTER (WHERE "role" = ${UserRole.ADMINISTRATOR}) AS "totalAdmins" ` + + `FROM "user"` + + return UserModel.sequelize.query(query, { + type: QueryTypes.SELECT, + raw: true + }).then(([ row ]) => { + return { + totalUsers: parseAggregateResult(row.totalUsers), + totalDailyActiveUsers: parseAggregateResult(row.totalDailyActiveUsers), + totalWeeklyActiveUsers: parseAggregateResult(row.totalWeeklyActiveUsers), + totalMonthlyActiveUsers: parseAggregateResult(row.totalMonthlyActiveUsers), + totalHalfYearActiveUsers: parseAggregateResult(row.totalHalfYearActiveUsers), + totalModerators: parseAggregateResult(row.totalModerators), + totalAdmins: parseAggregateResult(row.totalAdmins) } - - return UserModel.unscoped().count(query) - } - - const totalUsers = await UserModel.unscoped().count() - const totalDailyActiveUsers = await getActiveUsers(1) - const totalWeeklyActiveUsers = await getActiveUsers(7) - const totalMonthlyActiveUsers = await getActiveUsers(30) - const totalHalfYearActiveUsers = await getActiveUsers(180) - - return { - totalUsers, - totalDailyActiveUsers, - totalWeeklyActiveUsers, - totalMonthlyActiveUsers, - totalHalfYearActiveUsers - } + }) } static autoComplete (search: string) { diff --git a/support/doc/api/openapi.yaml b/support/doc/api/openapi.yaml index 200b037af..84ac053cb 100644 --- a/support/doc/api/openapi.yaml +++ b/support/doc/api/openapi.yaml @@ -8569,6 +8569,12 @@ components: type: number totalMonthlyActiveUsers: type: number + totalModerators: + type: number + description: "**PeerTube >= 6.1** Value is null if the admin disabled total moderators stats" + totalAdmins: + type: number + description: "**PeerTube >= 6.1** Value is null if the admin disabled total admins stats" totalLocalVideos: type: number totalLocalVideoViews: