Slack notification channel; refactoring
This commit is contained in:
		
							parent
							
								
									4b39d2a221
								
							
						
					
					
						commit
						738b9dc494
					
				@ -356,6 +356,10 @@
 | 
				
			|||||||
            <groupId>org.jboss.aerogear</groupId>
 | 
					            <groupId>org.jboss.aerogear</groupId>
 | 
				
			||||||
            <artifactId>aerogear-otp-java</artifactId>
 | 
					            <artifactId>aerogear-otp-java</artifactId>
 | 
				
			||||||
        </dependency>
 | 
					        </dependency>
 | 
				
			||||||
 | 
					        <dependency>
 | 
				
			||||||
 | 
					            <groupId>com.slack.api</groupId>
 | 
				
			||||||
 | 
					            <artifactId>slack-api-client</artifactId>
 | 
				
			||||||
 | 
					        </dependency>
 | 
				
			||||||
    </dependencies>
 | 
					    </dependencies>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <build>
 | 
					    <build>
 | 
				
			||||||
 | 
				
			|||||||
@ -79,16 +79,3 @@ CREATE INDEX IF NOT EXISTS idx_notification_notification_request_id ON notificat
 | 
				
			|||||||
ALTER TABLE alarm ADD COLUMN IF NOT EXISTS notification_rule_id UUID;
 | 
					ALTER TABLE alarm ADD COLUMN IF NOT EXISTS notification_rule_id UUID;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ALTER TABLE tb_user ADD COLUMN IF NOT EXISTS phone VARCHAR(255);
 | 
					ALTER TABLE tb_user ADD COLUMN IF NOT EXISTS phone VARCHAR(255);
 | 
				
			||||||
 | 
					 | 
				
			||||||
CREATE OR REPLACE FUNCTION on_notification_deleted() RETURNS TRIGGER as $notification_deleted$
 | 
					 | 
				
			||||||
BEGIN
 | 
					 | 
				
			||||||
    RAISE NOTICE 'ABAAAAAAAAAAAAAAAAAAA';
 | 
					 | 
				
			||||||
    INSERT INTO id_and_time values ('13814000-1dd2-11b2-8080-808080808080', 0);
 | 
					 | 
				
			||||||
    RETURN NULL;
 | 
					 | 
				
			||||||
END;
 | 
					 | 
				
			||||||
$notification_deleted$ LANGUAGE plpgsql;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
CREATE TRIGGER notification_deleted_trigger
 | 
					 | 
				
			||||||
    AFTER DELETE ON id_and_time
 | 
					 | 
				
			||||||
    REFERENCING OLD TABLE AS deleted
 | 
					 | 
				
			||||||
    FOR EACH STATEMENT EXECUTE FUNCTION on_notification_deleted();
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -35,17 +35,24 @@ import org.thingsboard.server.common.data.exception.ThingsboardException;
 | 
				
			|||||||
import org.thingsboard.server.common.data.id.NotificationId;
 | 
					import org.thingsboard.server.common.data.id.NotificationId;
 | 
				
			||||||
import org.thingsboard.server.common.data.id.NotificationRequestId;
 | 
					import org.thingsboard.server.common.data.id.NotificationRequestId;
 | 
				
			||||||
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.NotificationOriginatorType;
 | 
					import org.thingsboard.server.common.data.notification.NotificationOriginatorType;
 | 
				
			||||||
import org.thingsboard.server.common.data.notification.NotificationRequest;
 | 
					import org.thingsboard.server.common.data.notification.NotificationRequest;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig;
 | 
				
			||||||
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.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.queue.util.TbCoreComponent;
 | 
					import org.thingsboard.server.queue.util.TbCoreComponent;
 | 
				
			||||||
 | 
					import org.thingsboard.server.service.notification.NotificationManagerHelper;
 | 
				
			||||||
import org.thingsboard.server.service.security.model.SecurityUser;
 | 
					import org.thingsboard.server.service.security.model.SecurityUser;
 | 
				
			||||||
import org.thingsboard.server.service.security.permission.Operation;
 | 
					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 org.thingsboard.server.service.slack.SlackConversation;
 | 
				
			||||||
 | 
					import org.thingsboard.server.service.slack.SlackService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
import java.util.UUID;
 | 
					import java.util.UUID;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@RestController
 | 
					@RestController
 | 
				
			||||||
@ -58,6 +65,8 @@ public class NotificationController extends BaseController {
 | 
				
			|||||||
    private final NotificationService notificationService;
 | 
					    private final NotificationService notificationService;
 | 
				
			||||||
    private final NotificationRequestService notificationRequestService;
 | 
					    private final NotificationRequestService notificationRequestService;
 | 
				
			||||||
    private final NotificationManager notificationManager;
 | 
					    private final NotificationManager notificationManager;
 | 
				
			||||||
 | 
					    private final NotificationManagerHelper notificationManagerHelper;
 | 
				
			||||||
 | 
					    private final SlackService slackService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @GetMapping("/notifications")
 | 
					    @GetMapping("/notifications")
 | 
				
			||||||
    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
 | 
					    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
 | 
				
			||||||
@ -133,4 +142,33 @@ public class NotificationController extends BaseController {
 | 
				
			|||||||
        doDeleteAndLog(EntityType.NOTIFICATION_REQUEST, notificationRequest, notificationManager::deleteNotificationRequest);
 | 
					        doDeleteAndLog(EntityType.NOTIFICATION_REQUEST, notificationRequest, notificationManager::deleteNotificationRequest);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @PostMapping("/notification/settings")
 | 
				
			||||||
 | 
					    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
 | 
				
			||||||
 | 
					    public NotificationSettings saveNotificationSettings(@RequestBody NotificationSettings notificationSettings,
 | 
				
			||||||
 | 
					                                                         @AuthenticationPrincipal SecurityUser user) {
 | 
				
			||||||
 | 
					        notificationManagerHelper.saveNotificationSettings(user.getTenantId(), notificationSettings);
 | 
				
			||||||
 | 
					        return notificationSettings;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @GetMapping("/notification/settings")
 | 
				
			||||||
 | 
					    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
 | 
				
			||||||
 | 
					    public NotificationSettings getNotificationSettings(@AuthenticationPrincipal SecurityUser user) {
 | 
				
			||||||
 | 
					        return notificationManagerHelper.getNotificationSettings(user.getTenantId());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @GetMapping("/notification/slack/conversations")
 | 
				
			||||||
 | 
					    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
 | 
				
			||||||
 | 
					    public List<SlackConversation> listSlackConversations(@RequestParam SlackConversation.Type type,
 | 
				
			||||||
 | 
					                                                          @AuthenticationPrincipal SecurityUser user) throws Exception {
 | 
				
			||||||
 | 
					        NotificationSettings settings = getNotificationSettings(user);
 | 
				
			||||||
 | 
					        SlackNotificationDeliveryMethodConfig slackConfig = (SlackNotificationDeliveryMethodConfig)
 | 
				
			||||||
 | 
					                settings.getDeliveryMethodsConfigs().get(NotificationDeliveryMethod.SLACK);
 | 
				
			||||||
 | 
					        if (slackConfig == null) {
 | 
				
			||||||
 | 
					            throw new IllegalArgumentException("Slack is not configured");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return slackService.listConversations(user.getTenantId(), slackConfig.getBotToken(), type);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -15,7 +15,6 @@
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
package org.thingsboard.server.service.notification;
 | 
					package org.thingsboard.server.service.notification;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import com.google.common.base.Strings;
 | 
					 | 
				
			||||||
import com.google.common.util.concurrent.Futures;
 | 
					import com.google.common.util.concurrent.Futures;
 | 
				
			||||||
import com.google.common.util.concurrent.ListenableFuture;
 | 
					import com.google.common.util.concurrent.ListenableFuture;
 | 
				
			||||||
import lombok.RequiredArgsConstructor;
 | 
					import lombok.RequiredArgsConstructor;
 | 
				
			||||||
@ -33,11 +32,10 @@ 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.NotificationRequest;
 | 
					import org.thingsboard.server.common.data.notification.NotificationRequest;
 | 
				
			||||||
import org.thingsboard.server.common.data.notification.NotificationRequestConfig;
 | 
					import org.thingsboard.server.common.data.notification.NotificationRequestConfig;
 | 
				
			||||||
import org.thingsboard.server.common.data.notification.NotificationRequestStats;
 | 
					 | 
				
			||||||
import org.thingsboard.server.common.data.notification.NotificationRequestStatus;
 | 
					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.template.NotificationText;
 | 
					import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
 | 
				
			||||||
import org.thingsboard.server.common.data.notification.template.NotificationTextTemplate;
 | 
					import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate;
 | 
				
			||||||
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;
 | 
				
			||||||
@ -57,7 +55,6 @@ import org.thingsboard.server.service.ws.notification.sub.NotificationRequestUpd
 | 
				
			|||||||
import org.thingsboard.server.service.ws.notification.sub.NotificationUpdate;
 | 
					import org.thingsboard.server.service.ws.notification.sub.NotificationUpdate;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.util.ArrayList;
 | 
					import java.util.ArrayList;
 | 
				
			||||||
import java.util.HashMap;
 | 
					 | 
				
			||||||
import java.util.HashSet;
 | 
					import java.util.HashSet;
 | 
				
			||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
import java.util.Map;
 | 
					import java.util.Map;
 | 
				
			||||||
@ -74,7 +71,7 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl
 | 
				
			|||||||
    private final NotificationTargetService notificationTargetService;
 | 
					    private final NotificationTargetService notificationTargetService;
 | 
				
			||||||
    private final NotificationRequestService notificationRequestService;
 | 
					    private final NotificationRequestService notificationRequestService;
 | 
				
			||||||
    private final NotificationService notificationService;
 | 
					    private final NotificationService notificationService;
 | 
				
			||||||
    private final NotificationTemplateUtil notificationTemplateUtil;
 | 
					    private final NotificationManagerHelper notificationManagerHelper;
 | 
				
			||||||
    private final DbCallbackExecutorService dbCallbackExecutorService;
 | 
					    private final DbCallbackExecutorService dbCallbackExecutorService;
 | 
				
			||||||
    private final NotificationsTopicService notificationsTopicService;
 | 
					    private final NotificationsTopicService notificationsTopicService;
 | 
				
			||||||
    private final TbQueueProducerProvider producerProvider;
 | 
					    private final TbQueueProducerProvider producerProvider;
 | 
				
			||||||
@ -85,6 +82,13 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl
 | 
				
			|||||||
    public NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) {
 | 
					    public NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) {
 | 
				
			||||||
        log.debug("Processing notification request (tenant id: {}, notification target id: {})", tenantId, notificationRequest.getTargetId());
 | 
					        log.debug("Processing notification request (tenant id: {}, notification target id: {})", tenantId, notificationRequest.getTargetId());
 | 
				
			||||||
        notificationRequest.setTenantId(tenantId);
 | 
					        notificationRequest.setTenantId(tenantId);
 | 
				
			||||||
 | 
					        NotificationSettings settings = notificationManagerHelper.getNotificationSettings(tenantId);
 | 
				
			||||||
 | 
					        notificationRequest.getDeliveryMethods().forEach(deliveryMethod -> {
 | 
				
			||||||
 | 
					            if (!settings.getDeliveryMethodsConfigs().containsKey(deliveryMethod) || !settings.getDeliveryMethodsConfigs().get(deliveryMethod).isEnabled()) {
 | 
				
			||||||
 | 
					                throw new IllegalArgumentException("Delivery method " + deliveryMethod + " is not enabled or configured");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (notificationRequest.getAdditionalConfig() != null) {
 | 
					        if (notificationRequest.getAdditionalConfig() != null) {
 | 
				
			||||||
            NotificationRequestConfig config = notificationRequest.getAdditionalConfig();
 | 
					            NotificationRequestConfig config = notificationRequest.getAdditionalConfig();
 | 
				
			||||||
            if (config.getSendingDelayInSec() > 0 && notificationRequest.getId() == null) {
 | 
					            if (config.getSendingDelayInSec() > 0 && notificationRequest.getId() == null) {
 | 
				
			||||||
@ -98,9 +102,13 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl
 | 
				
			|||||||
        notificationRequest.setStatus(NotificationRequestStatus.PROCESSED);
 | 
					        notificationRequest.setStatus(NotificationRequestStatus.PROCESSED);
 | 
				
			||||||
        NotificationRequest savedNotificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest);
 | 
					        NotificationRequest savedNotificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        NotificationRequestStats stats = new NotificationRequestStats();
 | 
					        NotificationProcessingContext ctx = NotificationProcessingContext.builder()
 | 
				
			||||||
        Map<NotificationDeliveryMethod, NotificationTextTemplate> textTemplates = notificationTemplateUtil.getTemplates(tenantId, notificationRequest.getTemplateId(), savedNotificationRequest.getDeliveryMethods());
 | 
					                .tenantId(tenantId)
 | 
				
			||||||
        savedNotificationRequest.setTemplateContext(notificationRequest.getTemplateContext());
 | 
					                .settings(settings)
 | 
				
			||||||
 | 
					                .request(savedNotificationRequest)
 | 
				
			||||||
 | 
					                .additionalTemplateContext(notificationRequest.getTemplateContext())
 | 
				
			||||||
 | 
					                .build();
 | 
				
			||||||
 | 
					        ctx.init(notificationManagerHelper);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        DaoUtil.processBatches(pageLink -> {
 | 
					        DaoUtil.processBatches(pageLink -> {
 | 
				
			||||||
            return notificationTargetService.findRecipientsForNotificationTarget(tenantId, notificationRequest.getTargetId(), pageLink);
 | 
					            return notificationTargetService.findRecipientsForNotificationTarget(tenantId, notificationRequest.getTargetId(), pageLink);
 | 
				
			||||||
@ -111,20 +119,19 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl
 | 
				
			|||||||
                log.debug("Sending {} notifications for request {} to recipients batch", deliveryMethod, savedNotificationRequest.getId());
 | 
					                log.debug("Sending {} notifications for request {} to recipients batch", deliveryMethod, savedNotificationRequest.getId());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                List<User> recipients = recipientsBatch.getData();
 | 
					                List<User> recipients = recipientsBatch.getData();
 | 
				
			||||||
                NotificationTextTemplate textTemplate = textTemplates.get(deliveryMethod);
 | 
					 | 
				
			||||||
                for (User recipient : recipients) {
 | 
					                for (User recipient : recipients) {
 | 
				
			||||||
                    ListenableFuture<Void> resultFuture = processForRecipient(recipient, savedNotificationRequest, notificationChannel, textTemplate, stats);
 | 
					                    ListenableFuture<Void> resultFuture = processForRecipient(notificationChannel, recipient, ctx);
 | 
				
			||||||
                    DonAsynchron.withCallback(resultFuture, result -> {
 | 
					                    DonAsynchron.withCallback(resultFuture, result -> {
 | 
				
			||||||
                        stats.reportSent(deliveryMethod);
 | 
					                        ctx.getStats().reportSent(deliveryMethod);
 | 
				
			||||||
                    }, error -> {
 | 
					                    }, error -> {
 | 
				
			||||||
                        stats.reportError(deliveryMethod, recipient, error);
 | 
					                        ctx.getStats().reportError(deliveryMethod, recipient, error);
 | 
				
			||||||
                    }, dbCallbackExecutorService);
 | 
					                    }, dbCallbackExecutorService);
 | 
				
			||||||
                    results.add(resultFuture);
 | 
					                    results.add(resultFuture);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            Futures.whenAllComplete(results).run(() -> {
 | 
					            Futures.whenAllComplete(results).run(() -> {
 | 
				
			||||||
                try {
 | 
					                try {
 | 
				
			||||||
                    notificationRequestService.updateNotificationRequestStats(tenantId, savedNotificationRequest.getId(), stats);
 | 
					                    notificationRequestService.updateNotificationRequestStats(tenantId, savedNotificationRequest.getId(), ctx.getStats());
 | 
				
			||||||
                } 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 {}", savedNotificationRequest.getId(), e);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@ -134,22 +141,15 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl
 | 
				
			|||||||
        return savedNotificationRequest;
 | 
					        return savedNotificationRequest;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private ListenableFuture<Void> processForRecipient(User recipient, NotificationRequest notificationRequest, NotificationChannel notificationChannel,
 | 
					    private ListenableFuture<Void> processForRecipient(NotificationChannel notificationChannel, User recipient, NotificationProcessingContext ctx) {
 | 
				
			||||||
                                                       NotificationTextTemplate textTemplate, NotificationRequestStats stats) {
 | 
					        String text;
 | 
				
			||||||
        NotificationText text;
 | 
					 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            Map<String, String> templateContext = new HashMap<>();
 | 
					            DeliveryMethodNotificationTemplate template = ctx.getTemplate(notificationChannel.getDeliveryMethod());
 | 
				
			||||||
            templateContext.put("email", recipient.getEmail());
 | 
					            text = notificationManagerHelper.processTemplate(template.getBody(), ctx.createTemplateContext(recipient));
 | 
				
			||||||
            templateContext.put("firstName", Strings.nullToEmpty(recipient.getFirstName()));
 | 
					 | 
				
			||||||
            templateContext.put("lastName", Strings.nullToEmpty(recipient.getLastName()));
 | 
					 | 
				
			||||||
            if (notificationRequest.getTemplateContext() != null) {
 | 
					 | 
				
			||||||
                templateContext.putAll(notificationRequest.getTemplateContext());
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            text = notificationTemplateUtil.processTemplate(textTemplate, templateContext);
 | 
					 | 
				
			||||||
        } catch (Exception e) {
 | 
					        } catch (Exception e) {
 | 
				
			||||||
            return Futures.immediateFailedFuture(e);
 | 
					            return Futures.immediateFailedFuture(e);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return notificationChannel.sendNotification(recipient, notificationRequest, text);
 | 
					        return notificationChannel.sendNotification(recipient, text, ctx);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private void forwardToNotificationSchedulerService(TenantId tenantId, NotificationRequestId notificationRequestId, boolean deleted) {
 | 
					    private void forwardToNotificationSchedulerService(TenantId tenantId, NotificationRequestId notificationRequestId, boolean deleted) {
 | 
				
			||||||
@ -167,13 +167,14 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public ListenableFuture<Void> sendNotification(User recipient, NotificationRequest request, NotificationText text) {
 | 
					    public ListenableFuture<Void> sendNotification(User recipient, String text, NotificationProcessingContext ctx) {
 | 
				
			||||||
 | 
					        NotificationRequest request = ctx.getRequest();
 | 
				
			||||||
        log.trace("Creating notification for recipient {} (notification request id: {})", recipient.getId(), request.getId());
 | 
					        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())
 | 
				
			||||||
                .type(request.getType())
 | 
					                .type(request.getType())
 | 
				
			||||||
                .text(text.getBody())
 | 
					                .text(text)
 | 
				
			||||||
                .info(request.getInfo())
 | 
					                .info(request.getInfo())
 | 
				
			||||||
                .originatorType(request.getOriginatorType())
 | 
					                .originatorType(request.getOriginatorType())
 | 
				
			||||||
                .status(NotificationStatus.SENT)
 | 
					                .status(NotificationStatus.SENT)
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,90 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 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.service.notification;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import lombok.RequiredArgsConstructor;
 | 
				
			||||||
 | 
					import org.springframework.stereotype.Component;
 | 
				
			||||||
 | 
					import org.thingsboard.common.util.JacksonUtil;
 | 
				
			||||||
 | 
					import org.thingsboard.rule.engine.api.util.TbNodeUtils;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.AdminSettings;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.id.NotificationTemplateId;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.id.TenantId;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.notification.settings.NotificationDeliveryMethodConfig;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
 | 
				
			||||||
 | 
					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.NotificationTemplateConfig;
 | 
				
			||||||
 | 
					import org.thingsboard.server.dao.notification.NotificationTemplateService;
 | 
				
			||||||
 | 
					import org.thingsboard.server.dao.settings.AdminSettingsService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					import java.util.Map;
 | 
				
			||||||
 | 
					import java.util.Optional;
 | 
				
			||||||
 | 
					import java.util.stream.Collectors;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Component
 | 
				
			||||||
 | 
					@RequiredArgsConstructor
 | 
				
			||||||
 | 
					public class NotificationManagerHelper {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static final String SETTINGS_KEY = "notifications";
 | 
				
			||||||
 | 
					    private final NotificationTemplateService templateService;
 | 
				
			||||||
 | 
					    private final AdminSettingsService adminSettingsService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public Map<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> getTemplates(TenantId tenantId, NotificationTemplateId templateId, List<NotificationDeliveryMethod> deliveryMethods) {
 | 
				
			||||||
 | 
					        NotificationTemplate notificationTemplate = templateService.findNotificationTemplateById(tenantId, templateId);
 | 
				
			||||||
 | 
					        NotificationTemplateConfig config = notificationTemplate.getConfiguration();
 | 
				
			||||||
 | 
					        return deliveryMethods.stream()
 | 
				
			||||||
 | 
					                .collect(Collectors.toMap(k -> k, deliveryMethod -> {
 | 
				
			||||||
 | 
					                    return Optional.ofNullable(config.getTemplates())
 | 
				
			||||||
 | 
					                            .map(templates -> templates.get(deliveryMethod))
 | 
				
			||||||
 | 
					                            .orElse(config.getDefaultTemplate());
 | 
				
			||||||
 | 
					                }));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public String processTemplate(String template, Map<String, String> templateContext) {
 | 
				
			||||||
 | 
					        return TbNodeUtils.processTemplate(template, templateContext);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public NotificationSettings getNotificationSettings(TenantId tenantId) {
 | 
				
			||||||
 | 
					        return Optional.ofNullable(adminSettingsService.findAdminSettingsByTenantIdAndKey(tenantId, SETTINGS_KEY))
 | 
				
			||||||
 | 
					                .map(adminSettings -> JacksonUtil.treeToValue(adminSettings.getJsonValue(), NotificationSettings.class))
 | 
				
			||||||
 | 
					                .orElseGet(() -> {
 | 
				
			||||||
 | 
					                    NotificationSettings settings = new NotificationSettings();
 | 
				
			||||||
 | 
					                    NotificationDeliveryMethodConfig defaultConfig = new NotificationDeliveryMethodConfig();
 | 
				
			||||||
 | 
					                    defaultConfig.setEnabled(true);
 | 
				
			||||||
 | 
					                    settings.setDeliveryMethodsConfigs(Map.of(
 | 
				
			||||||
 | 
					                            NotificationDeliveryMethod.WEBSOCKET, defaultConfig,
 | 
				
			||||||
 | 
					                            NotificationDeliveryMethod.EMAIL, defaultConfig,
 | 
				
			||||||
 | 
					                            NotificationDeliveryMethod.SMS, defaultConfig
 | 
				
			||||||
 | 
					                    ));
 | 
				
			||||||
 | 
					                    return settings;
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public void saveNotificationSettings(TenantId tenantId, NotificationSettings notificationSettings) {
 | 
				
			||||||
 | 
					        AdminSettings adminSettings = Optional.ofNullable(adminSettingsService.findAdminSettingsByTenantIdAndKey(tenantId, SETTINGS_KEY))
 | 
				
			||||||
 | 
					                .orElseGet(() -> {
 | 
				
			||||||
 | 
					                    AdminSettings newAdminSettings = new AdminSettings();
 | 
				
			||||||
 | 
					                    newAdminSettings.setKey(SETTINGS_KEY);
 | 
				
			||||||
 | 
					                    newAdminSettings.setTenantId(tenantId);
 | 
				
			||||||
 | 
					                    return newAdminSettings;
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					        adminSettings.setJsonValue(JacksonUtil.valueToTree(notificationSettings));
 | 
				
			||||||
 | 
					        adminSettingsService.saveAdminSettings(tenantId, adminSettings);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,79 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 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.service.notification;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.google.common.base.Strings;
 | 
				
			||||||
 | 
					import lombok.Builder;
 | 
				
			||||||
 | 
					import lombok.Getter;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.User;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.id.TenantId;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.notification.NotificationRequest;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.notification.NotificationRequestStats;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.notification.settings.NotificationDeliveryMethodConfig;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.HashMap;
 | 
				
			||||||
 | 
					import java.util.Map;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@SuppressWarnings("unchecked")
 | 
				
			||||||
 | 
					public class NotificationProcessingContext {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Getter
 | 
				
			||||||
 | 
					    private final TenantId tenantId;
 | 
				
			||||||
 | 
					    private final NotificationSettings settings;
 | 
				
			||||||
 | 
					    @Getter
 | 
				
			||||||
 | 
					    private final NotificationRequest request;
 | 
				
			||||||
 | 
					    private final Map<String, String> additionalTemplateContext;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private Map<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> templates;
 | 
				
			||||||
 | 
					    @Getter
 | 
				
			||||||
 | 
					    private NotificationRequestStats stats;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Builder
 | 
				
			||||||
 | 
					    public NotificationProcessingContext(TenantId tenantId, NotificationSettings settings, NotificationRequest request, Map<String, String> additionalTemplateContext) {
 | 
				
			||||||
 | 
					        this.tenantId = tenantId;
 | 
				
			||||||
 | 
					        this.settings = settings;
 | 
				
			||||||
 | 
					        this.request = request;
 | 
				
			||||||
 | 
					        this.additionalTemplateContext = additionalTemplateContext;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public void init(NotificationManagerHelper notificationManagerHelper) {
 | 
				
			||||||
 | 
					        templates = notificationManagerHelper.getTemplates(tenantId, request.getTemplateId(), request.getDeliveryMethods());
 | 
				
			||||||
 | 
					        stats = new NotificationRequestStats();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public <T extends DeliveryMethodNotificationTemplate> T getTemplate(NotificationDeliveryMethod deliveryMethod) {
 | 
				
			||||||
 | 
					        return (T) templates.get(deliveryMethod);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public <C extends NotificationDeliveryMethodConfig> C getDeliveryMethodConfig(NotificationDeliveryMethod deliveryMethod) {
 | 
				
			||||||
 | 
					        return (C) settings.getDeliveryMethodsConfigs().get(deliveryMethod);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public Map<String, String> createTemplateContext(User recipient) {
 | 
				
			||||||
 | 
					        Map<String, String> templateContext = new HashMap<>();
 | 
				
			||||||
 | 
					        templateContext.put("email", recipient.getEmail());
 | 
				
			||||||
 | 
					        templateContext.put("firstName", Strings.nullToEmpty(recipient.getFirstName()));
 | 
				
			||||||
 | 
					        templateContext.put("lastName", Strings.nullToEmpty(recipient.getLastName()));
 | 
				
			||||||
 | 
					        if (additionalTemplateContext != null) {
 | 
				
			||||||
 | 
					            templateContext.putAll(additionalTemplateContext);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return templateContext;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,56 +0,0 @@
 | 
				
			|||||||
/**
 | 
					 | 
				
			||||||
 * 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.service.notification;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import lombok.RequiredArgsConstructor;
 | 
					 | 
				
			||||||
import org.springframework.stereotype.Component;
 | 
					 | 
				
			||||||
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
 | 
					 | 
				
			||||||
import org.thingsboard.server.common.data.id.NotificationTemplateId;
 | 
					 | 
				
			||||||
import org.thingsboard.server.common.data.id.TenantId;
 | 
					 | 
				
			||||||
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
 | 
					 | 
				
			||||||
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.NotificationText;
 | 
					 | 
				
			||||||
import org.thingsboard.server.common.data.notification.template.NotificationTextTemplate;
 | 
					 | 
				
			||||||
import org.thingsboard.server.dao.notification.NotificationTemplateService;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import java.util.List;
 | 
					 | 
				
			||||||
import java.util.Map;
 | 
					 | 
				
			||||||
import java.util.Optional;
 | 
					 | 
				
			||||||
import java.util.stream.Collectors;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Component
 | 
					 | 
				
			||||||
@RequiredArgsConstructor
 | 
					 | 
				
			||||||
public class NotificationTemplateUtil {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private final NotificationTemplateService templateService;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public Map<NotificationDeliveryMethod, NotificationTextTemplate> getTemplates(TenantId tenantId, NotificationTemplateId templateId, List<NotificationDeliveryMethod> deliveryMethods) {
 | 
					 | 
				
			||||||
        NotificationTemplate notificationTemplate = templateService.findNotificationTemplateById(tenantId, templateId);
 | 
					 | 
				
			||||||
        NotificationTemplateConfig config = notificationTemplate.getConfiguration();
 | 
					 | 
				
			||||||
        return deliveryMethods.stream()
 | 
					 | 
				
			||||||
                .collect(Collectors.toMap(k -> k, deliveryMethod -> {
 | 
					 | 
				
			||||||
                    return Optional.ofNullable(config.getTextTemplates())
 | 
					 | 
				
			||||||
                            .map(templates -> templates.get(deliveryMethod))
 | 
					 | 
				
			||||||
                            .orElse(config.getDefaultTextTemplate());
 | 
					 | 
				
			||||||
                }));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public NotificationText processTemplate(NotificationTextTemplate template, Map<String, String> templateContext) {
 | 
					 | 
				
			||||||
        return new NotificationText(TbNodeUtils.processTemplate(template.getBody(), templateContext), template.getSubject());
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -21,9 +21,9 @@ import org.springframework.stereotype.Component;
 | 
				
			|||||||
import org.thingsboard.rule.engine.api.MailService;
 | 
					import org.thingsboard.rule.engine.api.MailService;
 | 
				
			||||||
import org.thingsboard.server.common.data.User;
 | 
					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.NotificationRequest;
 | 
					import org.thingsboard.server.common.data.notification.template.EmailDeliveryMethodNotificationTemplate;
 | 
				
			||||||
import org.thingsboard.server.common.data.notification.template.NotificationText;
 | 
					 | 
				
			||||||
import org.thingsboard.server.service.mail.MailExecutorService;
 | 
					import org.thingsboard.server.service.mail.MailExecutorService;
 | 
				
			||||||
 | 
					import org.thingsboard.server.service.notification.NotificationProcessingContext;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Component
 | 
					@Component
 | 
				
			||||||
@RequiredArgsConstructor
 | 
					@RequiredArgsConstructor
 | 
				
			||||||
@ -33,9 +33,10 @@ public class EmailNotificationChannel implements NotificationChannel {
 | 
				
			|||||||
    private final MailExecutorService executor;
 | 
					    private final MailExecutorService executor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public ListenableFuture<Void> sendNotification(User recipient, NotificationRequest request, NotificationText text) {
 | 
					    public ListenableFuture<Void> sendNotification(User recipient, String text, NotificationProcessingContext ctx) {
 | 
				
			||||||
 | 
					        EmailDeliveryMethodNotificationTemplate template = ctx.getTemplate(NotificationDeliveryMethod.EMAIL);
 | 
				
			||||||
        return executor.submit(() -> {
 | 
					        return executor.submit(() -> {
 | 
				
			||||||
            mailService.sendEmail(recipient.getTenantId(), recipient.getEmail(), text.getSubject(), text.getBody());
 | 
					            mailService.sendEmail(recipient.getTenantId(), recipient.getEmail(), text, template.getSubject());
 | 
				
			||||||
            return null;
 | 
					            return null;
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -18,12 +18,11 @@ 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.User;
 | 
				
			||||||
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
 | 
					import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
 | 
				
			||||||
import org.thingsboard.server.common.data.notification.NotificationRequest;
 | 
					import org.thingsboard.server.service.notification.NotificationProcessingContext;
 | 
				
			||||||
import org.thingsboard.server.common.data.notification.template.NotificationText;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
public interface NotificationChannel {
 | 
					public interface NotificationChannel {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ListenableFuture<Void> sendNotification(User recipient, NotificationRequest request, NotificationText text);
 | 
					    ListenableFuture<Void> sendNotification(User recipient, String text, NotificationProcessingContext ctx);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    NotificationDeliveryMethod getDeliveryMethod();
 | 
					    NotificationDeliveryMethod getDeliveryMethod();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,81 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 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.service.notification.channels;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.google.common.util.concurrent.Futures;
 | 
				
			||||||
 | 
					import com.google.common.util.concurrent.ListenableFuture;
 | 
				
			||||||
 | 
					import lombok.RequiredArgsConstructor;
 | 
				
			||||||
 | 
					import org.apache.commons.lang3.StringUtils;
 | 
				
			||||||
 | 
					import org.springframework.stereotype.Component;
 | 
				
			||||||
 | 
					import org.thingsboard.server.service.slack.SlackService;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.User;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.id.TenantId;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.notification.template.SlackDeliveryMethodNotificationTemplate;
 | 
				
			||||||
 | 
					import org.thingsboard.server.service.notification.NotificationProcessingContext;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import javax.annotation.PostConstruct;
 | 
				
			||||||
 | 
					import java.util.concurrent.ExecutorService;
 | 
				
			||||||
 | 
					import java.util.concurrent.Executors;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Component
 | 
				
			||||||
 | 
					@RequiredArgsConstructor
 | 
				
			||||||
 | 
					public class SlackNotificationChannel implements NotificationChannel {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private final SlackService slackService;
 | 
				
			||||||
 | 
					    private ExecutorService executor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @PostConstruct
 | 
				
			||||||
 | 
					    private void init() {
 | 
				
			||||||
 | 
					        executor = Executors.newSingleThreadExecutor();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public ListenableFuture<Void> sendNotification(User recipient, String text, NotificationProcessingContext ctx) {
 | 
				
			||||||
 | 
					        SlackDeliveryMethodNotificationTemplate template = ctx.getTemplate(NotificationDeliveryMethod.SLACK);
 | 
				
			||||||
 | 
					        SlackNotificationDeliveryMethodConfig config = ctx.getDeliveryMethodConfig(NotificationDeliveryMethod.SLACK);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        String conversationId = template.getConversationId();
 | 
				
			||||||
 | 
					        if (StringUtils.isNotEmpty(conversationId)) {
 | 
				
			||||||
 | 
					            if (ctx.getStats().contains(NotificationDeliveryMethod.SLACK)) {
 | 
				
			||||||
 | 
					                // FIXME stats.sent will be reported anyway
 | 
				
			||||||
 | 
					                return Futures.immediateFuture(null); // if conversationId is set, we only need to send message once
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            String username = StringUtils.join(new String[]{recipient.getFirstName(), recipient.getLastName()}, ' ');
 | 
				
			||||||
 | 
					            if (StringUtils.isNotEmpty(username)) {
 | 
				
			||||||
 | 
					                conversationId = username;
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                return Futures.immediateFailedFuture(new IllegalArgumentException("Couldn't determine Slack username for the user"));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return send(ctx.getTenantId(), config.getBotToken(), conversationId, text);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private ListenableFuture<Void> send(TenantId tenantId, String botToken, String conversationId, String text) {
 | 
				
			||||||
 | 
					        return Futures.submit(() -> {
 | 
				
			||||||
 | 
					            slackService.sendMessage(tenantId, botToken, conversationId, text);
 | 
				
			||||||
 | 
					            return null;
 | 
				
			||||||
 | 
					        }, executor);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public NotificationDeliveryMethod getDeliveryMethod() {
 | 
				
			||||||
 | 
					        return NotificationDeliveryMethod.SLACK;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -23,8 +23,7 @@ import org.springframework.stereotype.Component;
 | 
				
			|||||||
import org.thingsboard.rule.engine.api.SmsService;
 | 
					import org.thingsboard.rule.engine.api.SmsService;
 | 
				
			||||||
import org.thingsboard.server.common.data.User;
 | 
					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.NotificationRequest;
 | 
					import org.thingsboard.server.service.notification.NotificationProcessingContext;
 | 
				
			||||||
import org.thingsboard.server.common.data.notification.template.NotificationText;
 | 
					 | 
				
			||||||
import org.thingsboard.server.service.sms.SmsExecutorService;
 | 
					import org.thingsboard.server.service.sms.SmsExecutorService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Component
 | 
					@Component
 | 
				
			||||||
@ -35,11 +34,11 @@ public class SmsNotificationChannel implements NotificationChannel {
 | 
				
			|||||||
    private final SmsExecutorService executor;
 | 
					    private final SmsExecutorService executor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public ListenableFuture<Void> sendNotification(User recipient, NotificationRequest request, NotificationText text) {
 | 
					    public ListenableFuture<Void> sendNotification(User recipient, String text, NotificationProcessingContext ctx) {
 | 
				
			||||||
        String phone = recipient.getPhone();
 | 
					        String phone = recipient.getPhone();
 | 
				
			||||||
        if (StringUtils.isBlank(phone)) return Futures.immediateFailedFuture(new RuntimeException("User does not have phone number"));
 | 
					        if (StringUtils.isBlank(phone)) return Futures.immediateFailedFuture(new RuntimeException("User does not have phone number"));
 | 
				
			||||||
        return executor.submit(() -> {
 | 
					        return executor.submit(() -> {
 | 
				
			||||||
            smsService.sendSms(recipient.getTenantId(), recipient.getCustomerId(), new String[]{phone}, text.getBody());
 | 
					            smsService.sendSms(recipient.getTenantId(), recipient.getCustomerId(), new String[]{phone}, text);
 | 
				
			||||||
            return null;
 | 
					            return null;
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,101 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 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.service.slack;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.slack.api.Slack;
 | 
				
			||||||
 | 
					import com.slack.api.methods.MethodsClient;
 | 
				
			||||||
 | 
					import com.slack.api.methods.SlackApiTextResponse;
 | 
				
			||||||
 | 
					import com.slack.api.methods.request.chat.ChatPostMessageRequest;
 | 
				
			||||||
 | 
					import com.slack.api.methods.request.conversations.ConversationsListRequest;
 | 
				
			||||||
 | 
					import com.slack.api.methods.request.users.UsersListRequest;
 | 
				
			||||||
 | 
					import com.slack.api.methods.response.chat.ChatPostMessageResponse;
 | 
				
			||||||
 | 
					import com.slack.api.methods.response.conversations.ConversationsListResponse;
 | 
				
			||||||
 | 
					import com.slack.api.methods.response.users.UsersListResponse;
 | 
				
			||||||
 | 
					import com.slack.api.model.ConversationType;
 | 
				
			||||||
 | 
					import org.springframework.stereotype.Service;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.id.TenantId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					import java.util.stream.Collectors;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Service
 | 
				
			||||||
 | 
					public class DefaultSlackService implements SlackService {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private final Slack slack = Slack.getInstance();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public void sendMessage(TenantId tenantId, String token, String conversationId, String message) throws Exception {
 | 
				
			||||||
 | 
					        ChatPostMessageRequest request = ChatPostMessageRequest.builder()
 | 
				
			||||||
 | 
					                .channel(conversationId)
 | 
				
			||||||
 | 
					                .text(message)
 | 
				
			||||||
 | 
					                .build();
 | 
				
			||||||
 | 
					        ChatPostMessageResponse response = slack.methods(token).chatPostMessage(request);
 | 
				
			||||||
 | 
					        check(response);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public List<SlackConversation> listConversations(TenantId tenantId, String token, SlackConversation.Type conversationType) throws Exception {
 | 
				
			||||||
 | 
					        MethodsClient methods = slack.methods(token);
 | 
				
			||||||
 | 
					        if (conversationType == SlackConversation.Type.USER) {
 | 
				
			||||||
 | 
					            UsersListResponse usersListResponse = methods.usersList(UsersListRequest.builder()
 | 
				
			||||||
 | 
					                    .limit(1000)
 | 
				
			||||||
 | 
					                    .build());
 | 
				
			||||||
 | 
					            check(usersListResponse);
 | 
				
			||||||
 | 
					            return usersListResponse.getMembers().stream()
 | 
				
			||||||
 | 
					                    .filter(user -> !user.isDeleted() && !user.isStranger() && !user.isBot())
 | 
				
			||||||
 | 
					                    .map(user -> {
 | 
				
			||||||
 | 
					                        SlackConversation conversation = new SlackConversation();
 | 
				
			||||||
 | 
					                        conversation.setId(user.getId());
 | 
				
			||||||
 | 
					                        conversation.setName(String.format("@%s (%s)", user.getName(), user.getRealName()));
 | 
				
			||||||
 | 
					                        return conversation;
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                    .collect(Collectors.toList());
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            ConversationsListResponse conversationsListResponse = methods.conversationsList(ConversationsListRequest.builder()
 | 
				
			||||||
 | 
					                    .types(List.of(conversationType == SlackConversation.Type.PUBLIC_CHANNEL ?
 | 
				
			||||||
 | 
					                            ConversationType.PUBLIC_CHANNEL :
 | 
				
			||||||
 | 
					                            ConversationType.PRIVATE_CHANNEL))
 | 
				
			||||||
 | 
					                    .limit(1000)
 | 
				
			||||||
 | 
					                    .excludeArchived(true)
 | 
				
			||||||
 | 
					                    .build());
 | 
				
			||||||
 | 
					            check(conversationsListResponse);
 | 
				
			||||||
 | 
					            return conversationsListResponse.getChannels().stream()
 | 
				
			||||||
 | 
					                    .filter(channel -> !channel.isArchived())
 | 
				
			||||||
 | 
					                    .map(channel -> {
 | 
				
			||||||
 | 
					                        SlackConversation conversation = new SlackConversation();
 | 
				
			||||||
 | 
					                        conversation.setId(channel.getId());
 | 
				
			||||||
 | 
					                        conversation.setName("#" + channel.getName());
 | 
				
			||||||
 | 
					                        return conversation;
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                    .collect(Collectors.toList());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private void check(SlackApiTextResponse slackResponse) {
 | 
				
			||||||
 | 
					        if (!slackResponse.isOk()) {
 | 
				
			||||||
 | 
					            String error = slackResponse.getError();
 | 
				
			||||||
 | 
					            if (error == null) {
 | 
				
			||||||
 | 
					                error = "unknown error";
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (error.contains("missing_scope")) {
 | 
				
			||||||
 | 
					                String neededScope = slackResponse.getNeeded();
 | 
				
			||||||
 | 
					                throw new RuntimeException("Bot token scope '" + neededScope + "' is needed");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            throw new RuntimeException("Failed to send message via Slack: " + error);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 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.service.slack;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import lombok.Data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Data
 | 
				
			||||||
 | 
					public class SlackConversation {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private String id;
 | 
				
			||||||
 | 
					    private String name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public enum Type {
 | 
				
			||||||
 | 
					        USER,
 | 
				
			||||||
 | 
					        PUBLIC_CHANNEL,
 | 
				
			||||||
 | 
					        PRIVATE_CHANNEL
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 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.service.slack;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.id.TenantId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public interface SlackService {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void sendMessage(TenantId tenantId, String token, String conversationId, String message) throws Exception;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    List<SlackConversation> listConversations(TenantId tenantId, String token, SlackConversation.Type conversationType) throws Exception;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -34,12 +34,15 @@ import org.thingsboard.server.common.data.notification.NotificationRequest;
 | 
				
			|||||||
import org.thingsboard.server.common.data.notification.NotificationRequestConfig;
 | 
					import org.thingsboard.server.common.data.notification.NotificationRequestConfig;
 | 
				
			||||||
import org.thingsboard.server.common.data.notification.NotificationRequestStats;
 | 
					import org.thingsboard.server.common.data.notification.NotificationRequestStats;
 | 
				
			||||||
import org.thingsboard.server.common.data.notification.NotificationRequestStatus;
 | 
					import org.thingsboard.server.common.data.notification.NotificationRequestStatus;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.notification.settings.NotificationDeliveryMethodConfig;
 | 
				
			||||||
 | 
					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.SingleUserNotificationTargetConfig;
 | 
					import org.thingsboard.server.common.data.notification.targets.SingleUserNotificationTargetConfig;
 | 
				
			||||||
import org.thingsboard.server.common.data.notification.targets.UserListNotificationTargetConfig;
 | 
					import org.thingsboard.server.common.data.notification.targets.UserListNotificationTargetConfig;
 | 
				
			||||||
 | 
					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.NotificationTextTemplate;
 | 
					import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate;
 | 
				
			||||||
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.security.Authority;
 | 
					import org.thingsboard.server.common.data.security.Authority;
 | 
				
			||||||
@ -363,7 +366,7 @@ public class NotificationApiTest extends AbstractControllerTest {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private NotificationRequest submitNotificationRequest(NotificationTargetId targetId, String text, int delayInSec, NotificationDeliveryMethod... deliveryMethods) {
 | 
					    private NotificationRequest submitNotificationRequest(NotificationTargetId targetId, String text, int delayInSec, NotificationDeliveryMethod... deliveryMethods) {
 | 
				
			||||||
        NotificationTemplate notificationTemplate = createNotificationTemplate(text);
 | 
					        NotificationTemplate notificationTemplate = createNotificationTemplate(text, deliveryMethods);
 | 
				
			||||||
        NotificationRequestConfig config = new NotificationRequestConfig();
 | 
					        NotificationRequestConfig config = new NotificationRequestConfig();
 | 
				
			||||||
        config.setSendingDelayInSec(delayInSec);
 | 
					        config.setSendingDelayInSec(delayInSec);
 | 
				
			||||||
        NotificationInfo notificationInfo = new NotificationInfo();
 | 
					        NotificationInfo notificationInfo = new NotificationInfo();
 | 
				
			||||||
@ -380,18 +383,36 @@ public class NotificationApiTest extends AbstractControllerTest {
 | 
				
			|||||||
        return doPost("/api/notification/request", notificationRequest, NotificationRequest.class);
 | 
					        return doPost("/api/notification/request", notificationRequest, NotificationRequest.class);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private NotificationTemplate createNotificationTemplate(String text) {
 | 
					    private NotificationTemplate createNotificationTemplate(String text, NotificationDeliveryMethod... deliveryMethods) {
 | 
				
			||||||
        NotificationTemplate notificationTemplate = new NotificationTemplate();
 | 
					        NotificationTemplate notificationTemplate = new NotificationTemplate();
 | 
				
			||||||
        notificationTemplate.setTenantId(tenantId);
 | 
					        notificationTemplate.setTenantId(tenantId);
 | 
				
			||||||
        notificationTemplate.setName("Notification template for testing");
 | 
					        notificationTemplate.setName("Notification template for testing");
 | 
				
			||||||
        NotificationTemplateConfig config = new NotificationTemplateConfig();
 | 
					        NotificationTemplateConfig config = new NotificationTemplateConfig();
 | 
				
			||||||
        NotificationTextTemplate textTemplate = new NotificationTextTemplate();
 | 
					        DeliveryMethodNotificationTemplate defaultTemplate = new DeliveryMethodNotificationTemplate();
 | 
				
			||||||
        textTemplate.setBody(text);
 | 
					        defaultTemplate.setBody(text);
 | 
				
			||||||
        config.setDefaultTextTemplate(textTemplate);
 | 
					        config.setDefaultTemplate(defaultTemplate);
 | 
				
			||||||
 | 
					        for (NotificationDeliveryMethod deliveryMethod : deliveryMethods) {
 | 
				
			||||||
 | 
					            if (deliveryMethod == NotificationDeliveryMethod.EMAIL) {
 | 
				
			||||||
 | 
					                EmailDeliveryMethodNotificationTemplate emailNotificationTemplate = new EmailDeliveryMethodNotificationTemplate();
 | 
				
			||||||
 | 
					                emailNotificationTemplate.setSubject("Hello from test");
 | 
				
			||||||
 | 
					                emailNotificationTemplate.setBody(text);
 | 
				
			||||||
 | 
					                config.setTemplates(Map.of(
 | 
				
			||||||
 | 
					                        deliveryMethod, emailNotificationTemplate
 | 
				
			||||||
 | 
					                ));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        notificationTemplate.setConfiguration(config);
 | 
					        notificationTemplate.setConfiguration(config);
 | 
				
			||||||
        return doPost("/api/notification/template", notificationTemplate, NotificationTemplate.class);
 | 
					        return doPost("/api/notification/template", notificationTemplate, NotificationTemplate.class);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private void configureNotificationDeliveryMethods(NotificationDeliveryMethod... deliveryMethods) {
 | 
				
			||||||
 | 
					        NotificationSettings notificationSettings = new NotificationSettings();
 | 
				
			||||||
 | 
					        notificationSettings.setDeliveryMethodsConfigs(new HashMap<>());
 | 
				
			||||||
 | 
					        for (NotificationDeliveryMethod deliveryMethod : deliveryMethods) {
 | 
				
			||||||
 | 
					            notificationSettings.getDeliveryMethodsConfigs().put(deliveryMethod, new NotificationDeliveryMethodConfig());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private NotificationRequest findNotificationRequest(NotificationRequestId id) throws Exception {
 | 
					    private NotificationRequest findNotificationRequest(NotificationRequestId id) throws Exception {
 | 
				
			||||||
        return doGet("/api/notification/request/" + id, NotificationRequest.class);
 | 
					        return doGet("/api/notification/request/" + id, NotificationRequest.class);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -51,4 +51,8 @@ public class NotificationRequestStats {
 | 
				
			|||||||
        errors.computeIfAbsent(deliveryMethod, k -> new ConcurrentHashMap<>()).put(recipient.getEmail(), errorMessage);
 | 
					        errors.computeIfAbsent(deliveryMethod, k -> new ConcurrentHashMap<>()).put(recipient.getEmail(), errorMessage);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public boolean contains(NotificationDeliveryMethod deliveryMethod) {
 | 
				
			||||||
 | 
					        return sent.containsKey(deliveryMethod) || errors.containsKey(deliveryMethod);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -17,22 +17,21 @@ package org.thingsboard.server.common.data.notification.settings;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 | 
					import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 | 
				
			||||||
import com.fasterxml.jackson.annotation.JsonSubTypes;
 | 
					import com.fasterxml.jackson.annotation.JsonSubTypes;
 | 
				
			||||||
 | 
					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.NotificationDeliveryMethod;
 | 
					import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@JsonIgnoreProperties(ignoreUnknown = true)
 | 
					@JsonIgnoreProperties(ignoreUnknown = true)
 | 
				
			||||||
//@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "method", visible = true, include = JsonTypeInfo.As.EXISTING_PROPERTY, defaultImpl = NotificationDeliveryMethodConfig.class)
 | 
					@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "method", include = JsonTypeInfo.As.EXISTING_PROPERTY,
 | 
				
			||||||
//@JsonSubTypes({
 | 
					        visible = true, defaultImpl = NotificationDeliveryMethodConfig.class)
 | 
				
			||||||
////        @JsonSubTypes.Type(name = "SLACK", value = SlackNotificationDeliveryMethodConfig.class),
 | 
					@JsonSubTypes({
 | 
				
			||||||
//})
 | 
					        @Type(name = "SLACK", value = SlackNotificationDeliveryMethodConfig.class),
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
@Data
 | 
					@Data
 | 
				
			||||||
public class NotificationDeliveryMethodConfig {
 | 
					public class NotificationDeliveryMethodConfig {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private boolean enabled;
 | 
					    private boolean enabled;
 | 
				
			||||||
 | 
					    private NotificationDeliveryMethod method;
 | 
				
			||||||
    public NotificationDeliveryMethod getMethod() {
 | 
					 | 
				
			||||||
        return null;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 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.settings;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import lombok.Data;
 | 
				
			||||||
 | 
					import lombok.EqualsAndHashCode;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Data
 | 
				
			||||||
 | 
					@EqualsAndHashCode(callSuper = true)
 | 
				
			||||||
 | 
					public class SlackNotificationDeliveryMethodConfig extends NotificationDeliveryMethodConfig {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private String botToken;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -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.template;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 | 
				
			||||||
 | 
					import com.fasterxml.jackson.annotation.JsonSubTypes;
 | 
				
			||||||
 | 
					import com.fasterxml.jackson.annotation.JsonTypeInfo;
 | 
				
			||||||
 | 
					import lombok.Data;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@JsonIgnoreProperties(ignoreUnknown = true)
 | 
				
			||||||
 | 
					@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "method", visible = true, include = JsonTypeInfo.As.EXISTING_PROPERTY, defaultImpl = DeliveryMethodNotificationTemplate.class)
 | 
				
			||||||
 | 
					@JsonSubTypes({
 | 
				
			||||||
 | 
					        @JsonSubTypes.Type(name = "EMAIL", value = EmailDeliveryMethodNotificationTemplate.class),
 | 
				
			||||||
 | 
					        @JsonSubTypes.Type(name = "SLACK", value = SlackDeliveryMethodNotificationTemplate.class)
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					@Data
 | 
				
			||||||
 | 
					public class DeliveryMethodNotificationTemplate {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private String body;
 | 
				
			||||||
 | 
					    private NotificationDeliveryMethod method;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -16,11 +16,12 @@
 | 
				
			|||||||
package org.thingsboard.server.common.data.notification.template;
 | 
					package org.thingsboard.server.common.data.notification.template;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import lombok.Data;
 | 
					import lombok.Data;
 | 
				
			||||||
 | 
					import lombok.EqualsAndHashCode;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Data
 | 
					@Data
 | 
				
			||||||
public class NotificationTextTemplate {
 | 
					@EqualsAndHashCode(callSuper = true)
 | 
				
			||||||
 | 
					public class EmailDeliveryMethodNotificationTemplate extends DeliveryMethodNotificationTemplate {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private String body;
 | 
					 | 
				
			||||||
    private String subject;
 | 
					    private String subject;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -23,7 +23,7 @@ import java.util.Map;
 | 
				
			|||||||
@Data
 | 
					@Data
 | 
				
			||||||
public class NotificationTemplateConfig {
 | 
					public class NotificationTemplateConfig {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private NotificationTextTemplate defaultTextTemplate;
 | 
					    private DeliveryMethodNotificationTemplate defaultTemplate;
 | 
				
			||||||
    private Map<NotificationDeliveryMethod, NotificationTextTemplate> textTemplates;
 | 
					    private Map<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> templates;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 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.template;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import lombok.Data;
 | 
				
			||||||
 | 
					import lombok.EqualsAndHashCode;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Data
 | 
				
			||||||
 | 
					@EqualsAndHashCode(callSuper = true)
 | 
				
			||||||
 | 
					public class SlackDeliveryMethodNotificationTemplate extends DeliveryMethodNotificationTemplate {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private String conversationId; // not required, set from user's name if not set
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										6
									
								
								pom.xml
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								pom.xml
									
									
									
									
									
								
							@ -144,6 +144,7 @@
 | 
				
			|||||||
        <jgit.version>6.1.0.202203080745-r</jgit.version>
 | 
					        <jgit.version>6.1.0.202203080745-r</jgit.version>
 | 
				
			||||||
        <exp4j.version>0.4.8</exp4j.version>
 | 
					        <exp4j.version>0.4.8</exp4j.version>
 | 
				
			||||||
        <aerogear-otp.version>1.0.0</aerogear-otp.version>
 | 
					        <aerogear-otp.version>1.0.0</aerogear-otp.version>
 | 
				
			||||||
 | 
					        <slack-api.version>1.12.1</slack-api.version>
 | 
				
			||||||
    </properties>
 | 
					    </properties>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <modules>
 | 
					    <modules>
 | 
				
			||||||
@ -1945,6 +1946,11 @@
 | 
				
			|||||||
                <artifactId>aerogear-otp-java</artifactId>
 | 
					                <artifactId>aerogear-otp-java</artifactId>
 | 
				
			||||||
                <version>${aerogear-otp.version}</version>
 | 
					                <version>${aerogear-otp.version}</version>
 | 
				
			||||||
            </dependency>
 | 
					            </dependency>
 | 
				
			||||||
 | 
					            <dependency>
 | 
				
			||||||
 | 
					                <groupId>com.slack.api</groupId>
 | 
				
			||||||
 | 
					                <artifactId>slack-api-client</artifactId>
 | 
				
			||||||
 | 
					                <version>${slack-api.version}</version>
 | 
				
			||||||
 | 
					            </dependency>
 | 
				
			||||||
            <dependency>
 | 
					            <dependency>
 | 
				
			||||||
                <groupId>org.eclipse.jgit</groupId>
 | 
					                <groupId>org.eclipse.jgit</groupId>
 | 
				
			||||||
                <artifactId>org.eclipse.jgit</artifactId>
 | 
					                <artifactId>org.eclipse.jgit</artifactId>
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user