From 1c79480b332c20151c9d99acef82f3059522617a Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 17 Nov 2020 17:32:47 +0200 Subject: [PATCH 1/5] created sendApiFeatureStateEmail --- .../DefaultTbApiUsageStateService.java | 37 +++-- .../service/mail/DefaultMailService.java | 29 ++++ .../main/resources/i18n/messages.properties | 3 +- .../resources/templates/state.disabled.ftl | 126 ++++++++++++++++++ .../resources/templates/state.enabled.ftl | 126 ++++++++++++++++++ .../resources/templates/state.warning.ftl | 126 ++++++++++++++++++ .../common/data/ApiUsageStateMailMessage.java | 25 ++++ .../rule/engine/api/MailService.java | 5 + 8 files changed, 467 insertions(+), 10 deletions(-) create mode 100644 application/src/main/resources/templates/state.disabled.ftl create mode 100644 application/src/main/resources/templates/state.enabled.ftl create mode 100644 application/src/main/resources/templates/state.warning.ftl create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageStateMailMessage.java diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index c7206712c3..9b45ffdaed 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -17,21 +17,21 @@ package org.thingsboard.server.service.apiusage; import com.google.common.util.concurrent.FutureCallback; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.checkerframework.checker.nullness.qual.Nullable; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.annotation.Lazy; -import org.springframework.context.event.EventListener; -import org.springframework.core.annotation.Order; -import org.springframework.data.util.Pair; import org.springframework.stereotype.Service; +import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.server.common.data.ApiFeature; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.ApiUsageStateMailMessage; import org.thingsboard.server.common.data.ApiUsageStateValue; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.ApiUsageStateId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; @@ -45,6 +45,7 @@ import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.tools.SchedulerUtils; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; @@ -54,14 +55,11 @@ import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.discovery.PartitionChangeEvent; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.scheduler.SchedulerComponent; -import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.telemetry.InternalTelemetryService; import javax.annotation.PostConstruct; import java.util.ArrayList; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -94,6 +92,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { private final ApiUsageStateService apiUsageStateService; private final SchedulerComponent scheduler; private final TbTenantProfileCache tenantProfileCache; + private final MailService mailService; @Lazy @Autowired @@ -118,7 +117,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { TimeseriesService tsService, ApiUsageStateService apiUsageStateService, SchedulerComponent scheduler, - TbTenantProfileCache tenantProfileCache) { + TbTenantProfileCache tenantProfileCache, MailService mailService) { this.clusterService = clusterService; this.partitionService = partitionService; this.tenantService = tenantService; @@ -126,6 +125,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { this.apiUsageStateService = apiUsageStateService; this.scheduler = scheduler; this.tenantProfileCache = tenantProfileCache; + this.mailService = mailService; } @PostConstruct @@ -286,7 +286,26 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { List stateTelemetry = new ArrayList<>(); result.forEach(((apiFeature, aState) -> stateTelemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(apiFeature.getApiStateKey(), aState.name()))))); tsWsService.saveAndNotifyInternal(state.getTenantId(), state.getApiUsageState().getId(), stateTelemetry, VOID_CALLBACK); - //TODO: notify tenant admin via email! + + String email = tenantService.findTenantById(state.getTenantId()).getEmail(); + + if (StringUtils.isNotEmpty(email)) { + result.forEach((apiFeature, stateValue) -> { + ApiUsageRecordKey[] keys = ApiUsageRecordKey.getKeys(apiFeature); + ApiUsageStateMailMessage[] msgs = new ApiUsageStateMailMessage[keys.length]; + for (int i = 0; i < keys.length; i++) { + ApiUsageRecordKey key = keys[i]; + msgs[i] = new ApiUsageStateMailMessage(key, state.getProfileThreshold(key), state.get(key)); + } + try { + mailService.sendApiFeatureStateEmail(apiFeature, stateValue, email, msgs); + } catch (ThingsboardException e) { + log.warn("[{}] Can't send update of the API state to tenant with provided email [{}]", state.getTenantId(), email, e); + } + }); + } else { + log.warn("[{}] Can't send update of the API state to tenant with empty email!", state.getTenantId()); + } } private void checkStartOfNextCycle() { diff --git a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java index bcf541880c..1b85c73196 100644 --- a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java +++ b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java @@ -29,6 +29,9 @@ import org.springframework.stereotype.Service; import org.springframework.ui.freemarker.FreeMarkerTemplateUtils; import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.server.common.data.AdminSettings; +import org.thingsboard.server.common.data.ApiFeature; +import org.thingsboard.server.common.data.ApiUsageStateMailMessage; +import org.thingsboard.server.common.data.ApiUsageStateValue; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.EntityId; @@ -246,6 +249,32 @@ public class DefaultMailService implements MailService { sendMail(mailSender, mailFrom, email, subject, message); } + @Override + public void sendApiFeatureStateEmail(ApiFeature apiFeature, ApiUsageStateValue stateValue, String email, ApiUsageStateMailMessage[] stateMailMessages) throws ThingsboardException { + String subject = messages.getMessage("api.usage.state", null, Locale.US); + + Map model = new HashMap<>(); + model.put("apiFeature", apiFeature.getApiStateKey()); + model.put("apiUsageStateMailMessages", stateMailMessages); + + model.put(TARGET_EMAIL, email); + + String message = null; + + switch (stateValue) { + case ENABLED: + message = mergeTemplateIntoString("state.enabled.ftl", model); + break; + case WARNING: + message = mergeTemplateIntoString("state.warning.ftl", model); + break; + case DISABLED: + message = mergeTemplateIntoString("state.disabled.ftl", model); + break; + } + sendMail(mailSender, mailFrom, email, subject, message); + } + private void sendMail(JavaMailSenderImpl mailSender, String mailFrom, String email, String subject, String message) throws ThingsboardException { diff --git a/application/src/main/resources/i18n/messages.properties b/application/src/main/resources/i18n/messages.properties index a34582e132..9de9c4b789 100644 --- a/application/src/main/resources/i18n/messages.properties +++ b/application/src/main/resources/i18n/messages.properties @@ -3,4 +3,5 @@ activation.subject=Your account activation on Thingsboard account.activated.subject=Thingsboard - your account has been activated reset.password.subject=Thingsboard - Password reset has been requested password.was.reset.subject=Thingsboard - your account password has been reset -account.lockout.subject=Thingsboard - User account has been lockout \ No newline at end of file +account.lockout.subject=Thingsboard - User account has been lockout +api.usage.state=Thingsboard - Api Usage State for tenant has been updated \ No newline at end of file diff --git a/application/src/main/resources/templates/state.disabled.ftl b/application/src/main/resources/templates/state.disabled.ftl new file mode 100644 index 0000000000..c1f81da181 --- /dev/null +++ b/application/src/main/resources/templates/state.disabled.ftl @@ -0,0 +1,126 @@ +<#-- + + Copyright © 2016-2020 The Thingsboard Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +--> + + + + + +Thingsboard - Api Usage State + + + + + + + + + + + +
+
+ + +
+ + + + + + + + <#list apiUsageStateMailMessages as msg> + + + + + + + + + +
+

Thingsboard Api Usage State for tenant has been updated

+
+ Thingsboard Usage state ${apiFeature} was updated to status DISABLED. +
+ ${msg.key.apiLimitKey} = ${msg.threshold} +
+ ${msg.key.apiCountKey} = ${msg.value} +
+ — The Thingsboard +
+ +
+
+ + diff --git a/application/src/main/resources/templates/state.enabled.ftl b/application/src/main/resources/templates/state.enabled.ftl new file mode 100644 index 0000000000..497b3cd780 --- /dev/null +++ b/application/src/main/resources/templates/state.enabled.ftl @@ -0,0 +1,126 @@ +<#-- + + Copyright © 2016-2020 The Thingsboard Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +--> + + + + + +Thingsboard - Api Usage State + + + + + + + + + + + +
+
+ + +
+ + + + + + + + <#list apiUsageStateMailMessages as msg> + + + + + + + + + +
+

Thingsboard Api Usage State for tenant has been updated

+
+ Thingsboard Usage state ${apiFeature} was updated to status ENABLED. +
+ ${msg.key.apiLimitKey} = ${msg.threshold} +
+ ${msg.key.apiCountKey} = ${msg.value} +
+ — The Thingsboard +
+ +
+
+ + diff --git a/application/src/main/resources/templates/state.warning.ftl b/application/src/main/resources/templates/state.warning.ftl new file mode 100644 index 0000000000..bd4424a0b9 --- /dev/null +++ b/application/src/main/resources/templates/state.warning.ftl @@ -0,0 +1,126 @@ +<#-- + + Copyright © 2016-2020 The Thingsboard Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +--> + + + + + +Thingsboard - Api Usage State + + + + + + + + + + + +
+
+ + +
+ + + + + + + + <#list apiUsageStateMailMessages as msg> + + + + + + + + + +
+

Thingsboard Api Usage State for tenant has been updated

+
+ Thingsboard Usage state ${apiFeature} was updated to status WARNING. +
+ ${msg.key.apiLimitKey} = ${msg.threshold} +
+ ${msg.key.apiCountKey} = ${msg.value} +
+ — The Thingsboard +
+ +
+
+ + diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageStateMailMessage.java b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageStateMailMessage.java new file mode 100644 index 0000000000..cab3789fdc --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageStateMailMessage.java @@ -0,0 +1,25 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data; + +import lombok.Data; + +@Data +public class ApiUsageStateMailMessage { + private final ApiUsageRecordKey key; + private final long threshold; + private final long value; +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java index a36089107d..59b96a2e67 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java @@ -16,6 +16,9 @@ package org.thingsboard.rule.engine.api; import com.fasterxml.jackson.databind.JsonNode; +import org.thingsboard.server.common.data.ApiFeature; +import org.thingsboard.server.common.data.ApiUsageStateMailMessage; +import org.thingsboard.server.common.data.ApiUsageStateValue; import org.thingsboard.server.common.data.exception.ThingsboardException; import javax.mail.MessagingException; @@ -39,4 +42,6 @@ public interface MailService { void send(String from, String to, String cc, String bcc, String subject, String body) throws MessagingException; void sendAccountLockoutEmail( String lockoutEmail, String email, Integer maxFailedLoginAttempts) throws ThingsboardException; + + void sendApiFeatureStateEmail(ApiFeature apiFeature, ApiUsageStateValue stateValue, String email, ApiUsageStateMailMessage[] stateMailMessages) throws ThingsboardException; } From ee3abe59eabc8463ae0be84cb65b7731ae9d1f35 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 17 Nov 2020 18:06:08 +0200 Subject: [PATCH 2/5] added mail executor to DefaultTbApiUsageStateService --- .../DefaultTbApiUsageStateService.java | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index 9b45ffdaed..84dfd7310b 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -59,6 +59,7 @@ import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.telemetry.InternalTelemetryService; import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -67,6 +68,8 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -111,6 +114,8 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { private final Lock updateLock = new ReentrantLock(); + private final ExecutorService mailExecutor; + public DefaultTbApiUsageStateService(TbClusterService clusterService, PartitionService partitionService, TenantService tenantService, @@ -126,6 +131,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { this.scheduler = scheduler; this.tenantProfileCache = tenantProfileCache; this.mailService = mailService; + this.mailExecutor = Executors.newSingleThreadExecutor(); } @PostConstruct @@ -297,11 +303,13 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { ApiUsageRecordKey key = keys[i]; msgs[i] = new ApiUsageStateMailMessage(key, state.getProfileThreshold(key), state.get(key)); } - try { - mailService.sendApiFeatureStateEmail(apiFeature, stateValue, email, msgs); - } catch (ThingsboardException e) { - log.warn("[{}] Can't send update of the API state to tenant with provided email [{}]", state.getTenantId(), email, e); - } + mailExecutor.submit(() -> { + try { + mailService.sendApiFeatureStateEmail(apiFeature, stateValue, email, msgs); + } catch (ThingsboardException e) { + log.warn("[{}] Can't send update of the API state to tenant with provided email [{}]", state.getTenantId(), email, e); + } + }); }); } else { log.warn("[{}] Can't send update of the API state to tenant with empty email!", state.getTenantId()); @@ -386,4 +394,10 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } } + @PreDestroy + private void destroy() { + if (mailExecutor != null) { + mailExecutor.shutdownNow(); + } + } } From 3feb32c0f6fae02c3a51ba99457a7da1fb8e2fb0 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 17 Nov 2020 19:00:38 +0200 Subject: [PATCH 3/5] Improvements to the templates --- .../DefaultTbApiUsageStateService.java | 2 +- .../service/mail/DefaultMailService.java | 37 ++- .../resources/templates/state.disabled.ftl | 242 ++++++++++-------- .../server/common/data/ApiFeature.java | 13 +- .../rule/engine/api/MailService.java | 2 +- 5 files changed, 182 insertions(+), 114 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index 84dfd7310b..59b63d7200 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -305,7 +305,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } mailExecutor.submit(() -> { try { - mailService.sendApiFeatureStateEmail(apiFeature, stateValue, email, msgs); + mailService.sendApiFeatureStateEmail(apiFeature, stateValue, email, msgs[0]); } catch (ThingsboardException e) { log.warn("[{}] Can't send update of the API state to tenant with provided email [{}]", state.getTenantId(), email, e); } diff --git a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java index 1b85c73196..19509243ef 100644 --- a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java +++ b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java @@ -250,13 +250,11 @@ public class DefaultMailService implements MailService { } @Override - public void sendApiFeatureStateEmail(ApiFeature apiFeature, ApiUsageStateValue stateValue, String email, ApiUsageStateMailMessage[] stateMailMessages) throws ThingsboardException { + public void sendApiFeatureStateEmail(ApiFeature apiFeature, ApiUsageStateValue stateValue, String email, ApiUsageStateMailMessage msg) throws ThingsboardException { String subject = messages.getMessage("api.usage.state", null, Locale.US); Map model = new HashMap<>(); - model.put("apiFeature", apiFeature.getApiStateKey()); - model.put("apiUsageStateMailMessages", stateMailMessages); - + model.put("apiFeature", apiFeature.getLabel()); model.put(TARGET_EMAIL, email); String message = null; @@ -269,12 +267,43 @@ public class DefaultMailService implements MailService { message = mergeTemplateIntoString("state.warning.ftl", model); break; case DISABLED: + model.put("apiLimitValueLabel", toDisabledValueLabel(apiFeature) + " " + toDisabledValueLabel(msg)); message = mergeTemplateIntoString("state.disabled.ftl", model); break; } sendMail(mailSender, mailFrom, email, subject, message); } + private String toDisabledValueLabel(ApiFeature apiFeature) { + switch (apiFeature) { + case DB: + return "saved"; + case TRANSPORT: + return "received"; + case JS: + case RE: + return "invoked"; + default: + throw new RuntimeException("Not implemented!"); + } + } + + private String toDisabledValueLabel(ApiUsageStateMailMessage msg) { + switch (msg.getKey()) { + case STORAGE_DP_COUNT: + case TRANSPORT_DP_COUNT: + return (msg.getThreshold() / 1000000) + "M data points"; + case TRANSPORT_MSG_COUNT: + return (msg.getThreshold() / 1000000) + "M messages"; + case JS_EXEC_COUNT: + return (msg.getThreshold() / 1000000) + "M JavaScript functions"; + case RE_EXEC_COUNT: + return (msg.getThreshold() / 1000000) + "M Rule Engine nodes"; + default: + throw new RuntimeException("Not implemented!"); + } + } + private void sendMail(JavaMailSenderImpl mailSender, String mailFrom, String email, String subject, String message) throws ThingsboardException { diff --git a/application/src/main/resources/templates/state.disabled.ftl b/application/src/main/resources/templates/state.disabled.ftl index c1f81da181..33a368cceb 100644 --- a/application/src/main/resources/templates/state.disabled.ftl +++ b/application/src/main/resources/templates/state.disabled.ftl @@ -15,112 +15,148 @@ limitations under the License. --> - - + + - - -Thingsboard - Api Usage State - - - + + + Thingsboard - Api Usage State + + + - - - - - - + + +
-
- - -
- - - - - - - - <#list apiUsageStateMailMessages as msg> - - - - - - - - - -
-

Thingsboard Api Usage State for tenant has been updated

-
- Thingsboard Usage state ${apiFeature} was updated to status DISABLED. -
- ${msg.key.apiLimitKey} = ${msg.threshold} -
- ${msg.key.apiCountKey} = ${msg.value} -
- — The Thingsboard -
- -
-
+ + + + + +
+ + + + + + + + + + + + + + + +
+

Your ThingsBoard account feature was disabled

+
We have disabled the ${apiFeature} for your account because ThingsBoard has already  ${apiLimitValueLabel} + . +
Please contact your system administrator to resolve the issue. +
— The ThingsBoard +
+
+ + + + + +
This email was sent to ${targetEmail} by ThingsBoard. +
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java b/common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java index e12cb5fb14..fe923cefaa 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java @@ -18,16 +18,19 @@ package org.thingsboard.server.common.data; import lombok.Getter; public enum ApiFeature { - TRANSPORT("transportApiState"), - DB("dbApiState"), - RE("ruleEngineApiState"), - JS("jsExecutionApiState"); + TRANSPORT("transportApiState", "Device API"), + DB("dbApiState", "Telemetry persistence"), + RE("ruleEngineApiState", "Rule engine execution"), + JS("jsExecutionApiState", "JavaScript functions execution"); @Getter private final String apiStateKey; + @Getter + private final String label; - ApiFeature(String apiStateKey) { + ApiFeature(String apiStateKey, String label) { this.apiStateKey = apiStateKey; + this.label = label; } } diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java index 59b96a2e67..4a4228731c 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java @@ -43,5 +43,5 @@ public interface MailService { void sendAccountLockoutEmail( String lockoutEmail, String email, Integer maxFailedLoginAttempts) throws ThingsboardException; - void sendApiFeatureStateEmail(ApiFeature apiFeature, ApiUsageStateValue stateValue, String email, ApiUsageStateMailMessage[] stateMailMessages) throws ThingsboardException; + void sendApiFeatureStateEmail(ApiFeature apiFeature, ApiUsageStateValue stateValue, String email, ApiUsageStateMailMessage msg) throws ThingsboardException; } From fc7b42c3d6738a63905b0c02b42ee2f7681a9d1e Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 18 Nov 2020 15:28:16 +0200 Subject: [PATCH 4/5] Improvements to the templates --- .../DefaultTbApiUsageStateService.java | 35 ++- .../service/mail/DefaultMailService.java | 32 ++- .../resources/templates/state.enabled.ftl | 231 +++++++++-------- .../resources/templates/state.warning.ftl | 238 ++++++++++-------- 4 files changed, 320 insertions(+), 216 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index 59b63d7200..330ecc23f5 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -297,15 +297,9 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { if (StringUtils.isNotEmpty(email)) { result.forEach((apiFeature, stateValue) -> { - ApiUsageRecordKey[] keys = ApiUsageRecordKey.getKeys(apiFeature); - ApiUsageStateMailMessage[] msgs = new ApiUsageStateMailMessage[keys.length]; - for (int i = 0; i < keys.length; i++) { - ApiUsageRecordKey key = keys[i]; - msgs[i] = new ApiUsageStateMailMessage(key, state.getProfileThreshold(key), state.get(key)); - } mailExecutor.submit(() -> { try { - mailService.sendApiFeatureStateEmail(apiFeature, stateValue, email, msgs[0]); + mailService.sendApiFeatureStateEmail(apiFeature, stateValue, email, createStateMailMessage(state, apiFeature, stateValue)); } catch (ThingsboardException e) { log.warn("[{}] Can't send update of the API state to tenant with provided email [{}]", state.getTenantId(), email, e); } @@ -316,6 +310,33 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } } + private ApiUsageStateMailMessage createStateMailMessage(TenantApiUsageState state, ApiFeature apiFeature, ApiUsageStateValue stateValue) { + StateChecker checker = getStateChecker(stateValue); + for (ApiUsageRecordKey apiUsageRecordKey : ApiUsageRecordKey.getKeys(apiFeature)) { + long threshold = state.getProfileThreshold(apiUsageRecordKey); + long warnThreshold = state.getProfileWarnThreshold(apiUsageRecordKey); + long value = state.get(apiUsageRecordKey); + if (checker.check(threshold, warnThreshold, value)) { + return new ApiUsageStateMailMessage(apiUsageRecordKey, threshold, value); + } + } + return null; + } + + private StateChecker getStateChecker(ApiUsageStateValue stateValue) { + if (ApiUsageStateValue.ENABLED.equals(stateValue)) { + return (t, wt, v) -> true; + } else if (ApiUsageStateValue.WARNING.equals(stateValue)) { + return (t, wt, v) -> v < t && v >= wt; + } else { + return (t, wt, v) -> v >= t; + } + } + + private interface StateChecker { + boolean check(long threshold, long warnThreshold, long value); + } + private void checkStartOfNextCycle() { updateLock.lock(); try { diff --git a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java index 19509243ef..3045fe0cce 100644 --- a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java +++ b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java @@ -30,6 +30,7 @@ import org.springframework.ui.freemarker.FreeMarkerTemplateUtils; import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.ApiFeature; +import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageStateMailMessage; import org.thingsboard.server.common.data.ApiUsageStateValue; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; @@ -261,19 +262,36 @@ public class DefaultMailService implements MailService { switch (stateValue) { case ENABLED: + model.put("apiLabel", toEnabledValueLabel(apiFeature)); message = mergeTemplateIntoString("state.enabled.ftl", model); break; case WARNING: + model.put("apiLimitValueLabel", toDisabledValueLabel(msg.getKey(), msg.getThreshold())); + model.put("apiValueLabel", toDisabledValueLabel(apiFeature) + " " + toDisabledValueLabel(msg.getKey(), msg.getValue())); message = mergeTemplateIntoString("state.warning.ftl", model); break; case DISABLED: - model.put("apiLimitValueLabel", toDisabledValueLabel(apiFeature) + " " + toDisabledValueLabel(msg)); + model.put("apiLimitValueLabel", toDisabledValueLabel(apiFeature) + " " + toDisabledValueLabel(msg.getKey(), msg.getThreshold())); message = mergeTemplateIntoString("state.disabled.ftl", model); break; } sendMail(mailSender, mailFrom, email, subject, message); } + private String toEnabledValueLabel(ApiFeature apiFeature) { + switch (apiFeature) { + case DB: + return "save"; + case TRANSPORT: + return "receive"; + case JS: + case RE: + return "invoke"; + default: + throw new RuntimeException("Not implemented!"); + } + } + private String toDisabledValueLabel(ApiFeature apiFeature) { switch (apiFeature) { case DB: @@ -288,17 +306,17 @@ public class DefaultMailService implements MailService { } } - private String toDisabledValueLabel(ApiUsageStateMailMessage msg) { - switch (msg.getKey()) { + private String toDisabledValueLabel(ApiUsageRecordKey key, long value) { + switch (key) { case STORAGE_DP_COUNT: case TRANSPORT_DP_COUNT: - return (msg.getThreshold() / 1000000) + "M data points"; + return (value / 1000000) + "M data points"; case TRANSPORT_MSG_COUNT: - return (msg.getThreshold() / 1000000) + "M messages"; + return (value / 1000000) + "M messages"; case JS_EXEC_COUNT: - return (msg.getThreshold() / 1000000) + "M JavaScript functions"; + return (value / 1000000) + "M JavaScript functions"; case RE_EXEC_COUNT: - return (msg.getThreshold() / 1000000) + "M Rule Engine nodes"; + return (value / 1000000) + "M Rule Engine nodes"; default: throw new RuntimeException("Not implemented!"); } diff --git a/application/src/main/resources/templates/state.enabled.ftl b/application/src/main/resources/templates/state.enabled.ftl index 497b3cd780..52db9683b7 100644 --- a/application/src/main/resources/templates/state.enabled.ftl +++ b/application/src/main/resources/templates/state.enabled.ftl @@ -15,112 +15,141 @@ limitations under the License. --> - - + + - - -Thingsboard - Api Usage State - - - + + + Thingsboard - Api Usage State + + + - - - -
-
- - -
- - - - - - - - <#list apiUsageStateMailMessages as msg> - - - - - - - - - -
-

Thingsboard Api Usage State for tenant has been updated

-
- Thingsboard Usage state ${apiFeature} was updated to status ENABLED. -
- ${msg.key.apiLimitKey} = ${msg.threshold} -
- ${msg.key.apiCountKey} = ${msg.value} -
- — The Thingsboard -
- -
+ + + + + + + + +
+ + + + + + + + + + + + +
+

Your ThingsBoard account feature was enabled

+
We have enabled the ${apiFeature} for your account and ThingsBoard already able to ${apiLabel} messages. +
— The ThingsBoard +
+
+ + + + - +
This email was sent to ${targetEmail} by ThingsBoard.
diff --git a/application/src/main/resources/templates/state.warning.ftl b/application/src/main/resources/templates/state.warning.ftl index bd4424a0b9..1a2787a9f5 100644 --- a/application/src/main/resources/templates/state.warning.ftl +++ b/application/src/main/resources/templates/state.warning.ftl @@ -15,112 +15,148 @@ limitations under the License. --> - - + + - - -Thingsboard - Api Usage State - - - + + + Thingsboard - Api Usage State + + + - - - - diff --git a/application/src/main/resources/templates/state.enabled.ftl b/application/src/main/resources/templates/state.enabled.ftl index 52db9683b7..0a7841f871 100644 --- a/application/src/main/resources/templates/state.enabled.ftl +++ b/application/src/main/resources/templates/state.enabled.ftl @@ -123,7 +123,7 @@ diff --git a/application/src/main/resources/templates/state.warning.ftl b/application/src/main/resources/templates/state.warning.ftl index 1a2787a9f5..1ce29100a7 100644 --- a/application/src/main/resources/templates/state.warning.ftl +++ b/application/src/main/resources/templates/state.warning.ftl @@ -117,14 +117,14 @@ diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java b/common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java index fe923cefaa..1f693a51b9 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java @@ -20,7 +20,7 @@ import lombok.Getter; public enum ApiFeature { TRANSPORT("transportApiState", "Device API"), DB("dbApiState", "Telemetry persistence"), - RE("ruleEngineApiState", "Rule engine execution"), + RE("ruleEngineApiState", "Rule Engine execution"), JS("jsExecutionApiState", "JavaScript functions execution"); @Getter
-
- - -
- - - - - - - - <#list apiUsageStateMailMessages as msg> - - - - - - - - - -
-

Thingsboard Api Usage State for tenant has been updated

-
- Thingsboard Usage state ${apiFeature} was updated to status WARNING. -
- ${msg.key.apiLimitKey} = ${msg.threshold} -
- ${msg.key.apiCountKey} = ${msg.value} -
- — The Thingsboard -
- -
+ + + + + + + + +
+ + + + + + + + + + + + + + + +
+

Your ThingsBoard account feature may be disabled

+
+ Your ${apiFeature} limit (${apiLimitValueLabel}) is almost exhausted.
ThingsBoard has already ${apiValueLabel}.
${apiFeature} will be disabled for your account when limit will be reached. +
Please contact your system administrator to resolve the issue. +
— The ThingsBoard +
+
+ + + + - +
This email was sent to ${targetEmail} by ThingsBoard.
From 031f579bd6cd4986dcd9bddd42771b2368842dd7 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Fri, 20 Nov 2020 15:44:42 +0200 Subject: [PATCH 5/5] Email notifications for API state --- .../service/mail/DefaultMailService.java | 49 ++++++++++++++++--- .../resources/templates/state.disabled.ftl | 3 +- .../resources/templates/state.enabled.ftl | 2 +- .../resources/templates/state.warning.ftl | 4 +- .../server/common/data/ApiFeature.java | 2 +- 5 files changed, 46 insertions(+), 14 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java index 3045fe0cce..a26363c5ff 100644 --- a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java +++ b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java @@ -20,6 +20,7 @@ import freemarker.template.Configuration; import freemarker.template.Template; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.MessageSource; import org.springframework.core.NestedRuntimeException; @@ -55,6 +56,8 @@ public class DefaultMailService implements MailService { public static final String MAIL_PROP = "mail."; public static final String TARGET_EMAIL = "targetEmail"; public static final String UTF_8 = "UTF-8"; + public static final int _10K = 10000; + public static final int _1M = 1000000; @Autowired private MessageSource messages; @@ -266,8 +269,7 @@ public class DefaultMailService implements MailService { message = mergeTemplateIntoString("state.enabled.ftl", model); break; case WARNING: - model.put("apiLimitValueLabel", toDisabledValueLabel(msg.getKey(), msg.getThreshold())); - model.put("apiValueLabel", toDisabledValueLabel(apiFeature) + " " + toDisabledValueLabel(msg.getKey(), msg.getValue())); + model.put("apiValueLabel", toDisabledValueLabel(apiFeature) + " " + toWarningValueLabel(msg.getKey(), msg.getValue(), msg.getThreshold())); message = mergeTemplateIntoString("state.warning.ftl", model); break; case DISABLED: @@ -285,8 +287,9 @@ public class DefaultMailService implements MailService { case TRANSPORT: return "receive"; case JS: - case RE: return "invoke"; + case RE: + return "process"; default: throw new RuntimeException("Not implemented!"); } @@ -299,8 +302,27 @@ public class DefaultMailService implements MailService { case TRANSPORT: return "received"; case JS: - case RE: return "invoked"; + case RE: + return "processed"; + default: + throw new RuntimeException("Not implemented!"); + } + } + + private String toWarningValueLabel(ApiUsageRecordKey key, long value, long threshold) { + String valueInM = getValueAsString(value); + String thresholdInM = getValueAsString(threshold); + switch (key) { + case STORAGE_DP_COUNT: + case TRANSPORT_DP_COUNT: + return valueInM + " out of " + thresholdInM + " allowed data points"; + case TRANSPORT_MSG_COUNT: + return valueInM + " out of " + thresholdInM + " allowed messages"; + case JS_EXEC_COUNT: + return valueInM + " out of " + thresholdInM + " allowed JavaScript functions"; + case RE_EXEC_COUNT: + return valueInM + " out of " + thresholdInM + " allowed Rule Engine messages"; default: throw new RuntimeException("Not implemented!"); } @@ -310,18 +332,29 @@ public class DefaultMailService implements MailService { switch (key) { case STORAGE_DP_COUNT: case TRANSPORT_DP_COUNT: - return (value / 1000000) + "M data points"; + return getValueAsString(value) + " data points"; case TRANSPORT_MSG_COUNT: - return (value / 1000000) + "M messages"; + return getValueAsString(value) + " messages"; case JS_EXEC_COUNT: - return (value / 1000000) + "M JavaScript functions"; + return "JavaScript functions " + getValueAsString(value) + " times"; case RE_EXEC_COUNT: - return (value / 1000000) + "M Rule Engine nodes"; + return getValueAsString(value) + " Rule Engine messages"; default: throw new RuntimeException("Not implemented!"); } } + @NotNull + private String getValueAsString(long value) { + if (value > _1M && value % _1M < _10K) { + return value / _1M + "M"; + } else if (value > _10K) { + return String.format("%.2fM", ((double) value) / 1000000); + } else { + return value + ""; + } + } + private void sendMail(JavaMailSenderImpl mailSender, String mailFrom, String email, String subject, String message) throws ThingsboardException { diff --git a/application/src/main/resources/templates/state.disabled.ftl b/application/src/main/resources/templates/state.disabled.ftl index 33a368cceb..f14bdd2235 100644 --- a/application/src/main/resources/templates/state.disabled.ftl +++ b/application/src/main/resources/templates/state.disabled.ftl @@ -123,8 +123,7 @@
We have disabled the ${apiFeature} for your account because ThingsBoard has already  ${apiLimitValueLabel} - . + valign="top">We have disabled the ${apiFeature} for your account because ThingsBoard has already ${apiLimitValueLabel}.
We have enabled the ${apiFeature} for your account and ThingsBoard already able to ${apiLabel} messages. + valign="top">We have enabled the ${apiFeature} for your account and ThingsBoard is already able to ${apiLabel} messages.
-

Your ThingsBoard account feature may be disabled

+

Your ThingsBoard account feature may be disabled soon

- Your ${apiFeature} limit (${apiLimitValueLabel}) is almost exhausted.
ThingsBoard has already ${apiValueLabel}.
${apiFeature} will be disabled for your account when limit will be reached. + ThingsBoard has already ${apiValueLabel}.
${apiFeature} will be disabled for your account once the limit will be reached.