From f6322a50e1e674eeba74e2f45651fb08165a19b1 Mon Sep 17 00:00:00 2001 From: Sergey Konovalov Date: Fri, 4 Oct 2024 16:45:23 +0300 Subject: [PATCH] [bug] Add notification rules; For bug 70325 --- Common/config/default.json | 29 ++++++++++++++++++++--- Common/sources/notificationService.js | 12 ++++++---- DocService/sources/DocsCoServer.js | 34 +++++++++++++++------------ DocService/sources/utilsDocService.js | 20 +++++++++------- 4 files changed, 63 insertions(+), 32 deletions(-) diff --git a/Common/config/default.json b/Common/config/default.json index aaf876c3..84f9dfb6 100644 --- a/Common/config/default.json +++ b/Common/config/default.json @@ -43,14 +43,25 @@ ], "template": { "title": "License expiration warning", - "bodyWarn": "Attention! Your license is about to expire on %s.\nUpon reaching this date, you will no longer be entitled to receive personal technical support and install new Docs versions released after this date.", - "bodyError": "Attention! Your license expired on %s.\nYou are no longer entitled to receive personal technical support and install new Docs versions released after this date.\nPlease contact sales@onlyoffice.com to discuss license renewal." + "body": "Attention! Your license is about to expire on %s.\nUpon reaching this date, you will no longer be entitled to receive personal technical support and install new Docs versions released after this date." }, "policies": { "repeatInterval": "1d" } }, - "licenseLimit": { + "licenseExpirationError": { + "transportType": [ + "email" + ], + "template": { + "title": "License expiration warning", + "body": "Attention! Your license expired on %s.\nYou are no longer entitled to receive personal technical support and install new Docs versions released after this date.\nPlease contact sales@onlyoffice.com to discuss license renewal." + }, + "policies": { + "repeatInterval": "1d" + } + }, + "licenseLimitEdit": { "transportType": [ "email" ], @@ -61,6 +72,18 @@ "policies": { "repeatInterval": "1h" } + }, + "licenseLimitLiveViewer": { + "transportType": [ + "email" + ], + "template": { + "title": "License connection limit warning", + "body": "Attention! You have reached %s%% of the live viewer %s limit set by your license." + }, + "policies": { + "repeatInterval": "1h" + } } } }, diff --git a/Common/sources/notificationService.js b/Common/sources/notificationService.js index ad1a327f..7be324da 100644 --- a/Common/sources/notificationService.js +++ b/Common/sources/notificationService.js @@ -47,7 +47,9 @@ const editorStatStorage = require('./../../DocService/sources/' + (cfgEditorStat const editorStat = editorStatStorage.EditorStat ? new editorStatStorage.EditorStat() : new editorStatStorage(); const notificationTypes = { LICENSE_EXPIRATION_WARNING: 'licenseExpirationWarning', - LICENSE_LIMIT: 'licenseLimit' + LICENSE_EXPIRATION_ERROR: 'licenseExpirationError', + LICENSE_LIMIT_EDIT: 'licenseLimitEdit', + LICENSE_LIMIT_LIVE_VIEWER: 'licenseLimitLiveViewer' }; class TransportInterface { @@ -105,7 +107,7 @@ class Transport { } } -async function notify(ctx, notificationType, message) { +async function notify(ctx, notificationType, message, opt_cacheKey = undefined) { const tenNotificationEnable = ctx.getCfg('notification.enable', cfgNotificationEnable); if (!tenNotificationEnable) { return; @@ -114,21 +116,21 @@ async function notify(ctx, notificationType, message) { const tenRule = ctx.getCfg(`notification.rules.${notificationType}`, config.get(`notification.rules.${notificationType}`)); if (tenRule) { - let checkRes = await checkRulePolicies(ctx, notificationType, tenRule); + let checkRes = await checkRulePolicies(ctx, notificationType, tenRule, opt_cacheKey); if (checkRes) { await notifyRule(ctx, tenRule, message); } } } -async function checkRulePolicies(ctx, notificationType, tenRule) { +async function checkRulePolicies(ctx, notificationType, tenRule, opt_cacheKey) { const { repeatInterval } = tenRule.policies; //decrease repeatInterval by 1% to avoid race condition if timeout=repeatInterval let ttl = Math.floor(ms(repeatInterval) * 0.99 / 1000); let isLock = false; //todo for compatibility remove if after 8.2 if (editorStat?.lockNotification) { - isLock = await editorStat.lockNotification(ctx, notificationType, ttl); + isLock = await editorStat.lockNotification(ctx, opt_cacheKey || notificationType, ttl); } if (!isLock) { ctx.logger.debug(`Notification service: skip rule "%s" due to repeat interval = %s`, notificationType, repeatInterval); diff --git a/DocService/sources/DocsCoServer.js b/DocService/sources/DocsCoServer.js index 6e3d287b..e1ad2203 100644 --- a/DocService/sources/DocsCoServer.js +++ b/DocService/sources/DocsCoServer.js @@ -138,7 +138,8 @@ const cfgForgottenFiles = config.get('services.CoAuthoring.server.forgottenfiles const cfgForgottenFilesName = config.get('services.CoAuthoring.server.forgottenfilesname'); const cfgMaxRequestChanges = config.get('services.CoAuthoring.server.maxRequestChanges'); const cfgWarningLimitPercents = config.get('license.warning_limit_percents'); -const cfgNotificationRuleLicenseLimit = config.get('notification.rules.licenseLimit.template.body'); +const cfgNotificationRuleLicenseLimitEdit = config.get('notification.rules.licenseLimitEdit.template.body'); +const cfgNotificationRuleLicenseLimitLiveViewer = config.get('notification.rules.licenseLimitLiveViewer.template.body'); const cfgErrorFiles = config.get('FileConverter.converter.errorfiles'); const cfgOpenProtectedFile = config.get('services.CoAuthoring.server.openProtectedFile'); const cfgIsAnonymousSupport = config.get('services.CoAuthoring.server.isAnonymousSupport'); @@ -3478,22 +3479,27 @@ exports.install = function(server, callbackFunction) { function* _checkLicenseAuth(ctx, licenseInfo, userId, isLiveViewer) { const tenWarningLimitPercents = ctx.getCfg('license.warning_limit_percents', cfgWarningLimitPercents) / 100; - const tenNotificationRuleLicenseLimit = ctx.getCfg(`notification.rules.licenseLimit.template.body`, cfgNotificationRuleLicenseLimit); + const tenNotificationRuleLicenseLimitEdit = ctx.getCfg(`notification.rules.licenseLimitEdit.template.body`, cfgNotificationRuleLicenseLimitEdit); + const tenNotificationRuleLicenseLimitLiveViewer = ctx.getCfg(`notification.rules.licenseLimitLiveViewer.template.body`, cfgNotificationRuleLicenseLimitLiveViewer); const c_LR = constants.LICENSE_RESULT; let licenseType = licenseInfo.type; if (c_LR.Success === licenseType || c_LR.SuccessLimit === licenseType) { let notificationLimit; - let notificationPercent = 0; + let notificationTemplate = tenNotificationRuleLicenseLimitEdit; + let notificationType = notificationTypes.LICENSE_LIMIT_EDIT; + let notificationPercent = 100; if (licenseInfo.usersCount) { const nowUTC = getLicenseNowUtc(); + notificationLimit = 'users'; if(isLiveViewer) { + notificationTemplate = tenNotificationRuleLicenseLimitLiveViewer; + notificationType = notificationTypes.LICENSE_LIMIT_LIVE_VIEWER; const arrUsers = yield editorStat.getPresenceUniqueViewUser(ctx, nowUTC); if (arrUsers.length >= licenseInfo.usersViewCount && (-1 === arrUsers.findIndex((element) => {return element.userid === userId}))) { licenseType = licenseInfo.hasLicense ? c_LR.UsersViewCount : c_LR.UsersViewCountOS; } else if (licenseInfo.usersViewCount * tenWarningLimitPercents <= arrUsers.length) { notificationPercent = tenWarningLimitPercents * 100; } - notificationLimit = 'live viewer users'; } else { const arrUsers = yield editorStat.getPresenceUniqueUser(ctx, nowUTC); if (arrUsers.length >= licenseInfo.usersCount && (-1 === arrUsers.findIndex((element) => {return element.userid === userId}))) { @@ -3501,10 +3507,12 @@ exports.install = function(server, callbackFunction) { } else if(licenseInfo.usersCount * tenWarningLimitPercents <= arrUsers.length) { notificationPercent = tenWarningLimitPercents * 100; } - notificationLimit = 'users'; } } else { + notificationLimit = 'connections'; if (isLiveViewer) { + notificationTemplate = tenNotificationRuleLicenseLimitLiveViewer; + notificationType = notificationTypes.LICENSE_LIMIT_LIVE_VIEWER; const connectionsLiveCount = licenseInfo.connectionsView; const liveViewerConnectionsCount = yield editorStat.getLiveViewerConnectionsCount(ctx, connections); if (liveViewerConnectionsCount >= connectionsLiveCount) { @@ -3512,7 +3520,6 @@ exports.install = function(server, callbackFunction) { } else if(connectionsLiveCount * tenWarningLimitPercents <= liveViewerConnectionsCount){ notificationPercent = tenWarningLimitPercents * 100; } - notificationLimit = 'live viewer connections'; } else { const connectionsCount = licenseInfo.connections; const editConnectionsCount = yield editorStat.getEditorConnectionsCount(ctx, connections); @@ -3521,20 +3528,17 @@ exports.install = function(server, callbackFunction) { } else if (connectionsCount * tenWarningLimitPercents <= editConnectionsCount) { notificationPercent = tenWarningLimitPercents * 100; } - notificationLimit = 'connections'; } } - if ((c_LR.Success !== licenseType && c_LR.SuccessLimit !== licenseType) || notificationPercent > 0) { - let message; - if (notificationPercent > 0) { - message = util.format(tenNotificationRuleLicenseLimit, notificationPercent, notificationLimit); - ctx.logger.warn(tenNotificationRuleLicenseLimit, notificationPercent, notificationLimit); + if ((c_LR.Success !== licenseType && c_LR.SuccessLimit !== licenseType) || 100 !== notificationPercent) { + const message = util.format(notificationTemplate, notificationPercent, notificationLimit); + if (100 !== notificationPercent) { + ctx.logger.warn(message); } else { - message = util.format(tenNotificationRuleLicenseLimit, 100, notificationLimit); - ctx.logger.error(tenNotificationRuleLicenseLimit, 100, notificationLimit); + ctx.logger.error(message); } //todo with yield service could throw error - void notificationService.notify(ctx, notificationTypes.LICENSE_LIMIT, message); + void notificationService.notify(ctx, notificationType, message, notificationType + notificationPercent); } } return licenseType; diff --git a/DocService/sources/utilsDocService.js b/DocService/sources/utilsDocService.js index b794b965..8bfec491 100644 --- a/DocService/sources/utilsDocService.js +++ b/DocService/sources/utilsDocService.js @@ -43,8 +43,8 @@ const tenantManager = require('../../Common/sources/tenantManager'); const { notificationTypes, ...notificationService } = require('../../Common/sources/notificationService'); const cfgStartNotifyFrom = ms(config.get('license.warning_license_expiration')); -const cfgNotificationRuleLicenseExpirationWarning = config.get('notification.rules.licenseExpirationWarning.template.bodyWarn'); -const cfgNotificationRuleLicenseExpirationError = config.get('notification.rules.licenseExpirationWarning.template.bodyError'); +const cfgNotificationRuleLicenseExpirationWarning = config.get('notification.rules.licenseExpirationWarning.template.body'); +const cfgNotificationRuleLicenseExpirationError = config.get('notification.rules.licenseExpirationError.template.body'); async function fixImageExifRotation(ctx, buffer) { if (!buffer) { @@ -126,15 +126,17 @@ async function notifyLicenseExpiration(ctx, endDate) { const currentDate = new Date(); if (currentDate.getTime() >= endDate.getTime() - cfgStartNotifyFrom) { const formattedExpirationTime = humanFriendlyExpirationTime(endDate); - //todo one body template - let message; - if (endDate < currentDate) { - message = util.format(cfgNotificationRuleLicenseExpirationError, formattedExpirationTime); + if (endDate <= currentDate) { + const tenNotificationRuleLicenseExpirationError = ctx.getCfg('notification.rules.licenseExpirationError.template.body', cfgNotificationRuleLicenseExpirationError); + const message = util.format(tenNotificationRuleLicenseExpirationError, formattedExpirationTime); + ctx.logger.error(message); + await notificationService.notify(ctx, notificationTypes.LICENSE_EXPIRATION_ERROR, message); } else { - message = util.format(cfgNotificationRuleLicenseExpirationWarning, formattedExpirationTime); + const tenNotificationRuleLicenseExpirationWarning = ctx.getCfg('notification.rules.licenseExpirationWarning.template.body', cfgNotificationRuleLicenseExpirationWarning); + const message = util.format(tenNotificationRuleLicenseExpirationWarning, formattedExpirationTime); + ctx.logger.warn(message); + await notificationService.notify(ctx, notificationTypes.LICENSE_EXPIRATION_WARNING, message); } - ctx.logger.warn(message); - await notificationService.notify(ctx, notificationTypes.LICENSE_EXPIRATION_WARNING, message); } }