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, created_time BIGINT NOT NULL,
tenant_id UUID NULL CONSTRAINT fk_notification_target_tenant_id REFERENCES tenant(id) ON DELETE CASCADE, tenant_id UUID NULL CONSTRAINT fk_notification_target_tenant_id REFERENCES tenant(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL,
type VARCHAR(32) NOT NULL,
configuration VARCHAR(10000) NOT NULL, configuration VARCHAR(10000) NOT NULL,
CONSTRAINT uq_notification_target_name UNIQUE (tenant_id, name) 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.NotificationRequestPreview;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings; 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.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.DeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate; import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
import org.thingsboard.server.common.data.page.PageData; 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 org.thingsboard.server.service.security.permission.Resource;
import javax.validation.Valid; import javax.validation.Valid;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
@ -198,11 +200,15 @@ public class NotificationController extends BaseController {
.settings(null) .settings(null)
.template(notificationTemplate) .template(notificationTemplate)
.build(); .build();
mockProcessingCtx.init();
Map<String, String> templateContext = mockProcessingCtx.createTemplateContext(user);
Map<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> processedTemplates = mockProcessingCtx.getDeliveryMethods().stream() Map<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> processedTemplates = mockProcessingCtx.getDeliveryMethods().stream()
.collect(Collectors.toMap(m -> m, deliveryMethod -> { .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); return mockProcessingCtx.getProcessedTemplate(deliveryMethod, templateContext);
})); }));
preview.setProcessedTemplates(processedTemplates); preview.setProcessedTemplates(processedTemplates);
@ -214,7 +220,12 @@ public class NotificationController extends BaseController {
throw new IllegalArgumentException("Notification target with id " + targetId + " not found"); 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); recipientsCountByTarget.put(notificationTarget.getName(), recipientsCount);
}); });
preview.setRecipientsCountByTarget(recipientsCountByTarget); 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.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.NotificationRuleId; 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.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.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
@ -62,21 +63,21 @@ public class NotificationRuleController extends BaseController {
@GetMapping("/rule/{id}") @GetMapping("/rule/{id}")
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @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); NotificationRuleId notificationRuleId = new NotificationRuleId(id);
return checkEntityId(notificationRuleId, notificationRuleService::findNotificationRuleById, Operation.READ); return checkEntityId(notificationRuleId, notificationRuleService::findNotificationRuleInfoById, Operation.READ);
} }
@GetMapping("/rules") @GetMapping("/rules")
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
public PageData<NotificationRule> getNotificationRules(@RequestParam int pageSize, public PageData<NotificationRuleInfo> getNotificationRules(@RequestParam int pageSize,
@RequestParam int page, @RequestParam int page,
@RequestParam(required = false) String textSearch, @RequestParam(required = false) String textSearch,
@RequestParam(required = false) String sortProperty, @RequestParam(required = false) String sortProperty,
@RequestParam(required = false) String sortOrder, @RequestParam(required = false) String sortOrder,
@AuthenticationPrincipal SecurityUser user) throws ThingsboardException { @AuthenticationPrincipal SecurityUser user) throws ThingsboardException {
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
return notificationRuleService.findNotificationRulesByTenantId(user.getTenantId(), pageLink); return notificationRuleService.findNotificationRulesInfosByTenantId(user.getTenantId(), pageLink);
} }
@DeleteMapping("/rule/{id}") @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.id.NotificationTargetId;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget; 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.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.PageData;
import org.thingsboard.server.common.data.page.PageDataIterable; import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.data.page.PageLink; 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 org.thingsboard.server.service.security.permission.Resource;
import javax.validation.Valid; import javax.validation.Valid;
import java.util.Arrays;
import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors;
import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH; 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)", @ApiOperation(value = "Save notification target (saveNotificationTarget)",
notes = "Create or update notification target.\n\n" + notes = "Create or update notification target.\n\n" +
"Examples with different configuration types:\n" + // "Examples with different configuration types:\n" +
"- USER_LIST:\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" + // "```\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" + // "- 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" + // "```\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" + // "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" + // "```\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" + // "- ALL_USERS:\n" +
"```\n{\n \"name\": \"All my users\",\n \"configuration\": {\n \"type\": \"ALL_USERS\"\n }\n}\n```\n\n" + // "```\n{\n \"name\": \"All my users\",\n \"configuration\": {\n \"type\": \"ALL_USERS\"\n }\n}\n```\n\n" +
SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@PostMapping("/target") @PostMapping("/target")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@ -77,11 +81,13 @@ public class NotificationTargetController extends BaseController {
checkEntity(notificationTarget.getId(), notificationTarget, Resource.NOTIFICATION_TARGET); checkEntity(notificationTarget.getId(), notificationTarget, Resource.NOTIFICATION_TARGET);
if (!user.isSystemAdmin()) { if (!user.isSystemAdmin()) {
NotificationTargetConfig targetConfig = notificationTarget.getConfiguration(); NotificationTargetConfig targetConfig = notificationTarget.getConfiguration();
PageDataIterable<User> recipients = new PageDataIterable<>(pageLink -> { if (targetConfig.getType() == NotificationTargetType.PLATFORM_USERS) {
return notificationTargetService.findRecipientsForNotificationTargetConfig(user.getTenantId(), null, targetConfig, pageLink); PageDataIterable<User> recipients = new PageDataIterable<>(pageLink -> {
}, 200); return notificationTargetService.findRecipientsForNotificationTargetConfig(user.getTenantId(), null, targetConfig, pageLink);
for (User recipient : recipients) { }, 200);
accessControlService.checkPermission(user, Resource.USER, Operation.READ, recipient.getId(), recipient); 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; 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)", @ApiOperation(value = "Get notification targets (getNotificationTargets)",
notes = "Fetch the page of notification targets owned by sysadmin or tenant." + notes = "Fetch the page of notification targets owned by sysadmin or tenant." +
SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) 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.NotificationType;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings; 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.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.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.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.notification.NotificationSettingsService; import org.thingsboard.server.dao.notification.NotificationSettingsService;
@ -133,7 +134,7 @@ public class NotificationTemplateController extends BaseController {
SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@GetMapping("/slack/conversations") @GetMapping("/slack/conversations")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public List<SlackConversation> listSlackConversations(@RequestParam SlackConversation.Type type, public List<SlackConversation> listSlackConversations(@RequestParam SlackConversationType type,
@AuthenticationPrincipal SecurityUser user) { @AuthenticationPrincipal SecurityUser user) {
NotificationSettings settings = notificationSettingsService.findNotificationSettings(user.getTenantId()); NotificationSettings settings = notificationSettingsService.findNotificationSettings(user.getTenantId());
SlackNotificationDeliveryMethodConfig slackConfig = (SlackNotificationDeliveryMethodConfig) 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.NotificationStatus;
import org.thingsboard.server.common.data.notification.NotificationType; 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.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.DeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate; import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
import org.thingsboard.server.common.data.notification.template.PushDeliveryMethodNotificationTemplate; 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.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; 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.NotificationRequestService;
import org.thingsboard.server.dao.notification.NotificationService; import org.thingsboard.server.dao.notification.NotificationService;
import org.thingsboard.server.dao.notification.NotificationSettingsService; import org.thingsboard.server.dao.notification.NotificationSettingsService;
@ -79,7 +83,7 @@ import java.util.stream.Collectors;
@Slf4j @Slf4j
@RequiredArgsConstructor @RequiredArgsConstructor
@SuppressWarnings({"UnstableApiUsage", "rawtypes"}) @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 NotificationTargetService notificationTargetService;
private final NotificationRequestService notificationRequestService; private final NotificationRequestService notificationRequestService;
@ -95,9 +99,17 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
@Override @Override
public NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) { public NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) {
notificationRequest.setTenantId(tenantId);
NotificationSettings settings = notificationSettingsService.findNotificationSettings(tenantId); NotificationSettings settings = notificationSettingsService.findNotificationSettings(tenantId);
NotificationTemplate notificationTemplate = notificationTemplateService.findNotificationTemplateById(tenantId, notificationRequest.getTemplateId()); 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) -> { notificationTemplate.getConfiguration().getDeliveryMethodsTemplates().forEach((deliveryMethod, template) -> {
if (!template.isEnabled()) return; if (!template.isEnabled()) return;
@ -110,6 +122,9 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
throw new IllegalArgumentException("Slack must be configured in the settings"); 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) { 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.setStatus(NotificationRequestStatus.PROCESSING);
NotificationRequest savedNotificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest); NotificationRequest savedNotificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest);
notificationExecutor.submit(() -> { NotificationProcessingContext ctx = NotificationProcessingContext.builder()
NotificationProcessingContext ctx = NotificationProcessingContext.builder() .tenantId(tenantId)
.tenantId(tenantId) .request(savedNotificationRequest)
.request(savedNotificationRequest) .settings(settings)
.settings(settings) .template(notificationTemplate)
.template(notificationTemplate) .build();
.build();
ctx.init();
Set<NotificationDeliveryMethod> deliveryMethods = ctx.getDeliveryMethods(); notificationExecutor.submit(() -> {
List<ListenableFuture<Void>> results = new ArrayList<>(); List<ListenableFuture<Void>> results = new ArrayList<>();
for (UUID targetId : notificationRequest.getTargets()) { for (NotificationTarget target : targets) {
DaoUtil.processBatches(pageLink -> { List<ListenableFuture<Void>> result = processForTarget(target, ctx);
return notificationTargetService.findRecipientsForNotificationTarget(tenantId, ctx.getCustomerId(), new NotificationTargetId(targetId), pageLink); results.addAll(result);
}, 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);
}
} }
Futures.whenAllComplete(results).run(() -> { Futures.whenAllComplete(results).run(() -> {
NotificationRequestId requestId = savedNotificationRequest.getId();
log.debug("[{}] Notification request processing is finished", requestId);
NotificationRequestStats stats = ctx.getStats(); NotificationRequestStats stats = ctx.getStats();
try { try {
notificationRequestService.updateNotificationRequest(tenantId, savedNotificationRequest.getId(), notificationRequestService.updateNotificationRequest(tenantId, requestId, NotificationRequestStatus.SENT, stats);
NotificationRequestStatus.SENT, stats);
} catch (Exception e) { } 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(); UserId senderId = notificationRequest.getSenderId();
@ -200,25 +184,70 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
return savedNotificationRequest; return savedNotificationRequest;
} }
private ListenableFuture<Void> process(NotificationChannel notificationChannel, User recipient, NotificationProcessingContext ctx) { private List<ListenableFuture<Void>> processForTarget(NotificationTarget target, NotificationProcessingContext ctx) {
NotificationDeliveryMethod deliveryMethod = notificationChannel.getDeliveryMethod(); Iterable<? extends NotificationRecipient> recipients;
if (recipient != null && ctx.getStats().contains(deliveryMethod, recipient.getId())) { 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()); return Futures.immediateFailedFuture(new AlreadySentException());
} }
Map<String, String> templateContext;
if (recipient instanceof User) {
templateContext = ctx.createTemplateContext(((User) recipient));
} else {
templateContext = Collections.emptyMap();
}
DeliveryMethodNotificationTemplate processedTemplate; DeliveryMethodNotificationTemplate processedTemplate;
try { try {
Map<String, String> templateContext = recipient != null ? ctx.createTemplateContext(recipient) : Collections.emptyMap();
processedTemplate = ctx.getProcessedTemplate(deliveryMethod, templateContext); processedTemplate = ctx.getProcessedTemplate(deliveryMethod, templateContext);
} catch (Exception e) { } catch (Exception e) {
return Futures.immediateFailedFuture(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); return notificationChannel.sendNotification(recipient, processedTemplate, ctx);
} }
@Override @Override
public ListenableFuture<Void> sendNotification(User recipient, PushDeliveryMethodNotificationTemplate processedTemplate, NotificationProcessingContext ctx) { public ListenableFuture<Void> sendNotification(User recipient, PushDeliveryMethodNotificationTemplate processedTemplate, NotificationProcessingContext ctx) {
NotificationRequest request = ctx.getRequest(); NotificationRequest request = ctx.getRequest();
log.trace("Creating notification for recipient {} (notification request id: {})", recipient.getId(), request.getId());
Notification notification = Notification.builder() Notification notification = Notification.builder()
.requestId(request.getId()) .requestId(request.getId())
.recipientId(recipient.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 @Component
@RequiredArgsConstructor @RequiredArgsConstructor
public class EmailNotificationChannel implements NotificationChannel<EmailDeliveryMethodNotificationTemplate> { public class EmailNotificationChannel implements NotificationChannel<User, EmailDeliveryMethodNotificationTemplate> {
private final MailService mailService; private final MailService mailService;
private final MailExecutorService executor; 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; package org.thingsboard.server.service.notification.channels;
import com.google.common.util.concurrent.ListenableFuture; 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.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.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(); 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 lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.rule.engine.api.slack.SlackService; 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.AlreadySentException;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; 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.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.common.data.notification.template.SlackDeliveryMethodNotificationTemplate;
import org.thingsboard.server.service.executors.ExternalCallExecutorService; import org.thingsboard.server.service.executors.ExternalCallExecutorService;
import org.thingsboard.server.common.data.notification.NotificationProcessingContext;
@Component @Component
@RequiredArgsConstructor @RequiredArgsConstructor
public class SlackNotificationChannel implements NotificationChannel<SlackDeliveryMethodNotificationTemplate> { public class SlackNotificationChannel implements NotificationChannel<SlackConversation, SlackDeliveryMethodNotificationTemplate> {
private final SlackService slackService; private final SlackService slackService;
private final ExternalCallExecutorService executor; private final ExternalCallExecutorService executor;
@Override @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)) { if (ctx.getStats().contains(NotificationDeliveryMethod.SLACK)) {
return Futures.immediateFailedFuture(new AlreadySentException()); return Futures.immediateFailedFuture(new AlreadySentException());
} }
SlackNotificationDeliveryMethodConfig config = ctx.getDeliveryMethodConfig(NotificationDeliveryMethod.SLACK); SlackNotificationDeliveryMethodConfig config = ctx.getDeliveryMethodConfig(NotificationDeliveryMethod.SLACK);
return executor.submit(() -> { 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; 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 @Component
@RequiredArgsConstructor @RequiredArgsConstructor
public class SmsNotificationChannel implements NotificationChannel<SmsDeliveryMethodNotificationTemplate> { public class SmsNotificationChannel implements NotificationChannel<User, SmsDeliveryMethodNotificationTemplate> {
private final SmsService smsService; private final SmsService smsService;
private final SmsExecutorService executor; 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 lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service; 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.rule.engine.api.slack.SlackService;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; 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.NotificationSettings;
import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig; 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.common.data.util.ThrowingBiFunction;
import org.thingsboard.server.dao.notification.NotificationSettingsService; import org.thingsboard.server.dao.notification.NotificationSettingsService;
@ -66,9 +67,9 @@ public class DefaultSlackService implements SlackService {
} }
@Override @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 -> { return cache.get(conversationType + ":" + token, k -> {
if (conversationType == SlackConversation.Type.DIRECT) { if (conversationType == SlackConversationType.DIRECT) {
UsersListRequest request = UsersListRequest.builder() UsersListRequest request = UsersListRequest.builder()
.limit(CONVERSATIONS_LIMIT) .limit(CONVERSATIONS_LIMIT)
.build(); .build();
@ -85,7 +86,7 @@ public class DefaultSlackService implements SlackService {
.collect(Collectors.toList()); .collect(Collectors.toList());
} else { } else {
ConversationsListRequest request = ConversationsListRequest.builder() ConversationsListRequest request = ConversationsListRequest.builder()
.types(List.of(conversationType == SlackConversation.Type.PUBLIC_CHANNEL ? .types(List.of(conversationType == SlackConversationType.PUBLIC_CHANNEL ?
ConversationType.PUBLIC_CHANNEL : ConversationType.PUBLIC_CHANNEL :
ConversationType.PRIVATE_CHANNEL)) ConversationType.PRIVATE_CHANNEL))
.limit(CONVERSATIONS_LIMIT) .limit(CONVERSATIONS_LIMIT)
@ -107,7 +108,7 @@ public class DefaultSlackService implements SlackService {
} }
@Override @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); List<SlackConversation> conversations = listConversations(tenantId, token, conversationType);
return conversations.stream() return conversations.stream()
.filter(conversation -> StringUtils.containsIgnoreCase(conversation.getName(), namePattern)) .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.info.UserOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings; 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.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.DeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.EmailDeliveryMethodNotificationTemplate; 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.NotificationTemplate;
@ -78,9 +81,12 @@ public abstract class AbstractNotificationApiTest extends AbstractControllerTest
NotificationTarget notificationTarget = new NotificationTarget(); NotificationTarget notificationTarget = new NotificationTarget();
notificationTarget.setTenantId(tenantId); notificationTarget.setTenantId(tenantId);
notificationTarget.setName("Users " + List.of(usersIds)); notificationTarget.setName("Users " + List.of(usersIds));
UserListNotificationTargetConfig config = new UserListNotificationTargetConfig(); notificationTarget.setType(NotificationTargetType.PLATFORM_USERS);
config.setUsersIds(DaoUtil.toUUIDs(List.of(usersIds))); PlatformUsersNotificationTargetConfig targetConfig = new PlatformUsersNotificationTargetConfig();
notificationTarget.setConfiguration(config); UserListFilter filter = new UserListFilter();
filter.setUsersIds(DaoUtil.toUUIDs(List.of(usersIds)));
targetConfig.setUsersFilter(filter);
notificationTarget.setConfiguration(targetConfig);
return saveNotificationTarget(notificationTarget); 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.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.NotificationCenter; import org.thingsboard.rule.engine.api.NotificationCenter;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.NotificationTargetId; 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.info.UserOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings; 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.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.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.DeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.EmailDeliveryMethodNotificationTemplate; 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.NotificationTemplate;
import org.thingsboard.server.common.data.notification.template.NotificationTemplateConfig; 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.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.SlackDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.SmsDeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.SmsDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
@ -370,8 +371,11 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
public void testNotificationRequestPreview() throws Exception { public void testNotificationRequestPreview() throws Exception {
NotificationTarget target1 = new NotificationTarget(); NotificationTarget target1 = new NotificationTarget();
target1.setName("Me"); target1.setName("Me");
UserListNotificationTargetConfig target1Config = new UserListNotificationTargetConfig(); target1.setType(NotificationTargetType.PLATFORM_USERS);
target1Config.setUsersIds(DaoUtil.toUUIDs(List.of(tenantAdminUserId))); PlatformUsersNotificationTargetConfig target1Config = new PlatformUsersNotificationTargetConfig();
UserListFilter userListFilter = new UserListFilter();
userListFilter.setUsersIds(DaoUtil.toUUIDs(List.of(tenantAdminUserId)));
target1Config.setUsersFilter(userListFilter);
target1.setConfiguration(target1Config); target1.setConfiguration(target1Config);
target1 = saveNotificationTarget(target1); target1 = saveNotificationTarget(target1);
@ -388,9 +392,12 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
} }
NotificationTarget target2 = new NotificationTarget(); NotificationTarget target2 = new NotificationTarget();
target2.setName("Other customer users"); target2.setName("Other customer users");
CustomerUsersNotificationTargetConfig target2Config = new CustomerUsersNotificationTargetConfig(); target2.setType(NotificationTargetType.PLATFORM_USERS);
target2Config.setCustomerId(differentCustomerId.getId()); PlatformUsersNotificationTargetConfig target2Config = new PlatformUsersNotificationTargetConfig();
target2Config.setGetCustomerIdFromOriginatorEntity(false); CustomerUsersFilter customerUsersFilter = new CustomerUsersFilter();
customerUsersFilter.setCustomerId(differentCustomerId.getId());
customerUsersFilter.setGetCustomerIdFromOriginatorEntity(false);
target2Config.setUsersFilter(customerUsersFilter);
target2.setConfiguration(target2Config); target2.setConfiguration(target2Config);
target2 = saveNotificationTarget(target2); target2 = saveNotificationTarget(target2);
@ -426,8 +433,6 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
SlackDeliveryMethodNotificationTemplate slackNotificationTemplate = new SlackDeliveryMethodNotificationTemplate(); SlackDeliveryMethodNotificationTemplate slackNotificationTemplate = new SlackDeliveryMethodNotificationTemplate();
slackNotificationTemplate.setEnabled(true); slackNotificationTemplate.setEnabled(true);
slackNotificationTemplate.setConversationType(SlackConversation.Type.PUBLIC_CHANNEL);
slackNotificationTemplate.setConversationId("U1234567");
slackNotificationTemplate.setBody("Message for SLACK: ${email}"); slackNotificationTemplate.setBody("Message for SLACK: ${email}");
templates.put(NotificationDeliveryMethod.SLACK, slackNotificationTemplate); templates.put(NotificationDeliveryMethod.SLACK, slackNotificationTemplate);
@ -472,8 +477,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
assertThat(processedTemplates.get(NotificationDeliveryMethod.SLACK)).asInstanceOf(type(SlackDeliveryMethodNotificationTemplate.class)) assertThat(processedTemplates.get(NotificationDeliveryMethod.SLACK)).asInstanceOf(type(SlackDeliveryMethodNotificationTemplate.class))
.satisfies(template -> { .satisfies(template -> {
assertThat(template.getBody()) assertThat(template.getBody())
.startsWith("Message for SLACK") .isEqualTo("Message for SLACK: ${email}"); // ${email} should not be processed
.endsWith(requestorEmail);
}); });
} }
@ -507,6 +511,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
.until(() -> findNotificationRequest(notificationRequest.getId()).isSent()); .until(() -> findNotificationRequest(notificationRequest.getId()).isSent());
NotificationRequestStats stats = getStats(notificationRequest.getId()); NotificationRequestStats stats = getStats(notificationRequest.getId());
System.err.println("STATS: " + stats);
assertThat(stats.getSent().get(NotificationDeliveryMethod.PUSH)).hasValue(1); assertThat(stats.getSent().get(NotificationDeliveryMethod.PUSH)).hasValue(1);
assertThat(stats.getSent().get(NotificationDeliveryMethod.EMAIL)).hasValue(1); assertThat(stats.getSent().get(NotificationDeliveryMethod.EMAIL)).hasValue(1);
assertThat(stats.getErrors().get(NotificationDeliveryMethod.SMS)).size().isOne(); assertThat(stats.getErrors().get(NotificationDeliveryMethod.SMS)).size().isOne();
@ -529,7 +534,10 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
NotificationTarget notificationTarget = new NotificationTarget(); NotificationTarget notificationTarget = new NotificationTarget();
notificationTarget.setTenantId(tenantId); notificationTarget.setTenantId(tenantId);
notificationTarget.setName("All my users"); 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.setConfiguration(config);
notificationTarget = saveNotificationTarget(notificationTarget); notificationTarget = saveNotificationTarget(notificationTarget);
NotificationTargetId notificationTargetId = notificationTarget.getId(); NotificationTargetId notificationTargetId = notificationTarget.getId();
@ -572,21 +580,28 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
notificationTemplate.setName("Slack notification template"); notificationTemplate.setName("Slack notification template");
notificationTemplate.setNotificationType(NotificationType.GENERAL); notificationTemplate.setNotificationType(NotificationType.GENERAL);
NotificationTemplateConfig config = new NotificationTemplateConfig(); NotificationTemplateConfig config = new NotificationTemplateConfig();
config.setDefaultTextTemplate("To Slack :)"); config.setDefaultTextTemplate("To Slack :) ${email}");
SlackDeliveryMethodNotificationTemplate slackNotificationTemplate = new SlackDeliveryMethodNotificationTemplate(); SlackDeliveryMethodNotificationTemplate slackNotificationTemplate = new SlackDeliveryMethodNotificationTemplate();
slackNotificationTemplate.setEnabled(true); slackNotificationTemplate.setEnabled(true);
slackNotificationTemplate.setConversationType(SlackConversation.Type.PUBLIC_CHANNEL);
String conversationId = "U154475415";
slackNotificationTemplate.setConversationId(conversationId);
config.setDeliveryMethodsTemplates(Map.of( config.setDeliveryMethodsTemplates(Map.of(
NotificationDeliveryMethod.SLACK, slackNotificationTemplate NotificationDeliveryMethod.SLACK, slackNotificationTemplate
)); ));
notificationTemplate.setConfiguration(config); notificationTemplate.setConfiguration(config);
notificationTemplate = saveNotificationTemplate(notificationTemplate); 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) await().atMost(2, TimeUnit.SECONDS)
.until(() -> findNotificationRequest(successfulNotificationRequest.getId()).isSent()); .until(() -> findNotificationRequest(successfulNotificationRequest.getId()).isSent());
verify(slackService).sendMessage(eq(tenantId), eq(slackToken), eq(conversationId), eq(config.getDefaultTextTemplate())); verify(slackService).sendMessage(eq(tenantId), eq(slackToken), eq(conversationId), eq(config.getDefaultTextTemplate()));
@ -595,7 +610,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
String errorMessage = "Error!!!"; String errorMessage = "Error!!!";
doThrow(new RuntimeException(errorMessage)).when(slackService).sendMessage(any(), any(), any(), any()); 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) await().atMost(2, TimeUnit.SECONDS)
.until(() -> findNotificationRequest(failedNotificationRequest.getId()).isSent()); .until(() -> findNotificationRequest(failedNotificationRequest.getId()).isSent());
stats = getStats(failedNotificationRequest.getId()); 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; package org.thingsboard.server.service.notification;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.BooleanNode; import com.fasterxml.jackson.databind.node.BooleanNode;
import org.junit.Before; 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.id.NotificationRuleId;
import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; 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.NotificationType;
import org.thingsboard.server.common.data.notification.info.AlarmNotificationInfo; 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.DefaultNotificationRuleRecipientsConfig;
import org.thingsboard.server.common.data.notification.rule.EscalatedNotificationRuleRecipientsConfig; 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.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.AlarmNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.EntityActionNotificationRuleTriggerConfig; 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.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget; import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate; 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.BooleanFilterPredicate;
import org.thingsboard.server.common.data.query.EntityKeyValueType; import org.thingsboard.server.common.data.query.EntityKeyValueType;
import org.thingsboard.server.common.data.query.FilterPredicateValue; import org.thingsboard.server.common.data.query.FilterPredicateValue;
import org.thingsboard.server.common.data.security.Authority; 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.dao.service.DaoSqlTest;
import org.thingsboard.server.service.telemetry.AlarmSubscriptionService; import org.thingsboard.server.service.telemetry.AlarmSubscriptionService;
@ -79,6 +85,9 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
@SpyBean @SpyBean
private AlarmSubscriptionService alarmSubscriptionService; private AlarmSubscriptionService alarmSubscriptionService;
@SpyBean
private AlarmService alarmService;
@Before @Before
public void beforeEach() throws Exception { public void beforeEach() throws Exception {
loginTenantAdmin(); loginTenantAdmin();
@ -183,7 +192,8 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
.set("bool", BooleanNode.TRUE); .set("bool", BooleanNode.TRUE);
doPost("/api/plugins/telemetry/" + device.getId() + "/" + DataConstants.SHARED_SCOPE, attr); 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(); Alarm alarm = alarmSubscriptionService.findLatestByOriginatorAndType(tenantId, device.getId(), alarmType).get();
long ts = System.currentTimeMillis(); 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) { private DeviceProfile createDeviceProfileWithAlarmRules(NotificationRuleId notificationRuleId, String alarmType) {
DeviceProfile deviceProfile = createDeviceProfile("For notification rule test"); DeviceProfile deviceProfile = createDeviceProfile("For notification rule test");
deviceProfile.setTenantId(tenantId); deviceProfile.setTenantId(tenantId);
@ -318,4 +353,8 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
return doPost("/api/notification/rule", notificationRule, NotificationRule.class); 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.springframework.test.web.servlet.ResultMatcher;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.TenantId; 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.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.common.data.page.PageData;
import org.thingsboard.server.controller.AbstractControllerTest; import org.thingsboard.server.controller.AbstractControllerTest;
import org.thingsboard.server.dao.notification.NotificationTargetDao; import org.thingsboard.server.dao.notification.NotificationTargetDao;
@ -51,21 +53,25 @@ public class NotificationTargetApiTest extends AbstractControllerTest {
@Test @Test
public void givenInvalidNotificationTarget_whenSaving_returnValidationError() throws Exception { public void givenInvalidNotificationTarget_whenSaving_returnValidationError() throws Exception {
NotificationTarget notificationTarget = new NotificationTarget(); NotificationTarget target = new NotificationTarget();
notificationTarget.setTenantId(null); target.setTenantId(null);
notificationTarget.setName(null); target.setName(null);
notificationTarget.setConfiguration(null); target.setType(null);
target.setConfiguration(null);
String validationError = saveAndGetError(notificationTarget, status().isBadRequest()); String validationError = saveAndGetError(target, status().isBadRequest());
assertThat(validationError) assertThat(validationError)
.contains("name must not be") .contains("name must not be")
.contains("type must not be")
.contains("configuration must not be"); .contains("configuration must not be");
UserListNotificationTargetConfig userListConfig = new UserListNotificationTargetConfig(); PlatformUsersNotificationTargetConfig targetConfig = new PlatformUsersNotificationTargetConfig();
userListConfig.setUsersIds(Collections.emptyList()); UserListFilter userListFilter = new UserListFilter();
notificationTarget.setConfiguration(userListConfig); userListFilter.setUsersIds(Collections.emptyList());
targetConfig.setUsersFilter(userListFilter);
target.setConfiguration(targetConfig);
validationError = saveAndGetError(notificationTarget, status().isBadRequest()); validationError = saveAndGetError(target, status().isBadRequest());
assertThat(validationError) assertThat(validationError)
.contains("usersIds must not be"); .contains("usersIds must not be");
} }
@ -76,16 +82,18 @@ public class NotificationTargetApiTest extends AbstractControllerTest {
NotificationTarget notificationTarget = new NotificationTarget(); NotificationTarget notificationTarget = new NotificationTarget();
notificationTarget.setTenantId(differentTenantId); notificationTarget.setTenantId(differentTenantId);
notificationTarget.setName("Target 1"); notificationTarget.setName("Target 1");
notificationTarget.setType(NotificationTargetType.PLATFORM_USERS);
UserListNotificationTargetConfig userListConfig = new UserListNotificationTargetConfig(); PlatformUsersNotificationTargetConfig targetConfig = new PlatformUsersNotificationTargetConfig();
userListConfig.setUsersIds(List.of(customerUserId.getId(), tenantAdminUserId.getId())); UserListFilter userListFilter = new UserListFilter();
notificationTarget.setConfiguration(userListConfig); userListFilter.setUsersIds(List.of(customerUserId.getId(), tenantAdminUserId.getId()));
targetConfig.setUsersFilter(userListFilter);
notificationTarget.setConfiguration(targetConfig);
saveAndGetError(notificationTarget, status().isForbidden()); saveAndGetError(notificationTarget, status().isForbidden());
loginSysAdmin(); loginSysAdmin();
notificationTarget.setTenantId(TenantId.SYS_TENANT_ID); notificationTarget.setTenantId(TenantId.SYS_TENANT_ID);
notificationTarget.setConfiguration(userListConfig);
save(notificationTarget, status().isOk()); save(notificationTarget, status().isOk());
} }
@ -94,9 +102,13 @@ public class NotificationTargetApiTest extends AbstractControllerTest {
NotificationTarget notificationTarget = new NotificationTarget(); NotificationTarget notificationTarget = new NotificationTarget();
notificationTarget.setTenantId(tenantId); notificationTarget.setTenantId(tenantId);
notificationTarget.setName("Test target"); notificationTarget.setName("Test target");
CustomerUsersNotificationTargetConfig customerUsersConfig = new CustomerUsersNotificationTargetConfig(); notificationTarget.setType(NotificationTargetType.PLATFORM_USERS);
customerUsersConfig.setCustomerId(customerId.getId());
notificationTarget.setConfiguration(customerUsersConfig); PlatformUsersNotificationTargetConfig targetConfig = new PlatformUsersNotificationTargetConfig();
CustomerUsersFilter customerUsersFilter = new CustomerUsersFilter();
customerUsersFilter.setCustomerId(customerId.getId());
targetConfig.setUsersFilter(customerUsersFilter);
notificationTarget.setConfiguration(targetConfig);
List<User> recipients = getRecipients(notificationTarget); List<User> recipients = getRecipients(notificationTarget);
assertThat(recipients).size().isNotZero(); assertThat(recipients).size().isNotZero();
@ -104,8 +116,8 @@ public class NotificationTargetApiTest extends AbstractControllerTest {
assertThat(recipient.getCustomerId()).isEqualTo(customerId); assertThat(recipient.getCustomerId()).isEqualTo(customerId);
}); });
AllUsersNotificationTargetConfig allUsersConfig = new AllUsersNotificationTargetConfig(); AllUsersFilter allUsersFilter = new AllUsersFilter();
notificationTarget.setConfiguration(allUsersConfig); targetConfig.setUsersFilter(allUsersFilter);
recipients = getRecipients(notificationTarget); recipients = getRecipients(notificationTarget);
assertThat(recipients).size().isGreaterThanOrEqualTo(2); assertThat(recipients).size().isGreaterThanOrEqualTo(2);
assertThat(recipients).allSatisfy(recipient -> { assertThat(recipients).allSatisfy(recipient -> {
@ -129,8 +141,11 @@ public class NotificationTargetApiTest extends AbstractControllerTest {
createDifferentTenant(); createDifferentTenant();
NotificationTarget notificationTarget = new NotificationTarget(); NotificationTarget notificationTarget = new NotificationTarget();
notificationTarget.setName("Test 1"); notificationTarget.setName("Test 1");
notificationTarget.setType(NotificationTargetType.PLATFORM_USERS);
notificationTarget.setTenantId(differentTenantId); notificationTarget.setTenantId(differentTenantId);
notificationTarget.setConfiguration(new AllUsersNotificationTargetConfig()); PlatformUsersNotificationTargetConfig targetConfig = new PlatformUsersNotificationTargetConfig();
targetConfig.setUsersFilter(new AllUsersFilter());
notificationTarget.setConfiguration(targetConfig);
save(notificationTarget, status().isOk()); save(notificationTarget, status().isOk());
assertThat(notificationTargetDao.find(TenantId.SYS_TENANT_ID)).isNotEmpty(); 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.NotificationRuleId;
import org.thingsboard.server.common.data.id.TenantId; 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.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.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
@ -30,6 +31,10 @@ public interface NotificationRuleService {
NotificationRule findNotificationRuleById(TenantId tenantId, NotificationRuleId id); 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); PageData<NotificationRule> findNotificationRulesByTenantId(TenantId tenantId, PageLink pageLink);
List<NotificationRule> findNotificationRulesByTenantIdAndTriggerType(TenantId tenantId, NotificationRuleTriggerType triggerType); 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.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import java.util.List;
public interface NotificationTargetService { public interface NotificationTargetService {
NotificationTarget saveNotificationTarget(TenantId tenantId, NotificationTarget notificationTarget); NotificationTarget saveNotificationTarget(TenantId tenantId, NotificationTarget notificationTarget);
@ -32,6 +34,8 @@ public interface NotificationTargetService {
PageData<NotificationTarget> findNotificationTargetsByTenantId(TenantId tenantId, PageLink pageLink); 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); PageData<User> findRecipientsForNotificationTarget(TenantId tenantId, CustomerId customerId, NotificationTargetId targetId, PageLink pageLink);
int countRecipientsForNotificationTargetConfig(TenantId tenantId, NotificationTargetConfig targetConfig); 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.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId; 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.security.Authority;
import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoXss; import org.thingsboard.server.common.data.validation.NoXss;
@ApiModel @ApiModel
@EqualsAndHashCode(callSuper = true) @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; 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; package org.thingsboard.server.common.data.notification;
import lombok.Getter;
public enum NotificationDeliveryMethod { public enum NotificationDeliveryMethod {
PUSH, PUSH,
EMAIL, EMAIL,
SMS, SMS,
SLACK(true); SLACK
@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;
}
} }

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.notificationTemplate = template;
this.templates = new EnumMap<>(NotificationDeliveryMethod.class); this.templates = new EnumMap<>(NotificationDeliveryMethod.class);
this.stats = new NotificationRequestStats(); this.stats = new NotificationRequestStats();
init();
} }
public void init() { private void init() {
NotificationTemplateConfig templateConfig = notificationTemplate.getConfiguration(); NotificationTemplateConfig templateConfig = notificationTemplate.getConfiguration();
templateConfig.getDeliveryMethodsTemplates().forEach((deliveryMethod, template) -> { templateConfig.getDeliveryMethodsTemplates().forEach((deliveryMethod, template) -> {
if (!template.isEnabled()) return; 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 lombok.Data;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.notification.targets.NotificationRecipient;
import java.util.Collections; import java.util.Collections;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@ -35,7 +35,7 @@ public class NotificationRequestStats {
private final Map<NotificationDeliveryMethod, AtomicInteger> sent; private final Map<NotificationDeliveryMethod, AtomicInteger> sent;
private final Map<NotificationDeliveryMethod, Map<String, String>> errors; private final Map<NotificationDeliveryMethod, Map<String, String>> errors;
@JsonIgnore @JsonIgnore
private final Map<NotificationDeliveryMethod, Set<UserId>> processedRecipients; private final Map<NotificationDeliveryMethod, Set<Object>> processedRecipients;
public NotificationRequestStats() { public NotificationRequestStats() {
this.sent = new ConcurrentHashMap<>(); this.sent = new ConcurrentHashMap<>();
@ -51,19 +51,22 @@ public class NotificationRequestStats {
this.processedRecipients = Collections.emptyMap(); 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(); 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) { if (error instanceof AlreadySentException) {
return; return;
} }
String errorMessage = error.getMessage(); 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); errors.computeIfAbsent(deliveryMethod, k -> new ConcurrentHashMap<>()).put(key, errorMessage);
} }
@ -71,8 +74,8 @@ public class NotificationRequestStats {
return sent.containsKey(deliveryMethod) || errors.containsKey(deliveryMethod); return sent.containsKey(deliveryMethod) || errors.containsKey(deliveryMethod);
} }
public boolean contains(NotificationDeliveryMethod deliveryMethod, UserId recipientId) { public boolean contains(NotificationDeliveryMethod deliveryMethod, Object recipientId) {
Set<UserId> processedRecipients = this.processedRecipients.get(deliveryMethod); Set<Object> processedRecipients = this.processedRecipients.get(deliveryMethod);
return processedRecipients != null && processedRecipients.contains(recipientId); 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 com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.HasTenantId;
@ -33,6 +34,7 @@ import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
@Data @Data
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class NotificationRule extends BaseData<NotificationRuleId> implements HasTenantId, HasName { public class NotificationRule extends BaseData<NotificationRuleId> implements HasTenantId, HasName {
@ -50,6 +52,16 @@ public class NotificationRule extends BaseData<NotificationRuleId> implements Ha
@Valid @Valid
private NotificationRuleRecipientsConfig recipientsConfig; // todo: add pg_tgrm index (but index is 2.5x size of the column) 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 @JsonIgnore
@AssertTrue(message = "trigger type not matching") @AssertTrue(message = "trigger type not matching")
public boolean isValid() { 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; package org.thingsboard.server.common.data.notification.targets;
import lombok.Data; public interface NotificationRecipient {
import lombok.EqualsAndHashCode;
@Data Object getId();
@EqualsAndHashCode(callSuper = true)
public class AllUsersNotificationTargetConfig extends NotificationTargetConfig {
@Override
public NotificationTargetConfigType getType() {
return NotificationTargetConfigType.ALL_USERS;
}
} }

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 @NoXss
private String name; private String name;
@NotNull @NotNull
private NotificationTargetType type;
@NotNull
@Valid @Valid
private NotificationTargetConfig configuration; 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.JsonSubTypes.Type;
import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeInfo;
import lombok.Data; 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) @JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
@JsonSubTypes({ @JsonSubTypes({
@Type(value = UserListNotificationTargetConfig.class, name = "USER_LIST"), @Type(value = PlatformUsersNotificationTargetConfig.class, name = "PLATFORM_USERS"),
@Type(value = CustomerUsersNotificationTargetConfig.class, name = "CUSTOMER_USERS"), @Type(value = SlackNotificationTargetConfig.class, name = "SLACK")
@Type(value = AllUsersNotificationTargetConfig.class, name = "ALL_USERS")
}) })
@Data @Data
public abstract class NotificationTargetConfig { public abstract class NotificationTargetConfig {
@ -35,6 +36,6 @@ public abstract class NotificationTargetConfig {
private String description; private String description;
@JsonIgnore @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 * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.common.data.notification.template; package org.thingsboard.server.common.data.notification.targets.platform;
import lombok.Data; import lombok.Data;
@Data @Data
public class SlackConversation { public class AllUsersFilter implements UsersFilter {
private String id; @Override
private String name; public UsersFilterType getType() {
return UsersFilterType.ALL_USERS;
public enum Type {
DIRECT,
PUBLIC_CHANNEL,
PRIVATE_CHANNEL
} }
} }

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 * See the License for the specific language governing permissions and
* limitations under the License. * 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 com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import javax.validation.constraints.AssertTrue; import javax.validation.constraints.AssertTrue;
import java.util.UUID; import java.util.UUID;
@Data @Data
@EqualsAndHashCode(callSuper = true) public class CustomerUsersFilter implements UsersFilter {
public class CustomerUsersNotificationTargetConfig extends NotificationTargetConfig {
private UUID customerId; // might not be set if using with notification rule private UUID customerId; // might not be set if using with notification rule
private boolean getCustomerIdFromOriginatorEntity; // e.g. from alarm private boolean getCustomerIdFromOriginatorEntity; // e.g. from alarm
@Override @Override
public NotificationTargetConfigType getType() { public UsersFilterType getType() {
return NotificationTargetConfigType.CUSTOMER_USERS; return UsersFilterType.CUSTOMER_USERS;
} }
@AssertTrue(message = "customerId is required") @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 * See the License for the specific language governing permissions and
* limitations under the License. * 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.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotEmpty;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@Data @Data
@EqualsAndHashCode(callSuper = true) public class UserListFilter implements UsersFilter {
public class UserListNotificationTargetConfig extends NotificationTargetConfig {
@NotEmpty @NotEmpty
private List<UUID> usersIds; private List<UUID> usersIds;
@Override @Override
public NotificationTargetConfigType getType() { public UsersFilterType getType() {
return NotificationTargetConfigType.USER_LIST; 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 * See the License for the specific language governing permissions and
* limitations under the License. * 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, USER_LIST,
CUSTOMER_USERS, CUSTOMER_USERS,
ALL_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; this.body = other.body;
} }
@JsonIgnore
public abstract NotificationDeliveryMethod getMethod(); public abstract NotificationDeliveryMethod getMethod();
@JsonIgnore @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 lombok.ToString;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import javax.validation.constraints.NotEmpty;
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true) @ToString(callSuper = true)
public class SlackDeliveryMethodNotificationTemplate extends DeliveryMethodNotificationTemplate { public class SlackDeliveryMethodNotificationTemplate extends DeliveryMethodNotificationTemplate {
private SlackConversation.Type conversationType; public SlackDeliveryMethodNotificationTemplate(DeliveryMethodNotificationTemplate other) {
@NotEmpty
private String conversationId;
public SlackDeliveryMethodNotificationTemplate(SlackDeliveryMethodNotificationTemplate other) {
super(other); super(other);
this.conversationType = other.conversationType;
this.conversationId = other.conversationId;
} }
@Override @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_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_TARGET_CONFIGURATION_PROPERTY = "configuration";
public static final String NOTIFICATION_TABLE_NAME = "notification"; 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())); 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 @Override
public NotificationRule toData() { public NotificationRule toData() {
NotificationRule notificationRule = new NotificationRule(); 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.Type;
import org.hibernate.annotations.TypeDef; import org.hibernate.annotations.TypeDef;
import org.thingsboard.server.common.data.id.NotificationTargetId; 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.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.NotificationTargetConfig; 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.BaseSqlEntity;
import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.util.mapping.JsonStringType; import org.thingsboard.server.dao.util.mapping.JsonStringType;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Table; import javax.persistence.Table;
import java.util.UUID; import java.util.UUID;
@ -46,6 +48,10 @@ public class NotificationTargetEntity extends BaseSqlEntity<NotificationTarget>
@Column(name = ModelConstants.NAME_PROPERTY, nullable = false) @Column(name = ModelConstants.NAME_PROPERTY, nullable = false)
private String name; private String name;
@Enumerated(EnumType.STRING)
@Column(name = ModelConstants.NOTIFICATION_TARGET_TYPE_PROPERTY, nullable = false)
private NotificationTargetType type;
@Type(type = "json") @Type(type = "json")
@Column(name = ModelConstants.NOTIFICATION_TARGET_CONFIGURATION_PROPERTY, nullable = false) @Column(name = ModelConstants.NOTIFICATION_TARGET_CONFIGURATION_PROPERTY, nullable = false)
private JsonNode configuration; private JsonNode configuration;
@ -57,6 +63,7 @@ public class NotificationTargetEntity extends BaseSqlEntity<NotificationTarget>
setCreatedTime(notificationTarget.getCreatedTime()); setCreatedTime(notificationTarget.getCreatedTime());
setTenantId(getTenantUuid(notificationTarget.getTenantId())); setTenantId(getTenantUuid(notificationTarget.getTenantId()));
setName(notificationTarget.getName()); setName(notificationTarget.getName());
setType(notificationTarget.getType());
setConfiguration(toJson(notificationTarget.getConfiguration())); setConfiguration(toJson(notificationTarget.getConfiguration()));
} }
@ -67,6 +74,7 @@ public class NotificationTargetEntity extends BaseSqlEntity<NotificationTarget>
notificationTarget.setCreatedTime(createdTime); notificationTarget.setCreatedTime(createdTime);
notificationTarget.setTenantId(getTenantId(tenantId)); notificationTarget.setTenantId(getTenantId(tenantId));
notificationTarget.setName(name); notificationTarget.setName(name);
notificationTarget.setType(type);
notificationTarget.setConfiguration(fromJson(configuration, NotificationTargetConfig.class)); notificationTarget.setConfiguration(fromJson(configuration, NotificationTargetConfig.class));
return notificationTarget; 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.NotificationRuleId;
import org.thingsboard.server.common.data.id.TenantId; 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.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.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
@ -63,6 +64,16 @@ public class DefaultNotificationRuleService extends AbstractCachedEntityService<
return notificationRuleDao.findById(tenantId, id.getId()); 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 @Override
public PageData<NotificationRule> findNotificationRulesByTenantId(TenantId tenantId, PageLink pageLink) { public PageData<NotificationRule> findNotificationRulesByTenantId(TenantId tenantId, PageLink pageLink) {
return notificationRuleDao.findByTenantIdAndPageLink(tenantId, 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.TenantId;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.notification.NotificationRequestStatus; 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.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.NotificationTargetConfig; 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.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.entity.AbstractEntityService;
@ -70,6 +72,11 @@ public class DefaultNotificationTargetService extends AbstractEntityService impl
return notificationTargetDao.findByTenantIdAndPageLink(tenantId, pageLink); return notificationTargetDao.findByTenantIdAndPageLink(tenantId, pageLink);
} }
@Override
public List<NotificationTarget> findNotificationTargetsByTenantIdAndIds(TenantId tenantId, List<NotificationTargetId> ids) {
return notificationTargetDao.findByTenantIdAndIds(tenantId, ids);
}
@Override @Override
public PageData<User> findRecipientsForNotificationTarget(TenantId tenantId, CustomerId customerId, NotificationTargetId targetId, PageLink pageLink) { public PageData<User> findRecipientsForNotificationTarget(TenantId tenantId, CustomerId customerId, NotificationTargetId targetId, PageLink pageLink) {
NotificationTarget notificationTarget = findNotificationTargetById(tenantId, targetId); NotificationTarget notificationTarget = findNotificationTargetById(tenantId, targetId);
@ -85,9 +92,13 @@ public class DefaultNotificationTargetService extends AbstractEntityService impl
@Override @Override
public PageData<User> findRecipientsForNotificationTargetConfig(TenantId tenantId, CustomerId customerId, NotificationTargetConfig targetConfig, PageLink pageLink) { 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: { 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)) .map(UserId::new).map(userId -> userService.findUserById(tenantId, userId))
.collect(Collectors.toList()); .collect(Collectors.toList());
return new PageData<>(users, 1, users.size(), false); 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)) { if (tenantId.equals(TenantId.SYS_TENANT_ID)) {
throw new IllegalArgumentException("Customer users target is not supported for system administrator"); throw new IllegalArgumentException("Customer users target is not supported for system administrator");
} }
CustomerUsersNotificationTargetConfig customerUsersConfig = (CustomerUsersNotificationTargetConfig) targetConfig; CustomerUsersFilter customerUsersConfig = (CustomerUsersFilter) usersFilter;
if (!customerUsersConfig.isGetCustomerIdFromOriginatorEntity()) { if (!customerUsersConfig.isGetCustomerIdFromOriginatorEntity()) {
customerId = new CustomerId(customerUsersConfig.getCustomerId()); 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; 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.NotificationTargetId;
import org.thingsboard.server.common.data.id.TenantId; 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.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.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; 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<NotificationRule> findByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink);
PageData<NotificationRuleInfo> findInfosByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink);
boolean existsByTargetId(TenantId tenantId, NotificationTargetId targetId); boolean existsByTargetId(TenantId tenantId, NotificationTargetId targetId);
List<NotificationRule> findByTenantIdAndTriggerType(TenantId tenantId, NotificationRuleTriggerType triggerType); 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; 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.id.TenantId;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget; import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.Dao; import org.thingsboard.server.dao.Dao;
import java.util.List;
public interface NotificationTargetDao extends Dao<NotificationTarget> { public interface NotificationTargetDao extends Dao<NotificationTarget> {
PageData<NotificationTarget> findByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink); 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.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.EntityType; 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.NotificationTargetId;
import org.thingsboard.server.common.data.id.TenantId; 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.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.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.model.sql.NotificationRuleEntity; 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.notification.NotificationRuleDao;
import org.thingsboard.server.dao.sql.JpaAbstractDao; import org.thingsboard.server.dao.sql.JpaAbstractDao;
import org.thingsboard.server.dao.util.SqlDao; import org.thingsboard.server.dao.util.SqlDao;
@ -50,6 +53,12 @@ public class JpaNotificationRuleDao extends JpaAbstractDao<NotificationRuleEntit
Strings.nullToEmpty(pageLink.getTextSearch()), DaoUtil.toPageable(pageLink))); 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 @Override
public boolean existsByTargetId(TenantId tenantId, NotificationTargetId targetId) { public boolean existsByTargetId(TenantId tenantId, NotificationTargetId targetId) {
return notificationRuleRepository.existsByRecipientsConfigContaining(targetId.getId().toString()); 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)); 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 @Override
protected Class<NotificationRuleEntity> getEntityClass() { protected Class<NotificationRuleEntity> getEntityClass() {
return NotificationRuleEntity.class; 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.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.EntityType; 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.TenantId;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget; import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; 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.sql.JpaAbstractDao;
import org.thingsboard.server.dao.util.SqlDao; import org.thingsboard.server.dao.util.SqlDao;
import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors;
import static org.thingsboard.server.dao.DaoUtil.getId; 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))); 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 @Override
protected Class<NotificationTargetEntity> getEntityClass() { protected Class<NotificationTargetEntity> getEntityClass() {
return NotificationTargetEntity.class; 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.jpa.repository.Query;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository; 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.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.dao.model.sql.NotificationRuleEntity; import org.thingsboard.server.dao.model.sql.NotificationRuleEntity;
import org.thingsboard.server.dao.model.sql.NotificationRuleInfoEntity;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@ -30,6 +32,9 @@ import java.util.UUID;
@Repository @Repository
public interface NotificationRuleRepository extends JpaRepository<NotificationRuleEntity, UUID> { 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 " + @Query("SELECT r FROM NotificationRuleEntity r WHERE r.tenantId = :tenantId " +
"AND lower(r.name) LIKE lower(concat('%', :searchText, '%')) ") "AND lower(r.name) LIKE lower(concat('%', :searchText, '%')) ")
Page<NotificationRuleEntity> findByTenantIdAndSearchText(@Param("tenantId") UUID tenantId, Page<NotificationRuleEntity> findByTenantIdAndSearchText(@Param("tenantId") UUID tenantId,
@ -40,4 +45,12 @@ public interface NotificationRuleRepository extends JpaRepository<NotificationRu
List<NotificationRuleEntity> findAllByTenantIdAndTriggerType(UUID tenantId, NotificationRuleTriggerType triggerType); 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.springframework.stereotype.Repository;
import org.thingsboard.server.dao.model.sql.NotificationTargetEntity; import org.thingsboard.server.dao.model.sql.NotificationTargetEntity;
import java.util.List;
import java.util.UUID; import java.util.UUID;
@Repository @Repository
@ -30,4 +31,6 @@ public interface NotificationTargetRepository extends JpaRepository<Notification
Page<NotificationTargetEntity> findByTenantIdAndNameContainingIgnoreCase(UUID tenantId, String searchText, Pageable pageable); 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, created_time BIGINT NOT NULL,
tenant_id UUID NULL CONSTRAINT fk_notification_target_tenant_id REFERENCES tenant(id) ON DELETE CASCADE, tenant_id UUID NULL CONSTRAINT fk_notification_target_tenant_id REFERENCES tenant(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL,
type VARCHAR(32) NOT NULL,
configuration VARCHAR(10000) NOT NULL, configuration VARCHAR(10000) NOT NULL,
CONSTRAINT uq_notification_target_name UNIQUE (tenant_id, name) 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; package org.thingsboard.rule.engine.api.slack;
import org.thingsboard.server.common.data.id.TenantId; 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; import java.util.List;
@ -24,9 +25,9 @@ public interface SlackService {
void sendMessage(TenantId tenantId, String token, String conversationId, String message); 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); 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.TbNode;
import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException; 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.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg; 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 lombok.Data;
import org.thingsboard.rule.engine.api.NodeConfiguration; 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.NotEmpty;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
@ -31,7 +31,7 @@ public class TbSlackNodeConfiguration implements NodeConfiguration<TbSlackNodeCo
private String messageTemplate; private String messageTemplate;
@NotNull @NotNull
private SlackConversation.Type conversationType; private SlackConversationType conversationType;
private String conversationId; // if not set, need to specify conversationNamePattern private String conversationId; // if not set, need to specify conversationNamePattern
private String conversationNamePattern; private String conversationNamePattern;
@ -40,7 +40,7 @@ public class TbSlackNodeConfiguration implements NodeConfiguration<TbSlackNodeCo
TbSlackNodeConfiguration config = new TbSlackNodeConfiguration(); TbSlackNodeConfiguration config = new TbSlackNodeConfiguration();
config.setBotToken("xoxb-"); config.setBotToken("xoxb-");
config.setMessageTemplate("Device ${deviceId}: temperature is $[temperature]"); config.setMessageTemplate("Device ${deviceId}: temperature is $[temperature]");
config.setConversationType(SlackConversation.Type.PUBLIC_CHANNEL); config.setConversationType(SlackConversationType.PUBLIC_CHANNEL);
return config; 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), (id) => this.widgetService.getWidgetsBundle(id, config),
entityIds); entityIds);
break; break;
case EntityType.NOTIFICATION_TARGET:
observable = of([]);
break;
} }
return observable; return observable;
} }

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

@ -161,6 +161,7 @@ export class NotificationWebsocketService implements NotificationWsService {
private publishCommands() { private publishCommands() {
while (this.isOpened && this.cmdsWrapper.hasCommands()) { while (this.isOpened && this.cmdsWrapper.hasCommands()) {
this.dataStream.next(this.cmdsWrapper.preparePublishCommands()); this.dataStream.next(this.cmdsWrapper.preparePublishCommands());
this.cmdsWrapper.clear();
this.checkToClose(); this.checkToClose();
} }
this.tryOpenSocket(); 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"> <div class="tb-notification-center">
<mat-drawer-container class="tb-absolute-fill tb-notification-draw-container"> <div class="tb-notification-center-content">
<div class="tb-notification-center-content"> <div class="tabs-container">
<div class="tabs-container"> <mat-tab-group #matTabGroup (selectedIndexChange)="updateData()">
<mat-tab-group #matTabGroup (selectedIndexChange)="updateData()"> <mat-tab label="{{ 'notification.inbox' | translate }}">
<mat-tab label="{{ 'notification.inbox' | translate }}"> <tb-notification-table
<tb-notification-table [notificationType]="entityType.NOTIFICATION">
[notificationType]="entityType.NOTIFICATION" </tb-notification-table>
(toggleSettings)="toggleNotificationSettings()"> </mat-tab>
</tb-notification-table> <mat-tab label="{{ 'notification.sent' | translate }}" #requestTab="matTab">
</mat-tab> <tb-notification-table
<mat-tab label="{{ 'notification.sent' | translate }}" #requestTab="matTab"> #notificationRequest
<tb-notification-table [notificationType]="entityType.NOTIFICATION_REQUEST">
#notificationRequest </tb-notification-table>
[notificationType]="entityType.NOTIFICATION_REQUEST"> </mat-tab>
</tb-notification-table> <mat-tab label="{{ 'notification.templates' | translate }}">
</mat-tab> <tb-notification-table
<mat-tab label="{{ 'notification.templates' | translate }}"> [notificationType]="entityType.NOTIFICATION_TEMPLATE">
<tb-notification-table </tb-notification-table>
[notificationType]="entityType.NOTIFICATION_TEMPLATE"> </mat-tab>
</tb-notification-table> <mat-tab label="{{ 'notification.targets' | translate }}">
</mat-tab> <tb-notification-table
<mat-tab label="{{ 'notification.targets' | translate }}"> [notificationType]="entityType.NOTIFICATION_TARGET">
<tb-notification-table </tb-notification-table>
[notificationType]="entityType.NOTIFICATION_TARGET"> </mat-tab>
</tb-notification-table> <mat-tab label="{{ 'notification.rules' | translate }}">
</mat-tab> <tb-notification-table
<mat-tab label="{{ 'notification.rules' | translate }}"> [notificationType]="entityType.NOTIFICATION_RULE">
<tb-notification-table </tb-notification-table>
[notificationType]="entityType.NOTIFICATION_RULE"> </mat-tab>
</tb-notification-table> <mat-tab disabled>
</mat-tab> <ng-template mat-tab-label>
<mat-tab disabled> <div fxFlex="100" fxLayout="row" fxLayoutAlign="end center">
<ng-template mat-tab-label> <button mat-flat-button color="primary" (click)="sendNotification($event)">
<div fxFlex="100" fxLayout="row" fxLayoutAlign="end center"> <div fxLayout="row" fxLayoutAlign="start center">
<button mat-flat-button color="primary" (click)="sendNotification($event)"> <mat-icon>send</mat-icon>{{ 'notification.send-notification' | translate }}
<div fxLayout="row" fxLayoutAlign="start center"> </div>
<mat-icon>send</mat-icon>{{ 'notification.send-notification' | translate }} </button>
</div> </div>
</button> </ng-template>
</div> </mat-tab>
</ng-template> </mat-tab-group>
</mat-tab>
</mat-tab-group>
</div>
</div> </div>
<mat-drawer mode="over" </div>
#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%; height: 100%;
display: block; display: block;
.tb-notification-center { .tb-notification-center {
padding: 8px;
width: 100%; width: 100%;
height: 100%; height: 100%;
display: block; display: block;
.tb-notification-draw-container {
margin: 8px;
}
.tb-notification-center-content { .tb-notification-center-content {
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -58,34 +55,52 @@
flex-direction: column; flex-direction: column;
flex: 1 1 100%; flex: 1 1 100%;
} }
}
.notification-settings {
width: 100%;
padding: 24px;
.title {
margin: 0;
line-height: 1.4rem;
}
.fields-group { //.mat-toolbar-tools{
padding: 8px 16px 0; // min-height: auto;
margin: 10px 0; //}
border: 1px groove rgba(0, 0, 0, .25); //
border-radius: 4px; //.title-container{
// overflow: hidden;
legend { //}
color: rgba(0, 0, 0, .7); //
} //.tb-entity-table-title {
// padding-right: 20px;
.delivery-method { // white-space: nowrap;
margin-bottom: 8px; // 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} { @media #{$mat-xs} {
.tb-notification-center { .tb-notification-center {
.tb-notification-center-content { .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. /// 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 { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
@ -23,30 +23,16 @@ import {
NotificationTableComponent NotificationTableComponent
} from '@home/pages/notification-center/notification-table/notification-table.component'; } from '@home/pages/notification-center/notification-table/notification-table.component';
import { EntityType } from '@shared/models/entity-type.models'; 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({ @Component({
selector: 'tb-notification-center', selector: 'tb-notification-center',
templateUrl: './notification-center.component.html', templateUrl: './notification-center.component.html',
styleUrls: ['notification-center.component.scss'] styleUrls: ['notification-center.component.scss']
}) })
export class NotificationCenterComponent extends PageComponent implements AfterViewInit { export class NotificationCenterComponent extends PageComponent {
entityType = EntityType; 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('matTabGroup', {static: true}) matTabs: MatTabGroup;
@ViewChild('requestTab', {static: true}) requestTab: MatTab; @ViewChild('requestTab', {static: true}) requestTab: MatTab;
@ViewChild('notificationRequest', {static: true}) notificationRequestTable: NotificationTableComponent; @ViewChild('notificationRequest', {static: true}) notificationRequestTable: NotificationTableComponent;
@ -54,38 +40,8 @@ export class NotificationCenterComponent extends PageComponent implements AfterV
constructor( constructor(
protected store: Store<AppState>, protected store: Store<AppState>) {
private notificationService: NotificationService,
private fb: FormBuilder) {
super(store); 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() { updateData() {
@ -103,19 +59,4 @@ export class NotificationCenterComponent extends PageComponent implements AfterV
sendNotification($event: Event) { sendNotification($event: Event) {
this.notificationRequestTable.entityTableConfig.onEntityAction({event: $event, action: this.requestTab.isActive ? 'add' : 'add-without-update', entity: null}); 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 { InboxTableHeaderComponent } from '@home/pages/notification-center/inbox-table/inbox-table-header.component';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { take } from 'rxjs/operators'; import { take } from 'rxjs/operators';
import { EventEmitter } from '@angular/core';
export class InboxTableConfig extends EntityTableConfig<Notification> { export class InboxTableConfig extends EntityTableConfig<Notification> {
constructor(private notificationService: NotificationService, constructor(private notificationService: NotificationService,
private translate: TranslateService, private translate: TranslateService,
private datePipe: DatePipe, private datePipe: DatePipe) {
private toggleSettings: EventEmitter<void>) {
super(); super();
this.entitiesDeleteEnabled = false; this.entitiesDeleteEnabled = false;
this.entityTranslations = { this.entityTranslations = {
@ -60,20 +58,12 @@ export class InboxTableConfig extends EntityTableConfig<Notification> {
this.headerComponent = InboxTableHeaderComponent; this.headerComponent = InboxTableHeaderComponent;
this.headerActionDescriptors = [ this.headerActionDescriptors = [{
{ name: this.translate.instant('notification.mark-all-as-read'),
name: 'Notification setting', icon: 'done_all',
icon: 'settings', isEnabled: () => true,
isEnabled: () => true, onAction: $event => this.markAllRead($event)
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.columns.push( this.columns.push(
new DateEntityTableColumn<Notification>('createdTime', 'notification.created-time', this.datePipe, '150px'), 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){ private markAsRead($event, entity){
if ($event) { if ($event) {
$event.stopPropagation(); $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. /// 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 { EntitiesTableComponent } from '@home/components/entity/entities-table.component';
import { NotificationService } from '@core/http/notification.service'; import { NotificationService } from '@core/http/notification.service';
import { TargetsTableConfig } from '@home/pages/notification-center/notification-table/targets-table-config'; import { TargetsTableConfig } from '@home/pages/notification-center/notification-table/targets-table-config';
@ -37,9 +37,6 @@ export class NotificationTableComponent implements OnInit {
@Input() @Input()
notificationType = EntityType.NOTIFICATION; notificationType = EntityType.NOTIFICATION;
@Output()
toggleSettings: EventEmitter<void> = new EventEmitter<void>();
@ViewChild(EntitiesTableComponent, {static: true}) entitiesTable: EntitiesTableComponent; @ViewChild(EntitiesTableComponent, {static: true}) entitiesTable: EntitiesTableComponent;
entityTableConfig: EntityTableConfig<any>; entityTableConfig: EntityTableConfig<any>;
@ -74,8 +71,7 @@ export class NotificationTableComponent implements OnInit {
return new InboxTableConfig( return new InboxTableConfig(
this.notificationService, this.notificationService,
this.translate, this.translate,
this.datePipe, this.datePipe
this.toggleSettings
); );
case EntityType.NOTIFICATION_TEMPLATE: case EntityType.NOTIFICATION_TEMPLATE:
return new TemplateTableConfig( 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>> { 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) { 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'; } from '@home/models/entity/entities-table-config.models';
import { EntityTypeResource } from '@shared/models/entity-type.models'; import { EntityTypeResource } from '@shared/models/entity-type.models';
import { Direction } from '@shared/models/page/sort-order'; 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 { NotificationService } from '@core/http/notification.service';
import { EntityAction } from '@home/models/entity/entity-component.models'; import { EntityAction } from '@home/models/entity/entity-component.models';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
@ -61,7 +61,8 @@ export class TemplateTableConfig extends EntityTableConfig<NotificationTemplate>
this.defaultSortOrder = {property: 'notificationType', direction: Direction.ASC}; this.defaultSortOrder = {property: 'notificationType', direction: Direction.ASC};
this.columns.push( 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>('name', 'notification.template', '25%'),
new EntityTableColumn<NotificationTemplate>('configuration.notificationSubject', 'notification.subject', '25%', new EntityTableColumn<NotificationTemplate>('configuration.notificationSubject', 'notification.subject', '25%',
(template) => template.configuration.notificationSubject, () => ({}), false), (template) => template.configuration.notificationSubject, () => ({}), false),
@ -71,10 +72,22 @@ export class TemplateTableConfig extends EntityTableConfig<NotificationTemplate>
} }
private configureCellActions(): Array<CellActionDescriptor<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) { if ($event) {
$event.stopPropagation(); $event.stopPropagation();
} }
@ -84,6 +97,7 @@ export class TemplateTableConfig extends EntityTableConfig<NotificationTemplate>
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: { data: {
isAdd, isAdd,
isCopy,
template template
} }
}).afterClosed() }).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"> <mat-toolbar color="primary">
<h2 translate>notification.new-notification</h2> <h2>{{ dialogTitle | translate }}</h2>
<span fxFlex></span> <span fxFlex></span>
<button mat-icon-button <button mat-icon-button
(click)="cancel()" (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 { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { NotificationService } from '@core/http/notification.service'; import { NotificationService } from '@core/http/notification.service';
import { deepTrim, isDefined } from '@core/utils'; import { deepTrim } from '@core/utils';
import { Observable, Subject } from 'rxjs'; import { Observable, Subject } from 'rxjs';
import { EntityType } from '@shared/models/entity-type.models'; import { EntityType } from '@shared/models/entity-type.models';
import { BreakpointObserver } from '@angular/cdk/layout'; import { BreakpointObserver } from '@angular/cdk/layout';
@ -55,6 +55,8 @@ export class RequestNotificationDialogComponent extends
selectedIndex = 0; selectedIndex = 0;
preview: NotificationRequestPreview = null; preview: NotificationRequestPreview = null;
dialogTitle = 'notification.notify-again';
private readonly destroy$ = new Subject<void>(); private readonly destroy$ = new Subject<void>();
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
@ -66,10 +68,6 @@ export class RequestNotificationDialogComponent extends
private notificationService: NotificationService) { private notificationService: NotificationService) {
super(store, router, dialogRef); super(store, router, dialogRef);
if (isDefined(data.isAdd)) {
this.isAdd = data.isAdd;
}
this.stepperOrientation = this.breakpointObserver.observe(MediaBreakpoints['gt-xs']) this.stepperOrientation = this.breakpointObserver.observe(MediaBreakpoints['gt-xs'])
.pipe(map(({matches}) => matches ? 'horizontal' : 'vertical')); .pipe(map(({matches}) => matches ? 'horizontal' : 'vertical'));
@ -77,6 +75,13 @@ export class RequestNotificationDialogComponent extends
templateId: [null, Validators.required], templateId: [null, Validators.required],
targets: [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() { 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> </mat-progress-bar>
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div> <div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
<div mat-dialog-content> <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)"> (selectionChange)="changeStep($event)">
<ng-template matStepperIcon="edit"> <ng-template matStepperIcon="edit">
<mat-icon>check</mat-icon> <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 { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { FormBuilder, FormGroup, ValidationErrors, Validators } from '@angular/forms'; import { FormBuilder, FormGroup, ValidationErrors, Validators } from '@angular/forms';
import { NotificationService } from '@core/http/notification.service'; 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 { Observable, Subject } from 'rxjs';
import { map, takeUntil } from 'rxjs/operators'; import { map, takeUntil } from 'rxjs/operators';
import { StepperOrientation, StepperSelectionEvent } from '@angular/cdk/stepper'; import { StepperOrientation, StepperSelectionEvent } from '@angular/cdk/stepper';
import { MatStepper } from '@angular/material/stepper'; import { MatStepper } from '@angular/material/stepper';
import { BreakpointObserver } from '@angular/cdk/layout'; import { BreakpointObserver } from '@angular/cdk/layout';
import { MediaBreakpoints } from '@shared/models/constants'; import { MediaBreakpoints } from '@shared/models/constants';
import { TranslateService } from '@ngx-translate/core';
export interface TemplateNotificationDialogData { export interface TemplateNotificationDialogData {
template?: NotificationTemplate; template?: NotificationTemplate;
@ -53,7 +54,7 @@ export interface TemplateNotificationDialogData {
export class TemplateNotificationDialogComponent export class TemplateNotificationDialogComponent
extends DialogComponent<TemplateNotificationDialogComponent, NotificationTemplate> implements OnDestroy { extends DialogComponent<TemplateNotificationDialogComponent, NotificationTemplate> implements OnDestroy {
@ViewChild('addNotificationTemplate', {static: true}) addNotificationTemplate: MatStepper; @ViewChild('notificationTemplateStepper', {static: true}) notificationTemplateStepper: MatStepper;
stepperOrientation: Observable<StepperOrientation>; stepperOrientation: Observable<StepperOrientation>;
@ -63,7 +64,6 @@ export class TemplateNotificationDialogComponent
smsTemplateForm: FormGroup; smsTemplateForm: FormGroup;
slackTemplateForm: FormGroup; slackTemplateForm: FormGroup;
dialogTitle = 'notification.edit-notification-template'; dialogTitle = 'notification.edit-notification-template';
saveButtonLabel = 'action.save';
notificationTypes = Object.keys(NotificationType) as NotificationType[]; notificationTypes = Object.keys(NotificationType) as NotificationType[];
slackChanelTypes = Object.keys(SlackChanelType) as SlackChanelType[]; slackChanelTypes = Object.keys(SlackChanelType) as SlackChanelType[];
@ -74,40 +74,33 @@ export class TemplateNotificationDialogComponent
selectedIndex = 0; 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 destroy$ = new Subject<void>();
private readonly templateNotification: NotificationTemplate;
private deliveryMethodFormsMap: Map<NotificationDeliveryMethod, FormGroup>;
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
protected router: Router, protected router: Router,
protected dialogRef: MatDialogRef<TemplateNotificationDialogComponent, NotificationTemplate>, protected dialogRef: MatDialogRef<TemplateNotificationDialogComponent, NotificationTemplate>,
@Inject(MAT_DIALOG_DATA) public data: TemplateNotificationDialogData, @Inject(MAT_DIALOG_DATA) public data: TemplateNotificationDialogData,
private breakpointObserver: BreakpointObserver, private breakpointObserver: BreakpointObserver,
private fb: FormBuilder, private fb: FormBuilder,
private notificationService: NotificationService) { private notificationService: NotificationService,
private translate: TranslateService) {
super(store, router, dialogRef); 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']) this.stepperOrientation = this.breakpointObserver.observe(MediaBreakpoints['gt-xs'])
.pipe(map(({matches}) => matches ? 'horizontal' : 'vertical')); .pipe(map(({matches}) => matches ? 'horizontal' : 'vertical'));
@ -122,7 +115,7 @@ export class TemplateNotificationDialogComponent
}); });
this.notificationDeliveryMethods.forEach(method => { this.notificationDeliveryMethods.forEach(method => {
(this.templateNotificationForm.get('configuration.deliveryMethodsTemplates') as FormGroup) (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({ this.pushTemplateForm = this.fb.group({
@ -179,6 +172,30 @@ export class TemplateNotificationDialogComponent
conversationId: ['', Validators.required], conversationId: ['', Validators.required],
conversationType: [SlackChanelType.DIRECT] 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() { ngOnDestroy() {
@ -191,86 +208,61 @@ export class TemplateNotificationDialogComponent
this.dialogRef.close(null); 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) { changeStep($event: StepperSelectionEvent) {
this.selectedIndex = $event.selectedIndex; this.selectedIndex = $event.selectedIndex;
} }
backStep() { backStep() {
this.addNotificationTemplate.previous(); this.notificationTemplateStepper.previous();
} }
nextStep() { nextStep() {
if (this.selectedIndex >= this.maxStepperIndex) { if (this.selectedIndex >= this.maxStepperIndex) {
this.add(); this.add();
} else { } else {
this.addNotificationTemplate.next(); this.notificationTemplateStepper.next();
} }
} }
nextStepLabel(): string { nextStepLabel(): string {
if (this.selectedIndex === 1 && this.selectedIndex < this.maxStepperIndex && this.pushTemplateForm.pristine) { if (this.selectedIndex !== 0) {
return 'action.skip'; if (this.selectedIndex >= this.maxStepperIndex) {
} return (this.data.isAdd || this.data.isCopy) ? 'action.add' : 'action.save';
if (this.selectedIndex === 2 && this.selectedIndex < this.maxStepperIndex && this.emailTemplateForm.pristine) { } else if (this.notificationTemplateStepper.selected.stepControl.pristine) {
return 'action.skip'; 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';
} }
return 'action.next'; return 'action.next';
} }
private get maxStepperIndex(): number { private get maxStepperIndex(): number {
return this.addNotificationTemplate?._steps?.length - 1; return this.notificationTemplateStepper?._steps?.length - 1;
} }
private add(): void { private add(): void {
if (this.allValid()) { if (this.allValid()) {
const formValue: NotificationTemplate = this.templateNotificationForm.value; let template: NotificationTemplate = this.templateNotificationForm.value;
if (formValue.configuration.deliveryMethodsTemplates.PUSH.enabled) { this.notificationDeliveryMethods.forEach(method => {
Object.assign(formValue.configuration.deliveryMethodsTemplates.PUSH, this.pushTemplateForm.value); if (template.configuration.deliveryMethodsTemplates[method].enabled) {
} else { Object.assign(template.configuration.deliveryMethodsTemplates[method], this.deliveryMethodFormsMap.get(method).value, {method});
delete formValue.configuration.deliveryMethodsTemplates.PUSH; } else {
} delete template.configuration.deliveryMethodsTemplates[method];
if (formValue.configuration.deliveryMethodsTemplates.EMAIL.enabled) { }
Object.assign(formValue.configuration.deliveryMethodsTemplates.EMAIL, this.emailTemplateForm.value); });
} else { if (this.templateNotification) {
delete formValue.configuration.deliveryMethodsTemplates.EMAIL; template = {...this.templateNotification, ...template};
}
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;
} }
this.notificationService.saveNotificationTemplate(deepTrim(formValue)).subscribe( this.notificationService.saveNotificationTemplate(deepTrim(template)).subscribe(
(target) => this.dialogRef.close(target) (target) => this.dialogRef.close(target)
); );
} }
} }
private allValid(): boolean { private allValid(): boolean {
return !this.addNotificationTemplate.steps.find((item, index) => { return !this.notificationTemplateStepper.steps.find((item, index) => {
if (item.stepControl.invalid) { if (item.stepControl.invalid) {
item.interacted = true; item.interacted = true;
this.addNotificationTemplate.selectedIndex = index; this.notificationTemplateStepper.selectedIndex = index;
return true; return true;
} else { } else {
return false; return false;

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

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

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

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

Loading…
Cancel
Save