diff --git a/application/src/main/data/upgrade/3.4.4/schema_update.sql b/application/src/main/data/upgrade/3.4.4/schema_update.sql index 91c49bf905..6ac33b255e 100644 --- a/application/src/main/data/upgrade/3.4.4/schema_update.sql +++ b/application/src/main/data/upgrade/3.4.4/schema_update.sql @@ -72,8 +72,6 @@ WHERE cleared = FALSE ORDER BY l.created_time DESC, l.id LIMIT 1); -VACUUM FULL ANALYZE alarm; - -- ALARM STATUS REFACTORING END -- ALARM COMMENTS START diff --git a/application/src/main/java/org/thingsboard/server/controller/AlarmCommentController.java b/application/src/main/java/org/thingsboard/server/controller/AlarmCommentController.java index 265db183a1..92b2cb923f 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AlarmCommentController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AlarmCommentController.java @@ -90,7 +90,7 @@ public class AlarmCommentController extends BaseController { public void deleteAlarmComment(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) @PathVariable(ALARM_ID) String strAlarmId, @ApiParam(value = ALARM_COMMENT_ID_PARAM_DESCRIPTION) @PathVariable(ALARM_COMMENT_ID) String strCommentId) throws ThingsboardException { checkParameter(ALARM_ID, strAlarmId); AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); - Alarm alarm = checkAlarmId(alarmId, Operation.DELETE); + Alarm alarm = checkAlarmId(alarmId, Operation.WRITE); AlarmCommentId alarmCommentId = new AlarmCommentId(toUUID(strCommentId)); AlarmComment alarmComment = checkAlarmCommentId(alarmCommentId, alarmId); @@ -104,7 +104,7 @@ public class AlarmCommentController extends BaseController { @RequestMapping(value = "/alarm/{alarmId}/comment", method = RequestMethod.GET) @ResponseBody public PageData getAlarmComments( - @ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) + @ApiParam(value = ALARM_ID_PARAM_DESCRIPTION, required = true) @PathVariable(ALARM_ID) String strAlarmId, @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, @@ -117,10 +117,7 @@ public class AlarmCommentController extends BaseController { ) throws Exception { checkParameter(ALARM_ID, strAlarmId); AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); - Alarm alarm = alarmService.findAlarmByIdAsync(getCurrentUser().getTenantId(), alarmId).get(); - checkNotNull(alarm, "Alarm with id [" + alarmId + "] is not found"); - checkEntityId(alarm.getOriginator(), Operation.READ); - + Alarm alarm = checkAlarmId(alarmId, Operation.READ); PageLink pageLink = createPageLink(pageSize, page, null, sortProperty, sortOrder); return checkNotNull(alarmCommentService.findAlarmComments(alarm.getTenantId(), alarmId, pageLink)); } diff --git a/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java b/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java index 53734b8633..a060335713 100644 --- a/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java +++ b/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java @@ -104,7 +104,7 @@ public class ControllerConstants { protected static final String ASSET_PROFILE_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, description, isDefault"; protected static final String ASSET_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, type, label, customerTitle"; protected static final String ALARM_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, startTs, endTs, type, ackTs, clearTs, severity, status"; - protected static final String ALARM_COMMENT_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime"; + protected static final String ALARM_COMMENT_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, id"; protected static final String EVENT_SORT_PROPERTY_ALLOWABLE_VALUES = "ts, id"; protected static final String EDGE_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, type, label, customerTitle"; protected static final String RULE_CHAIN_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, root"; diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java index a6f69d7079..24daab29f0 100644 --- a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java @@ -39,7 +39,6 @@ import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; -import org.thingsboard.server.service.notification.NotificationProcessingContext; import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.NotificationRequestInfo; import org.thingsboard.server.common.data.notification.NotificationRequestPreview; @@ -57,8 +56,10 @@ import org.thingsboard.server.dao.notification.NotificationSettingsService; import org.thingsboard.server.dao.notification.NotificationTargetService; import org.thingsboard.server.dao.notification.NotificationTemplateService; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.notification.NotificationProcessingContext; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; +import org.thingsboard.server.service.security.permission.Resource; import javax.validation.Valid; import java.util.Collections; @@ -294,8 +295,8 @@ public class NotificationController extends BaseController { @PostMapping("/notification/settings") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") public NotificationSettings saveNotificationSettings(@RequestBody @Valid NotificationSettings notificationSettings, - @AuthenticationPrincipal SecurityUser user) { - // generic permission + @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { + accessControlService.checkPermission(user, Resource.ADMIN_SETTINGS, Operation.WRITE); TenantId tenantId = user.isSystemAdmin() ? TenantId.SYS_TENANT_ID : user.getTenantId(); notificationSettingsService.saveNotificationSettings(tenantId, notificationSettings); return notificationSettings; @@ -303,10 +304,17 @@ public class NotificationController extends BaseController { @GetMapping("/notification/settings") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") - public NotificationSettings getNotificationSettings(@AuthenticationPrincipal SecurityUser user) { - // generic permission + public NotificationSettings getNotificationSettings(@AuthenticationPrincipal SecurityUser user) throws ThingsboardException { + accessControlService.checkPermission(user, Resource.ADMIN_SETTINGS, Operation.READ); TenantId tenantId = user.isSystemAdmin() ? TenantId.SYS_TENANT_ID : user.getTenantId(); return notificationSettingsService.findNotificationSettings(tenantId); } + @GetMapping("/notification/deliveryMethods") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public Set getAvailableDeliveryMethods(@AuthenticationPrincipal SecurityUser user) throws ThingsboardException { + accessControlService.checkPermission(user, Resource.ADMIN_SETTINGS, Operation.READ); + return notificationCenter.getAvailableDeliveryMethods(user.getTenantId()); + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmCommentService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmCommentService.java index 26c38188fd..6f86632ffd 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmCommentService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmCommentService.java @@ -18,11 +18,14 @@ package org.thingsboard.server.service.entitiy.alarm; import lombok.AllArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmComment; +import org.thingsboard.server.common.data.alarm.AlarmCommentType; import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.dao.alarm.AlarmCommentService; @@ -52,8 +55,17 @@ public class DefaultTbAlarmCommentService extends AbstractTbEntityService implem } @Override - public void deleteAlarmComment(Alarm alarm, AlarmComment alarmComment, User user) { - alarmCommentService.deleteAlarmComment(alarm.getTenantId(), alarmComment.getId()); - notificationEntityService.notifyAlarmComment(alarm, alarmComment, ActionType.DELETED_COMMENT, user); + public void deleteAlarmComment(Alarm alarm, AlarmComment alarmComment, User user) throws ThingsboardException { + if (alarmComment.getType() == AlarmCommentType.OTHER) { + alarmComment.setType(AlarmCommentType.SYSTEM); + alarmComment.setUserId(null); + alarmComment.setComment(JacksonUtil.newObjectNode().put("text", + String.format("User %s deleted his comment", + (user.getFirstName() == null || user.getLastName() == null) ? user.getName() : user.getFirstName() + " " + user.getLastName()))); + AlarmComment savedAlarmComment = checkNotNull(alarmCommentService.saveAlarmComment(alarm.getTenantId(), alarmComment)); + notificationEntityService.notifyAlarmComment(alarm, savedAlarmComment, ActionType.DELETED_COMMENT, user); + } else { + throw new ThingsboardException("System comment could not be deleted", ThingsboardErrorCode.BAD_REQUEST_PARAMS); + } } } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmCommentService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmCommentService.java index 8c6aa3366a..a2bca133cb 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmCommentService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmCommentService.java @@ -23,5 +23,5 @@ import org.thingsboard.server.common.data.exception.ThingsboardException; public interface TbAlarmCommentService { AlarmComment saveAlarmComment(Alarm alarm, AlarmComment alarmComment, User user) throws ThingsboardException; - void deleteAlarmComment(Alarm alarm, AlarmComment alarmComment, User user); + void deleteAlarmComment(Alarm alarm, AlarmComment alarmComment, User user) throws ThingsboardException; } diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java index b9ff5383b5..f5f212beb2 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java @@ -684,6 +684,11 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.4.4", SCHEMA_UPDATE_SQL); loadSql(schemaUpdateFile, conn); + try { + conn.createStatement().execute("VACUUM FULL ANALYZE alarm;"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script + } catch (Exception e) { + } + try { conn.createStatement().execute("ALTER TABLE asset_profile ADD COLUMN default_edge_rule_chain_id uuid"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script } catch (Exception e) { diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java index 4f13072504..619616bc8b 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java @@ -22,7 +22,9 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.thingsboard.common.util.DonAsynchron; +import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.rule.engine.api.NotificationCenter; +import org.thingsboard.rule.engine.api.SmsService; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.NotificationId; @@ -101,6 +103,8 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple private final NotificationsTopicService notificationsTopicService; private final TbQueueProducerProvider producerProvider; private final RateLimitService rateLimitService; + private final MailService mailService; + private final SmsService smsService; private Map channels; @@ -121,13 +125,12 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple List targets = notificationTargetService.findNotificationTargetsByTenantIdAndIds(tenantId, notificationRequest.getTargets().stream().map(NotificationTargetId::new).collect(Collectors.toList())); + Set availableDeliveryMethods = getAvailableDeliveryMethods(tenantId); notificationTemplate.getConfiguration().getDeliveryMethodsTemplates().forEach((deliveryMethod, template) -> { if (!template.isEnabled()) return; - if (deliveryMethod == NotificationDeliveryMethod.SLACK) { - if (!settings.getDeliveryMethodsConfigs().containsKey(deliveryMethod)) { - throw new IllegalArgumentException("Slack must be configured in the settings"); - } + if (!availableDeliveryMethods.contains(deliveryMethod)) { + throw new IllegalArgumentException("Settings for " + deliveryMethod.getName() + " are missing"); } if (notificationRequest.getRuleId() == null) { if (targets.stream().noneMatch(target -> target.getConfiguration().getType().getSupportedDeliveryMethods().contains(deliveryMethod))) { @@ -340,6 +343,24 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple } } + @Override + public Set getAvailableDeliveryMethods(TenantId tenantId) { + Set deliveryMethods = new HashSet<>(); + deliveryMethods.add(NotificationDeliveryMethod.WEB); + NotificationSettings notificationSettings = notificationSettingsService.findNotificationSettings(tenantId); + if (notificationSettings.getDeliveryMethodsConfigs().containsKey(NotificationDeliveryMethod.SLACK)) { + deliveryMethods.add(NotificationDeliveryMethod.SLACK); + } + try { + mailService.testConnection(tenantId); + deliveryMethods.add(NotificationDeliveryMethod.EMAIL); + } catch (Exception e) {} + if (smsService.isConfigured(tenantId)) { + deliveryMethods.add(NotificationDeliveryMethod.SMS); + } + return deliveryMethods; + } + @Override public void deleteNotificationRequest(TenantId tenantId, NotificationRequestId notificationRequestId) { log.debug("Deleting notification request {}", notificationRequestId); diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index d890ed3135..d96e50ca7f 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -440,6 +440,9 @@ cache: notificationRules: timeToLiveInMinutes: "${CACHE_SPECS_NOTIFICATION_RULES_TTL:1440}" maxSize: "${CACHE_SPECS_NOTIFICATION_RULES_MAX_SIZE:10000}" + notificationSettings: + timeToLiveInMinutes: "${CACHE_SPECS_NOTIFICATION_SETTINGS_TTL:10}" + maxSize: "${CACHE_SPECS_NOTIFICATION_SETTINGS_MAX_SIZE:1000}" attributes: timeToLiveInMinutes: "${CACHE_SPECS_ATTRIBUTES_TTL:1440}" maxSize: "${CACHE_SPECS_ATTRIBUTES_MAX_SIZE:100000}" diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseAlarmCommentControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseAlarmCommentControllerTest.java index 8566d9d6a4..b8c091de05 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseAlarmCommentControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseAlarmCommentControllerTest.java @@ -202,7 +202,13 @@ public abstract class BaseAlarmCommentControllerTest extends AbstractControllerT doDelete("/api/alarm/" + alarm.getId() + "/comment/" + alarmComment.getId()) .andExpect(status().isOk()); - testLogEntityAction(alarm, alarm.getId(), tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.DELETED_COMMENT, 1, alarmComment); + AlarmComment expectedAlarmComment = AlarmComment.builder() + .alarmId(alarm.getId()) + .type(AlarmCommentType.SYSTEM) + .comment(JacksonUtil.newObjectNode().put("text", String.format("User %s deleted his comment", + CUSTOMER_USER_EMAIL))) + .build(); + testLogEntityAction(alarm, alarm.getId(), tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.DELETED_COMMENT, 1, expectedAlarmComment); } @Test @@ -215,7 +221,13 @@ public abstract class BaseAlarmCommentControllerTest extends AbstractControllerT doDelete("/api/alarm/" + alarm.getId() + "/comment/" + alarmComment.getId()) .andExpect(status().isOk()); - testLogEntityAction(alarm, alarm.getId(), tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.DELETED_COMMENT, 1, alarmComment); + AlarmComment expectedAlarmComment = AlarmComment.builder() + .alarmId(alarm.getId()) + .type(AlarmCommentType.SYSTEM) + .comment(JacksonUtil.newObjectNode().put("text", String.format("User %s deleted his comment", + TENANT_ADMIN_EMAIL))) + .build(); + testLogEntityAction(alarm, alarm.getId(), tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.DELETED_COMMENT, 1, expectedAlarmComment); } @Test diff --git a/application/src/test/java/org/thingsboard/server/service/entitiy/alarmComment/DefaultTbAlarmCommentServiceTest.java b/application/src/test/java/org/thingsboard/server/service/entitiy/alarmComment/DefaultTbAlarmCommentServiceTest.java index 485049bfd6..1377b1a934 100644 --- a/application/src/test/java/org/thingsboard/server/service/entitiy/alarmComment/DefaultTbAlarmCommentServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/entitiy/alarmComment/DefaultTbAlarmCommentServiceTest.java @@ -28,9 +28,11 @@ import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmComment; +import org.thingsboard.server.common.data.alarm.AlarmCommentType; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.AlarmCommentId; import org.thingsboard.server.common.data.id.AlarmId; +import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.dao.alarm.AlarmCommentService; import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.customer.CustomerService; @@ -41,6 +43,7 @@ import org.thingsboard.server.service.telemetry.AlarmSubscriptionService; import java.util.UUID; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; @@ -84,13 +87,28 @@ public class DefaultTbAlarmCommentServiceTest { } @Test - public void testDelete() { + public void testDelete() throws ThingsboardException { var alarmId = new AlarmId(UUID.randomUUID()); - var alarmCommentId = new AlarmCommentId(UUID.randomUUID()); + var alarmComment = new AlarmComment(); + alarmComment.setAlarmId(alarmId); + alarmComment.setUserId(new UserId(UUID.randomUUID())); + alarmComment.setType(AlarmCommentType.OTHER); - doNothing().when(alarmCommentService).deleteAlarmComment(Mockito.any(), eq(alarmCommentId)); - service.deleteAlarmComment(new Alarm(alarmId), new AlarmComment(alarmCommentId), new User()); + when(alarmCommentService.saveAlarmComment(Mockito.any(), eq(alarmComment))).thenReturn(alarmComment); + service.deleteAlarmComment(new Alarm(alarmId), alarmComment, new User()); verify(notificationEntityService, times(1)).notifyAlarmComment(any(), any(), any(), any()); } + + @Test + public void testShouldNotDeleteSystemComment() { + var alarmId = new AlarmId(UUID.randomUUID()); + var alarmComment = new AlarmComment(); + alarmComment.setAlarmId(alarmId); + alarmComment.setType(AlarmCommentType.SYSTEM); + + assertThatThrownBy(() -> service.deleteAlarmComment(new Alarm(alarmId), alarmComment, new User())) + .isInstanceOf(ThingsboardException.class) + .hasMessageContaining("System comment could not be deleted"); + } } \ No newline at end of file diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmCommentService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmCommentService.java index 81795c092d..90fbc09fcf 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmCommentService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmCommentService.java @@ -16,6 +16,7 @@ package org.thingsboard.server.dao.alarm; import com.google.common.util.concurrent.ListenableFuture; +import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.alarm.AlarmComment; import org.thingsboard.server.common.data.alarm.AlarmCommentInfo; import org.thingsboard.server.common.data.id.AlarmCommentId; @@ -27,7 +28,7 @@ import org.thingsboard.server.common.data.page.PageLink; public interface AlarmCommentService { AlarmComment createOrUpdateAlarmComment(TenantId tenantId, AlarmComment alarmComment); - void deleteAlarmComment(TenantId tenantId, AlarmCommentId alarmCommentId); + AlarmComment saveAlarmComment(TenantId tenantId, AlarmComment alarmComment); PageData findAlarmComments(TenantId tenantId, AlarmId alarmId, PageLink pageLink); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java index f59ea8d5d0..eedf925d61 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java @@ -30,6 +30,7 @@ public class CacheConstants { public static final String TENANTS_EXIST_CACHE = "tenantsExist"; public static final String DEVICE_PROFILE_CACHE = "deviceProfiles"; public static final String NOTIFICATION_RULES_CACHE = "notificationRules"; + public static final String NOTIFICATION_SETTINGS_CACHE = "notificationSettings"; public static final String ASSET_PROFILE_CACHE = "assetProfiles"; public static final String ATTRIBUTES_CACHE = "attributes"; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java index fdcfb99da2..529bc9a3ab 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java @@ -33,11 +33,12 @@ import javax.validation.Valid; import javax.validation.constraints.AssertTrue; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; +import java.io.Serializable; @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) -public class NotificationRule extends BaseData implements HasTenantId, HasName { +public class NotificationRule extends BaseData implements HasTenantId, HasName, Serializable { private TenantId tenantId; @NotBlank diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRuleConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRuleConfig.java index 68683afcee..73522c7108 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRuleConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRuleConfig.java @@ -17,8 +17,10 @@ package org.thingsboard.server.common.data.notification.rule; import lombok.Data; +import java.io.Serializable; + @Data -public class NotificationRuleConfig { +public class NotificationRuleConfig implements Serializable { private String description; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRuleRecipientsConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRuleRecipientsConfig.java index f2c6ca2863..d31ec098ca 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRuleRecipientsConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRuleRecipientsConfig.java @@ -24,6 +24,7 @@ import lombok.Data; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; import javax.validation.constraints.NotNull; +import java.io.Serializable; import java.util.List; import java.util.Map; import java.util.UUID; @@ -34,7 +35,7 @@ import java.util.UUID; @Type(name = "ALARM", value = EscalatedNotificationRuleRecipientsConfig.class), }) @Data -public abstract class NotificationRuleRecipientsConfig { +public abstract class NotificationRuleRecipientsConfig implements Serializable { @NotNull private NotificationRuleTriggerType triggerType; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NotificationRuleTriggerConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NotificationRuleTriggerConfig.java index c60c72b8de..f67a144bed 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NotificationRuleTriggerConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NotificationRuleTriggerConfig.java @@ -20,6 +20,8 @@ import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonSubTypes.Type; import com.fasterxml.jackson.annotation.JsonTypeInfo; +import java.io.Serializable; + @JsonIgnoreProperties(ignoreUnknown = true) @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "triggerType") @JsonSubTypes({ @@ -32,7 +34,7 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; @Type(value = NewPlatformVersionNotificationRuleTriggerConfig.class, name = "NEW_PLATFORM_VERSION"), @Type(value = EntitiesLimitNotificationRuleTriggerConfig.class, name = "ENTITIES_LIMIT") }) -public interface NotificationRuleTriggerConfig { +public interface NotificationRuleTriggerConfig extends Serializable { NotificationRuleTriggerType getTriggerType(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/NotificationDeliveryMethodConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/NotificationDeliveryMethodConfig.java index ea3451338d..962eba6d06 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/NotificationDeliveryMethodConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/NotificationDeliveryMethodConfig.java @@ -22,12 +22,14 @@ import com.fasterxml.jackson.annotation.JsonSubTypes.Type; import com.fasterxml.jackson.annotation.JsonTypeInfo; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; +import java.io.Serializable; + @JsonIgnoreProperties(ignoreUnknown = true) @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "method") @JsonSubTypes({ @Type(name = "SLACK", value = SlackNotificationDeliveryMethodConfig.class) }) -public interface NotificationDeliveryMethodConfig { +public interface NotificationDeliveryMethodConfig extends Serializable { @JsonIgnore NotificationDeliveryMethod getMethod(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/NotificationSettings.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/NotificationSettings.java index face4ca017..4ed6cfc30d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/NotificationSettings.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/NotificationSettings.java @@ -20,10 +20,11 @@ import org.thingsboard.server.common.data.notification.NotificationDeliveryMetho import javax.validation.Valid; import javax.validation.constraints.NotNull; +import java.io.Serializable; import java.util.Map; @Data -public class NotificationSettings { +public class NotificationSettings implements Serializable { @NotNull @Valid diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmCommentService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmCommentService.java index aefae8778d..6c6cc18355 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmCommentService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmCommentService.java @@ -22,12 +22,16 @@ import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.alarm.AlarmComment; import org.thingsboard.server.common.data.alarm.AlarmCommentInfo; import org.thingsboard.server.common.data.alarm.AlarmCommentType; import org.thingsboard.server.common.data.id.AlarmCommentId; import org.thingsboard.server.common.data.id.AlarmId; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.entity.AbstractEntityService; @@ -39,7 +43,7 @@ import static org.thingsboard.server.dao.service.Validator.validateId; @Service @Slf4j -public class BaseAlarmCommentService extends AbstractEntityService implements AlarmCommentService{ +public class BaseAlarmCommentService extends AbstractEntityService implements AlarmCommentService { @Autowired private AlarmCommentDao alarmCommentDao; @@ -58,9 +62,10 @@ public class BaseAlarmCommentService extends AbstractEntityService implements Al } @Override - public void deleteAlarmComment(TenantId tenantId, AlarmCommentId alarmCommentId) { - log.debug("Deleting Alarm Comment with id: {}", alarmCommentId); - alarmCommentDao.deleteAlarmComment(tenantId, alarmCommentId); + public AlarmComment saveAlarmComment(TenantId tenantId, AlarmComment alarmComment) { + log.debug("Deleting Alarm Comment: {}", alarmComment); + alarmCommentDataValidator.validate(alarmComment, c -> tenantId); + return alarmCommentDao.save(tenantId, alarmComment); } @Override 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 f0f3a11d3a..1088347397 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 @@ -17,14 +17,16 @@ package org.thingsboard.server.dao.notification; import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.RequiredArgsConstructor; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.AdminSettings; +import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; -import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.NotificationTemplateId; import org.thingsboard.server.common.data.id.TenantId; @@ -78,6 +80,7 @@ public class DefaultNotificationSettingsService implements NotificationSettingsS private static final String SETTINGS_KEY = "notifications"; + @CacheEvict(cacheNames = CacheConstants.NOTIFICATION_SETTINGS_CACHE, key = "#tenantId") @Override public void saveNotificationSettings(TenantId tenantId, NotificationSettings settings) { AdminSettings adminSettings = Optional.ofNullable(adminSettingsService.findAdminSettingsByTenantIdAndKey(tenantId, SETTINGS_KEY)) @@ -91,6 +94,7 @@ public class DefaultNotificationSettingsService implements NotificationSettingsS adminSettingsService.saveAdminSettings(tenantId, adminSettings); } + @Cacheable(cacheNames = CacheConstants.NOTIFICATION_SETTINGS_CACHE, key = "#tenantId") @Override public NotificationSettings findNotificationSettings(TenantId tenantId) { return Optional.ofNullable(adminSettingsService.findAdminSettingsByTenantIdAndKey(tenantId, SETTINGS_KEY)) @@ -137,11 +141,11 @@ public class DefaultNotificationSettingsService implements NotificationSettingsS "notifications", null, null); AlarmNotificationRuleTriggerConfig alarmRuleTriggerConfig = new AlarmNotificationRuleTriggerConfig(); alarmRuleTriggerConfig.setAlarmTypes(null); - alarmRuleTriggerConfig.setAlarmSeverities(Set.of(AlarmSeverity.MAJOR, AlarmSeverity.CRITICAL)); + alarmRuleTriggerConfig.setAlarmSeverities(null); alarmRuleTriggerConfig.setNotifyOn(Set.of(AlarmAction.CREATED, AlarmAction.SEVERITY_CHANGED, AlarmAction.ACKNOWLEDGED, AlarmAction.CLEARED)); - createRule(tenantId, "Major or critical alarm", alarmNotificationTemplate.getId(), alarmRuleTriggerConfig, + createRule(tenantId, "Alarm", alarmNotificationTemplate.getId(), alarmRuleTriggerConfig, List.of(originatorEntityOwnerUsers.getId()), "Send notification to tenant admins or customer users " + - "when any major or critical alarm is created, updated or cleared"); + "when any alarm is created, updated or cleared"); NotificationTemplate deviceActionNotificationTemplate = createTemplate(tenantId, "Device action notification", NotificationType.ENTITY_ACTION, "${entityType} was ${actionType}", @@ -163,7 +167,7 @@ public class DefaultNotificationSettingsService implements NotificationSettingsS DeviceInactivityNotificationRuleTriggerConfig deviceInactivityRuleTriggerConfig = new DeviceInactivityNotificationRuleTriggerConfig(); deviceInactivityRuleTriggerConfig.setDevices(null); deviceInactivityRuleTriggerConfig.setDeviceProfiles(null); - createRule(tenantId, "Device inactivity", deviceInactivityNotificationTemplate.getId(), deviceInactivityRuleTriggerConfig, + createRule(tenantId, "Device became inactive", deviceInactivityNotificationTemplate.getId(), deviceInactivityRuleTriggerConfig, List.of(originatorEntityOwnerUsers.getId()), "Send notification to tenant admins or customer users " + "when any device became inactive"); @@ -190,7 +194,7 @@ public class DefaultNotificationSettingsService implements NotificationSettingsS alarmAssignmentRuleTriggerConfig.setAlarmSeverities(null); alarmAssignmentRuleTriggerConfig.setAlarmStatuses(null); alarmAssignmentRuleTriggerConfig.setNotifyOn(Set.of(AlarmAssignmentNotificationRuleTriggerConfig.Action.ASSIGNED)); - createRule(tenantId, "Alarm assigned", alarmAssignedNotificationTemplate.getId(), alarmAssignmentRuleTriggerConfig, + createRule(tenantId, "Alarm assignment", alarmAssignedNotificationTemplate.getId(), alarmAssignmentRuleTriggerConfig, List.of(affectedUser.getId()), "Send notification to user when any alarm was assigned to him"); NotificationTemplate ruleEngineComponentLifecycleFailureNotificationTemplate = createTemplate(tenantId, "Rule chain/node lifecycle failure notification", NotificationType.RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT, @@ -204,7 +208,7 @@ public class DefaultNotificationSettingsService implements NotificationSettingsS ruleEngineComponentLifecycleEventRuleTriggerConfig.setTrackRuleNodeEvents(true); ruleEngineComponentLifecycleEventRuleTriggerConfig.setRuleNodeEvents(Set.of(ComponentLifecycleEvent.STARTED, ComponentLifecycleEvent.UPDATED, ComponentLifecycleEvent.STOPPED)); ruleEngineComponentLifecycleEventRuleTriggerConfig.setOnlyRuleNodeLifecycleFailures(true); - createRule(tenantId, "Rule chain/node lifecycle failure", ruleEngineComponentLifecycleFailureNotificationTemplate.getId(), + createRule(tenantId, "Rule node initialization failure", ruleEngineComponentLifecycleFailureNotificationTemplate.getId(), ruleEngineComponentLifecycleEventRuleTriggerConfig, List.of(tenantAdmins.getId()), "Send notification to tenant admins when any Rule chain or Rule node failed to start, update or stop"); } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmCommentServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmCommentServiceTest.java index 1144ec6513..e79e00515b 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmCommentServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmCommentServiceTest.java @@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmComment; import org.thingsboard.server.common.data.alarm.AlarmCommentInfo; +import org.thingsboard.server.common.data.alarm.AlarmCommentType; import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.UserId; @@ -79,7 +80,7 @@ public abstract class BaseAlarmCommentServiceTest extends AbstractServiceTest { @Test - public void testSaveAndFetchAlarmComment() throws ExecutionException, InterruptedException { + public void testCreateAndFetchAlarmComment() throws ExecutionException, InterruptedException { AlarmComment alarmComment = AlarmComment.builder().alarmId(alarm.getId()) .userId(user.getId()) .type(OTHER) @@ -142,7 +143,7 @@ public abstract class BaseAlarmCommentServiceTest extends AbstractServiceTest { } @Test - public void testDeleteAlarmComment() throws ExecutionException, InterruptedException { + public void testSaveAlarmComment() throws ExecutionException, InterruptedException { UserId userId = new UserId(UUID.randomUUID()); AlarmComment alarmComment = AlarmComment.builder().alarmId(alarm.getId()) .userId(userId) @@ -152,13 +153,12 @@ public abstract class BaseAlarmCommentServiceTest extends AbstractServiceTest { AlarmComment createdComment = alarmCommentService.createOrUpdateAlarmComment(tenantId, alarmComment); - Assert.assertNotNull(createdComment); - Assert.assertNotNull(createdComment.getId()); - - alarmCommentService.deleteAlarmComment(tenantId, createdComment.getId()); + createdComment.setType(AlarmCommentType.SYSTEM); + createdComment.setUserId(null); + alarmCommentService.saveAlarmComment(tenantId, createdComment); AlarmComment fetched = alarmCommentService.findAlarmCommentByIdAsync(tenantId, createdComment.getId()).get(); - - Assert.assertNull("Alarm comment was returned when it was expected to be null", fetched); + Assert.assertNull(fetched.getUserId()); + Assert.assertEquals(AlarmCommentType.SYSTEM, fetched.getType()); } } diff --git a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java index 8dcdb86eab..0a3fb716ad 100644 --- a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java +++ b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java @@ -64,6 +64,8 @@ import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.UpdateMessage; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmComment; +import org.thingsboard.server.common.data.alarm.AlarmCommentInfo; import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; import org.thingsboard.server.common.data.alarm.AlarmSeverity; @@ -82,6 +84,7 @@ import org.thingsboard.server.common.data.edge.EdgeInfo; import org.thingsboard.server.common.data.edge.EdgeInstallInstructions; import org.thingsboard.server.common.data.edge.EdgeSearchQuery; import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery; +import org.thingsboard.server.common.data.id.AlarmCommentId; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.AssetProfileId; @@ -490,6 +493,29 @@ public class RestClient implements Closeable { return restTemplate.postForEntity(baseURL + "/api/alarm", alarm, Alarm.class).getBody(); } + public AlarmComment saveAlarmComment(AlarmId alarmId, AlarmComment alarmComment) { + return restTemplate.postForEntity(baseURL + "/api/alarm/{alarmId}/comment", alarmComment, AlarmComment.class, alarmId.getId()).getBody(); + } + + public void deleteAlarmComment(AlarmId alarmId, AlarmCommentId alarmCommentId) { + restTemplate.delete(baseURL + "/api/alarm/{alarmId}/comment/{alarmCommentId}", + alarmId.getId(), alarmCommentId.getId()); + } + + public PageData getAlarmComments(AlarmId alarmId, PageLink pageLink) { + String urlSecondPart = "/api/alarm/{alarmId}/comment"; + Map params = new HashMap<>(); + params.put("alarmId", alarmId.getId().toString()); + + return restTemplate.exchange( + baseURL + urlSecondPart + "&" + getUrlParams(pageLink), + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params).getBody(); + } + public Optional getAssetById(AssetId assetId) { try { ResponseEntity asset = restTemplate.getForEntity(baseURL + "/api/asset/{assetId}", Asset.class, assetId.getId()); diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NotificationCenter.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NotificationCenter.java index eccc4f1423..b263e8da17 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NotificationCenter.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NotificationCenter.java @@ -19,8 +19,11 @@ import org.thingsboard.server.common.data.id.NotificationId; import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationRequest; +import java.util.Set; + public interface NotificationCenter { NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest); @@ -29,11 +32,12 @@ public interface NotificationCenter { void sendBasicNotification(TenantId tenantId, UserId recipientId, String subject, String text); - void markNotificationAsRead(TenantId tenantId, UserId recipientId, NotificationId notificationId); void markAllNotificationsAsRead(TenantId tenantId, UserId recipientId); void deleteNotification(TenantId tenantId, UserId recipientId, NotificationId notificationId); + Set getAvailableDeliveryMethods(TenantId tenantId); + }