diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java index 44d81902a8..a79ed50eb7 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -110,7 +110,7 @@ public class ThingsboardInstallService { log.info("Upgrading ThingsBoard from version 3.5.1 to 3.6.0 ..."); databaseEntitiesUpgradeService.upgradeDatabase("3.5.1"); dataUpdateService.updateData("3.5.1"); - systemDataLoaderService.updateDefaultNotificationConfigs(); + systemDataLoaderService.updateDefaultNotificationConfigs(true); case "3.6.0": log.info("Upgrading ThingsBoard from version 3.6.0 to 3.6.1 ..."); databaseEntitiesUpgradeService.upgradeDatabase("3.6.0"); @@ -126,7 +126,10 @@ public class ThingsboardInstallService { case "3.6.2": log.info("Upgrading ThingsBoard from version 3.6.2 to 3.6.3 ..."); databaseEntitiesUpgradeService.upgradeDatabase("3.6.2"); - systemDataLoaderService.updateDefaultNotificationConfigs(); + systemDataLoaderService.updateDefaultNotificationConfigs(true); + case "3.6.3": + log.info("Upgrading ThingsBoard from version 3.6.3 to 3.6.4 ..."); + systemDataLoaderService.updateDefaultNotificationConfigs(false); //TODO DON'T FORGET to update switch statement in the CacheCleanupService if you need to clear the cache break; default: diff --git a/application/src/main/java/org/thingsboard/server/service/housekeeper/DefaultHousekeeperService.java b/application/src/main/java/org/thingsboard/server/service/housekeeper/DefaultHousekeeperService.java index c9054c30fa..56123af64d 100644 --- a/application/src/main/java/org/thingsboard/server/service/housekeeper/DefaultHousekeeperService.java +++ b/application/src/main/java/org/thingsboard/server/service/housekeeper/DefaultHousekeeperService.java @@ -21,10 +21,12 @@ import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ThingsBoardThreadFactory; +import org.thingsboard.server.common.data.housekeeper.HousekeeperTask; +import org.thingsboard.server.common.data.housekeeper.HousekeeperTaskType; +import org.thingsboard.server.common.data.notification.rule.trigger.TaskProcessingFailureTrigger; +import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.dao.housekeeper.HousekeeperService; -import org.thingsboard.server.dao.housekeeper.data.HousekeeperTask; -import org.thingsboard.server.dao.housekeeper.data.HousekeeperTaskType; import org.thingsboard.server.gen.transport.TransportProtos.HousekeeperTaskProto; import org.thingsboard.server.gen.transport.TransportProtos.ToHousekeeperServiceMsg; import org.thingsboard.server.queue.TbQueueConsumer; @@ -60,6 +62,7 @@ public class DefaultHousekeeperService implements HousekeeperService { private final TbQueueProducer> producer; private final HousekeeperReprocessingService reprocessingService; private final HousekeeperStatsService statsService; + private final NotificationRuleProcessor notificationRuleProcessor; private final ExecutorService consumerExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("housekeeper-consumer")); private final ExecutorService executor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("housekeeper-task-processor")); @@ -75,11 +78,13 @@ public class DefaultHousekeeperService implements HousekeeperService { TbCoreQueueFactory queueFactory, TbQueueProducerProvider producerProvider, HousekeeperStatsService statsService, + NotificationRuleProcessor notificationRuleProcessor, @Lazy List> taskProcessors) { this.consumer = queueFactory.createHousekeeperMsgConsumer(); this.producer = producerProvider.getHousekeeperMsgProducer(); this.reprocessingService = reprocessingService; this.statsService = statsService; + this.notificationRuleProcessor = notificationRuleProcessor; this.taskProcessors = taskProcessors.stream().collect(Collectors.toMap(HousekeeperTaskProcessor::getTaskType, p -> p)); } @@ -155,14 +160,14 @@ public class DefaultHousekeeperService implements HousekeeperService { task.getTaskType(), msg.getTask().getAttempt(), task, error); reprocessingService.submitForReprocessing(msg, error); statsService.reportFailure(task.getTaskType(), msg); + notificationRuleProcessor.process(TaskProcessingFailureTrigger.builder() + .task(task) + .error(error) + .attempt(msg.getTask().getAttempt()) + .build()); } } - @Override - public void submitTask(HousekeeperTask task) { - submitTask(task.getEntityId().getId(), task); - } - @Override public void submitTask(UUID key, HousekeeperTask task) { log.trace("[{}][{}][{}] Submitting task: {}", task.getTenantId(), task.getEntityId().getEntityType(), task.getEntityId(), task.getTaskType()); diff --git a/application/src/main/java/org/thingsboard/server/service/housekeeper/processor/AlarmsUnassignTaskProcessor.java b/application/src/main/java/org/thingsboard/server/service/housekeeper/processor/AlarmsUnassignTaskProcessor.java index 68fd05deec..9a8517903f 100644 --- a/application/src/main/java/org/thingsboard/server/service/housekeeper/processor/AlarmsUnassignTaskProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/housekeeper/processor/AlarmsUnassignTaskProcessor.java @@ -20,8 +20,8 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.UserId; -import org.thingsboard.server.dao.housekeeper.data.HousekeeperTaskType; -import org.thingsboard.server.dao.housekeeper.data.AlarmsUnassignHousekeeperTask; +import org.thingsboard.server.common.data.housekeeper.HousekeeperTaskType; +import org.thingsboard.server.common.data.housekeeper.AlarmsUnassignHousekeeperTask; import org.thingsboard.server.service.entitiy.alarm.TbAlarmService; import java.util.List; diff --git a/application/src/main/java/org/thingsboard/server/service/housekeeper/processor/AttributesDeletionTaskProcessor.java b/application/src/main/java/org/thingsboard/server/service/housekeeper/processor/AttributesDeletionTaskProcessor.java index 0467e50aeb..c8ff5306a7 100644 --- a/application/src/main/java/org/thingsboard/server/service/housekeeper/processor/AttributesDeletionTaskProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/housekeeper/processor/AttributesDeletionTaskProcessor.java @@ -19,8 +19,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.thingsboard.server.dao.attributes.AttributesService; -import org.thingsboard.server.dao.housekeeper.data.HousekeeperTask; -import org.thingsboard.server.dao.housekeeper.data.HousekeeperTaskType; +import org.thingsboard.server.common.data.housekeeper.HousekeeperTask; +import org.thingsboard.server.common.data.housekeeper.HousekeeperTaskType; @Component @RequiredArgsConstructor diff --git a/application/src/main/java/org/thingsboard/server/service/housekeeper/processor/EntitiesDeletionTaskProcessor.java b/application/src/main/java/org/thingsboard/server/service/housekeeper/processor/EntitiesDeletionTaskProcessor.java index da7af3c42b..8028a76199 100644 --- a/application/src/main/java/org/thingsboard/server/service/housekeeper/processor/EntitiesDeletionTaskProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/housekeeper/processor/EntitiesDeletionTaskProcessor.java @@ -19,8 +19,8 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; import org.thingsboard.server.dao.entity.EntityDaoService; import org.thingsboard.server.dao.entity.EntityServiceRegistry; -import org.thingsboard.server.dao.housekeeper.data.EntitiesDeletionHousekeeperTask; -import org.thingsboard.server.dao.housekeeper.data.HousekeeperTaskType; +import org.thingsboard.server.common.data.housekeeper.EntitiesDeletionHousekeeperTask; +import org.thingsboard.server.common.data.housekeeper.HousekeeperTaskType; @Component @RequiredArgsConstructor diff --git a/application/src/main/java/org/thingsboard/server/service/housekeeper/processor/EntityAlarmsDeletionTaskProcessor.java b/application/src/main/java/org/thingsboard/server/service/housekeeper/processor/EntityAlarmsDeletionTaskProcessor.java index a91f2262f9..974a95eb08 100644 --- a/application/src/main/java/org/thingsboard/server/service/housekeeper/processor/EntityAlarmsDeletionTaskProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/housekeeper/processor/EntityAlarmsDeletionTaskProcessor.java @@ -18,8 +18,8 @@ package org.thingsboard.server.service.housekeeper.processor; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; import org.thingsboard.server.dao.alarm.AlarmService; -import org.thingsboard.server.dao.housekeeper.data.HousekeeperTask; -import org.thingsboard.server.dao.housekeeper.data.HousekeeperTaskType; +import org.thingsboard.server.common.data.housekeeper.HousekeeperTask; +import org.thingsboard.server.common.data.housekeeper.HousekeeperTaskType; @Component @RequiredArgsConstructor diff --git a/application/src/main/java/org/thingsboard/server/service/housekeeper/processor/EventsDeletionTaskProcessor.java b/application/src/main/java/org/thingsboard/server/service/housekeeper/processor/EventsDeletionTaskProcessor.java index 481f9f1bbf..551c8ce7e0 100644 --- a/application/src/main/java/org/thingsboard/server/service/housekeeper/processor/EventsDeletionTaskProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/housekeeper/processor/EventsDeletionTaskProcessor.java @@ -18,8 +18,8 @@ package org.thingsboard.server.service.housekeeper.processor; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; import org.thingsboard.server.dao.event.EventService; -import org.thingsboard.server.dao.housekeeper.data.HousekeeperTask; -import org.thingsboard.server.dao.housekeeper.data.HousekeeperTaskType; +import org.thingsboard.server.common.data.housekeeper.HousekeeperTask; +import org.thingsboard.server.common.data.housekeeper.HousekeeperTaskType; @Component @RequiredArgsConstructor diff --git a/application/src/main/java/org/thingsboard/server/service/housekeeper/processor/HousekeeperTaskProcessor.java b/application/src/main/java/org/thingsboard/server/service/housekeeper/processor/HousekeeperTaskProcessor.java index 7daa99540e..79e87fcc91 100644 --- a/application/src/main/java/org/thingsboard/server/service/housekeeper/processor/HousekeeperTaskProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/housekeeper/processor/HousekeeperTaskProcessor.java @@ -15,8 +15,8 @@ */ package org.thingsboard.server.service.housekeeper.processor; -import org.thingsboard.server.dao.housekeeper.data.HousekeeperTask; -import org.thingsboard.server.dao.housekeeper.data.HousekeeperTaskType; +import org.thingsboard.server.common.data.housekeeper.HousekeeperTask; +import org.thingsboard.server.common.data.housekeeper.HousekeeperTaskType; public interface HousekeeperTaskProcessor { diff --git a/application/src/main/java/org/thingsboard/server/service/housekeeper/processor/TelemetryDeletionTaskProcessor.java b/application/src/main/java/org/thingsboard/server/service/housekeeper/processor/TelemetryDeletionTaskProcessor.java index e7da740261..c7f37ed091 100644 --- a/application/src/main/java/org/thingsboard/server/service/housekeeper/processor/TelemetryDeletionTaskProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/housekeeper/processor/TelemetryDeletionTaskProcessor.java @@ -20,8 +20,8 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.kv.BaseDeleteTsKvQuery; import org.thingsboard.server.common.data.kv.DeleteTsKvQuery; -import org.thingsboard.server.dao.housekeeper.data.HousekeeperTask; -import org.thingsboard.server.dao.housekeeper.data.HousekeeperTaskType; +import org.thingsboard.server.common.data.housekeeper.HousekeeperTask; +import org.thingsboard.server.common.data.housekeeper.HousekeeperTaskType; import org.thingsboard.server.dao.timeseries.TimeseriesService; import java.util.List; diff --git a/application/src/main/java/org/thingsboard/server/service/housekeeper/stats/HousekeeperStatsService.java b/application/src/main/java/org/thingsboard/server/service/housekeeper/stats/HousekeeperStatsService.java index ab4f699ed1..3f6e88a299 100644 --- a/application/src/main/java/org/thingsboard/server/service/housekeeper/stats/HousekeeperStatsService.java +++ b/application/src/main/java/org/thingsboard/server/service/housekeeper/stats/HousekeeperStatsService.java @@ -23,8 +23,7 @@ import org.thingsboard.server.common.stats.DefaultCounter; import org.thingsboard.server.common.stats.StatsCounter; import org.thingsboard.server.common.stats.StatsFactory; import org.thingsboard.server.common.stats.StatsType; -import org.thingsboard.server.dao.housekeeper.data.HousekeeperTask; -import org.thingsboard.server.dao.housekeeper.data.HousekeeperTaskType; +import org.thingsboard.server.common.data.housekeeper.HousekeeperTaskType; import org.thingsboard.server.gen.transport.TransportProtos.ToHousekeeperServiceMsg; import java.util.ArrayList; diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java index b20a548d90..45ae1280b8 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java @@ -548,7 +548,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { } else { ListenableFuture> saveFuture = attributesService.save(TenantId.SYS_TENANT_ID, deviceId, DataConstants.SERVER_SCOPE, Collections.singletonList(new BaseAttributeKvEntry(new BooleanDataEntry(key, value) - , System.currentTimeMillis()))); + , System.currentTimeMillis()))); addTsCallback(saveFuture, new TelemetrySaveCallback<>(deviceId, key, value)); } } @@ -692,23 +692,26 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { @Override @SneakyThrows - public void updateDefaultNotificationConfigs() { - PageDataIterable tenants = new PageDataIterable<>(tenantService::findTenantsIds, 500); - ExecutorService executor = Executors.newFixedThreadPool(Math.max(Runtime.getRuntime().availableProcessors(), 4)); - log.info("Updating default edge failure notification configs for all tenants"); - AtomicInteger count = new AtomicInteger(); - for (TenantId tenantId : tenants) { - executor.submit(() -> { - notificationSettingsService.updateDefaultNotificationConfigs(tenantId); - int n = count.incrementAndGet(); - if (n % 500 == 0) { - log.info("{} tenants processed", n); - } - }); - } - executor.shutdown(); - executor.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS); + public void updateDefaultNotificationConfigs(boolean updateTenants) { + log.info("Updating notification configs..."); notificationSettingsService.updateDefaultNotificationConfigs(TenantId.SYS_TENANT_ID); + + if (updateTenants) { + PageDataIterable tenants = new PageDataIterable<>(tenantService::findTenantsIds, 500); + ExecutorService executor = Executors.newFixedThreadPool(Math.max(Runtime.getRuntime().availableProcessors(), 4)); + AtomicInteger count = new AtomicInteger(); + for (TenantId tenantId : tenants) { + executor.submit(() -> { + notificationSettingsService.updateDefaultNotificationConfigs(tenantId); + int n = count.incrementAndGet(); + if (n % 500 == 0) { + log.info("{} tenants processed", n); + } + }); + } + executor.shutdown(); + executor.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS); + } } } diff --git a/application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java index eeac1b6aa4..7c05ff620f 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java @@ -35,6 +35,6 @@ public interface SystemDataLoaderService { void createDefaultNotificationConfigs(); - void updateDefaultNotificationConfigs(); + void updateDefaultNotificationConfigs(boolean updateTenants); } diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/TaskProcessingFailureTriggerProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/TaskProcessingFailureTriggerProcessor.java new file mode 100644 index 0000000000..7754690215 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/TaskProcessingFailureTriggerProcessor.java @@ -0,0 +1,53 @@ +/** + * Copyright © 2016-2024 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.service.notification.rule.trigger; + +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.data.housekeeper.HousekeeperTask; +import org.thingsboard.server.common.data.notification.info.TaskProcessingFailureNotificationInfo; +import org.thingsboard.server.common.data.notification.rule.trigger.TaskProcessingFailureTrigger; +import org.thingsboard.server.common.data.notification.rule.trigger.config.NotificationRuleTriggerType; +import org.thingsboard.server.common.data.notification.rule.trigger.config.TaskProcessingFailureNotificationRuleTriggerConfig; + +@Service +public class TaskProcessingFailureTriggerProcessor implements NotificationRuleTriggerProcessor { + + @Override + public boolean matchesFilter(TaskProcessingFailureTrigger trigger, TaskProcessingFailureNotificationRuleTriggerConfig triggerConfig) { + return true; + } + + @Override + public TaskProcessingFailureNotificationInfo constructNotificationInfo(TaskProcessingFailureTrigger trigger) { + HousekeeperTask task = trigger.getTask(); + return TaskProcessingFailureNotificationInfo.builder() + .tenantId(task.getTenantId()) + .entityId(task.getEntityId()) + .taskType(task.getTaskType()) + .taskDescription(task.getDescription()) + .error(StringUtils.truncate(ExceptionUtils.getStackTrace(trigger.getError()), 1024)) + .attempt(trigger.getAttempt()) + .build(); + } + + @Override + public NotificationRuleTriggerType getTriggerType() { + return NotificationRuleTriggerType.TASK_PROCESSING_FAILURE; + } + +} diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTemplateService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTemplateService.java index 1f4019063b..664f976904 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTemplateService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTemplateService.java @@ -32,6 +32,8 @@ public interface NotificationTemplateService { PageData findNotificationTemplatesByTenantIdAndNotificationTypes(TenantId tenantId, List notificationTypes, PageLink pageLink); + int countNotificationTemplatesByTenantIdAndNotificationTypes(TenantId tenantId, List notificationTypes); + void deleteNotificationTemplateById(TenantId tenantId, NotificationTemplateId id); void deleteNotificationTemplatesByTenantId(TenantId tenantId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/housekeeper/data/AlarmsUnassignHousekeeperTask.java b/common/data/src/main/java/org/thingsboard/server/common/data/housekeeper/AlarmsUnassignHousekeeperTask.java similarity index 95% rename from dao/src/main/java/org/thingsboard/server/dao/housekeeper/data/AlarmsUnassignHousekeeperTask.java rename to common/data/src/main/java/org/thingsboard/server/common/data/housekeeper/AlarmsUnassignHousekeeperTask.java index 8761229e89..af4e746987 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/housekeeper/data/AlarmsUnassignHousekeeperTask.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/housekeeper/AlarmsUnassignHousekeeperTask.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.housekeeper.data; +package org.thingsboard.server.common.data.housekeeper; import lombok.AccessLevel; import lombok.Data; diff --git a/dao/src/main/java/org/thingsboard/server/dao/housekeeper/data/EntitiesDeletionHousekeeperTask.java b/common/data/src/main/java/org/thingsboard/server/common/data/housekeeper/EntitiesDeletionHousekeeperTask.java similarity index 83% rename from dao/src/main/java/org/thingsboard/server/dao/housekeeper/data/EntitiesDeletionHousekeeperTask.java rename to common/data/src/main/java/org/thingsboard/server/common/data/housekeeper/EntitiesDeletionHousekeeperTask.java index e20f3ffe0c..515703991c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/housekeeper/data/EntitiesDeletionHousekeeperTask.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/housekeeper/EntitiesDeletionHousekeeperTask.java @@ -13,8 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.housekeeper.data; +package org.thingsboard.server.common.data.housekeeper; +import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.AccessLevel; import lombok.Data; import lombok.EqualsAndHashCode; @@ -34,4 +35,10 @@ public class EntitiesDeletionHousekeeperTask extends HousekeeperTask { this.entityType = entityType; } + @JsonIgnore + @Override + public String getDescription() { + return entityType.getNormalName().toLowerCase() + "s deletion"; + } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/housekeeper/data/HousekeeperTask.java b/common/data/src/main/java/org/thingsboard/server/common/data/housekeeper/HousekeeperTask.java similarity index 91% rename from dao/src/main/java/org/thingsboard/server/dao/housekeeper/data/HousekeeperTask.java rename to common/data/src/main/java/org/thingsboard/server/common/data/housekeeper/HousekeeperTask.java index 6b63d43d43..44598d511c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/housekeeper/data/HousekeeperTask.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/housekeeper/HousekeeperTask.java @@ -13,8 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.housekeeper.data; +package org.thingsboard.server.common.data.housekeeper; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonSubTypes.Type; @@ -76,4 +77,9 @@ public class HousekeeperTask implements Serializable { return new EntitiesDeletionHousekeeperTask(tenantId, entityType); } + @JsonIgnore + public String getDescription() { + return taskType.getDescription() + " for " + entityId.getEntityType().getNormalName().toLowerCase() + " " + entityId.getId(); + } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/housekeeper/HousekeeperTaskType.java b/common/data/src/main/java/org/thingsboard/server/common/data/housekeeper/HousekeeperTaskType.java new file mode 100644 index 0000000000..4f30f94a45 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/housekeeper/HousekeeperTaskType.java @@ -0,0 +1,34 @@ +/** + * Copyright © 2016-2024 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.housekeeper; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public enum HousekeeperTaskType { + + DELETE_ENTITIES("entities deletion"), + DELETE_ATTRIBUTES("attributes deletion"), + DELETE_TELEMETRY("telemetry deletion"), + DELETE_EVENTS("events deletion"), + UNASSIGN_ALARMS("alarms unassigning"), + DELETE_ENTITY_ALARMS("entity alarms deletion"); + + private final String description; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationType.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationType.java index 5a8fd87933..ec66a3d53a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationType.java @@ -30,6 +30,7 @@ public enum NotificationType { RULE_NODE, RATE_LIMITS, EDGE_CONNECTION, - EDGE_COMMUNICATION_FAILURE + EDGE_COMMUNICATION_FAILURE, + TASK_PROCESSING_FAILURE } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/TaskProcessingFailureNotificationInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/TaskProcessingFailureNotificationInfo.java new file mode 100644 index 0000000000..cbee861c04 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/TaskProcessingFailureNotificationInfo.java @@ -0,0 +1,61 @@ +/** + * Copyright © 2016-2024 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.notification.info; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.thingsboard.server.common.data.housekeeper.HousekeeperTaskType; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; + +import java.util.Map; + +import static org.thingsboard.server.common.data.util.CollectionsUtil.mapOf; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class TaskProcessingFailureNotificationInfo implements RuleOriginatedNotificationInfo { + + private TenantId tenantId; + private EntityId entityId; + private HousekeeperTaskType taskType; + private String taskDescription; + private String error; + private int attempt; + + @Override + public Map getTemplateData() { + return mapOf( + "tenantId", tenantId.toString(), + "entityType", entityId.getEntityType().getNormalName(), + "entityId", entityId.getId().toString(), + "taskType", taskType.getDescription(), + "taskDescription", taskDescription, + "error", error, + "attempt", String.valueOf(attempt) + ); + } + + @Override + public TenantId getAffectedTenantId() { + return tenantId; + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/TaskProcessingFailureTrigger.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/TaskProcessingFailureTrigger.java new file mode 100644 index 0000000000..9ccfac6aff --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/TaskProcessingFailureTrigger.java @@ -0,0 +1,65 @@ +/** + * Copyright © 2016-2024 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.notification.rule.trigger; + +import lombok.Builder; +import lombok.Data; +import org.thingsboard.server.common.data.housekeeper.HousekeeperTask; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.notification.rule.trigger.config.NotificationRuleTriggerType; + +import java.util.concurrent.TimeUnit; + +@Data +@Builder +public class TaskProcessingFailureTrigger implements NotificationRuleTrigger { + + private final HousekeeperTask task; + private final int attempt; + private final Throwable error; + + @Override + public NotificationRuleTriggerType getType() { + return NotificationRuleTriggerType.TASK_PROCESSING_FAILURE; + } + + @Override + public TenantId getTenantId() { + return task.getTenantId(); + } + + @Override + public EntityId getOriginatorEntityId() { + return task.getEntityId(); + } + + @Override + public boolean deduplicate() { + return true; + } + + @Override + public String getDeduplicationKey() { + return String.join(":", NotificationRuleTrigger.super.getDeduplicationKey(), task.getTaskType().toString()); + } + + @Override + public long getDefaultDeduplicationDuration() { + return TimeUnit.MINUTES.toMillis(30); + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/NotificationRuleTriggerConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/NotificationRuleTriggerConfig.java index 15a5e59255..1621ec8120 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/NotificationRuleTriggerConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/NotificationRuleTriggerConfig.java @@ -38,6 +38,7 @@ import java.io.Serializable; @Type(value = RateLimitsNotificationRuleTriggerConfig.class, name = "RATE_LIMITS"), @Type(value = EdgeConnectionNotificationRuleTriggerConfig.class, name = "EDGE_CONNECTION"), @Type(value = EdgeCommunicationFailureNotificationRuleTriggerConfig.class, name = "EDGE_COMMUNICATION_FAILURE"), + @Type(value = TaskProcessingFailureNotificationRuleTriggerConfig.class, name = "TASK_PROCESSING_FAILURE") }) public interface NotificationRuleTriggerConfig extends Serializable { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/NotificationRuleTriggerType.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/NotificationRuleTriggerType.java index 8469fac752..8846ae284d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/NotificationRuleTriggerType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/NotificationRuleTriggerType.java @@ -31,7 +31,8 @@ public enum NotificationRuleTriggerType { NEW_PLATFORM_VERSION(false), ENTITIES_LIMIT(false), API_USAGE_LIMIT(false), - RATE_LIMITS(false); + RATE_LIMITS(false), + TASK_PROCESSING_FAILURE(false); private final boolean tenantLevel; diff --git a/dao/src/main/java/org/thingsboard/server/dao/housekeeper/data/HousekeeperTaskType.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/TaskProcessingFailureNotificationRuleTriggerConfig.java similarity index 61% rename from dao/src/main/java/org/thingsboard/server/dao/housekeeper/data/HousekeeperTaskType.java rename to common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/TaskProcessingFailureNotificationRuleTriggerConfig.java index 51fb9ab926..73f0517238 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/housekeeper/data/HousekeeperTaskType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/TaskProcessingFailureNotificationRuleTriggerConfig.java @@ -13,13 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.housekeeper.data; +package org.thingsboard.server.common.data.notification.rule.trigger.config; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class TaskProcessingFailureNotificationRuleTriggerConfig implements NotificationRuleTriggerConfig { + + @Override + public NotificationRuleTriggerType getTriggerType() { + return NotificationRuleTriggerType.TASK_PROCESSING_FAILURE; + } -public enum HousekeeperTaskType { - DELETE_ENTITIES, - DELETE_ATTRIBUTES, - DELETE_TELEMETRY, // maybe divide into latest and ts kv history? - DELETE_EVENTS, - UNASSIGN_ALARMS, - DELETE_ENTITY_ALARMS } diff --git a/dao/src/main/java/org/thingsboard/server/dao/housekeeper/CleanUpService.java b/dao/src/main/java/org/thingsboard/server/dao/housekeeper/CleanUpService.java index 5bdcd341b8..b81b6dc5e6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/housekeeper/CleanUpService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/housekeeper/CleanUpService.java @@ -21,12 +21,13 @@ import org.springframework.stereotype.Component; import org.springframework.transaction.event.TransactionalEventListener; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.housekeeper.HousekeeperTask; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; -import org.thingsboard.server.dao.housekeeper.data.HousekeeperTask; import org.thingsboard.server.dao.relation.RelationService; +import java.util.Optional; import java.util.UUID; @Component @@ -34,7 +35,7 @@ import java.util.UUID; @Slf4j public class CleanUpService { - private final HousekeeperService housekeeperService; + private final Optional housekeeperService; private final RelationService relationService; @TransactionalEventListener(fallbackExecution = true) @@ -44,24 +45,31 @@ public class CleanUpService { log.trace("[{}] Handling entity deletion event: {}", tenantId, event); cleanUpRelatedData(tenantId, entityId); if (entityId.getEntityType() == EntityType.USER) { - housekeeperService.submitTask(HousekeeperTask.unassignAlarms((User) event.getEntity())); + housekeeperService.ifPresent(housekeeperService -> { + housekeeperService.submitTask(HousekeeperTask.unassignAlarms((User) event.getEntity())); + }); } } public void cleanUpRelatedData(TenantId tenantId, EntityId entityId) { // todo: skipped entities list relationService.deleteEntityRelations(tenantId, entityId); - housekeeperService.submitTask(HousekeeperTask.deleteAttributes(tenantId, entityId)); - housekeeperService.submitTask(HousekeeperTask.deleteTelemetry(tenantId, entityId)); - housekeeperService.submitTask(HousekeeperTask.deleteEvents(tenantId, entityId)); - housekeeperService.submitTask(HousekeeperTask.deleteEntityAlarms(tenantId, entityId)); + housekeeperService.ifPresent(housekeeperService -> { + housekeeperService.submitTask(HousekeeperTask.deleteAttributes(tenantId, entityId)); + housekeeperService.submitTask(HousekeeperTask.deleteTelemetry(tenantId, entityId)); + housekeeperService.submitTask(HousekeeperTask.deleteEvents(tenantId, entityId)); + housekeeperService.submitTask(HousekeeperTask.deleteEntityAlarms(tenantId, entityId)); + }); } public void removeTenantEntities(TenantId tenantId, EntityType... entityTypes) { UUID tasksKey = UUID.randomUUID(); // so that all tasks are pushed to single partition to be processed synchronously - for (EntityType entityType : entityTypes) { - housekeeperService.submitTask(tasksKey, HousekeeperTask.deleteEntities(tenantId, entityType)); - } + // todo: just use tenantId as key in the impl + housekeeperService.ifPresent(housekeeperService -> { + for (EntityType entityType : entityTypes) { + housekeeperService.submitTask(tasksKey, HousekeeperTask.deleteEntities(tenantId, entityType)); + } + }); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/housekeeper/HousekeeperService.java b/dao/src/main/java/org/thingsboard/server/dao/housekeeper/HousekeeperService.java index 5e55318953..1bbbb25df1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/housekeeper/HousekeeperService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/housekeeper/HousekeeperService.java @@ -15,13 +15,15 @@ */ package org.thingsboard.server.dao.housekeeper; -import org.thingsboard.server.dao.housekeeper.data.HousekeeperTask; +import org.thingsboard.server.common.data.housekeeper.HousekeeperTask; import java.util.UUID; public interface HousekeeperService { - void submitTask(HousekeeperTask task); + default void submitTask(HousekeeperTask task) { + submitTask(task.getEntityId().getId(), task); + } // tasks with the same key will be pushed to the same partition and thus processed synchronously void submitTask(UUID key, HousekeeperTask task); diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java index 1bdc835b29..a37d37e849 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java @@ -173,17 +173,15 @@ public class DefaultNotificationSettingsService implements NotificationSettingsS defaultNotifications.create(tenantId, DefaultNotifications.entitiesLimitForSysadmin, sysAdmins.getId()); defaultNotifications.create(tenantId, DefaultNotifications.entitiesLimitForTenant, affectedTenantAdmins.getId()); - defaultNotifications.create(tenantId, DefaultNotifications.apiFeatureWarningForSysadmin, sysAdmins.getId()); defaultNotifications.create(tenantId, DefaultNotifications.apiFeatureWarningForTenant, affectedTenantAdmins.getId()); defaultNotifications.create(tenantId, DefaultNotifications.apiFeatureDisabledForSysadmin, sysAdmins.getId()); defaultNotifications.create(tenantId, DefaultNotifications.apiFeatureDisabledForTenant, affectedTenantAdmins.getId()); - defaultNotifications.create(tenantId, DefaultNotifications.exceededRateLimits, affectedTenantAdmins.getId()); defaultNotifications.create(tenantId, DefaultNotifications.exceededPerEntityRateLimits, affectedTenantAdmins.getId()); defaultNotifications.create(tenantId, DefaultNotifications.exceededRateLimitsForSysadmin, sysAdmins.getId()); - defaultNotifications.create(tenantId, DefaultNotifications.newPlatformVersion, sysAdmins.getId()); + defaultNotifications.create(tenantId, DefaultNotifications.taskProcessingFailure, tenantAdmins.getId()); return; } @@ -206,19 +204,19 @@ public class DefaultNotificationSettingsService implements NotificationSettingsS @Override public void updateDefaultNotificationConfigs(TenantId tenantId) { if (tenantId.isSysTenantId()) { - if (notificationTemplateService.findNotificationTemplatesByTenantIdAndNotificationTypes(tenantId, - List.of(NotificationType.RATE_LIMITS), new PageLink(1)).getTotalElements() > 0) { - return; - } - NotificationTarget sysAdmins = notificationTargetService.findNotificationTargetsByTenantIdAndUsersFilterType(tenantId, UsersFilterType.SYSTEM_ADMINISTRATORS).stream() .findFirst().orElseGet(() -> createTarget(tenantId, "System administrators", new SystemAdministratorsFilter(), "All system administrators")); NotificationTarget affectedTenantAdmins = notificationTargetService.findNotificationTargetsByTenantIdAndUsersFilterType(tenantId, UsersFilterType.AFFECTED_TENANT_ADMINISTRATORS).stream() .findFirst().orElseGet(() -> createTarget(tenantId, "Affected tenant's administrators", new AffectedTenantAdministratorsFilter(), "")); - defaultNotifications.create(tenantId, DefaultNotifications.exceededRateLimits, affectedTenantAdmins.getId()); - defaultNotifications.create(tenantId, DefaultNotifications.exceededPerEntityRateLimits, affectedTenantAdmins.getId()); - defaultNotifications.create(tenantId, DefaultNotifications.exceededRateLimitsForSysadmin, sysAdmins.getId()); + if (!isNotificationConfigured(tenantId, NotificationType.RATE_LIMITS)) { + defaultNotifications.create(tenantId, DefaultNotifications.exceededRateLimits, affectedTenantAdmins.getId()); + defaultNotifications.create(tenantId, DefaultNotifications.exceededPerEntityRateLimits, affectedTenantAdmins.getId()); + defaultNotifications.create(tenantId, DefaultNotifications.exceededRateLimitsForSysadmin, sysAdmins.getId()); + } + if (!isNotificationConfigured(tenantId, NotificationType.TASK_PROCESSING_FAILURE)) { + defaultNotifications.create(tenantId, DefaultNotifications.taskProcessingFailure, sysAdmins.getId()); + } } else { var requiredNotificationTypes = List.of(NotificationType.EDGE_CONNECTION, NotificationType.EDGE_COMMUNICATION_FAILURE); var existingNotificationTypes = notificationTemplateService.findNotificationTemplatesByTenantIdAndNotificationTypes( @@ -252,6 +250,10 @@ public class DefaultNotificationSettingsService implements NotificationSettingsS } } + private boolean isNotificationConfigured(TenantId tenantId, NotificationType... notificationTypes) { + return notificationTemplateService.countNotificationTemplatesByTenantIdAndNotificationTypes(tenantId, List.of(notificationTypes)) > 0; + } + private NotificationTarget createTarget(TenantId tenantId, String name, UsersFilter filter, String description) { NotificationTarget target = new NotificationTarget(); target.setTenantId(tenantId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTemplateService.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTemplateService.java index 6cf8239f47..bec6c3d61c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTemplateService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTemplateService.java @@ -69,6 +69,11 @@ public class DefaultNotificationTemplateService extends AbstractEntityService im return notificationTemplateDao.findByTenantIdAndNotificationTypesAndPageLink(tenantId, notificationTypes, pageLink); } + @Override + public int countNotificationTemplatesByTenantIdAndNotificationTypes(TenantId tenantId, List notificationTypes) { + return notificationTemplateDao.countByTenantIdAndNotificationTypes(tenantId, notificationTypes); + } + @Override public void deleteNotificationTemplateById(TenantId tenantId, NotificationTemplateId id) { if (notificationRequestDao.existsByTenantIdAndStatusAndTemplateId(tenantId, NotificationRequestStatus.SCHEDULED, id)) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java index fd17618bd1..4f617c2b9d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java @@ -50,6 +50,7 @@ import org.thingsboard.server.common.data.notification.rule.trigger.config.Notif import org.thingsboard.server.common.data.notification.rule.trigger.config.NotificationRuleTriggerType; import org.thingsboard.server.common.data.notification.rule.trigger.config.RateLimitsNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.config.RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig; +import org.thingsboard.server.common.data.notification.rule.trigger.config.TaskProcessingFailureNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.template.NotificationTemplate; import org.thingsboard.server.common.data.notification.template.NotificationTemplateConfig; import org.thingsboard.server.common.data.notification.template.WebDeliveryMethodNotificationTemplate; @@ -358,6 +359,19 @@ public class DefaultNotifications { .build()) .build(); + public static final DefaultNotification taskProcessingFailure = DefaultNotification.builder() + .name("Task processing failure notification") + .type(NotificationType.TASK_PROCESSING_FAILURE) + .subject("Failed to process ${taskType}") + .text("Failed to process ${taskDescription} for tenant ${tenantId}: ${error}") + .icon("warning").color(YELLOW_COLOR) + .rule(DefaultRule.builder() + .name("Task processing failure") + .triggerConfig(TaskProcessingFailureNotificationRuleTriggerConfig.builder().build()) + .description("Send notification to system admins when task processing fails") + .build()) + .build(); + public static final DefaultNotification jwtSigningKeyIssue = DefaultNotification.builder() .name("JWT Signing Key issue notification") .type(NotificationType.GENERAL) diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTemplateDao.java b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTemplateDao.java index d9a80ce3f5..136bc4d472 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTemplateDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTemplateDao.java @@ -30,6 +30,8 @@ public interface NotificationTemplateDao extends Dao, Expo PageData findByTenantIdAndNotificationTypesAndPageLink(TenantId tenantId, List notificationTypes, PageLink pageLink); + int countByTenantIdAndNotificationTypes(TenantId tenantId, List notificationTypes); + void removeByTenantId(TenantId tenantId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTemplateDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTemplateDao.java index 8ee6c3df3a..8950ab1e35 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTemplateDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTemplateDao.java @@ -53,6 +53,11 @@ public class JpaNotificationTemplateDao extends JpaAbstractDao notificationTypes) { + return notificationTemplateRepository.countByTenantIdAndNotificationTypes(tenantId.getId(), notificationTypes); + } + @Override public void removeByTenantId(TenantId tenantId) { notificationTemplateRepository.deleteByTenantId(tenantId.getId()); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTemplateRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTemplateRepository.java index b3e416a491..bb45c10d06 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTemplateRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTemplateRepository.java @@ -42,6 +42,11 @@ public interface NotificationTemplateRepository extends JpaRepository notificationTypes); + @Transactional @Modifying @Query("DELETE FROM NotificationTemplateEntity t WHERE t.tenantId = :tenantId") diff --git a/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.html b/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.html index 5b477d0345..f5a2ddba17 100644 --- a/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.html @@ -581,6 +581,19 @@ + + + {{ 'notification.task-processing-failure-trigger-settings' | translate }} +
+
+ + notification.description + + +
+
+
diff --git a/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.ts index 89488b7517..bc6935b4e7 100644 --- a/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.ts @@ -101,6 +101,7 @@ export class RuleNotificationDialogComponent extends rateLimitsTemplateForm: FormGroup; edgeCommunicationFailureTemplateForm: FormGroup; edgeConnectionTemplateForm: FormGroup; + taskProcessingFailureTemplateForm: FormGroup; triggerType = TriggerType; triggerTypes: TriggerType[]; @@ -337,6 +338,12 @@ export class RuleNotificationDialogComponent extends }) }); + this.taskProcessingFailureTemplateForm = this.fb.group({ + triggerConfig: this.fb.group({ + taskTypes: [] + }) + }); + this.triggerTypeFormsMap = new Map([ [TriggerType.ALARM, this.alarmTemplateForm], [TriggerType.ALARM_COMMENT, this.alarmCommentTemplateForm], @@ -349,7 +356,8 @@ export class RuleNotificationDialogComponent extends [TriggerType.NEW_PLATFORM_VERSION, this.newPlatformVersionTemplateForm], [TriggerType.RATE_LIMITS, this.rateLimitsTemplateForm], [TriggerType.EDGE_COMMUNICATION_FAILURE, this.edgeCommunicationFailureTemplateForm], - [TriggerType.EDGE_CONNECTION, this.edgeConnectionTemplateForm] + [TriggerType.EDGE_CONNECTION, this.edgeConnectionTemplateForm], + [TriggerType.TASK_PROCESSING_FAILURE, this.taskProcessingFailureTemplateForm] ]); if (data.isAdd || data.isCopy) { @@ -488,7 +496,8 @@ export class RuleNotificationDialogComponent extends TriggerType.ENTITIES_LIMIT, TriggerType.API_USAGE_LIMIT, TriggerType.NEW_PLATFORM_VERSION, - TriggerType.RATE_LIMITS + TriggerType.RATE_LIMITS, + TriggerType.TASK_PROCESSING_FAILURE ]); if (this.isSysAdmin()) { diff --git a/ui-ngx/src/app/modules/home/pages/notification/template/template-notification-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/notification/template/template-notification-dialog.component.ts index c0ae4f1abb..ccbcb6bf22 100644 --- a/ui-ngx/src/app/modules/home/pages/notification/template/template-notification-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/notification/template/template-notification-dialog.component.ts @@ -179,7 +179,8 @@ export class TemplateNotificationDialogComponent NotificationType.ENTITIES_LIMIT, NotificationType.API_USAGE_LIMIT, NotificationType.NEW_PLATFORM_VERSION, - NotificationType.RATE_LIMITS + NotificationType.RATE_LIMITS, + NotificationType.TASK_PROCESSING_FAILURE ]); if (this.isSysAdmin()) { diff --git a/ui-ngx/src/app/shared/models/notification.models.ts b/ui-ngx/src/app/shared/models/notification.models.ts index 2cc7d8547b..0c5e1c5a90 100644 --- a/ui-ngx/src/app/shared/models/notification.models.ts +++ b/ui-ngx/src/app/shared/models/notification.models.ts @@ -523,7 +523,8 @@ export enum NotificationType { RULE_NODE = 'RULE_NODE', RATE_LIMITS = 'RATE_LIMITS', EDGE_CONNECTION = 'EDGE_CONNECTION', - EDGE_COMMUNICATION_FAILURE = 'EDGE_COMMUNICATION_FAILURE' + EDGE_COMMUNICATION_FAILURE = 'EDGE_COMMUNICATION_FAILURE', + TASK_PROCESSING_FAILURE = 'TASK_PROCESSING_FAILURE' } export const NotificationTypeIcons = new Map([ @@ -535,6 +536,7 @@ export const NotificationTypeIcons = new Map([ [NotificationType.RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT, 'settings_ethernet'], [NotificationType.ENTITIES_LIMIT, 'data_thresholding'], [NotificationType.API_USAGE_LIMIT, 'insert_chart'], + [NotificationType.TASK_PROCESSING_FAILURE, 'warning'] ]); export const AlarmSeverityNotificationColors = new Map( @@ -646,7 +648,13 @@ export const NotificationTemplateTypeTranslateMap = new Map([ @@ -676,7 +685,8 @@ export const TriggerTypeTranslationMap = new Map([ [TriggerType.NEW_PLATFORM_VERSION, 'notification.trigger.new-platform-version'], [TriggerType.RATE_LIMITS, 'notification.trigger.rate-limits'], [TriggerType.EDGE_CONNECTION, 'notification.trigger.edge-connection'], - [TriggerType.EDGE_COMMUNICATION_FAILURE, 'notification.trigger.edge-communication-failure'] + [TriggerType.EDGE_COMMUNICATION_FAILURE, 'notification.trigger.edge-communication-failure'], + [TriggerType.TASK_PROCESSING_FAILURE, 'notification.trigger.task-processing-failure'] ]); export interface NotificationUserSettings { diff --git a/ui-ngx/src/assets/help/en_US/notification/task_processing_failure.md b/ui-ngx/src/assets/help/en_US/notification/task_processing_failure.md new file mode 100644 index 0000000000..0bcf107298 --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/notification/task_processing_failure.md @@ -0,0 +1,46 @@ +#### Task processing failure notification templatization + +
+
+ +Notification subject and message fields support templatization. +The list of available templatization parameters depends on the template type. +See the available types and parameters below: + +Available template parameters: + +* `taskType` - the task type, e.g. 'telemetry deletion'; +* `taskDescription` - the task description, e.g. 'telemetry deletion for device c4d93dc0-63a1-11ee-aa6d-f7cbc0a71325'; +* `error` - the error stacktrace +* `tenantId` - the tenant id; +* `entityType` - the type of the entity to which the task is related; +* `entityId` - the id of the entity to which the task is related; +* `attempt` - the number of attempts processing the task + +Parameter names must be wrapped using `${...}`. For example: `${entityType}`. +You may also modify the value of the parameter with one of the suffixes: + +* `upperCase`, for example - `${entityType:upperCase}` +* `lowerCase`, for example - `${entityType:lowerCase}` +* `capitalize`, for example - `${entityType:capitalize}` + +
+ +##### Examples + +Let's assume that telemetry deletion failed for some device. +The following template: + +```text +Failed to process ${taskType} for ${entityType:lowerCase} ${entityId} +{:copy-code} +``` + +will be transformed to: + +```text +Failed to process telemetry deletion for device c4d93dc0-63a1-11ee-aa6d-f7cbc0a71325 +``` + +
+
diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 0428087620..f94a2fffd3 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -3241,6 +3241,7 @@ "api-usage-trigger-settings": "API usage trigger settings", "new-platform-version-trigger-settings": "New platform version trigger settings", "rate-limits-trigger-settings": "Exceeded rate limits trigger settings", + "task-processing-failure-trigger-settings": "Task processing failure trigger settings", "at-least-one-should-be-selected": "At least one should be selected", "basic-settings": "Basic settings", "button-text": "Button text", @@ -3450,7 +3451,8 @@ "new-platform-version": "New platform version", "rate-limits": "Exceeded rate limits", "edge-communication-failure": "Edge communication failure", - "edge-connection": "Edge connection" + "edge-connection": "Edge connection", + "task-processing-failure": "Task processing failure" }, "templates": "Templates", "notification-templates": "Notifications / Templates", @@ -3473,6 +3475,7 @@ "rate-limits": "Exceeded rate limits", "edge-connection": "Edge connection", "edge-communication-failure": "Edge communication failure", + "task-processing-failure": "Task processing failure", "trigger": "Trigger", "trigger-required": "Trigger is required" },