Browse Source

UI: update and resolve conflict

pull/8022/head
Artem Dzhereleiko 4 years ago
parent
commit
077cc3782c
  1. 1
      application/src/main/data/upgrade/3.4.3/schema_update.sql
  2. 17
      application/src/main/java/org/thingsboard/server/controller/NotificationController.java
  3. 9
      application/src/main/java/org/thingsboard/server/controller/NotificationRuleController.java
  4. 42
      application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java
  5. 5
      application/src/main/java/org/thingsboard/server/controller/NotificationTemplateController.java
  6. 137
      application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java
  7. 2
      application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java
  8. 8
      application/src/main/java/org/thingsboard/server/service/notification/channels/NotificationChannel.java
  9. 10
      application/src/main/java/org/thingsboard/server/service/notification/channels/SlackNotificationChannel.java
  10. 2
      application/src/main/java/org/thingsboard/server/service/notification/channels/SmsNotificationChannel.java
  11. 11
      application/src/main/java/org/thingsboard/server/service/slack/DefaultSlackService.java
  12. 14
      application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java
  13. 61
      application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java
  14. 41
      application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java
  15. 59
      application/src/test/java/org/thingsboard/server/service/notification/NotificationTargetApiTest.java
  16. 5
      common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRuleService.java
  17. 4
      common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetService.java
  18. 3
      common/data/src/main/java/org/thingsboard/server/common/data/User.java
  19. 15
      common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationDeliveryMethod.java
  20. 3
      common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationProcessingContext.java
  21. 23
      common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestStats.java
  22. 12
      common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java
  23. 39
      common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRuleInfo.java
  24. 12
      common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationRecipient.java
  25. 2
      common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTarget.java
  26. 9
      common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetConfig.java
  27. 33
      common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetType.java
  28. 14
      common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/platform/AllUsersFilter.java
  29. 10
      common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/platform/CustomerUsersFilter.java
  30. 39
      common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/platform/PlatformUsersNotificationTargetConfig.java
  31. 10
      common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/platform/UserListFilter.java
  32. 36
      common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/platform/UsersFilter.java
  33. 4
      common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/platform/UsersFilterType.java
  34. 29
      common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/slack/SlackConversation.java
  35. 22
      common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/slack/SlackConversationType.java
  36. 40
      common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/slack/SlackNotificationTargetConfig.java
  37. 1
      common/data/src/main/java/org/thingsboard/server/common/data/notification/template/DeliveryMethodNotificationTemplate.java
  38. 10
      common/data/src/main/java/org/thingsboard/server/common/data/notification/template/SlackDeliveryMethodNotificationTemplate.java
  39. 1
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  40. 11
      dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRuleEntity.java
  41. 53
      dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRuleInfoEntity.java
  42. 10
      dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTargetEntity.java
  43. 11
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRuleService.java
  44. 21
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java
  45. 6
      dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRuleDao.java
  46. 5
      dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetDao.java
  47. 15
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRuleDao.java
  48. 9
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTargetDao.java
  49. 13
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRuleRepository.java
  50. 3
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTargetRepository.java
  51. 1
      dao/src/main/resources/sql/schema-entities.sql
  52. 7
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/slack/SlackService.java
  53. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbSlackNode.java
  54. 6
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbSlackNodeConfiguration.java
  55. 3
      ui-ngx/src/app/core/http/entity.service.ts
  56. 1
      ui-ngx/src/app/core/ws/notification-websocket.service.ts
  57. 129
      ui-ngx/src/app/modules/home/pages/notification-center/notification-center.component.html
  58. 71
      ui-ngx/src/app/modules/home/pages/notification-center/notification-center.component.scss
  59. 65
      ui-ngx/src/app/modules/home/pages/notification-center/notification-center.component.ts
  60. 31
      ui-ngx/src/app/modules/home/pages/notification-center/notification-table/inbox-table-config.ts
  61. 8
      ui-ngx/src/app/modules/home/pages/notification-center/notification-table/notification-table.component.ts
  62. 7
      ui-ngx/src/app/modules/home/pages/notification-center/notification-table/request-table-config.ts
  63. 22
      ui-ngx/src/app/modules/home/pages/notification-center/notification-table/template-table-config.ts
  64. 2
      ui-ngx/src/app/modules/home/pages/notification-center/request-table/request-notification-dialog.component.html
  65. 15
      ui-ngx/src/app/modules/home/pages/notification-center/request-table/request-notification-dialog.componet.ts
  66. 2
      ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-notification-dialog.component.html
  67. 140
      ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-notification-dialog.component.ts
  68. 7
      ui-ngx/src/app/shared/models/notification-ws.models.ts
  69. 5
      ui-ngx/src/assets/locale/locale.constant-en_US.json

1
application/src/main/data/upgrade/3.4.3/schema_update.sql

@ -19,6 +19,7 @@ CREATE TABLE IF NOT EXISTS notification_target (
created_time BIGINT NOT NULL,
tenant_id UUID NULL CONSTRAINT fk_notification_target_tenant_id REFERENCES tenant(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
type VARCHAR(32) NOT NULL,
configuration VARCHAR(10000) NOT NULL,
CONSTRAINT uq_notification_target_name UNIQUE (tenant_id, name)
);

17
application/src/main/java/org/thingsboard/server/controller/NotificationController.java

@ -44,6 +44,7 @@ import org.thingsboard.server.common.data.notification.NotificationRequestInfo;
import org.thingsboard.server.common.data.notification.NotificationRequestPreview;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.NotificationTargetType;
import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
import org.thingsboard.server.common.data.page.PageData;
@ -59,6 +60,7 @@ import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
import javax.validation.Valid;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@ -198,11 +200,15 @@ public class NotificationController extends BaseController {
.settings(null)
.template(notificationTemplate)
.build();
mockProcessingCtx.init();
Map<String, String> templateContext = mockProcessingCtx.createTemplateContext(user);
Map<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> processedTemplates = mockProcessingCtx.getDeliveryMethods().stream()
.collect(Collectors.toMap(m -> m, deliveryMethod -> {
Map<String, String> templateContext;
if (NotificationTargetType.PLATFORM_USERS.getSupportedDeliveryMethods().contains(deliveryMethod)) {
templateContext = mockProcessingCtx.createTemplateContext(user);
} else {
templateContext = Collections.emptyMap();
}
return mockProcessingCtx.getProcessedTemplate(deliveryMethod, templateContext);
}));
preview.setProcessedTemplates(processedTemplates);
@ -214,7 +220,12 @@ public class NotificationController extends BaseController {
throw new IllegalArgumentException("Notification target with id " + targetId + " not found");
}
int recipientsCount = notificationTargetService.countRecipientsForNotificationTargetConfig(user.getTenantId(), notificationTarget.getConfiguration());
int recipientsCount;
if (notificationTarget.getType() == NotificationTargetType.PLATFORM_USERS) {
recipientsCount = notificationTargetService.countRecipientsForNotificationTargetConfig(user.getTenantId(), notificationTarget.getConfiguration());
} else {
recipientsCount = 1;
}
recipientsCountByTarget.put(notificationTarget.getName(), recipientsCount);
});
preview.setRecipientsCountByTarget(recipientsCountByTarget);

9
application/src/main/java/org/thingsboard/server/controller/NotificationRuleController.java

@ -31,6 +31,7 @@ import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.NotificationRuleId;
import org.thingsboard.server.common.data.notification.rule.NotificationRule;
import org.thingsboard.server.common.data.notification.rule.NotificationRuleInfo;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
@ -62,21 +63,21 @@ public class NotificationRuleController extends BaseController {
@GetMapping("/rule/{id}")
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
public NotificationRule getNotificationRuleById(@PathVariable UUID id) throws ThingsboardException {
public NotificationRuleInfo getNotificationRuleById(@PathVariable UUID id) throws ThingsboardException {
NotificationRuleId notificationRuleId = new NotificationRuleId(id);
return checkEntityId(notificationRuleId, notificationRuleService::findNotificationRuleById, Operation.READ);
return checkEntityId(notificationRuleId, notificationRuleService::findNotificationRuleInfoById, Operation.READ);
}
@GetMapping("/rules")
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
public PageData<NotificationRule> getNotificationRules(@RequestParam int pageSize,
public PageData<NotificationRuleInfo> getNotificationRules(@RequestParam int pageSize,
@RequestParam int page,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String sortProperty,
@RequestParam(required = false) String sortOrder,
@AuthenticationPrincipal SecurityUser user) throws ThingsboardException {
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
return notificationRuleService.findNotificationRulesByTenantId(user.getTenantId(), pageLink);
return notificationRuleService.findNotificationRulesInfosByTenantId(user.getTenantId(), pageLink);
}
@DeleteMapping("/rule/{id}")

42
application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java

@ -34,6 +34,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.NotificationTargetConfig;
import org.thingsboard.server.common.data.notification.targets.NotificationTargetType;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.data.page.PageLink;
@ -44,7 +45,10 @@ import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
import javax.validation.Valid;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH;
@ -59,15 +63,15 @@ public class NotificationTargetController extends BaseController {
@ApiOperation(value = "Save notification target (saveNotificationTarget)",
notes = "Create or update notification target.\n\n" +
"Examples with different configuration types:\n" +
"- USER_LIST:\n" +
"```\n{\n \"name\": \"Special users\",\n \"configuration\": {\n \"type\": \"USER_LIST\",\n \"usersIds\": [\n \"ea31a460-3d85-11ed-9200-77fc04fa14fa\",\n \"86f7b260-3d88-11ed-ad72-ad2ee0f70ba1\"\n ]\n }\n}\n```\n" +
"- CUSTOMER_USERS (not accessible to system administrator):\n" +
"```\n{\n \"name\": \"Users of my customer\",\n \"configuration\": {\n \"type\": \"CUSTOMER_USERS\",\n \"customerId\": \"ea31a460-3d85-11ed-9200-77fc04fa14fa\"\n }\n}\n```\n" +
"or if you would like to use the target in notification rule and get customerId from alarm:\n" +
"```\n{\n \"name\": \"Alarm's customer users\",\n \"configuration\": {\n \"type\": \"CUSTOMER_USERS\",\n \"customerId\": null,\n \"getCustomerIdFromOriginatorEntity\": true\n }\n}\n```\n" +
"- ALL_USERS:\n" +
"```\n{\n \"name\": \"All my users\",\n \"configuration\": {\n \"type\": \"ALL_USERS\"\n }\n}\n```\n\n" +
// "Examples with different configuration types:\n" +
// "- USER_LIST:\n" +
// "```\n{\n \"name\": \"Special users\",\n \"configuration\": {\n \"type\": \"USER_LIST\",\n \"usersIds\": [\n \"ea31a460-3d85-11ed-9200-77fc04fa14fa\",\n \"86f7b260-3d88-11ed-ad72-ad2ee0f70ba1\"\n ]\n }\n}\n```\n" +
// "- CUSTOMER_USERS (not accessible to system administrator):\n" +
// "```\n{\n \"name\": \"Users of my customer\",\n \"configuration\": {\n \"type\": \"CUSTOMER_USERS\",\n \"customerId\": \"ea31a460-3d85-11ed-9200-77fc04fa14fa\"\n }\n}\n```\n" +
// "or if you would like to use the target in notification rule and get customerId from alarm:\n" +
// "```\n{\n \"name\": \"Alarm's customer users\",\n \"configuration\": {\n \"type\": \"CUSTOMER_USERS\",\n \"customerId\": null,\n \"getCustomerIdFromOriginatorEntity\": true\n }\n}\n```\n" +
// "- ALL_USERS:\n" +
// "```\n{\n \"name\": \"All my users\",\n \"configuration\": {\n \"type\": \"ALL_USERS\"\n }\n}\n```\n\n" +
SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@PostMapping("/target")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@ -77,11 +81,13 @@ public class NotificationTargetController extends BaseController {
checkEntity(notificationTarget.getId(), notificationTarget, Resource.NOTIFICATION_TARGET);
if (!user.isSystemAdmin()) {
NotificationTargetConfig targetConfig = notificationTarget.getConfiguration();
PageDataIterable<User> recipients = new PageDataIterable<>(pageLink -> {
return notificationTargetService.findRecipientsForNotificationTargetConfig(user.getTenantId(), null, targetConfig, pageLink);
}, 200);
for (User recipient : recipients) {
accessControlService.checkPermission(user, Resource.USER, Operation.READ, recipient.getId(), recipient);
if (targetConfig.getType() == NotificationTargetType.PLATFORM_USERS) {
PageDataIterable<User> recipients = new PageDataIterable<>(pageLink -> {
return notificationTargetService.findRecipientsForNotificationTargetConfig(user.getTenantId(), null, targetConfig, pageLink);
}, 200);
for (User recipient : recipients) {
accessControlService.checkPermission(user, Resource.USER, Operation.READ, recipient.getId(), recipient);
}
}
}
@ -117,6 +123,14 @@ public class NotificationTargetController extends BaseController {
return recipients;
}
@GetMapping(value = "/targets", params = {"ids"})
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public List<NotificationTarget> getNotificationTargetsByIds(@RequestParam("ids") UUID[] ids,
@AuthenticationPrincipal SecurityUser user) {
List<NotificationTargetId> targetsIds = Arrays.stream(ids).map(NotificationTargetId::new).collect(Collectors.toList());
return notificationTargetService.findNotificationTargetsByTenantIdAndIds(user.getTenantId(), targetsIds);
}
@ApiOperation(value = "Get notification targets (getNotificationTargets)",
notes = "Fetch the page of notification targets owned by sysadmin or tenant." +
SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)

5
application/src/main/java/org/thingsboard/server/controller/NotificationTemplateController.java

@ -35,8 +35,9 @@ import org.thingsboard.server.common.data.notification.NotificationDeliveryMetho
import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig;
import org.thingsboard.server.common.data.notification.targets.slack.SlackConversationType;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
import org.thingsboard.server.common.data.notification.template.SlackConversation;
import org.thingsboard.server.common.data.notification.targets.slack.SlackConversation;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.notification.NotificationSettingsService;
@ -133,7 +134,7 @@ public class NotificationTemplateController extends BaseController {
SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@GetMapping("/slack/conversations")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public List<SlackConversation> listSlackConversations(@RequestParam SlackConversation.Type type,
public List<SlackConversation> listSlackConversations(@RequestParam SlackConversationType type,
@AuthenticationPrincipal SecurityUser user) {
NotificationSettings settings = notificationSettingsService.findNotificationSettings(user.getTenantId());
SlackNotificationDeliveryMethodConfig slackConfig = (SlackNotificationDeliveryMethodConfig)

137
application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java

@ -40,14 +40,18 @@ import org.thingsboard.server.common.data.notification.NotificationRequestStatus
import org.thingsboard.server.common.data.notification.NotificationStatus;
import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.targets.NotificationRecipient;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.slack.SlackConversation;
import org.thingsboard.server.common.data.notification.targets.slack.SlackNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
import org.thingsboard.server.common.data.notification.template.PushDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.notification.NotificationRequestService;
import org.thingsboard.server.dao.notification.NotificationService;
import org.thingsboard.server.dao.notification.NotificationSettingsService;
@ -79,7 +83,7 @@ import java.util.stream.Collectors;
@Slf4j
@RequiredArgsConstructor
@SuppressWarnings({"UnstableApiUsage", "rawtypes"})
public class DefaultNotificationCenter extends AbstractSubscriptionService implements NotificationCenter, NotificationChannel<PushDeliveryMethodNotificationTemplate> {
public class DefaultNotificationCenter extends AbstractSubscriptionService implements NotificationCenter, NotificationChannel<User, PushDeliveryMethodNotificationTemplate> {
private final NotificationTargetService notificationTargetService;
private final NotificationRequestService notificationRequestService;
@ -95,9 +99,17 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
@Override
public NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) {
notificationRequest.setTenantId(tenantId);
NotificationSettings settings = notificationSettingsService.findNotificationSettings(tenantId);
NotificationTemplate notificationTemplate = notificationTemplateService.findNotificationTemplateById(tenantId, notificationRequest.getTemplateId());
List<NotificationTarget> targets = notificationRequest.getTargets().stream()
.map(NotificationTargetId::new)
.map(targetId -> notificationTargetService.findNotificationTargetById(tenantId, targetId))
.peek(target -> {
if (target == null) {
throw new IllegalArgumentException("Some of the targets no longer exist");
}
})
.collect(Collectors.toList());
notificationTemplate.getConfiguration().getDeliveryMethodsTemplates().forEach((deliveryMethod, template) -> {
if (!template.isEnabled()) return;
@ -110,6 +122,9 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
throw new IllegalArgumentException("Slack must be configured in the settings");
}
}
if (targets.stream().noneMatch(target -> target.getType().getSupportedDeliveryMethods().contains(deliveryMethod))) {
throw new IllegalArgumentException("Target for " + deliveryMethod + " delivery method is missing");
}
});
if (notificationRequest.getAdditionalConfig() != null) {
@ -122,64 +137,33 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
}
}
log.debug("Processing notification request (tenant id: {}, notification targets: {})", tenantId, notificationRequest.getTargets());
log.debug("Processing notification request (tenantId: {}, targets: {})", tenantId, notificationRequest.getTargets());
notificationRequest.setStatus(NotificationRequestStatus.PROCESSING);
NotificationRequest savedNotificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest);
notificationExecutor.submit(() -> {
NotificationProcessingContext ctx = NotificationProcessingContext.builder()
.tenantId(tenantId)
.request(savedNotificationRequest)
.settings(settings)
.template(notificationTemplate)
.build();
ctx.init();
NotificationProcessingContext ctx = NotificationProcessingContext.builder()
.tenantId(tenantId)
.request(savedNotificationRequest)
.settings(settings)
.template(notificationTemplate)
.build();
Set<NotificationDeliveryMethod> deliveryMethods = ctx.getDeliveryMethods();
notificationExecutor.submit(() -> {
List<ListenableFuture<Void>> results = new ArrayList<>();
for (UUID targetId : notificationRequest.getTargets()) {
DaoUtil.processBatches(pageLink -> {
return notificationTargetService.findRecipientsForNotificationTarget(tenantId, ctx.getCustomerId(), new NotificationTargetId(targetId), pageLink);
}, 200, recipientsBatch -> {
for (NotificationDeliveryMethod deliveryMethod : deliveryMethods) {
if (deliveryMethod.isStandalone()) continue;
List<User> recipients = recipientsBatch.getData();
log.debug("Sending {} notifications for request {} to recipients batch ({})", deliveryMethod, savedNotificationRequest.getId(), recipients.size());
NotificationChannel notificationChannel = channels.get(deliveryMethod);
for (User recipient : recipients) {
ListenableFuture<Void> resultFuture = process(notificationChannel, recipient, ctx);
DonAsynchron.withCallback(resultFuture, result -> {
ctx.getStats().reportSent(deliveryMethod, recipient);
}, error -> {
ctx.getStats().reportError(deliveryMethod, error, recipient);
});
results.add(resultFuture);
}
}
});
}
for (NotificationDeliveryMethod deliveryMethod : deliveryMethods) {
if (deliveryMethod.isStandalone()) {
NotificationChannel notificationChannel = channels.get(deliveryMethod);
ListenableFuture<Void> resultFuture = process(notificationChannel, null, ctx);
DonAsynchron.withCallback(resultFuture, result -> {
ctx.getStats().reportSent(deliveryMethod, null);
}, error -> {
ctx.getStats().reportError(deliveryMethod, error, null);
});
results.add(resultFuture);
}
for (NotificationTarget target : targets) {
List<ListenableFuture<Void>> result = processForTarget(target, ctx);
results.addAll(result);
}
Futures.whenAllComplete(results).run(() -> {
NotificationRequestId requestId = savedNotificationRequest.getId();
log.debug("[{}] Notification request processing is finished", requestId);
NotificationRequestStats stats = ctx.getStats();
try {
notificationRequestService.updateNotificationRequest(tenantId, savedNotificationRequest.getId(),
NotificationRequestStatus.SENT, stats);
notificationRequestService.updateNotificationRequest(tenantId, requestId, NotificationRequestStatus.SENT, stats);
} catch (Exception e) {
log.error("Failed to update stats for notification request {}", savedNotificationRequest.getId(), e);
log.error("[{}] Failed to update stats for notification request", requestId, e);
}
UserId senderId = notificationRequest.getSenderId();
@ -200,25 +184,70 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
return savedNotificationRequest;
}
private ListenableFuture<Void> process(NotificationChannel notificationChannel, User recipient, NotificationProcessingContext ctx) {
NotificationDeliveryMethod deliveryMethod = notificationChannel.getDeliveryMethod();
if (recipient != null && ctx.getStats().contains(deliveryMethod, recipient.getId())) {
private List<ListenableFuture<Void>> processForTarget(NotificationTarget target, NotificationProcessingContext ctx) {
Iterable<? extends NotificationRecipient> recipients;
switch (target.getType()) {
case PLATFORM_USERS: {
recipients = new PageDataIterable<>(pageLink -> {
return notificationTargetService.findRecipientsForNotificationTargetConfig(ctx.getTenantId(), ctx.getCustomerId(), target.getConfiguration(), pageLink);
}, 200);
break;
}
case SLACK: {
SlackNotificationTargetConfig slackTargetConfig = (SlackNotificationTargetConfig) target.getConfiguration();
SlackConversation conversation = new SlackConversation(slackTargetConfig.getConversationId(), slackTargetConfig.getConversationName());
recipients = List.of(conversation);
break;
}
default: {
recipients = Collections.emptyList();
}
}
Set<NotificationDeliveryMethod> deliveryMethods = new HashSet<>(ctx.getDeliveryMethods());
deliveryMethods.removeIf(deliveryMethod -> !target.getType().getSupportedDeliveryMethods().contains(deliveryMethod));
log.debug("[{}] Processing notification request for {} target ({}) for delivery methods {}", ctx.getRequest().getId(), target.getType(), target.getId(), deliveryMethods);
List<ListenableFuture<Void>> results = new ArrayList<>();
for (NotificationRecipient recipient : recipients) {
for (NotificationDeliveryMethod deliveryMethod : deliveryMethods) {
ListenableFuture<Void> resultFuture = processForRecipient(deliveryMethod, recipient, ctx);
DonAsynchron.withCallback(resultFuture, result -> {
ctx.getStats().reportSent(deliveryMethod, recipient);
}, error -> {
ctx.getStats().reportError(deliveryMethod, error, recipient);
});
results.add(resultFuture);
}
}
return results;
}
private ListenableFuture<Void> processForRecipient(NotificationDeliveryMethod deliveryMethod, NotificationRecipient recipient, NotificationProcessingContext ctx) {
if (ctx.getStats().contains(deliveryMethod, recipient.getId())) {
return Futures.immediateFailedFuture(new AlreadySentException());
}
Map<String, String> templateContext;
if (recipient instanceof User) {
templateContext = ctx.createTemplateContext(((User) recipient));
} else {
templateContext = Collections.emptyMap();
}
DeliveryMethodNotificationTemplate processedTemplate;
try {
Map<String, String> templateContext = recipient != null ? ctx.createTemplateContext(recipient) : Collections.emptyMap();
processedTemplate = ctx.getProcessedTemplate(deliveryMethod, templateContext);
} catch (Exception e) {
return Futures.immediateFailedFuture(e);
}
NotificationChannel notificationChannel = channels.get(deliveryMethod);
log.trace("[{}] Sending {} notification for recipient {}", ctx.getRequest().getId(), deliveryMethod, recipient);
return notificationChannel.sendNotification(recipient, processedTemplate, ctx);
}
@Override
public ListenableFuture<Void> sendNotification(User recipient, PushDeliveryMethodNotificationTemplate processedTemplate, NotificationProcessingContext ctx) {
NotificationRequest request = ctx.getRequest();
log.trace("Creating notification for recipient {} (notification request id: {})", recipient.getId(), request.getId());
Notification notification = Notification.builder()
.requestId(request.getId())
.recipientId(recipient.getId())

2
application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java

@ -27,7 +27,7 @@ import org.thingsboard.server.common.data.notification.NotificationProcessingCon
@Component
@RequiredArgsConstructor
public class EmailNotificationChannel implements NotificationChannel<EmailDeliveryMethodNotificationTemplate> {
public class EmailNotificationChannel implements NotificationChannel<User, EmailDeliveryMethodNotificationTemplate> {
private final MailService mailService;
private final MailExecutorService executor;

8
application/src/main/java/org/thingsboard/server/service/notification/channels/NotificationChannel.java

@ -16,14 +16,14 @@
package org.thingsboard.server.service.notification.channels;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.NotificationProcessingContext;
import org.thingsboard.server.common.data.notification.targets.NotificationRecipient;
import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate;
public interface NotificationChannel<T extends DeliveryMethodNotificationTemplate> {
public interface NotificationChannel<R extends NotificationRecipient, T extends DeliveryMethodNotificationTemplate> {
ListenableFuture<Void> sendNotification(User recipient, T processedTemplate, NotificationProcessingContext ctx);
ListenableFuture<Void> sendNotification(R recipient, T processedTemplate, NotificationProcessingContext ctx);
NotificationDeliveryMethod getDeliveryMethod();

10
application/src/main/java/org/thingsboard/server/service/notification/channels/SlackNotificationChannel.java

@ -20,30 +20,30 @@ import com.google.common.util.concurrent.ListenableFuture;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.thingsboard.rule.engine.api.slack.SlackService;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.notification.AlreadySentException;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationProcessingContext;
import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig;
import org.thingsboard.server.common.data.notification.targets.slack.SlackConversation;
import org.thingsboard.server.common.data.notification.template.SlackDeliveryMethodNotificationTemplate;
import org.thingsboard.server.service.executors.ExternalCallExecutorService;
import org.thingsboard.server.common.data.notification.NotificationProcessingContext;
@Component
@RequiredArgsConstructor
public class SlackNotificationChannel implements NotificationChannel<SlackDeliveryMethodNotificationTemplate> {
public class SlackNotificationChannel implements NotificationChannel<SlackConversation, SlackDeliveryMethodNotificationTemplate> {
private final SlackService slackService;
private final ExternalCallExecutorService executor;
@Override
public ListenableFuture<Void> sendNotification(User recipient, SlackDeliveryMethodNotificationTemplate processedTemplate, NotificationProcessingContext ctx) {
public ListenableFuture<Void> sendNotification(SlackConversation conversation, SlackDeliveryMethodNotificationTemplate processedTemplate, NotificationProcessingContext ctx) {
if (ctx.getStats().contains(NotificationDeliveryMethod.SLACK)) {
return Futures.immediateFailedFuture(new AlreadySentException());
}
SlackNotificationDeliveryMethodConfig config = ctx.getDeliveryMethodConfig(NotificationDeliveryMethod.SLACK);
return executor.submit(() -> {
slackService.sendMessage(ctx.getTenantId(), config.getBotToken(), processedTemplate.getConversationId(), processedTemplate.getBody());
slackService.sendMessage(ctx.getTenantId(), config.getBotToken(), conversation.getId(), processedTemplate.getBody());
return null;
});
}

2
application/src/main/java/org/thingsboard/server/service/notification/channels/SmsNotificationChannel.java

@ -29,7 +29,7 @@ import org.thingsboard.server.service.sms.SmsExecutorService;
@Component
@RequiredArgsConstructor
public class SmsNotificationChannel implements NotificationChannel<SmsDeliveryMethodNotificationTemplate> {
public class SmsNotificationChannel implements NotificationChannel<User, SmsDeliveryMethodNotificationTemplate> {
private final SmsService smsService;
private final SmsExecutorService executor;

11
application/src/main/java/org/thingsboard/server/service/slack/DefaultSlackService.java

@ -30,12 +30,13 @@ import com.slack.api.model.ConversationType;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.notification.template.SlackConversation;
import org.thingsboard.server.common.data.notification.targets.slack.SlackConversation;
import org.thingsboard.rule.engine.api.slack.SlackService;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig;
import org.thingsboard.server.common.data.notification.targets.slack.SlackConversationType;
import org.thingsboard.server.common.data.util.ThrowingBiFunction;
import org.thingsboard.server.dao.notification.NotificationSettingsService;
@ -66,9 +67,9 @@ public class DefaultSlackService implements SlackService {
}
@Override
public List<SlackConversation> listConversations(TenantId tenantId, String token, SlackConversation.Type conversationType) {
public List<SlackConversation> listConversations(TenantId tenantId, String token, SlackConversationType conversationType) {
return cache.get(conversationType + ":" + token, k -> {
if (conversationType == SlackConversation.Type.DIRECT) {
if (conversationType == SlackConversationType.DIRECT) {
UsersListRequest request = UsersListRequest.builder()
.limit(CONVERSATIONS_LIMIT)
.build();
@ -85,7 +86,7 @@ public class DefaultSlackService implements SlackService {
.collect(Collectors.toList());
} else {
ConversationsListRequest request = ConversationsListRequest.builder()
.types(List.of(conversationType == SlackConversation.Type.PUBLIC_CHANNEL ?
.types(List.of(conversationType == SlackConversationType.PUBLIC_CHANNEL ?
ConversationType.PUBLIC_CHANNEL :
ConversationType.PRIVATE_CHANNEL))
.limit(CONVERSATIONS_LIMIT)
@ -107,7 +108,7 @@ public class DefaultSlackService implements SlackService {
}
@Override
public SlackConversation findConversation(TenantId tenantId, String token, SlackConversation.Type conversationType, String namePattern) {
public SlackConversation findConversation(TenantId tenantId, String token, SlackConversationType conversationType, String namePattern) {
List<SlackConversation> conversations = listConversations(tenantId, token, conversationType);
return conversations.stream()
.filter(conversation -> StringUtils.containsIgnoreCase(conversation.getName(), namePattern))

14
application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java

@ -38,7 +38,10 @@ import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.common.data.notification.info.UserOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.UserListNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.targets.NotificationTargetType;
import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.targets.platform.UserListFilter;
import org.thingsboard.server.common.data.notification.targets.slack.SlackNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.EmailDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
@ -78,9 +81,12 @@ public abstract class AbstractNotificationApiTest extends AbstractControllerTest
NotificationTarget notificationTarget = new NotificationTarget();
notificationTarget.setTenantId(tenantId);
notificationTarget.setName("Users " + List.of(usersIds));
UserListNotificationTargetConfig config = new UserListNotificationTargetConfig();
config.setUsersIds(DaoUtil.toUUIDs(List.of(usersIds)));
notificationTarget.setConfiguration(config);
notificationTarget.setType(NotificationTargetType.PLATFORM_USERS);
PlatformUsersNotificationTargetConfig targetConfig = new PlatformUsersNotificationTargetConfig();
UserListFilter filter = new UserListFilter();
filter.setUsersIds(DaoUtil.toUUIDs(List.of(usersIds)));
targetConfig.setUsersFilter(filter);
notificationTarget.setConfiguration(targetConfig);
return saveNotificationTarget(notificationTarget);
}

61
application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java

@ -22,7 +22,6 @@ import org.java_websocket.client.WebSocketClient;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.NotificationCenter;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.NotificationTargetId;
@ -38,16 +37,18 @@ import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.common.data.notification.info.UserOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig;
import org.thingsboard.server.common.data.notification.targets.AllUsersNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.targets.CustomerUsersNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.UserListNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.targets.NotificationTargetType;
import org.thingsboard.server.common.data.notification.targets.platform.AllUsersFilter;
import org.thingsboard.server.common.data.notification.targets.platform.CustomerUsersFilter;
import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.targets.platform.UserListFilter;
import org.thingsboard.server.common.data.notification.targets.slack.SlackNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.EmailDeliveryMethodNotificationTemplate;
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.PushDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.SlackConversation;
import org.thingsboard.server.common.data.notification.template.SlackDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.SmsDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.page.PageData;
@ -370,8 +371,11 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
public void testNotificationRequestPreview() throws Exception {
NotificationTarget target1 = new NotificationTarget();
target1.setName("Me");
UserListNotificationTargetConfig target1Config = new UserListNotificationTargetConfig();
target1Config.setUsersIds(DaoUtil.toUUIDs(List.of(tenantAdminUserId)));
target1.setType(NotificationTargetType.PLATFORM_USERS);
PlatformUsersNotificationTargetConfig target1Config = new PlatformUsersNotificationTargetConfig();
UserListFilter userListFilter = new UserListFilter();
userListFilter.setUsersIds(DaoUtil.toUUIDs(List.of(tenantAdminUserId)));
target1Config.setUsersFilter(userListFilter);
target1.setConfiguration(target1Config);
target1 = saveNotificationTarget(target1);
@ -388,9 +392,12 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
}
NotificationTarget target2 = new NotificationTarget();
target2.setName("Other customer users");
CustomerUsersNotificationTargetConfig target2Config = new CustomerUsersNotificationTargetConfig();
target2Config.setCustomerId(differentCustomerId.getId());
target2Config.setGetCustomerIdFromOriginatorEntity(false);
target2.setType(NotificationTargetType.PLATFORM_USERS);
PlatformUsersNotificationTargetConfig target2Config = new PlatformUsersNotificationTargetConfig();
CustomerUsersFilter customerUsersFilter = new CustomerUsersFilter();
customerUsersFilter.setCustomerId(differentCustomerId.getId());
customerUsersFilter.setGetCustomerIdFromOriginatorEntity(false);
target2Config.setUsersFilter(customerUsersFilter);
target2.setConfiguration(target2Config);
target2 = saveNotificationTarget(target2);
@ -426,8 +433,6 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
SlackDeliveryMethodNotificationTemplate slackNotificationTemplate = new SlackDeliveryMethodNotificationTemplate();
slackNotificationTemplate.setEnabled(true);
slackNotificationTemplate.setConversationType(SlackConversation.Type.PUBLIC_CHANNEL);
slackNotificationTemplate.setConversationId("U1234567");
slackNotificationTemplate.setBody("Message for SLACK: ${email}");
templates.put(NotificationDeliveryMethod.SLACK, slackNotificationTemplate);
@ -472,8 +477,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
assertThat(processedTemplates.get(NotificationDeliveryMethod.SLACK)).asInstanceOf(type(SlackDeliveryMethodNotificationTemplate.class))
.satisfies(template -> {
assertThat(template.getBody())
.startsWith("Message for SLACK")
.endsWith(requestorEmail);
.isEqualTo("Message for SLACK: ${email}"); // ${email} should not be processed
});
}
@ -507,6 +511,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
.until(() -> findNotificationRequest(notificationRequest.getId()).isSent());
NotificationRequestStats stats = getStats(notificationRequest.getId());
System.err.println("STATS: " + stats);
assertThat(stats.getSent().get(NotificationDeliveryMethod.PUSH)).hasValue(1);
assertThat(stats.getSent().get(NotificationDeliveryMethod.EMAIL)).hasValue(1);
assertThat(stats.getErrors().get(NotificationDeliveryMethod.SMS)).size().isOne();
@ -529,7 +534,10 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
NotificationTarget notificationTarget = new NotificationTarget();
notificationTarget.setTenantId(tenantId);
notificationTarget.setName("All my users");
AllUsersNotificationTargetConfig config = new AllUsersNotificationTargetConfig();
notificationTarget.setType(NotificationTargetType.PLATFORM_USERS);
PlatformUsersNotificationTargetConfig config = new PlatformUsersNotificationTargetConfig();
AllUsersFilter filter = new AllUsersFilter();
config.setUsersFilter(filter);
notificationTarget.setConfiguration(config);
notificationTarget = saveNotificationTarget(notificationTarget);
NotificationTargetId notificationTargetId = notificationTarget.getId();
@ -572,21 +580,28 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
notificationTemplate.setName("Slack notification template");
notificationTemplate.setNotificationType(NotificationType.GENERAL);
NotificationTemplateConfig config = new NotificationTemplateConfig();
config.setDefaultTextTemplate("To Slack :)");
config.setDefaultTextTemplate("To Slack :) ${email}");
SlackDeliveryMethodNotificationTemplate slackNotificationTemplate = new SlackDeliveryMethodNotificationTemplate();
slackNotificationTemplate.setEnabled(true);
slackNotificationTemplate.setConversationType(SlackConversation.Type.PUBLIC_CHANNEL);
String conversationId = "U154475415";
slackNotificationTemplate.setConversationId(conversationId);
config.setDeliveryMethodsTemplates(Map.of(
NotificationDeliveryMethod.SLACK, slackNotificationTemplate
));
notificationTemplate.setConfiguration(config);
notificationTemplate = saveNotificationTemplate(notificationTemplate);
NotificationRequest successfulNotificationRequest = submitNotificationRequest(Collections.emptyList(), notificationTemplate.getId(), 0);
String conversationId = "U154475415";
String conversationName = "#my-channel";
NotificationTarget notificationTarget = new NotificationTarget();
notificationTarget.setTenantId(tenantId);
notificationTarget.setName(conversationName + " in Slack");
notificationTarget.setType(NotificationTargetType.SLACK);
SlackNotificationTargetConfig targetConfig = new SlackNotificationTargetConfig();
targetConfig.setConversationId(conversationId);
targetConfig.setConversationName(conversationName);
notificationTarget.setConfiguration(targetConfig);
notificationTarget = saveNotificationTarget(notificationTarget);
NotificationRequest successfulNotificationRequest = submitNotificationRequest(List.of(notificationTarget.getId()), notificationTemplate.getId(), 0);
await().atMost(2, TimeUnit.SECONDS)
.until(() -> findNotificationRequest(successfulNotificationRequest.getId()).isSent());
verify(slackService).sendMessage(eq(tenantId), eq(slackToken), eq(conversationId), eq(config.getDefaultTextTemplate()));
@ -595,7 +610,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
String errorMessage = "Error!!!";
doThrow(new RuntimeException(errorMessage)).when(slackService).sendMessage(any(), any(), any(), any());
NotificationRequest failedNotificationRequest = submitNotificationRequest(Collections.emptyList(), notificationTemplate.getId(), 0);
NotificationRequest failedNotificationRequest = submitNotificationRequest(List.of(notificationTarget.getId()), notificationTemplate.getId(), 0);
await().atMost(2, TimeUnit.SECONDS)
.until(() -> findNotificationRequest(failedNotificationRequest.getId()).isSent());
stats = getStats(failedNotificationRequest.getId());

41
application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.service.notification;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.BooleanNode;
import org.junit.Before;
@ -40,20 +41,25 @@ import org.thingsboard.server.common.data.device.profile.SimpleAlarmConditionSpe
import org.thingsboard.server.common.data.id.NotificationRuleId;
import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationRequestInfo;
import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.common.data.notification.info.AlarmNotificationInfo;
import org.thingsboard.server.common.data.notification.rule.DefaultNotificationRuleRecipientsConfig;
import org.thingsboard.server.common.data.notification.rule.EscalatedNotificationRuleRecipientsConfig;
import org.thingsboard.server.common.data.notification.rule.NotificationRule;
import org.thingsboard.server.common.data.notification.rule.NotificationRuleInfo;
import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.EntityActionNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.query.BooleanFilterPredicate;
import org.thingsboard.server.common.data.query.EntityKeyValueType;
import org.thingsboard.server.common.data.query.FilterPredicateValue;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.service.telemetry.AlarmSubscriptionService;
@ -79,6 +85,9 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
@SpyBean
private AlarmSubscriptionService alarmSubscriptionService;
@SpyBean
private AlarmService alarmService;
@Before
public void beforeEach() throws Exception {
loginTenantAdmin();
@ -183,7 +192,8 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
.set("bool", BooleanNode.TRUE);
doPost("/api/plugins/telemetry/" + device.getId() + "/" + DataConstants.SHARED_SCOPE, attr);
verify(alarmSubscriptionService, timeout(2000)).createOrUpdateAlarm(argThat(alarm -> alarm.getType().equals(alarmType)));
await().atMost(2, TimeUnit.SECONDS)
.until(() -> alarmSubscriptionService.findLatestByOriginatorAndType(tenantId, device.getId(), alarmType).get() != null);
Alarm alarm = alarmSubscriptionService.findLatestByOriginatorAndType(tenantId, device.getId(), alarmType).get();
long ts = System.currentTimeMillis();
@ -278,6 +288,31 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
*/
}
@Test
public void testNotificationRuleInfo() throws Exception {
NotificationDeliveryMethod[] deliveryMethods = {NotificationDeliveryMethod.PUSH, NotificationDeliveryMethod.EMAIL};
NotificationTemplate template = createNotificationTemplate(NotificationType.ENTITY_ACTION, "Subject", "Text", deliveryMethods);
NotificationRule rule = new NotificationRule();
rule.setName("Test");
rule.setTemplateId(template.getId());
rule.setTriggerType(NotificationRuleTriggerType.ENTITY_ACTION);
EntityActionNotificationRuleTriggerConfig triggerConfig = new EntityActionNotificationRuleTriggerConfig();
rule.setTriggerConfig(triggerConfig);
DefaultNotificationRuleRecipientsConfig recipientsConfig = new DefaultNotificationRuleRecipientsConfig();
recipientsConfig.setTriggerType(NotificationRuleTriggerType.ENTITY_ACTION);
recipientsConfig.setTargets(List.of(createNotificationTarget(tenantAdminUserId).getUuidId()));
rule.setRecipientsConfig(recipientsConfig);
rule = saveNotificationRule(rule);
NotificationRuleInfo ruleInfo = findNotificationRules().getData().get(0);
assertThat(ruleInfo.getId()).isEqualTo(ruleInfo.getId());
assertThat(ruleInfo.getTemplateName()).isEqualTo(template.getName());
assertThat(ruleInfo.getDeliveryMethods()).containsOnly(deliveryMethods);
}
private DeviceProfile createDeviceProfileWithAlarmRules(NotificationRuleId notificationRuleId, String alarmType) {
DeviceProfile deviceProfile = createDeviceProfile("For notification rule test");
deviceProfile.setTenantId(tenantId);
@ -318,4 +353,8 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
return doPost("/api/notification/rule", notificationRule, NotificationRule.class);
}
private PageData<NotificationRuleInfo> findNotificationRules() throws Exception {
PageLink pageLink = new PageLink(10);
return doGetTypedWithPageLink("/api/notification/rules?", new TypeReference<PageData<NotificationRuleInfo>>() {}, pageLink);
}
}

59
application/src/test/java/org/thingsboard/server/service/notification/NotificationTargetApiTest.java

@ -23,10 +23,12 @@ import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.ResultMatcher;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.targets.AllUsersNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.targets.CustomerUsersNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.UserListNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.targets.NotificationTargetType;
import org.thingsboard.server.common.data.notification.targets.platform.AllUsersFilter;
import org.thingsboard.server.common.data.notification.targets.platform.CustomerUsersFilter;
import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.targets.platform.UserListFilter;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.controller.AbstractControllerTest;
import org.thingsboard.server.dao.notification.NotificationTargetDao;
@ -51,21 +53,25 @@ public class NotificationTargetApiTest extends AbstractControllerTest {
@Test
public void givenInvalidNotificationTarget_whenSaving_returnValidationError() throws Exception {
NotificationTarget notificationTarget = new NotificationTarget();
notificationTarget.setTenantId(null);
notificationTarget.setName(null);
notificationTarget.setConfiguration(null);
NotificationTarget target = new NotificationTarget();
target.setTenantId(null);
target.setName(null);
target.setType(null);
target.setConfiguration(null);
String validationError = saveAndGetError(notificationTarget, status().isBadRequest());
String validationError = saveAndGetError(target, status().isBadRequest());
assertThat(validationError)
.contains("name must not be")
.contains("type must not be")
.contains("configuration must not be");
UserListNotificationTargetConfig userListConfig = new UserListNotificationTargetConfig();
userListConfig.setUsersIds(Collections.emptyList());
notificationTarget.setConfiguration(userListConfig);
PlatformUsersNotificationTargetConfig targetConfig = new PlatformUsersNotificationTargetConfig();
UserListFilter userListFilter = new UserListFilter();
userListFilter.setUsersIds(Collections.emptyList());
targetConfig.setUsersFilter(userListFilter);
target.setConfiguration(targetConfig);
validationError = saveAndGetError(notificationTarget, status().isBadRequest());
validationError = saveAndGetError(target, status().isBadRequest());
assertThat(validationError)
.contains("usersIds must not be");
}
@ -76,16 +82,18 @@ public class NotificationTargetApiTest extends AbstractControllerTest {
NotificationTarget notificationTarget = new NotificationTarget();
notificationTarget.setTenantId(differentTenantId);
notificationTarget.setName("Target 1");
notificationTarget.setType(NotificationTargetType.PLATFORM_USERS);
UserListNotificationTargetConfig userListConfig = new UserListNotificationTargetConfig();
userListConfig.setUsersIds(List.of(customerUserId.getId(), tenantAdminUserId.getId()));
notificationTarget.setConfiguration(userListConfig);
PlatformUsersNotificationTargetConfig targetConfig = new PlatformUsersNotificationTargetConfig();
UserListFilter userListFilter = new UserListFilter();
userListFilter.setUsersIds(List.of(customerUserId.getId(), tenantAdminUserId.getId()));
targetConfig.setUsersFilter(userListFilter);
notificationTarget.setConfiguration(targetConfig);
saveAndGetError(notificationTarget, status().isForbidden());
loginSysAdmin();
notificationTarget.setTenantId(TenantId.SYS_TENANT_ID);
notificationTarget.setConfiguration(userListConfig);
save(notificationTarget, status().isOk());
}
@ -94,9 +102,13 @@ public class NotificationTargetApiTest extends AbstractControllerTest {
NotificationTarget notificationTarget = new NotificationTarget();
notificationTarget.setTenantId(tenantId);
notificationTarget.setName("Test target");
CustomerUsersNotificationTargetConfig customerUsersConfig = new CustomerUsersNotificationTargetConfig();
customerUsersConfig.setCustomerId(customerId.getId());
notificationTarget.setConfiguration(customerUsersConfig);
notificationTarget.setType(NotificationTargetType.PLATFORM_USERS);
PlatformUsersNotificationTargetConfig targetConfig = new PlatformUsersNotificationTargetConfig();
CustomerUsersFilter customerUsersFilter = new CustomerUsersFilter();
customerUsersFilter.setCustomerId(customerId.getId());
targetConfig.setUsersFilter(customerUsersFilter);
notificationTarget.setConfiguration(targetConfig);
List<User> recipients = getRecipients(notificationTarget);
assertThat(recipients).size().isNotZero();
@ -104,8 +116,8 @@ public class NotificationTargetApiTest extends AbstractControllerTest {
assertThat(recipient.getCustomerId()).isEqualTo(customerId);
});
AllUsersNotificationTargetConfig allUsersConfig = new AllUsersNotificationTargetConfig();
notificationTarget.setConfiguration(allUsersConfig);
AllUsersFilter allUsersFilter = new AllUsersFilter();
targetConfig.setUsersFilter(allUsersFilter);
recipients = getRecipients(notificationTarget);
assertThat(recipients).size().isGreaterThanOrEqualTo(2);
assertThat(recipients).allSatisfy(recipient -> {
@ -129,8 +141,11 @@ public class NotificationTargetApiTest extends AbstractControllerTest {
createDifferentTenant();
NotificationTarget notificationTarget = new NotificationTarget();
notificationTarget.setName("Test 1");
notificationTarget.setType(NotificationTargetType.PLATFORM_USERS);
notificationTarget.setTenantId(differentTenantId);
notificationTarget.setConfiguration(new AllUsersNotificationTargetConfig());
PlatformUsersNotificationTargetConfig targetConfig = new PlatformUsersNotificationTargetConfig();
targetConfig.setUsersFilter(new AllUsersFilter());
notificationTarget.setConfiguration(targetConfig);
save(notificationTarget, status().isOk());
assertThat(notificationTargetDao.find(TenantId.SYS_TENANT_ID)).isNotEmpty();

5
common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRuleService.java

@ -18,6 +18,7 @@ package org.thingsboard.server.dao.notification;
import org.thingsboard.server.common.data.id.NotificationRuleId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.rule.NotificationRule;
import org.thingsboard.server.common.data.notification.rule.NotificationRuleInfo;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
@ -30,6 +31,10 @@ public interface NotificationRuleService {
NotificationRule findNotificationRuleById(TenantId tenantId, NotificationRuleId id);
NotificationRuleInfo findNotificationRuleInfoById(TenantId tenantId, NotificationRuleId id);
PageData<NotificationRuleInfo> findNotificationRulesInfosByTenantId(TenantId tenantId, PageLink pageLink);
PageData<NotificationRule> findNotificationRulesByTenantId(TenantId tenantId, PageLink pageLink);
List<NotificationRule> findNotificationRulesByTenantIdAndTriggerType(TenantId tenantId, NotificationRuleTriggerType triggerType);

4
common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetService.java

@ -24,6 +24,8 @@ import org.thingsboard.server.common.data.notification.targets.NotificationTarge
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import java.util.List;
public interface NotificationTargetService {
NotificationTarget saveNotificationTarget(TenantId tenantId, NotificationTarget notificationTarget);
@ -32,6 +34,8 @@ public interface NotificationTargetService {
PageData<NotificationTarget> findNotificationTargetsByTenantId(TenantId tenantId, PageLink pageLink);
List<NotificationTarget> findNotificationTargetsByTenantIdAndIds(TenantId tenantId, List<NotificationTargetId> ids);
PageData<User> findRecipientsForNotificationTarget(TenantId tenantId, CustomerId customerId, NotificationTargetId targetId, PageLink pageLink);
int countRecipientsForNotificationTargetConfig(TenantId tenantId, NotificationTargetConfig targetConfig);

3
common/data/src/main/java/org/thingsboard/server/common/data/User.java

@ -25,13 +25,14 @@ import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.notification.targets.NotificationRecipient;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoXss;
@ApiModel
@EqualsAndHashCode(callSuper = true)
public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements HasName, HasTenantId, HasCustomerId {
public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements HasName, HasTenantId, HasCustomerId, NotificationRecipient {
private static final long serialVersionUID = 8250339805336035966L;

15
common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationDeliveryMethod.java

@ -15,24 +15,11 @@
*/
package org.thingsboard.server.common.data.notification;
import lombok.Getter;
public enum NotificationDeliveryMethod {
PUSH,
EMAIL,
SMS,
SLACK(true);
@Getter
private final boolean standalone; // means that notifications for the delivery method are sent independently of specified recipients
NotificationDeliveryMethod() {
this(false);
}
NotificationDeliveryMethod(boolean standalone) {
this.standalone = standalone;
}
SLACK
}

3
common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationProcessingContext.java

@ -62,9 +62,10 @@ public class NotificationProcessingContext {
this.notificationTemplate = template;
this.templates = new EnumMap<>(NotificationDeliveryMethod.class);
this.stats = new NotificationRequestStats();
init();
}
public void init() {
private void init() {
NotificationTemplateConfig templateConfig = notificationTemplate.getConfiguration();
templateConfig.getDeliveryMethodsTemplates().forEach((deliveryMethod, template) -> {
if (!template.isEnabled()) return;

23
common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestStats.java

@ -21,10 +21,10 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.notification.targets.NotificationRecipient;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
@ -35,7 +35,7 @@ public class NotificationRequestStats {
private final Map<NotificationDeliveryMethod, AtomicInteger> sent;
private final Map<NotificationDeliveryMethod, Map<String, String>> errors;
@JsonIgnore
private final Map<NotificationDeliveryMethod, Set<UserId>> processedRecipients;
private final Map<NotificationDeliveryMethod, Set<Object>> processedRecipients;
public NotificationRequestStats() {
this.sent = new ConcurrentHashMap<>();
@ -51,19 +51,22 @@ public class NotificationRequestStats {
this.processedRecipients = Collections.emptyMap();
}
public void reportSent(NotificationDeliveryMethod deliveryMethod, User recipient) {
public void reportSent(NotificationDeliveryMethod deliveryMethod, NotificationRecipient recipient) {
sent.computeIfAbsent(deliveryMethod, k -> new AtomicInteger()).incrementAndGet();
if (recipient != null) {
processedRecipients.computeIfAbsent(deliveryMethod, k -> ConcurrentHashMap.newKeySet()).add(recipient.getId());
}
processedRecipients.computeIfAbsent(deliveryMethod, k -> ConcurrentHashMap.newKeySet()).add(recipient.getId());
}
public void reportError(NotificationDeliveryMethod deliveryMethod, Throwable error, User recipient) {
public void reportError(NotificationDeliveryMethod deliveryMethod, Throwable error, NotificationRecipient recipient) {
if (error instanceof AlreadySentException) {
return;
}
String errorMessage = error.getMessage();
String key = Optional.ofNullable(recipient).map(User::getEmail).orElse("");
String key;
if (recipient instanceof User) {
key = ((User) recipient).getEmail();
} else {
key = "";
}
errors.computeIfAbsent(deliveryMethod, k -> new ConcurrentHashMap<>()).put(key, errorMessage);
}
@ -71,8 +74,8 @@ public class NotificationRequestStats {
return sent.containsKey(deliveryMethod) || errors.containsKey(deliveryMethod);
}
public boolean contains(NotificationDeliveryMethod deliveryMethod, UserId recipientId) {
Set<UserId> processedRecipients = this.processedRecipients.get(deliveryMethod);
public boolean contains(NotificationDeliveryMethod deliveryMethod, Object recipientId) {
Set<Object> processedRecipients = this.processedRecipients.get(deliveryMethod);
return processedRecipients != null && processedRecipients.contains(recipientId);
}

12
common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java

@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.notification.rule;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId;
@ -33,6 +34,7 @@ import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Data
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class NotificationRule extends BaseData<NotificationRuleId> implements HasTenantId, HasName {
@ -50,6 +52,16 @@ public class NotificationRule extends BaseData<NotificationRuleId> implements Ha
@Valid
private NotificationRuleRecipientsConfig recipientsConfig; // todo: add pg_tgrm index (but index is 2.5x size of the column)
public NotificationRule(NotificationRule other) {
super(other);
this.tenantId = other.tenantId;
this.name = other.name;
this.templateId = other.templateId;
this.triggerType = other.triggerType;
this.triggerConfig = other.triggerConfig;
this.recipientsConfig = other.recipientsConfig;
}
@JsonIgnore
@AssertTrue(message = "trigger type not matching")
public boolean isValid() {

39
common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRuleInfo.java

@ -0,0 +1,39 @@
/**
* Copyright © 2016-2022 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;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import java.util.List;
@Data
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class NotificationRuleInfo extends NotificationRule {
private String templateName;
private List<NotificationDeliveryMethod> deliveryMethods;
public NotificationRuleInfo(NotificationRule rule, String templateName, List<NotificationDeliveryMethod> deliveryMethods) {
super(rule);
this.templateName = templateName;
this.deliveryMethods = deliveryMethods;
}
}

12
common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/AllUsersNotificationTargetConfig.java → common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationRecipient.java

@ -15,16 +15,8 @@
*/
package org.thingsboard.server.common.data.notification.targets;
import lombok.Data;
import lombok.EqualsAndHashCode;
public interface NotificationRecipient {
@Data
@EqualsAndHashCode(callSuper = true)
public class AllUsersNotificationTargetConfig extends NotificationTargetConfig {
@Override
public NotificationTargetConfigType getType() {
return NotificationTargetConfigType.ALL_USERS;
}
Object getId();
}

2
common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTarget.java

@ -37,6 +37,8 @@ public class NotificationTarget extends BaseData<NotificationTargetId> implement
@NoXss
private String name;
@NotNull
private NotificationTargetType type;
@NotNull
@Valid
private NotificationTargetConfig configuration;

9
common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetConfig.java

@ -21,13 +21,14 @@ import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import lombok.Data;
import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.targets.slack.SlackNotificationTargetConfig;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
@JsonSubTypes({
@Type(value = UserListNotificationTargetConfig.class, name = "USER_LIST"),
@Type(value = CustomerUsersNotificationTargetConfig.class, name = "CUSTOMER_USERS"),
@Type(value = AllUsersNotificationTargetConfig.class, name = "ALL_USERS")
@Type(value = PlatformUsersNotificationTargetConfig.class, name = "PLATFORM_USERS"),
@Type(value = SlackNotificationTargetConfig.class, name = "SLACK")
})
@Data
public abstract class NotificationTargetConfig {
@ -35,6 +36,6 @@ public abstract class NotificationTargetConfig {
private String description;
@JsonIgnore
public abstract NotificationTargetConfigType getType();
public abstract NotificationTargetType getType();
}

33
common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetType.java

@ -0,0 +1,33 @@
/**
* Copyright © 2016-2022 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.targets;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import java.util.Set;
@RequiredArgsConstructor
public enum NotificationTargetType {
PLATFORM_USERS(Set.of(NotificationDeliveryMethod.PUSH, NotificationDeliveryMethod.EMAIL, NotificationDeliveryMethod.SMS)),
SLACK(Set.of(NotificationDeliveryMethod.SLACK));
@Getter
private final Set<NotificationDeliveryMethod> supportedDeliveryMethods;
}

14
common/data/src/main/java/org/thingsboard/server/common/data/notification/template/SlackConversation.java → common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/platform/AllUsersFilter.java

@ -13,20 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.notification.template;
package org.thingsboard.server.common.data.notification.targets.platform;
import lombok.Data;
@Data
public class SlackConversation {
public class AllUsersFilter implements UsersFilter {
private String id;
private String name;
public enum Type {
DIRECT,
PUBLIC_CHANNEL,
PRIVATE_CHANNEL
@Override
public UsersFilterType getType() {
return UsersFilterType.ALL_USERS;
}
}

10
common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/CustomerUsersNotificationTargetConfig.java → common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/platform/CustomerUsersFilter.java

@ -13,26 +13,24 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.notification.targets;
package org.thingsboard.server.common.data.notification.targets.platform;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.id.EntityId;
import javax.validation.constraints.AssertTrue;
import java.util.UUID;
@Data
@EqualsAndHashCode(callSuper = true)
public class CustomerUsersNotificationTargetConfig extends NotificationTargetConfig {
public class CustomerUsersFilter implements UsersFilter {
private UUID customerId; // might not be set if using with notification rule
private boolean getCustomerIdFromOriginatorEntity; // e.g. from alarm
@Override
public NotificationTargetConfigType getType() {
return NotificationTargetConfigType.CUSTOMER_USERS;
public UsersFilterType getType() {
return UsersFilterType.CUSTOMER_USERS;
}
@AssertTrue(message = "customerId is required")

39
common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/platform/PlatformUsersNotificationTargetConfig.java

@ -0,0 +1,39 @@
/**
* Copyright © 2016-2022 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.targets.platform;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.notification.targets.NotificationTargetConfig;
import org.thingsboard.server.common.data.notification.targets.NotificationTargetType;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
@Data
@EqualsAndHashCode(callSuper = true)
public class PlatformUsersNotificationTargetConfig extends NotificationTargetConfig {
@NotNull
@Valid
private UsersFilter usersFilter;
@Override
public NotificationTargetType getType() {
return NotificationTargetType.PLATFORM_USERS;
}
}

10
common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/UserListNotificationTargetConfig.java → common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/platform/UserListFilter.java

@ -13,25 +13,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.notification.targets;
package org.thingsboard.server.common.data.notification.targets.platform;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotEmpty;
import java.util.List;
import java.util.UUID;
@Data
@EqualsAndHashCode(callSuper = true)
public class UserListNotificationTargetConfig extends NotificationTargetConfig {
public class UserListFilter implements UsersFilter {
@NotEmpty
private List<UUID> usersIds;
@Override
public NotificationTargetConfigType getType() {
return NotificationTargetConfigType.USER_LIST;
public UsersFilterType getType() {
return UsersFilterType.USER_LIST;
}
}

36
common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/platform/UsersFilter.java

@ -0,0 +1,36 @@
/**
* Copyright © 2016-2022 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.targets.platform;
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;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
@JsonSubTypes({
@Type(value = UserListFilter.class, name = "USER_LIST"),
@Type(value = CustomerUsersFilter.class, name = "CUSTOMER_USERS"),
@Type(value = AllUsersFilter.class, name = "ALL_USERS")
})
public interface UsersFilter {
@JsonIgnore
UsersFilterType getType();
}

4
common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetConfigType.java → common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/platform/UsersFilterType.java

@ -13,9 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.notification.targets;
package org.thingsboard.server.common.data.notification.targets.platform;
public enum NotificationTargetConfigType {
public enum UsersFilterType {
USER_LIST,
CUSTOMER_USERS,
ALL_USERS

29
common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/slack/SlackConversation.java

@ -0,0 +1,29 @@
/**
* Copyright © 2016-2022 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.targets.slack;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.notification.targets.NotificationRecipient;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SlackConversation implements NotificationRecipient {
private String id;
private String name;
}

22
common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/slack/SlackConversationType.java

@ -0,0 +1,22 @@
/**
* Copyright © 2016-2022 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.targets.slack;
public enum SlackConversationType {
DIRECT,
PUBLIC_CHANNEL,
PRIVATE_CHANNEL
}

40
common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/slack/SlackNotificationTargetConfig.java

@ -0,0 +1,40 @@
/**
* Copyright © 2016-2022 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.targets.slack;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.notification.targets.NotificationTargetConfig;
import org.thingsboard.server.common.data.notification.targets.NotificationTargetType;
import javax.validation.constraints.NotEmpty;
@Data
@EqualsAndHashCode(callSuper = true)
public class SlackNotificationTargetConfig extends NotificationTargetConfig {
private SlackConversationType conversationType;
@NotEmpty
private String conversationId;
@NotEmpty
private String conversationName;
@Override
public NotificationTargetType getType() {
return NotificationTargetType.SLACK;
}
}

1
common/data/src/main/java/org/thingsboard/server/common/data/notification/template/DeliveryMethodNotificationTemplate.java

@ -44,6 +44,7 @@ public abstract class DeliveryMethodNotificationTemplate {
this.body = other.body;
}
@JsonIgnore
public abstract NotificationDeliveryMethod getMethod();
@JsonIgnore

10
common/data/src/main/java/org/thingsboard/server/common/data/notification/template/SlackDeliveryMethodNotificationTemplate.java

@ -21,22 +21,14 @@ import lombok.NoArgsConstructor;
import lombok.ToString;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import javax.validation.constraints.NotEmpty;
@Data
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SlackDeliveryMethodNotificationTemplate extends DeliveryMethodNotificationTemplate {
private SlackConversation.Type conversationType;
@NotEmpty
private String conversationId;
public SlackDeliveryMethodNotificationTemplate(SlackDeliveryMethodNotificationTemplate other) {
public SlackDeliveryMethodNotificationTemplate(DeliveryMethodNotificationTemplate other) {
super(other);
this.conversationType = other.conversationType;
this.conversationId = other.conversationId;
}
@Override

1
dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java

@ -650,6 +650,7 @@ public class ModelConstants {
* */
public static final String NOTIFICATION_TARGET_TABLE_NAME = "notification_target";
public static final String NOTIFICATION_TARGET_TYPE_PROPERTY = "type";
public static final String NOTIFICATION_TARGET_CONFIGURATION_PROPERTY = "configuration";
public static final String NOTIFICATION_TABLE_NAME = "notification";

11
dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRuleEntity.java

@ -77,6 +77,17 @@ public class NotificationRuleEntity extends BaseSqlEntity<NotificationRule> {
setRecipientsConfig(toJson(notificationRule.getRecipientsConfig()));
}
public NotificationRuleEntity(NotificationRuleEntity other) {
this.id = other.id;
this.createdTime = other.createdTime;
this.tenantId = other.tenantId;
this.name = other.name;
this.templateId = other.templateId;
this.triggerType = other.triggerType;
this.triggerConfig = other.triggerConfig;
this.recipientsConfig = other.recipientsConfig;
}
@Override
public NotificationRule toData() {
NotificationRule notificationRule = new NotificationRule();

53
dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRuleInfoEntity.java

@ -0,0 +1,53 @@
/**
* Copyright © 2016-2022 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.dao.model.sql;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.rule.NotificationRule;
import org.thingsboard.server.common.data.notification.rule.NotificationRuleInfo;
import org.thingsboard.server.common.data.notification.template.NotificationTemplateConfig;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Data
@EqualsAndHashCode(callSuper = true)
public class NotificationRuleInfoEntity extends NotificationRuleEntity {
private String templateName;
private JsonNode templateConfig;
public NotificationRuleInfoEntity(NotificationRuleEntity ruleEntity, String templateName, Object templateConfig) {
super(ruleEntity);
this.templateName = templateName;
this.templateConfig = (JsonNode) templateConfig;
}
@Override
public NotificationRuleInfo toData() {
NotificationRule rule = super.toData();
List<NotificationDeliveryMethod> deliveryMethods = fromJson(templateConfig, NotificationTemplateConfig.class)
.getDeliveryMethodsTemplates().entrySet().stream()
.filter(entry -> entry.getValue().isEnabled())
.map(Map.Entry::getKey).collect(Collectors.toList());
return new NotificationRuleInfo(rule, templateName, deliveryMethods);
}
}

10
dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTargetEntity.java

@ -21,15 +21,17 @@ import lombok.EqualsAndHashCode;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.NotificationTargetConfig;
import org.thingsboard.server.common.data.notification.targets.NotificationTargetType;
import org.thingsboard.server.dao.model.BaseSqlEntity;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.util.mapping.JsonStringType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Table;
import java.util.UUID;
@ -46,6 +48,10 @@ public class NotificationTargetEntity extends BaseSqlEntity<NotificationTarget>
@Column(name = ModelConstants.NAME_PROPERTY, nullable = false)
private String name;
@Enumerated(EnumType.STRING)
@Column(name = ModelConstants.NOTIFICATION_TARGET_TYPE_PROPERTY, nullable = false)
private NotificationTargetType type;
@Type(type = "json")
@Column(name = ModelConstants.NOTIFICATION_TARGET_CONFIGURATION_PROPERTY, nullable = false)
private JsonNode configuration;
@ -57,6 +63,7 @@ public class NotificationTargetEntity extends BaseSqlEntity<NotificationTarget>
setCreatedTime(notificationTarget.getCreatedTime());
setTenantId(getTenantUuid(notificationTarget.getTenantId()));
setName(notificationTarget.getName());
setType(notificationTarget.getType());
setConfiguration(toJson(notificationTarget.getConfiguration()));
}
@ -67,6 +74,7 @@ public class NotificationTargetEntity extends BaseSqlEntity<NotificationTarget>
notificationTarget.setCreatedTime(createdTime);
notificationTarget.setTenantId(getTenantId(tenantId));
notificationTarget.setName(name);
notificationTarget.setType(type);
notificationTarget.setConfiguration(fromJson(configuration, NotificationTargetConfig.class));
return notificationTarget;
}

11
dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRuleService.java

@ -20,6 +20,7 @@ import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.id.NotificationRuleId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.rule.NotificationRule;
import org.thingsboard.server.common.data.notification.rule.NotificationRuleInfo;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
@ -63,6 +64,16 @@ public class DefaultNotificationRuleService extends AbstractCachedEntityService<
return notificationRuleDao.findById(tenantId, id.getId());
}
@Override
public NotificationRuleInfo findNotificationRuleInfoById(TenantId tenantId, NotificationRuleId id) {
return notificationRuleDao.findInfoById(tenantId, id);
}
@Override
public PageData<NotificationRuleInfo> findNotificationRulesInfosByTenantId(TenantId tenantId, PageLink pageLink) {
return notificationRuleDao.findInfosByTenantIdAndPageLink(tenantId, pageLink);
}
@Override
public PageData<NotificationRule> findNotificationRulesByTenantId(TenantId tenantId, PageLink pageLink) {
return notificationRuleDao.findByTenantIdAndPageLink(tenantId, pageLink);

21
dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java

@ -24,10 +24,12 @@ import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.notification.NotificationRequestStatus;
import org.thingsboard.server.common.data.notification.targets.CustomerUsersNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.targets.platform.CustomerUsersFilter;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.NotificationTargetConfig;
import org.thingsboard.server.common.data.notification.targets.UserListNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.targets.platform.UserListFilter;
import org.thingsboard.server.common.data.notification.targets.platform.UsersFilter;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.entity.AbstractEntityService;
@ -70,6 +72,11 @@ public class DefaultNotificationTargetService extends AbstractEntityService impl
return notificationTargetDao.findByTenantIdAndPageLink(tenantId, pageLink);
}
@Override
public List<NotificationTarget> findNotificationTargetsByTenantIdAndIds(TenantId tenantId, List<NotificationTargetId> ids) {
return notificationTargetDao.findByTenantIdAndIds(tenantId, ids);
}
@Override
public PageData<User> findRecipientsForNotificationTarget(TenantId tenantId, CustomerId customerId, NotificationTargetId targetId, PageLink pageLink) {
NotificationTarget notificationTarget = findNotificationTargetById(tenantId, targetId);
@ -85,9 +92,13 @@ public class DefaultNotificationTargetService extends AbstractEntityService impl
@Override
public PageData<User> findRecipientsForNotificationTargetConfig(TenantId tenantId, CustomerId customerId, NotificationTargetConfig targetConfig, PageLink pageLink) {
switch (targetConfig.getType()) {
if (!(targetConfig instanceof PlatformUsersNotificationTargetConfig)) {
throw new IllegalArgumentException("Unsupported target type " + targetConfig.getType());
}
UsersFilter usersFilter = ((PlatformUsersNotificationTargetConfig) targetConfig).getUsersFilter();
switch (usersFilter.getType()) {
case USER_LIST: {
List<User> users = ((UserListNotificationTargetConfig) targetConfig).getUsersIds().stream()
List<User> users = ((UserListFilter) usersFilter).getUsersIds().stream()
.map(UserId::new).map(userId -> userService.findUserById(tenantId, userId))
.collect(Collectors.toList());
return new PageData<>(users, 1, users.size(), false);
@ -96,7 +107,7 @@ public class DefaultNotificationTargetService extends AbstractEntityService impl
if (tenantId.equals(TenantId.SYS_TENANT_ID)) {
throw new IllegalArgumentException("Customer users target is not supported for system administrator");
}
CustomerUsersNotificationTargetConfig customerUsersConfig = (CustomerUsersNotificationTargetConfig) targetConfig;
CustomerUsersFilter customerUsersConfig = (CustomerUsersFilter) usersFilter;
if (!customerUsersConfig.isGetCustomerIdFromOriginatorEntity()) {
customerId = new CustomerId(customerUsersConfig.getCustomerId());
}

6
dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRuleDao.java

@ -15,9 +15,11 @@
*/
package org.thingsboard.server.dao.notification;
import org.thingsboard.server.common.data.id.NotificationRuleId;
import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.rule.NotificationRule;
import org.thingsboard.server.common.data.notification.rule.NotificationRuleInfo;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
@ -29,8 +31,12 @@ public interface NotificationRuleDao extends Dao<NotificationRule> {
PageData<NotificationRule> findByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink);
PageData<NotificationRuleInfo> findInfosByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink);
boolean existsByTargetId(TenantId tenantId, NotificationTargetId targetId);
List<NotificationRule> findByTenantIdAndTriggerType(TenantId tenantId, NotificationRuleTriggerType triggerType);
NotificationRuleInfo findInfoById(TenantId tenantId, NotificationRuleId id);
}

5
dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetDao.java

@ -15,14 +15,19 @@
*/
package org.thingsboard.server.dao.notification;
import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.Dao;
import java.util.List;
public interface NotificationTargetDao extends Dao<NotificationTarget> {
PageData<NotificationTarget> findByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink);
List<NotificationTarget> findByTenantIdAndIds(TenantId tenantId, List<NotificationTargetId> ids);
}

15
dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRuleDao.java

@ -20,14 +20,17 @@ import lombok.RequiredArgsConstructor;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.NotificationRuleId;
import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.rule.NotificationRule;
import org.thingsboard.server.common.data.notification.rule.NotificationRuleInfo;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.model.sql.NotificationRuleEntity;
import org.thingsboard.server.dao.model.sql.NotificationRuleInfoEntity;
import org.thingsboard.server.dao.notification.NotificationRuleDao;
import org.thingsboard.server.dao.sql.JpaAbstractDao;
import org.thingsboard.server.dao.util.SqlDao;
@ -50,6 +53,12 @@ public class JpaNotificationRuleDao extends JpaAbstractDao<NotificationRuleEntit
Strings.nullToEmpty(pageLink.getTextSearch()), DaoUtil.toPageable(pageLink)));
}
@Override
public PageData<NotificationRuleInfo> findInfosByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink) {
return DaoUtil.pageToPageData(notificationRuleRepository.findInfosByTenantIdAndSearchText(getId(tenantId, true),
Strings.nullToEmpty(pageLink.getTextSearch()), DaoUtil.toPageable(pageLink))).mapData(NotificationRuleInfoEntity::toData);
}
@Override
public boolean existsByTargetId(TenantId tenantId, NotificationTargetId targetId) {
return notificationRuleRepository.existsByRecipientsConfigContaining(targetId.getId().toString());
@ -60,6 +69,12 @@ public class JpaNotificationRuleDao extends JpaAbstractDao<NotificationRuleEntit
return DaoUtil.convertDataList(notificationRuleRepository.findAllByTenantIdAndTriggerType(getId(tenantId, true), triggerType));
}
@Override
public NotificationRuleInfo findInfoById(TenantId tenantId, NotificationRuleId id) {
NotificationRuleInfoEntity infoEntity = notificationRuleRepository.findInfoById(id.getId());
return infoEntity != null ? infoEntity.toData() : null;
}
@Override
protected Class<NotificationRuleEntity> getEntityClass() {
return NotificationRuleEntity.class;

9
dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTargetDao.java

@ -20,7 +20,9 @@ import lombok.RequiredArgsConstructor;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
@ -30,7 +32,9 @@ import org.thingsboard.server.dao.notification.NotificationTargetDao;
import org.thingsboard.server.dao.sql.JpaAbstractDao;
import org.thingsboard.server.dao.util.SqlDao;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import static org.thingsboard.server.dao.DaoUtil.getId;
@ -47,6 +51,11 @@ public class JpaNotificationTargetDao extends JpaAbstractDao<NotificationTargetE
Strings.nullToEmpty(pageLink.getTextSearch()), DaoUtil.toPageable(pageLink)));
}
@Override
public List<NotificationTarget> findByTenantIdAndIds(TenantId tenantId, List<NotificationTargetId> ids) {
return DaoUtil.convertDataList(notificationTargetRepository.findByTenantIdAndIdIn(tenantId.getId(), DaoUtil.toUUIDs(ids)));
}
@Override
protected Class<NotificationTargetEntity> getEntityClass() {
return NotificationTargetEntity.class;

13
dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRuleRepository.java

@ -21,8 +21,10 @@ import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import org.thingsboard.server.common.data.notification.rule.NotificationRuleInfo;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.dao.model.sql.NotificationRuleEntity;
import org.thingsboard.server.dao.model.sql.NotificationRuleInfoEntity;
import java.util.List;
import java.util.UUID;
@ -30,6 +32,9 @@ import java.util.UUID;
@Repository
public interface NotificationRuleRepository extends JpaRepository<NotificationRuleEntity, UUID> {
String RULE_INFO_QUERY = "SELECT new org.thingsboard.server.dao.model.sql.NotificationRuleInfoEntity(r, t.name, t.configuration) " +
"FROM NotificationRuleEntity r INNER JOIN NotificationTemplateEntity t ON r.templateId = t.id";
@Query("SELECT r FROM NotificationRuleEntity r WHERE r.tenantId = :tenantId " +
"AND lower(r.name) LIKE lower(concat('%', :searchText, '%')) ")
Page<NotificationRuleEntity> findByTenantIdAndSearchText(@Param("tenantId") UUID tenantId,
@ -40,4 +45,12 @@ public interface NotificationRuleRepository extends JpaRepository<NotificationRu
List<NotificationRuleEntity> findAllByTenantIdAndTriggerType(UUID tenantId, NotificationRuleTriggerType triggerType);
@Query(RULE_INFO_QUERY + " WHERE r.id = :id")
NotificationRuleInfoEntity findInfoById(@Param("id") UUID id);
@Query(RULE_INFO_QUERY + " WHERE r.tenantId = :tenantId AND lower(r.name) LIKE lower(concat('%', :searchText, '%'))")
Page<NotificationRuleInfoEntity> findInfosByTenantIdAndSearchText(@Param("tenantId") UUID tenantId,
@Param("searchText") String searchText,
Pageable pageable);
}

3
dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTargetRepository.java

@ -23,6 +23,7 @@ import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import org.thingsboard.server.dao.model.sql.NotificationTargetEntity;
import java.util.List;
import java.util.UUID;
@Repository
@ -30,4 +31,6 @@ public interface NotificationTargetRepository extends JpaRepository<Notification
Page<NotificationTargetEntity> findByTenantIdAndNameContainingIgnoreCase(UUID tenantId, String searchText, Pageable pageable);
List<NotificationTargetEntity> findByTenantIdAndIdIn(UUID tenantId, List<UUID> ids);
}

1
dao/src/main/resources/sql/schema-entities.sql

@ -784,6 +784,7 @@ CREATE TABLE IF NOT EXISTS notification_target (
created_time BIGINT NOT NULL,
tenant_id UUID NULL CONSTRAINT fk_notification_target_tenant_id REFERENCES tenant(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
type VARCHAR(32) NOT NULL,
configuration VARCHAR(10000) NOT NULL,
CONSTRAINT uq_notification_target_name UNIQUE (tenant_id, name)
);

7
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/slack/SlackService.java

@ -16,7 +16,8 @@
package org.thingsboard.rule.engine.api.slack;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.template.SlackConversation;
import org.thingsboard.server.common.data.notification.targets.slack.SlackConversation;
import org.thingsboard.server.common.data.notification.targets.slack.SlackConversationType;
import java.util.List;
@ -24,9 +25,9 @@ public interface SlackService {
void sendMessage(TenantId tenantId, String token, String conversationId, String message);
List<SlackConversation> listConversations(TenantId tenantId, String token, SlackConversation.Type conversationType);
List<SlackConversation> listConversations(TenantId tenantId, String token, SlackConversationType conversationType);
SlackConversation findConversation(TenantId tenantId, String token, SlackConversation.Type conversationType, String namePattern);
SlackConversation findConversation(TenantId tenantId, String token, SlackConversationType conversationType, String namePattern);
String getToken(TenantId tenantId);

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbSlackNode.java

@ -23,7 +23,7 @@ import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNode;
import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.server.common.data.notification.template.SlackConversation;
import org.thingsboard.server.common.data.notification.targets.slack.SlackConversation;
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg;

6
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbSlackNodeConfiguration.java

@ -17,7 +17,7 @@ package org.thingsboard.rule.engine.notification;
import lombok.Data;
import org.thingsboard.rule.engine.api.NodeConfiguration;
import org.thingsboard.server.common.data.notification.template.SlackConversation;
import org.thingsboard.server.common.data.notification.targets.slack.SlackConversationType;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
@ -31,7 +31,7 @@ public class TbSlackNodeConfiguration implements NodeConfiguration<TbSlackNodeCo
private String messageTemplate;
@NotNull
private SlackConversation.Type conversationType;
private SlackConversationType conversationType;
private String conversationId; // if not set, need to specify conversationNamePattern
private String conversationNamePattern;
@ -40,7 +40,7 @@ public class TbSlackNodeConfiguration implements NodeConfiguration<TbSlackNodeCo
TbSlackNodeConfiguration config = new TbSlackNodeConfiguration();
config.setBotToken("xoxb-");
config.setMessageTemplate("Device ${deviceId}: temperature is $[temperature]");
config.setConversationType(SlackConversation.Type.PUBLIC_CHANNEL);
config.setConversationType(SlackConversationType.PUBLIC_CHANNEL);
return config;
}

3
ui-ngx/src/app/core/http/entity.service.ts

@ -251,6 +251,9 @@ export class EntityService {
(id) => this.widgetService.getWidgetsBundle(id, config),
entityIds);
break;
case EntityType.NOTIFICATION_TARGET:
observable = of([]);
break;
}
return observable;
}

1
ui-ngx/src/app/core/ws/notification-websocket.service.ts

@ -161,6 +161,7 @@ export class NotificationWebsocketService implements NotificationWsService {
private publishCommands() {
while (this.isOpened && this.cmdsWrapper.hasCommands()) {
this.dataStream.next(this.cmdsWrapper.preparePublishCommands());
this.cmdsWrapper.clear();
this.checkToClose();
}
this.tryOpenSocket();

129
ui-ngx/src/app/modules/home/pages/notification-center/notification-center.component.html

@ -16,92 +16,47 @@
-->
<div class="tb-notification-center">
<mat-drawer-container class="tb-absolute-fill tb-notification-draw-container">
<div class="tb-notification-center-content">
<div class="tabs-container">
<mat-tab-group #matTabGroup (selectedIndexChange)="updateData()">
<mat-tab label="{{ 'notification.inbox' | translate }}">
<tb-notification-table
[notificationType]="entityType.NOTIFICATION"
(toggleSettings)="toggleNotificationSettings()">
</tb-notification-table>
</mat-tab>
<mat-tab label="{{ 'notification.sent' | translate }}" #requestTab="matTab">
<tb-notification-table
#notificationRequest
[notificationType]="entityType.NOTIFICATION_REQUEST">
</tb-notification-table>
</mat-tab>
<mat-tab label="{{ 'notification.templates' | translate }}">
<tb-notification-table
[notificationType]="entityType.NOTIFICATION_TEMPLATE">
</tb-notification-table>
</mat-tab>
<mat-tab label="{{ 'notification.targets' | translate }}">
<tb-notification-table
[notificationType]="entityType.NOTIFICATION_TARGET">
</tb-notification-table>
</mat-tab>
<mat-tab label="{{ 'notification.rules' | translate }}">
<tb-notification-table
[notificationType]="entityType.NOTIFICATION_RULE">
</tb-notification-table>
</mat-tab>
<mat-tab disabled>
<ng-template mat-tab-label>
<div fxFlex="100" fxLayout="row" fxLayoutAlign="end center">
<button mat-flat-button color="primary" (click)="sendNotification($event)">
<div fxLayout="row" fxLayoutAlign="start center">
<mat-icon>send</mat-icon>{{ 'notification.send-notification' | translate }}
</div>
</button>
</div>
</ng-template>
</mat-tab>
</mat-tab-group>
</div>
<div class="tb-notification-center-content">
<div class="tabs-container">
<mat-tab-group #matTabGroup (selectedIndexChange)="updateData()">
<mat-tab label="{{ 'notification.inbox' | translate }}">
<tb-notification-table
[notificationType]="entityType.NOTIFICATION">
</tb-notification-table>
</mat-tab>
<mat-tab label="{{ 'notification.sent' | translate }}" #requestTab="matTab">
<tb-notification-table
#notificationRequest
[notificationType]="entityType.NOTIFICATION_REQUEST">
</tb-notification-table>
</mat-tab>
<mat-tab label="{{ 'notification.templates' | translate }}">
<tb-notification-table
[notificationType]="entityType.NOTIFICATION_TEMPLATE">
</tb-notification-table>
</mat-tab>
<mat-tab label="{{ 'notification.targets' | translate }}">
<tb-notification-table
[notificationType]="entityType.NOTIFICATION_TARGET">
</tb-notification-table>
</mat-tab>
<mat-tab label="{{ 'notification.rules' | translate }}">
<tb-notification-table
[notificationType]="entityType.NOTIFICATION_RULE">
</tb-notification-table>
</mat-tab>
<mat-tab disabled>
<ng-template mat-tab-label>
<div fxFlex="100" fxLayout="row" fxLayoutAlign="end center">
<button mat-flat-button color="primary" (click)="sendNotification($event)">
<div fxLayout="row" fxLayoutAlign="start center">
<mat-icon>send</mat-icon>{{ 'notification.send-notification' | translate }}
</div>
</button>
</div>
</ng-template>
</mat-tab>
</mat-tab-group>
</div>
<mat-drawer mode="over"
#notificationSettings
position="end"
class="notification-settings">
<div>
<div fxLayout="row" fxLayoutAlign="start center">
<button mat-icon-button (click)="toggleNotificationSettings()">
<mat-icon>chevron_left</mat-icon>
</button>
<h6 class="title">Notification settings</h6>
</div>
<div style="max-width: 450px">
<fieldset class="fields-group">
<legend class="group-title">Current user allow delivery method</legend>
<form [formGroup]="notificationSettingsForm">
<section formGroupName="deliveryMethodsConfigs">
<section *ngFor="let deliveryMethods of notificationDeliveryMethods" [formGroupName]="deliveryMethods">
<mat-slide-toggle class="delivery-method" formControlName="enabled">
{{ notificationDeliveryMethodTranslateMap.get(deliveryMethods) | translate }}
</mat-slide-toggle>
</section>
<section formGroupName="SLACK">
<mat-form-field class="mat-block">
<mat-label>Slack bot token</mat-label>
<input matInput formControlName="botToken" required>
<mat-error *ngIf="notificationSettingsForm.get('deliveryMethodsConfigs.SLACK.botToken').hasError('required')">
{{ 'notification.link-required' | translate }}
</mat-error>
</mat-form-field>
</section>
</section>
</form>
</fieldset>
<button mat-raised-button
[disabled]="notificationSettingsForm.pristine || notificationSettingsForm.invalid"
(click)="saveNotificationSettigs($event)"
color="primary">
Save
</button>
</div>
</div>
</mat-drawer>
</mat-drawer-container>
</div>
</div>

71
ui-ngx/src/app/modules/home/pages/notification-center/notification-center.component.scss

@ -20,14 +20,11 @@
height: 100%;
display: block;
.tb-notification-center {
padding: 8px;
width: 100%;
height: 100%;
display: block;
.tb-notification-draw-container {
margin: 8px;
}
.tb-notification-center-content {
width: 100%;
height: 100%;
@ -58,34 +55,52 @@
flex-direction: column;
flex: 1 1 100%;
}
}
.notification-settings {
width: 100%;
padding: 24px;
.title {
margin: 0;
line-height: 1.4rem;
}
.fields-group {
padding: 8px 16px 0;
margin: 10px 0;
border: 1px groove rgba(0, 0, 0, .25);
border-radius: 4px;
legend {
color: rgba(0, 0, 0, .7);
}
.delivery-method {
margin-bottom: 8px;
}
}
//.mat-toolbar-tools{
// min-height: auto;
//}
//
//.title-container{
// overflow: hidden;
//}
//
//.tb-entity-table-title {
// padding-right: 20px;
// white-space: nowrap;
// overflow: hidden;
// text-overflow: ellipsis;
//}
//
//.table-container {
// overflow: auto;
//}
//
//.tb-entity-table-info{
// white-space: nowrap;
// overflow: hidden;
// text-overflow: ellipsis;
//}
//
//.button-widget-action{
// margin-left: auto;
// overflow: hidden;
// text-overflow: ellipsis;
//}
}
}
//@media #{$mat-xs} {
// .mat-toolbar {
// height: auto;
// min-height: 100px;
//
// .tb-entity-table-title{
// padding-bottom: 5px;
// width: 100%;
// }
// }
//}
@media #{$mat-xs} {
.tb-notification-center {
.tb-notification-center-content {

65
ui-ngx/src/app/modules/home/pages/notification-center/notification-center.component.ts

@ -14,7 +14,7 @@
/// limitations under the License.
///
import { AfterViewInit, Component, QueryList, ViewChild, ViewChildren } from '@angular/core';
import { Component, QueryList, ViewChild, ViewChildren } from '@angular/core';
import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
@ -23,30 +23,16 @@ import {
NotificationTableComponent
} from '@home/pages/notification-center/notification-table/notification-table.component';
import { EntityType } from '@shared/models/entity-type.models';
import { MatDrawer } from '@angular/material/sidenav';
import { NotificationService } from '@core/http/notification.service';
import {
NotificationDeliveryMethod,
NotificationDeliveryMethodTranslateMap,
NotificationTemplateTypeTranslateMap
} from '@shared/models/notification.models';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { deepTrim } from '@core/utils';
@Component({
selector: 'tb-notification-center',
templateUrl: './notification-center.component.html',
styleUrls: ['notification-center.component.scss']
})
export class NotificationCenterComponent extends PageComponent implements AfterViewInit {
export class NotificationCenterComponent extends PageComponent {
entityType = EntityType;
notificationSettingsForm: FormGroup;
notificationDeliveryMethods = Object.keys(NotificationDeliveryMethod) as NotificationDeliveryMethod[];
notificationDeliveryMethodTranslateMap = NotificationDeliveryMethodTranslateMap;
@ViewChild('notificationSettings', {static: true}) notificationSettings!: MatDrawer;
@ViewChild('matTabGroup', {static: true}) matTabs: MatTabGroup;
@ViewChild('requestTab', {static: true}) requestTab: MatTab;
@ViewChild('notificationRequest', {static: true}) notificationRequestTable: NotificationTableComponent;
@ -54,38 +40,8 @@ export class NotificationCenterComponent extends PageComponent implements AfterV
constructor(
protected store: Store<AppState>,
private notificationService: NotificationService,
private fb: FormBuilder) {
protected store: Store<AppState>) {
super(store);
this.notificationSettingsForm = this.fb.group({
deliveryMethodsConfigs: this.fb.group({})
});
this.notificationDeliveryMethods.forEach(method => {
(this.notificationSettingsForm.get('deliveryMethodsConfigs') as FormGroup)
.addControl(method, this.fb.group({method, enabled: method !== NotificationDeliveryMethod.SLACK}), {emitEvent: false});
});
(this.notificationSettingsForm.get('deliveryMethodsConfigs.SLACK') as FormGroup)
.addControl('botToken', this.fb.control({value: '', disabled: true}, Validators.required), {emitEvent: false});
this.notificationSettingsForm.get('deliveryMethodsConfigs.SLACK.enabled').valueChanges.subscribe(value => {
if (value) {
this.notificationSettingsForm.get('deliveryMethodsConfigs.SLACK.botToken').enable({emitEvent: true});
} else {
this.notificationSettingsForm.get('deliveryMethodsConfigs.SLACK.botToken').disable({emitEvent: true});
}
});
}
ngAfterViewInit() {
this.notificationSettings.openedStart.subscribe(() => {
this.notificationService.getNotificationSettings().subscribe(
value => {
this.notificationSettingsForm.patchValue(value, {emitEvent: false});
this.notificationSettingsForm.get('deliveryMethodsConfigs.SLACK.enabled').updateValueAndValidity({onlySelf: true});
}
);
});
}
updateData() {
@ -103,19 +59,4 @@ export class NotificationCenterComponent extends PageComponent implements AfterV
sendNotification($event: Event) {
this.notificationRequestTable.entityTableConfig.onEntityAction({event: $event, action: this.requestTab.isActive ? 'add' : 'add-without-update', entity: null});
}
toggleNotificationSettings() {
this.notificationSettings.toggle().then(() => {});
}
saveNotificationSettigs($event: Event) {
if ($event) {
$event.stopPropagation();
}
const formValue = deepTrim(this.notificationSettingsForm.getRawValue());
this.notificationService.saveNotificationSettings(formValue).subscribe(value => {
this.notificationSettingsForm.patchValue(value, {emitEvent: false});
this.notificationSettingsForm.markAsPristine();
});
}
}

31
ui-ngx/src/app/modules/home/pages/notification-center/notification-table/inbox-table-config.ts

@ -32,14 +32,12 @@ import { NotificationService } from '@core/http/notification.service';
import { InboxTableHeaderComponent } from '@home/pages/notification-center/inbox-table/inbox-table-header.component';
import { TranslateService } from '@ngx-translate/core';
import { take } from 'rxjs/operators';
import { EventEmitter } from '@angular/core';
export class InboxTableConfig extends EntityTableConfig<Notification> {
constructor(private notificationService: NotificationService,
private translate: TranslateService,
private datePipe: DatePipe,
private toggleSettings: EventEmitter<void>) {
private datePipe: DatePipe) {
super();
this.entitiesDeleteEnabled = false;
this.entityTranslations = {
@ -60,20 +58,12 @@ export class InboxTableConfig extends EntityTableConfig<Notification> {
this.headerComponent = InboxTableHeaderComponent;
this.headerActionDescriptors = [
{
name: 'Notification setting',
icon: 'settings',
isEnabled: () => true,
onAction: $event => this.togleNotificationSettings($event)
},
{
name: this.translate.instant('notification.mark-all-as-read'),
icon: 'done_all',
isEnabled: () => true,
onAction: $event => this.markAllRead($event)
}
];
this.headerActionDescriptors = [{
name: this.translate.instant('notification.mark-all-as-read'),
icon: 'done_all',
isEnabled: () => true,
onAction: $event => this.markAllRead($event)
}];
this.columns.push(
new DateEntityTableColumn<Notification>('createdTime', 'notification.created-time', this.datePipe, '150px'),
@ -107,13 +97,6 @@ export class InboxTableConfig extends EntityTableConfig<Notification> {
});
}
private togleNotificationSettings($event: Event) {
if ($event) {
$event.stopPropagation();
}
this.toggleSettings.emit();
}
private markAsRead($event, entity){
if ($event) {
$event.stopPropagation();

8
ui-ngx/src/app/modules/home/pages/notification-center/notification-table/notification-table.component.ts

@ -14,7 +14,7 @@
/// limitations under the License.
///
import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';
import { Component, Input, OnInit, ViewChild } from '@angular/core';
import { EntitiesTableComponent } from '@home/components/entity/entities-table.component';
import { NotificationService } from '@core/http/notification.service';
import { TargetsTableConfig } from '@home/pages/notification-center/notification-table/targets-table-config';
@ -37,9 +37,6 @@ export class NotificationTableComponent implements OnInit {
@Input()
notificationType = EntityType.NOTIFICATION;
@Output()
toggleSettings: EventEmitter<void> = new EventEmitter<void>();
@ViewChild(EntitiesTableComponent, {static: true}) entitiesTable: EntitiesTableComponent;
entityTableConfig: EntityTableConfig<any>;
@ -74,8 +71,7 @@ export class NotificationTableComponent implements OnInit {
return new InboxTableConfig(
this.notificationService,
this.translate,
this.datePipe,
this.toggleSettings
this.datePipe
);
case EntityType.NOTIFICATION_TEMPLATE:
return new TemplateTableConfig(

7
ui-ngx/src/app/modules/home/pages/notification-center/notification-table/request-table-config.ts

@ -81,7 +81,12 @@ export class RequestTableConfig extends EntityTableConfig<NotificationRequest, P
}
private configureCellActions(): Array<CellActionDescriptor<NotificationRequestInfo>> {
return [];
return [{
name: this.translate.instant('notification.notify-again'),
mdiIcon: 'mdi:repeat-variant',
isEnabled: (request) => request.status !== NotificationRequestStatus.SCHEDULED,
onAction: ($event, entity) => this.createRequest($event, entity)
}];
}
private createRequest($event: Event, request: NotificationRequest, isAdd = false, updateData = true) {

22
ui-ngx/src/app/modules/home/pages/notification-center/notification-table/template-table-config.ts

@ -21,7 +21,7 @@ import {
} from '@home/models/entity/entities-table-config.models';
import { EntityTypeResource } from '@shared/models/entity-type.models';
import { Direction } from '@shared/models/page/sort-order';
import { NotificationTemplate } from '@shared/models/notification.models';
import { NotificationTemplate, NotificationTemplateTypeTranslateMap } from '@shared/models/notification.models';
import { NotificationService } from '@core/http/notification.service';
import { EntityAction } from '@home/models/entity/entity-component.models';
import { MatDialog } from '@angular/material/dialog';
@ -61,7 +61,8 @@ export class TemplateTableConfig extends EntityTableConfig<NotificationTemplate>
this.defaultSortOrder = {property: 'notificationType', direction: Direction.ASC};
this.columns.push(
new EntityTableColumn<NotificationTemplate>('notificationType', 'notification.type', '15%'),
new EntityTableColumn<NotificationTemplate>('notificationType', 'notification.type', '15%',
(template) => this.translate.instant(NotificationTemplateTypeTranslateMap.get(template.notificationType).name)),
new EntityTableColumn<NotificationTemplate>('name', 'notification.template', '25%'),
new EntityTableColumn<NotificationTemplate>('configuration.notificationSubject', 'notification.subject', '25%',
(template) => template.configuration.notificationSubject, () => ({}), false),
@ -71,10 +72,22 @@ export class TemplateTableConfig extends EntityTableConfig<NotificationTemplate>
}
private configureCellActions(): Array<CellActionDescriptor<NotificationTemplate>> {
return [];
return [
{
name: this.translate.instant('notification.copy-template'),
icon: 'content_copy',
isEnabled: () => true,
onAction: ($event, entity) => this.editTemplate($event, entity, false, true)
}, {
name: this.translate.instant('notification.edit-template'),
icon: 'edit',
isEnabled: () => true,
onAction: ($event, entity) => this.editTemplate($event, entity)
}
];
}
private editTemplate($event: Event, template: NotificationTemplate, isAdd = false) {
private editTemplate($event: Event, template: NotificationTemplate, isAdd = false, isCopy = false) {
if ($event) {
$event.stopPropagation();
}
@ -84,6 +97,7 @@ export class TemplateTableConfig extends EntityTableConfig<NotificationTemplate>
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
isAdd,
isCopy,
template
}
}).afterClosed()

2
ui-ngx/src/app/modules/home/pages/notification-center/request-table/request-notification-dialog.component.html

@ -16,7 +16,7 @@
-->
<mat-toolbar color="primary">
<h2 translate>notification.new-notification</h2>
<h2>{{ dialogTitle | translate }}</h2>
<span fxFlex></span>
<button mat-icon-button
(click)="cancel()"

15
ui-ngx/src/app/modules/home/pages/notification-center/request-table/request-notification-dialog.componet.ts

@ -23,7 +23,7 @@ import { Router } from '@angular/router';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { NotificationService } from '@core/http/notification.service';
import { deepTrim, isDefined } from '@core/utils';
import { deepTrim } from '@core/utils';
import { Observable, Subject } from 'rxjs';
import { EntityType } from '@shared/models/entity-type.models';
import { BreakpointObserver } from '@angular/cdk/layout';
@ -55,6 +55,8 @@ export class RequestNotificationDialogComponent extends
selectedIndex = 0;
preview: NotificationRequestPreview = null;
dialogTitle = 'notification.notify-again';
private readonly destroy$ = new Subject<void>();
constructor(protected store: Store<AppState>,
@ -66,10 +68,6 @@ export class RequestNotificationDialogComponent extends
private notificationService: NotificationService) {
super(store, router, dialogRef);
if (isDefined(data.isAdd)) {
this.isAdd = data.isAdd;
}
this.stepperOrientation = this.breakpointObserver.observe(MediaBreakpoints['gt-xs'])
.pipe(map(({matches}) => matches ? 'horizontal' : 'vertical'));
@ -77,6 +75,13 @@ export class RequestNotificationDialogComponent extends
templateId: [null, Validators.required],
targets: [null, Validators.required]
});
if (data.isAdd) {
this.dialogTitle = 'notification.new-notification';
}
if (data.request) {
this.notificationRequestForm.patchValue(this.data.request, {emitEvent: false});
}
}
ngOnDestroy() {

2
ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-notification-dialog.component.html

@ -28,7 +28,7 @@
</mat-progress-bar>
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
<div mat-dialog-content>
<mat-horizontal-stepper [linear]="true" labelPosition="end" #addNotificationTemplate [orientation]="(stepperOrientation | async)"
<mat-horizontal-stepper [linear]="true" labelPosition="end" #notificationTemplateStepper [orientation]="(stepperOrientation | async)"
(selectionChange)="changeStep($event)">
<ng-template matStepperIcon="edit">
<mat-icon>check</mat-icon>

140
ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-notification-dialog.component.ts

@ -31,13 +31,14 @@ import { Router } from '@angular/router';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { FormBuilder, FormGroup, ValidationErrors, Validators } from '@angular/forms';
import { NotificationService } from '@core/http/notification.service';
import { deepTrim, isDefined } from '@core/utils';
import { deepClone, deepTrim } from '@core/utils';
import { Observable, Subject } from 'rxjs';
import { map, takeUntil } from 'rxjs/operators';
import { StepperOrientation, StepperSelectionEvent } from '@angular/cdk/stepper';
import { MatStepper } from '@angular/material/stepper';
import { BreakpointObserver } from '@angular/cdk/layout';
import { MediaBreakpoints } from '@shared/models/constants';
import { TranslateService } from '@ngx-translate/core';
export interface TemplateNotificationDialogData {
template?: NotificationTemplate;
@ -53,7 +54,7 @@ export interface TemplateNotificationDialogData {
export class TemplateNotificationDialogComponent
extends DialogComponent<TemplateNotificationDialogComponent, NotificationTemplate> implements OnDestroy {
@ViewChild('addNotificationTemplate', {static: true}) addNotificationTemplate: MatStepper;
@ViewChild('notificationTemplateStepper', {static: true}) notificationTemplateStepper: MatStepper;
stepperOrientation: Observable<StepperOrientation>;
@ -63,7 +64,6 @@ export class TemplateNotificationDialogComponent
smsTemplateForm: FormGroup;
slackTemplateForm: FormGroup;
dialogTitle = 'notification.edit-notification-template';
saveButtonLabel = 'action.save';
notificationTypes = Object.keys(NotificationType) as NotificationType[];
slackChanelTypes = Object.keys(SlackChanelType) as SlackChanelType[];
@ -74,40 +74,33 @@ export class TemplateNotificationDialogComponent
selectedIndex = 0;
tinyMceOptions: Record<string, any>;
tinyMceOptions: Record<string, any> = {
base_url: '/assets/tinymce',
suffix: '.min',
plugins: ['link table image imagetools code fullscreen'],
menubar: 'edit insert tools view format table',
toolbar: 'fontselect fontsizeselect | formatselect | bold italic strikethrough forecolor backcolor ' +
'| link | table | image | alignleft aligncenter alignright alignjustify ' +
'| numlist bullist outdent indent | removeformat | code | fullscreen',
height: 400,
autofocus: false,
branding: false
};
private readonly destroy$ = new Subject<void>();
private readonly templateNotification: NotificationTemplate;
private deliveryMethodFormsMap: Map<NotificationDeliveryMethod, FormGroup>;
constructor(protected store: Store<AppState>,
protected router: Router,
protected dialogRef: MatDialogRef<TemplateNotificationDialogComponent, NotificationTemplate>,
@Inject(MAT_DIALOG_DATA) public data: TemplateNotificationDialogData,
private breakpointObserver: BreakpointObserver,
private fb: FormBuilder,
private notificationService: NotificationService) {
private notificationService: NotificationService,
private translate: TranslateService) {
super(store, router, dialogRef);
if (data.isAdd) {
this.dialogTitle = 'notification.add-notification-template';
this.saveButtonLabel = 'action.add';
}
if (data.isCopy) {
this.dialogTitle = 'notification.copy-notification-template';
}
this.tinyMceOptions = {
base_url: '/assets/tinymce',
suffix: '.min',
plugins: ['link table image imagetools code fullscreen'],
menubar: 'edit insert tools view format table',
toolbar: 'fontselect fontsizeselect | formatselect | bold italic strikethrough forecolor backcolor ' +
'| link | table | image | alignleft aligncenter alignright alignjustify ' +
'| numlist bullist outdent indent | removeformat | code | fullscreen',
height: 400,
autofocus: false,
branding: false
};
this.stepperOrientation = this.breakpointObserver.observe(MediaBreakpoints['gt-xs'])
.pipe(map(({matches}) => matches ? 'horizontal' : 'vertical'));
@ -122,7 +115,7 @@ export class TemplateNotificationDialogComponent
});
this.notificationDeliveryMethods.forEach(method => {
(this.templateNotificationForm.get('configuration.deliveryMethodsTemplates') as FormGroup)
.addControl(method, this.fb.group({method, enabled: method === NotificationDeliveryMethod.PUSH}), {emitEvent: false});
.addControl(method, this.fb.group({enabled: method === NotificationDeliveryMethod.PUSH}), {emitEvent: false});
});
this.pushTemplateForm = this.fb.group({
@ -179,6 +172,30 @@ export class TemplateNotificationDialogComponent
conversationId: ['', Validators.required],
conversationType: [SlackChanelType.DIRECT]
});
this.deliveryMethodFormsMap = new Map<NotificationDeliveryMethod, FormGroup>([
[NotificationDeliveryMethod.PUSH, this.pushTemplateForm],
[NotificationDeliveryMethod.EMAIL, this.emailTemplateForm],
[NotificationDeliveryMethod.SMS, this.smsTemplateForm],
[NotificationDeliveryMethod.SLACK, this.slackTemplateForm]
]);
if (data.isAdd || data.isCopy) {
this.dialogTitle = 'notification.add-notification-template';
}
this.templateNotification = deepClone(this.data.template);
if (this.templateNotification) {
if (this.data.isCopy) {
this.templateNotification.name += ` (${this.translate.instant('action.copy')})`;
}
this.templateNotificationForm.reset({}, {emitEvent: false});
this.templateNotificationForm.patchValue(this.templateNotification, {emitEvent: false});
for (const method in this.templateNotification.configuration.deliveryMethodsTemplates) {
this.deliveryMethodFormsMap.get(NotificationDeliveryMethod[method])
.patchValue(this.templateNotification.configuration.deliveryMethodsTemplates[method]);
}
}
}
ngOnDestroy() {
@ -191,86 +208,61 @@ export class TemplateNotificationDialogComponent
this.dialogRef.close(null);
}
save() {
let formValue = deepTrim(this.templateNotificationForm.value);
if (isDefined(this.data.template)) {
formValue = Object.assign({}, this.data.template, formValue);
}
this.notificationService.saveNotificationTemplate(formValue).subscribe(
(target) => this.dialogRef.close(target)
);
}
changeStep($event: StepperSelectionEvent) {
this.selectedIndex = $event.selectedIndex;
}
backStep() {
this.addNotificationTemplate.previous();
this.notificationTemplateStepper.previous();
}
nextStep() {
if (this.selectedIndex >= this.maxStepperIndex) {
this.add();
} else {
this.addNotificationTemplate.next();
this.notificationTemplateStepper.next();
}
}
nextStepLabel(): string {
if (this.selectedIndex === 1 && this.selectedIndex < this.maxStepperIndex && this.pushTemplateForm.pristine) {
return 'action.skip';
}
if (this.selectedIndex === 2 && this.selectedIndex < this.maxStepperIndex && this.emailTemplateForm.pristine) {
return 'action.skip';
}
if (this.selectedIndex === 3 && this.selectedIndex < this.maxStepperIndex && this.smsTemplateForm.pristine) {
return 'action.skip';
}
if (this.selectedIndex !== 0 && this.selectedIndex >= this.maxStepperIndex) {
return 'action.add';
if (this.selectedIndex !== 0) {
if (this.selectedIndex >= this.maxStepperIndex) {
return (this.data.isAdd || this.data.isCopy) ? 'action.add' : 'action.save';
} else if (this.notificationTemplateStepper.selected.stepControl.pristine) {
return 'action.skip';
}
}
return 'action.next';
}
private get maxStepperIndex(): number {
return this.addNotificationTemplate?._steps?.length - 1;
return this.notificationTemplateStepper?._steps?.length - 1;
}
private add(): void {
if (this.allValid()) {
const formValue: NotificationTemplate = this.templateNotificationForm.value;
if (formValue.configuration.deliveryMethodsTemplates.PUSH.enabled) {
Object.assign(formValue.configuration.deliveryMethodsTemplates.PUSH, this.pushTemplateForm.value);
} else {
delete formValue.configuration.deliveryMethodsTemplates.PUSH;
}
if (formValue.configuration.deliveryMethodsTemplates.EMAIL.enabled) {
Object.assign(formValue.configuration.deliveryMethodsTemplates.EMAIL, this.emailTemplateForm.value);
} else {
delete formValue.configuration.deliveryMethodsTemplates.EMAIL;
}
if (formValue.configuration.deliveryMethodsTemplates.SMS.enabled) {
Object.assign(formValue.configuration.deliveryMethodsTemplates.SMS, this.smsTemplateForm.value);
} else {
delete formValue.configuration.deliveryMethodsTemplates.SMS;
}
if (formValue.configuration.deliveryMethodsTemplates.SLACK.enabled) {
Object.assign(formValue.configuration.deliveryMethodsTemplates.SLACK, this.slackTemplateForm.value);
} else {
delete formValue.configuration.deliveryMethodsTemplates.SLACK;
let template: NotificationTemplate = this.templateNotificationForm.value;
this.notificationDeliveryMethods.forEach(method => {
if (template.configuration.deliveryMethodsTemplates[method].enabled) {
Object.assign(template.configuration.deliveryMethodsTemplates[method], this.deliveryMethodFormsMap.get(method).value, {method});
} else {
delete template.configuration.deliveryMethodsTemplates[method];
}
});
if (this.templateNotification) {
template = {...this.templateNotification, ...template};
}
this.notificationService.saveNotificationTemplate(deepTrim(formValue)).subscribe(
this.notificationService.saveNotificationTemplate(deepTrim(template)).subscribe(
(target) => this.dialogRef.close(target)
);
}
}
private allValid(): boolean {
return !this.addNotificationTemplate.steps.find((item, index) => {
return !this.notificationTemplateStepper.steps.find((item, index) => {
if (item.stepControl.invalid) {
item.interacted = true;
this.addNotificationTemplate.selectedIndex = index;
this.notificationTemplateStepper.selectedIndex = index;
return true;
} else {
return false;

7
ui-ngx/src/app/shared/models/notification-ws.models.ts

@ -236,14 +236,13 @@ export class NotificationPluginCmdsWrapper {
unsubCmd: UnsubscribeCmd;
markAsReadCmd: MarkAsReadCmd;
markAllAsReadCmd: MarkAllAsReadCmd;
#hasSend = false;
public hasCommands(): boolean {
return !this.#hasSend && (isDefinedAndNotNull(this.unreadCountSubCmd) ||
return isDefinedAndNotNull(this.unreadCountSubCmd) ||
isDefinedAndNotNull(this.unreadSubCmd) ||
isDefinedAndNotNull(this.unsubCmd) ||
isDefinedAndNotNull(this.markAsReadCmd) ||
isDefinedAndNotNull(this.markAllAsReadCmd));
isDefinedAndNotNull(this.markAllAsReadCmd);
}
public clear() {
@ -252,7 +251,6 @@ export class NotificationPluginCmdsWrapper {
this.unsubCmd = null;
this.markAsReadCmd = null;
this.markAllAsReadCmd = null;
this.#hasSend = false;
}
public preparePublishCommands(): NotificationPluginCmdsWrapper {
@ -262,7 +260,6 @@ export class NotificationPluginCmdsWrapper {
preparedWrapper.unsubCmd = this.unsubCmd || undefined;
preparedWrapper.markAsReadCmd = this.markAsReadCmd || undefined;
preparedWrapper.markAllAsReadCmd = this.markAllAsReadCmd || undefined;
this.#hasSend = true;
return preparedWrapper;
}
}

5
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -2691,6 +2691,7 @@
"conversation-required": "Conversation is required",
"add-rule": "Add rule",
"copy-notification-template": "Copy notification template",
"copy-template": "Copy template",
"create-target": "Create recipient",
"created-time": "Created time",
"delete-request-text": "Be careful, after the confirmation the notification request will become unrecoverable.",
@ -2700,7 +2701,6 @@
"delete-template-text": "Be careful, after the confirmation the notification template will become unrecoverable.",
"delete-template-title": "Are you sure you want to delete the notification template '{{templateName}}'?",
"delivery-method": "Delivery method",
"delivery-methods": "Delivery methods",
"delivery-method-type": {
"email": "Email",
"push": "Web",
@ -2709,9 +2709,11 @@
},
"delete-rule-text": "Be careful, after the confirmation the notification rule will become unrecoverable.",
"delete-rule-title": "Are you sure you want to delete the notification rule '{{ruleName}}'?",
"delivery-methods": "Delivery methods",
"description": "Description",
"edit-notification-target": "Edit notification recipient",
"edit-notification-template": "Edit notification template",
"edit-template": "Edit template",
"email-settings": "Email settings",
"get-customer-id-from-originator": "Get Customer id from originator",
"inbox": "Inbox",
@ -2732,6 +2734,7 @@
"notification": "Notification",
"notification-center": "Notification center",
"notification-target": "Notification recipient",
"notify-again": "Notify again",
"recipients-count": "{ count, plural, 1 {1 Recipient} other {# Recipients} }",
"requet-status": {
"processing": "Processing",

Loading…
Cancel
Save