From c0ef13a0bfd254dc5837303c16f9fd655ba69736 Mon Sep 17 00:00:00 2001 From: Heitor Tanoue <68477006+heitortanoue@users.noreply.github.com> Date: Tue, 3 Oct 2023 12:44:12 -0300 Subject: [PATCH 1/5] feat: push notification statistics (#30269) Co-authored-by: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> --- .changeset/nice-chairs-add.md | 13 +++++++++++++ apps/meteor/app/statistics/server/lib/statistics.ts | 9 +++++++++ .../views/admin/info/DeploymentCard.stories.tsx | 1 + .../views/admin/info/InformationPage.stories.tsx | 1 + .../client/views/admin/info/UsageCard.stories.tsx | 1 + packages/core-typings/src/IStats.ts | 1 + 6 files changed, 26 insertions(+) create mode 100644 .changeset/nice-chairs-add.md diff --git a/.changeset/nice-chairs-add.md b/.changeset/nice-chairs-add.md new file mode 100644 index 000000000000..dfc9d763e1c0 --- /dev/null +++ b/.changeset/nice-chairs-add.md @@ -0,0 +1,13 @@ +--- +"@rocket.chat/meteor": minor +"@rocket.chat/core-typings": minor +--- + +Added `push` statistic, containing three bits. Each bit represents a boolean: +``` +1 1 1 +| | | +| | +- push enabled = 0b1 = 1 +| +--- push gateway enabled = 0b10 = 2 ++----- push gateway changed = 0b100 = 4 +``` diff --git a/apps/meteor/app/statistics/server/lib/statistics.ts b/apps/meteor/app/statistics/server/lib/statistics.ts index b6b983d92fce..89b068c11341 100644 --- a/apps/meteor/app/statistics/server/lib/statistics.ts +++ b/apps/meteor/app/statistics/server/lib/statistics.ts @@ -517,6 +517,15 @@ export const statistics = { statistics.totalWebRTCCalls = settings.get('WebRTC_Calls_Count'); statistics.uncaughtExceptionsCount = settings.get('Uncaught_Exceptions_Count'); + const defaultGateway = (await Settings.findOneById('Push_gateway', { projection: { packageValue: 1 } }))?.packageValue; + + // one bit for each of the following: + const pushEnabled = settings.get('Push_enable') ? 1 : 0; + const pushGatewayEnabled = settings.get('Push_enable_gateway') ? 2 : 0; + const pushGatewayChanged = settings.get('Push_gateway') !== defaultGateway ? 4 : 0; + + statistics.push = pushEnabled | pushGatewayEnabled | pushGatewayChanged; + const defaultHomeTitle = (await Settings.findOneById('Layout_Home_Title'))?.packageValue; statistics.homeTitleChanged = settings.get('Layout_Home_Title') !== defaultHomeTitle; diff --git a/apps/meteor/client/views/admin/info/DeploymentCard.stories.tsx b/apps/meteor/client/views/admin/info/DeploymentCard.stories.tsx index ebb92b040c83..98aa3a7073ff 100644 --- a/apps/meteor/client/views/admin/info/DeploymentCard.stories.tsx +++ b/apps/meteor/client/views/admin/info/DeploymentCard.stories.tsx @@ -265,6 +265,7 @@ export default { totalCustomRoles: 0, totalWebRTCCalls: 0, uncaughtExceptionsCount: 0, + push: 0, matrixFederation: { enabled: false, }, diff --git a/apps/meteor/client/views/admin/info/InformationPage.stories.tsx b/apps/meteor/client/views/admin/info/InformationPage.stories.tsx index 222f31f88334..a6ef0c8e9289 100644 --- a/apps/meteor/client/views/admin/info/InformationPage.stories.tsx +++ b/apps/meteor/client/views/admin/info/InformationPage.stories.tsx @@ -295,6 +295,7 @@ export default { totalCustomRoles: 0, totalWebRTCCalls: 0, uncaughtExceptionsCount: 0, + push: 0, matrixFederation: { enabled: false, }, diff --git a/apps/meteor/client/views/admin/info/UsageCard.stories.tsx b/apps/meteor/client/views/admin/info/UsageCard.stories.tsx index 14a6cac8633d..da49ee88fa6b 100644 --- a/apps/meteor/client/views/admin/info/UsageCard.stories.tsx +++ b/apps/meteor/client/views/admin/info/UsageCard.stories.tsx @@ -243,6 +243,7 @@ export default { totalCustomRoles: 0, totalWebRTCCalls: 0, uncaughtExceptionsCount: 0, + push: 0, matrixFederation: { enabled: false, }, diff --git a/packages/core-typings/src/IStats.ts b/packages/core-typings/src/IStats.ts index cd8aeb9f1762..6bbc2da81b74 100644 --- a/packages/core-typings/src/IStats.ts +++ b/packages/core-typings/src/IStats.ts @@ -211,6 +211,7 @@ export interface IStats { totalCustomRoles: number; totalWebRTCCalls: number; uncaughtExceptionsCount: number; + push: number; matrixFederation: { enabled: boolean; }; From 1065cd8870cc9df1d3824b91b8760bd4c12d3ec5 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Tue, 3 Oct 2023 14:28:14 -0300 Subject: [PATCH 2/5] regression: fix initializing startup order (#30555) --- apps/meteor/ee/server/index.ts | 2 -- apps/meteor/server/main.ts | 3 ++- ee/packages/presence/package.json | 3 +++ packages/core-services/package.json | 3 +++ packages/core-services/src/lib/Api.ts | 6 +++++- 5 files changed, 13 insertions(+), 4 deletions(-) diff --git a/apps/meteor/ee/server/index.ts b/apps/meteor/ee/server/index.ts index 9b56239ad046..f5b385c9a805 100644 --- a/apps/meteor/ee/server/index.ts +++ b/apps/meteor/ee/server/index.ts @@ -1,5 +1,3 @@ -import './startup'; - import '../app/license/server/index'; import '../app/api-enterprise/server/index'; import '../app/authorization/server/index'; diff --git a/apps/meteor/server/main.ts b/apps/meteor/server/main.ts index 5579261911f5..09edca701540 100644 --- a/apps/meteor/server/main.ts +++ b/apps/meteor/server/main.ts @@ -9,9 +9,10 @@ import './importPackages'; import '../imports/startup/server'; import '../app/lib/server/startup'; +import '../ee/server/startup'; +import './startup'; import '../ee/server'; import './lib/pushConfig'; -import './startup'; import './configuration/accounts_meld'; import './configuration/ldap'; import './methods/OEmbedCacheCleanup'; diff --git a/ee/packages/presence/package.json b/ee/packages/presence/package.json index 9011dab086b6..fdb6a16393b3 100644 --- a/ee/packages/presence/package.json +++ b/ee/packages/presence/package.json @@ -28,6 +28,9 @@ "files": [ "/dist" ], + "volta": { + "extends": "../../../package.json" + }, "dependencies": { "@rocket.chat/core-services": "workspace:^", "@rocket.chat/core-typings": "workspace:^", diff --git a/packages/core-services/package.json b/packages/core-services/package.json index 4cce8aebe07b..3492cc1f77bf 100644 --- a/packages/core-services/package.json +++ b/packages/core-services/package.json @@ -30,6 +30,9 @@ "files": [ "/dist" ], + "volta": { + "extends": "../../package.json" + }, "dependencies": { "@rocket.chat/apps-engine": "1.41.0-alpha.290", "@rocket.chat/core-typings": "workspace:^", diff --git a/packages/core-services/src/lib/Api.ts b/packages/core-services/src/lib/Api.ts index 66806dc54fde..f0b5e67594c2 100644 --- a/packages/core-services/src/lib/Api.ts +++ b/packages/core-services/src/lib/Api.ts @@ -46,7 +46,11 @@ export class Api implements IApiService { } async broadcast(event: T, ...args: Parameters): Promise { - return this.broker?.broadcast(event, ...args); + if (!this.broker) { + throw new Error(`No broker set to broadcast: ${event}`); + } + + return this.broker.broadcast(event, ...args); } async broadcastToServices( From 3979680e8fb2eb735a5b3bdb5f9839eff79d3bff Mon Sep 17 00:00:00 2001 From: Pierre Lehnen <55164754+pierre-lehnen-rc@users.noreply.github.com> Date: Tue, 3 Oct 2023 16:33:14 -0300 Subject: [PATCH 3/5] regression: unmarked dangling promise on license validation (#30557) --- ee/packages/license/src/pendingLicense.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ee/packages/license/src/pendingLicense.ts b/ee/packages/license/src/pendingLicense.ts index 2c2140044336..8dd82dcd7774 100644 --- a/ee/packages/license/src/pendingLicense.ts +++ b/ee/packages/license/src/pendingLicense.ts @@ -8,10 +8,10 @@ export function setPendingLicense(this: LicenseManager, encryptedLicense: string } } -export function applyPendingLicense(this: LicenseManager) { +export async function applyPendingLicense(this: LicenseManager) { if (this.pendingLicense) { logger.info('Applying pending license.'); - this.setLicense(this.pendingLicense); + return this.setLicense(this.pendingLicense); } } From b810163a24220bbe57db44cd8ae226e770818f27 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Tue, 3 Oct 2023 17:21:47 -0300 Subject: [PATCH 4/5] ci: run tests from forks (#30556) --- .github/actions/build-docker/action.yml | 2 ++ .github/workflows/ci-test-e2e.yml | 8 ++++++++ .github/workflows/ci.yml | 4 +++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/actions/build-docker/action.yml b/.github/actions/build-docker/action.yml index 808b8acdcbe3..753bdc2169a8 100644 --- a/.github/actions/build-docker/action.yml +++ b/.github/actions/build-docker/action.yml @@ -19,6 +19,7 @@ runs: steps: - name: Login to GitHub Container Registry + if: github.event.pull_request.head.repo.full_name == github.repository uses: docker/login-action@v2 with: registry: ghcr.io @@ -62,6 +63,7 @@ runs: docker compose -f docker-compose-ci.yml build "${args[@]}" - name: Publish Docker images to GitHub Container Registry + if: github.event.pull_request.head.repo.full_name == github.repository shell: bash run: | args=(rocketchat) diff --git a/.github/workflows/ci-test-e2e.yml b/.github/workflows/ci-test-e2e.yml index e14857a97a09..d77966f186b3 100644 --- a/.github/workflows/ci-test-e2e.yml +++ b/.github/workflows/ci-test-e2e.yml @@ -97,6 +97,14 @@ jobs: cache-modules: true install: true + # if we are testing a PR from a fork, we need to build the docker image at this point + - uses: ./.github/actions/build-docker + if: github.event.pull_request.head.repo.full_name != github.repository + with: + CR_USER: ${{ secrets.CR_USER }} + CR_PAT: ${{ secrets.CR_PAT }} + node-version: ${{ inputs.node-version }} + - uses: dtinth/setup-github-actions-caching-for-turbo@v1 - name: Start httpbin container and wait for it to be ready diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 31c2c42718b6..ec8e905cd803 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -172,7 +172,6 @@ jobs: build-gh-docker-coverage: name: 🚢 Build Docker Images for Testing needs: [build, release-versions] - if: (github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.ref == 'refs/heads/develop') runs-on: ubuntu-20.04 env: @@ -189,7 +188,10 @@ jobs: steps: - uses: actions/checkout@v3 + + # we only build and publish the actual docker images if not a PR from a fork - uses: ./.github/actions/build-docker + if: (github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.ref == 'refs/heads/develop') with: CR_USER: ${{ secrets.CR_USER }} CR_PAT: ${{ secrets.CR_PAT }} From a98f3ff303b2dac8e4c96947fc28ec9ef7e0d74c Mon Sep 17 00:00:00 2001 From: Pierre Lehnen <55164754+pierre-lehnen-rc@users.noreply.github.com> Date: Tue, 3 Oct 2023 17:27:17 -0300 Subject: [PATCH 5/5] feat: new `licenses.info` endpoint (#30473) --- .changeset/tough-carrots-walk.md | 7 ++++ apps/meteor/ee/server/api/licenses.ts | 13 ++++++++ ee/packages/license/src/index.ts | 10 +++++- ee/packages/license/src/license.ts | 41 +++++++++++++++++++++++- packages/rest-typings/src/index.ts | 1 + packages/rest-typings/src/v1/licenses.ts | 29 ++++++++++++++++- 6 files changed, 98 insertions(+), 3 deletions(-) create mode 100644 .changeset/tough-carrots-walk.md diff --git a/.changeset/tough-carrots-walk.md b/.changeset/tough-carrots-walk.md new file mode 100644 index 000000000000..2851e697b85e --- /dev/null +++ b/.changeset/tough-carrots-walk.md @@ -0,0 +1,7 @@ +--- +'@rocket.chat/rest-typings': minor +'@rocket.chat/license': patch +'@rocket.chat/meteor': patch +--- + +feat: added `licenses.info` endpoint diff --git a/apps/meteor/ee/server/api/licenses.ts b/apps/meteor/ee/server/api/licenses.ts index cfd657a1f0e9..ff5c3fcc3e47 100644 --- a/apps/meteor/ee/server/api/licenses.ts +++ b/apps/meteor/ee/server/api/licenses.ts @@ -1,5 +1,6 @@ import { License } from '@rocket.chat/license'; import { Settings, Users } from '@rocket.chat/models'; +import { isLicensesInfoProps } from '@rocket.chat/rest-typings'; import { check } from 'meteor/check'; import { API } from '../../../app/api/server/api'; @@ -22,6 +23,18 @@ API.v1.addRoute( }, ); +API.v1.addRoute( + 'licenses.info', + { authRequired: true, validateParams: isLicensesInfoProps, permissionsRequired: ['view-privileged-setting'] }, + { + async get() { + const data = await License.getInfo(Boolean(this.queryParams.loadValues)); + + return API.v1.success({ data }); + }, + }, +); + API.v1.addRoute( 'licenses.add', { authRequired: true }, diff --git a/ee/packages/license/src/index.ts b/ee/packages/license/src/index.ts index 9dbd94db53ed..c5dbd9f9496f 100644 --- a/ee/packages/license/src/index.ts +++ b/ee/packages/license/src/index.ts @@ -1,4 +1,5 @@ -import type { LicenseLimitKind } from './definition/ILicenseV3'; +import type { ILicenseV3, LicenseLimitKind } from './definition/ILicenseV3'; +import type { LicenseModule } from './definition/LicenseModule'; import type { LimitContext } from './definition/LimitContext'; import { getAppsConfig, getMaxActiveUsers, getUnmodifiedLicenseAndModules } from './deprecated'; import { onLicense } from './events/deprecated'; @@ -45,6 +46,13 @@ interface License { onInvalidateLicense: typeof onInvalidateLicense; onLimitReached: typeof onLimitReached; + getInfo: (loadCurrentValues: boolean) => Promise<{ + license: ILicenseV3 | undefined; + activeModules: LicenseModule[]; + limits: Record; + inFairPolicy: boolean; + }>; + // Deprecated: onLicense: typeof onLicense; // Deprecated: diff --git a/ee/packages/license/src/license.ts b/ee/packages/license/src/license.ts index 2fb25b0e3b4f..a420eb2b0d57 100644 --- a/ee/packages/license/src/license.ts +++ b/ee/packages/license/src/license.ts @@ -9,7 +9,7 @@ import { DuplicatedLicenseError } from './errors/DuplicatedLicenseError'; import { InvalidLicenseError } from './errors/InvalidLicenseError'; import { NotReadyForValidation } from './errors/NotReadyForValidation'; import { logger } from './logger'; -import { invalidateAll, replaceModules } from './modules'; +import { getModules, invalidateAll, replaceModules } from './modules'; import { applyPendingLicense, clearPendingLicense, hasPendingLicense, isPendingLicense, setPendingLicense } from './pendingLicense'; import { showLicense } from './showLicense'; import { replaceTags } from './tags'; @@ -227,4 +227,43 @@ export class LicenseManager extends Emitter< .some(({ max }) => max < currentValue), ); } + + public async getInfo(loadCurrentValues = false): Promise<{ + license: ILicenseV3 | undefined; + activeModules: LicenseModule[]; + limits: Record; + inFairPolicy: boolean; + }> { + const activeModules = getModules.call(this); + const license = this.getLicense(); + + // Get all limits present in the license and their current value + const limits = ( + (license && + (await Promise.all( + (['activeUsers', 'guestUsers', 'privateApps', 'marketplaceApps', 'monthlyActiveContacts'] as LicenseLimitKind[]) + .map((limitKey) => ({ + limitKey, + max: Math.max(-1, Math.min(...Array.from(license.limits[limitKey as LicenseLimitKind] || [])?.map(({ max }) => max))), + })) + .filter(({ max }) => max >= 0 && max < Infinity) + .map(async ({ max, limitKey }) => { + return { + [limitKey as LicenseLimitKind]: { + ...(loadCurrentValues ? { value: await getCurrentValueForLicenseLimit.call(this, limitKey as LicenseLimitKind) } : {}), + max, + }, + }; + }), + ))) || + [] + ).reduce((prev, curr) => ({ ...prev, ...curr }), {}); + + return { + license, + activeModules, + limits: limits as Record, + inFairPolicy: this.inFairPolicy, + }; + } } diff --git a/packages/rest-typings/src/index.ts b/packages/rest-typings/src/index.ts index 066e3248dc33..3b8197ce20bf 100644 --- a/packages/rest-typings/src/index.ts +++ b/packages/rest-typings/src/index.ts @@ -228,6 +228,7 @@ export * from './v1/invites'; export * from './v1/dm'; export * from './v1/dm/DmHistoryProps'; export * from './v1/integrations'; +export * from './v1/licenses'; export * from './v1/omnichannel'; export * from './v1/oauthapps'; export * from './v1/oauthapps/UpdateOAuthAppParamsPOST'; diff --git a/packages/rest-typings/src/v1/licenses.ts b/packages/rest-typings/src/v1/licenses.ts index 96c67e2654bb..6dc935aae739 100644 --- a/packages/rest-typings/src/v1/licenses.ts +++ b/packages/rest-typings/src/v1/licenses.ts @@ -1,4 +1,4 @@ -import type { ILicenseV2, ILicenseV3 } from '@rocket.chat/license'; +import type { ILicenseV2, ILicenseV3, LicenseLimitKind } from '@rocket.chat/license'; import Ajv from 'ajv'; const ajv = new Ajv({ @@ -22,10 +22,37 @@ const licensesAddPropsSchema = { export const isLicensesAddProps = ajv.compile(licensesAddPropsSchema); +type licensesInfoProps = { + loadValues?: boolean; +}; + +const licensesInfoPropsSchema = { + type: 'object', + properties: { + loadValues: { + type: 'boolean', + }, + }, + required: [], + additionalProperties: false, +}; + +export const isLicensesInfoProps = ajv.compile(licensesInfoPropsSchema); + export type LicensesEndpoints = { '/v1/licenses.get': { GET: () => { licenses: Array }; }; + '/v1/licenses.info': { + GET: (params: licensesInfoProps) => { + data: { + license: ILicenseV3 | undefined; + activeModules: string[]; + limits: Record; + inFairPolicy: boolean; + }; + }; + }; '/v1/licenses.add': { POST: (params: licensesAddProps) => void; };