Slack notification channel; refactoring
This commit is contained in:
		
							parent
							
								
									4b39d2a221
								
							
						
					
					
						commit
						738b9dc494
					
				@ -356,6 +356,10 @@
 | 
			
		||||
            <groupId>org.jboss.aerogear</groupId>
 | 
			
		||||
            <artifactId>aerogear-otp-java</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>com.slack.api</groupId>
 | 
			
		||||
            <artifactId>slack-api-client</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
    </dependencies>
 | 
			
		||||
 | 
			
		||||
    <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 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.NotificationRequestId;
 | 
			
		||||
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.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.PageLink;
 | 
			
		||||
import org.thingsboard.server.dao.notification.NotificationRequestService;
 | 
			
		||||
import org.thingsboard.server.dao.notification.NotificationService;
 | 
			
		||||
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.permission.Operation;
 | 
			
		||||
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;
 | 
			
		||||
 | 
			
		||||
@RestController
 | 
			
		||||
@ -58,6 +65,8 @@ public class NotificationController extends BaseController {
 | 
			
		||||
    private final NotificationService notificationService;
 | 
			
		||||
    private final NotificationRequestService notificationRequestService;
 | 
			
		||||
    private final NotificationManager notificationManager;
 | 
			
		||||
    private final NotificationManagerHelper notificationManagerHelper;
 | 
			
		||||
    private final SlackService slackService;
 | 
			
		||||
 | 
			
		||||
    @GetMapping("/notifications")
 | 
			
		||||
    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
 | 
			
		||||
@ -133,4 +142,33 @@ public class NotificationController extends BaseController {
 | 
			
		||||
        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;
 | 
			
		||||
 | 
			
		||||
import com.google.common.base.Strings;
 | 
			
		||||
import com.google.common.util.concurrent.Futures;
 | 
			
		||||
import com.google.common.util.concurrent.ListenableFuture;
 | 
			
		||||
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.NotificationRequest;
 | 
			
		||||
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.NotificationStatus;
 | 
			
		||||
import org.thingsboard.server.common.data.notification.template.NotificationText;
 | 
			
		||||
import org.thingsboard.server.common.data.notification.template.NotificationTextTemplate;
 | 
			
		||||
import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
 | 
			
		||||
import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate;
 | 
			
		||||
import org.thingsboard.server.common.msg.queue.ServiceType;
 | 
			
		||||
import org.thingsboard.server.common.msg.queue.TbCallback;
 | 
			
		||||
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 java.util.ArrayList;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.HashSet;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
@ -74,7 +71,7 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl
 | 
			
		||||
    private final NotificationTargetService notificationTargetService;
 | 
			
		||||
    private final NotificationRequestService notificationRequestService;
 | 
			
		||||
    private final NotificationService notificationService;
 | 
			
		||||
    private final NotificationTemplateUtil notificationTemplateUtil;
 | 
			
		||||
    private final NotificationManagerHelper notificationManagerHelper;
 | 
			
		||||
    private final DbCallbackExecutorService dbCallbackExecutorService;
 | 
			
		||||
    private final NotificationsTopicService notificationsTopicService;
 | 
			
		||||
    private final TbQueueProducerProvider producerProvider;
 | 
			
		||||
@ -85,6 +82,13 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl
 | 
			
		||||
    public NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) {
 | 
			
		||||
        log.debug("Processing notification request (tenant id: {}, notification target id: {})", tenantId, notificationRequest.getTargetId());
 | 
			
		||||
        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) {
 | 
			
		||||
            NotificationRequestConfig config = notificationRequest.getAdditionalConfig();
 | 
			
		||||
            if (config.getSendingDelayInSec() > 0 && notificationRequest.getId() == null) {
 | 
			
		||||
@ -98,9 +102,13 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl
 | 
			
		||||
        notificationRequest.setStatus(NotificationRequestStatus.PROCESSED);
 | 
			
		||||
        NotificationRequest savedNotificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest);
 | 
			
		||||
 | 
			
		||||
        NotificationRequestStats stats = new NotificationRequestStats();
 | 
			
		||||
        Map<NotificationDeliveryMethod, NotificationTextTemplate> textTemplates = notificationTemplateUtil.getTemplates(tenantId, notificationRequest.getTemplateId(), savedNotificationRequest.getDeliveryMethods());
 | 
			
		||||
        savedNotificationRequest.setTemplateContext(notificationRequest.getTemplateContext());
 | 
			
		||||
        NotificationProcessingContext ctx = NotificationProcessingContext.builder()
 | 
			
		||||
                .tenantId(tenantId)
 | 
			
		||||
                .settings(settings)
 | 
			
		||||
                .request(savedNotificationRequest)
 | 
			
		||||
                .additionalTemplateContext(notificationRequest.getTemplateContext())
 | 
			
		||||
                .build();
 | 
			
		||||
        ctx.init(notificationManagerHelper);
 | 
			
		||||
 | 
			
		||||
        DaoUtil.processBatches(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());
 | 
			
		||||
 | 
			
		||||
                List<User> recipients = recipientsBatch.getData();
 | 
			
		||||
                NotificationTextTemplate textTemplate = textTemplates.get(deliveryMethod);
 | 
			
		||||
                for (User recipient : recipients) {
 | 
			
		||||
                    ListenableFuture<Void> resultFuture = processForRecipient(recipient, savedNotificationRequest, notificationChannel, textTemplate, stats);
 | 
			
		||||
                    ListenableFuture<Void> resultFuture = processForRecipient(notificationChannel, recipient, ctx);
 | 
			
		||||
                    DonAsynchron.withCallback(resultFuture, result -> {
 | 
			
		||||
                        stats.reportSent(deliveryMethod);
 | 
			
		||||
                        ctx.getStats().reportSent(deliveryMethod);
 | 
			
		||||
                    }, error -> {
 | 
			
		||||
                        stats.reportError(deliveryMethod, recipient, error);
 | 
			
		||||
                        ctx.getStats().reportError(deliveryMethod, recipient, error);
 | 
			
		||||
                    }, dbCallbackExecutorService);
 | 
			
		||||
                    results.add(resultFuture);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Futures.whenAllComplete(results).run(() -> {
 | 
			
		||||
                try {
 | 
			
		||||
                    notificationRequestService.updateNotificationRequestStats(tenantId, savedNotificationRequest.getId(), stats);
 | 
			
		||||
                    notificationRequestService.updateNotificationRequestStats(tenantId, savedNotificationRequest.getId(), ctx.getStats());
 | 
			
		||||
                } catch (Exception 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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private ListenableFuture<Void> processForRecipient(User recipient, NotificationRequest notificationRequest, NotificationChannel notificationChannel,
 | 
			
		||||
                                                       NotificationTextTemplate textTemplate, NotificationRequestStats stats) {
 | 
			
		||||
        NotificationText text;
 | 
			
		||||
    private ListenableFuture<Void> processForRecipient(NotificationChannel notificationChannel, User recipient, NotificationProcessingContext ctx) {
 | 
			
		||||
        String text;
 | 
			
		||||
        try {
 | 
			
		||||
            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 (notificationRequest.getTemplateContext() != null) {
 | 
			
		||||
                templateContext.putAll(notificationRequest.getTemplateContext());
 | 
			
		||||
            }
 | 
			
		||||
            text = notificationTemplateUtil.processTemplate(textTemplate, templateContext);
 | 
			
		||||
            DeliveryMethodNotificationTemplate template = ctx.getTemplate(notificationChannel.getDeliveryMethod());
 | 
			
		||||
            text = notificationManagerHelper.processTemplate(template.getBody(), ctx.createTemplateContext(recipient));
 | 
			
		||||
        } catch (Exception 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) {
 | 
			
		||||
@ -167,13 +167,14 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @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());
 | 
			
		||||
        Notification notification = Notification.builder()
 | 
			
		||||
                .requestId(request.getId())
 | 
			
		||||
                .recipientId(recipient.getId())
 | 
			
		||||
                .type(request.getType())
 | 
			
		||||
                .text(text.getBody())
 | 
			
		||||
                .text(text)
 | 
			
		||||
                .info(request.getInfo())
 | 
			
		||||
                .originatorType(request.getOriginatorType())
 | 
			
		||||
                .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.server.common.data.User;
 | 
			
		||||
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
 | 
			
		||||
import org.thingsboard.server.common.data.notification.NotificationRequest;
 | 
			
		||||
import org.thingsboard.server.common.data.notification.template.NotificationText;
 | 
			
		||||
import org.thingsboard.server.common.data.notification.template.EmailDeliveryMethodNotificationTemplate;
 | 
			
		||||
import org.thingsboard.server.service.mail.MailExecutorService;
 | 
			
		||||
import org.thingsboard.server.service.notification.NotificationProcessingContext;
 | 
			
		||||
 | 
			
		||||
@Component
 | 
			
		||||
@RequiredArgsConstructor
 | 
			
		||||
@ -33,9 +33,10 @@ public class EmailNotificationChannel implements NotificationChannel {
 | 
			
		||||
    private final MailExecutorService executor;
 | 
			
		||||
 | 
			
		||||
    @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(() -> {
 | 
			
		||||
            mailService.sendEmail(recipient.getTenantId(), recipient.getEmail(), text.getSubject(), text.getBody());
 | 
			
		||||
            mailService.sendEmail(recipient.getTenantId(), recipient.getEmail(), text, template.getSubject());
 | 
			
		||||
            return null;
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -18,12 +18,11 @@ package org.thingsboard.server.service.notification.channels;
 | 
			
		||||
import com.google.common.util.concurrent.ListenableFuture;
 | 
			
		||||
import org.thingsboard.server.common.data.User;
 | 
			
		||||
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
 | 
			
		||||
import org.thingsboard.server.common.data.notification.NotificationRequest;
 | 
			
		||||
import org.thingsboard.server.common.data.notification.template.NotificationText;
 | 
			
		||||
import org.thingsboard.server.service.notification.NotificationProcessingContext;
 | 
			
		||||
 | 
			
		||||
public interface NotificationChannel {
 | 
			
		||||
 | 
			
		||||
    ListenableFuture<Void> sendNotification(User recipient, NotificationRequest request, NotificationText text);
 | 
			
		||||
    ListenableFuture<Void> sendNotification(User recipient, String text, NotificationProcessingContext ctx);
 | 
			
		||||
 | 
			
		||||
    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.server.common.data.User;
 | 
			
		||||
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
 | 
			
		||||
import org.thingsboard.server.common.data.notification.NotificationRequest;
 | 
			
		||||
import org.thingsboard.server.common.data.notification.template.NotificationText;
 | 
			
		||||
import org.thingsboard.server.service.notification.NotificationProcessingContext;
 | 
			
		||||
import org.thingsboard.server.service.sms.SmsExecutorService;
 | 
			
		||||
 | 
			
		||||
@Component
 | 
			
		||||
@ -35,11 +34,11 @@ public class SmsNotificationChannel implements NotificationChannel {
 | 
			
		||||
    private final SmsExecutorService executor;
 | 
			
		||||
 | 
			
		||||
    @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();
 | 
			
		||||
        if (StringUtils.isBlank(phone)) return Futures.immediateFailedFuture(new RuntimeException("User does not have phone number"));
 | 
			
		||||
        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;
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -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.NotificationRequestStats;
 | 
			
		||||
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.SingleUserNotificationTargetConfig;
 | 
			
		||||
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.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.PageLink;
 | 
			
		||||
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) {
 | 
			
		||||
        NotificationTemplate notificationTemplate = createNotificationTemplate(text);
 | 
			
		||||
        NotificationTemplate notificationTemplate = createNotificationTemplate(text, deliveryMethods);
 | 
			
		||||
        NotificationRequestConfig config = new NotificationRequestConfig();
 | 
			
		||||
        config.setSendingDelayInSec(delayInSec);
 | 
			
		||||
        NotificationInfo notificationInfo = new NotificationInfo();
 | 
			
		||||
@ -380,18 +383,36 @@ public class NotificationApiTest extends AbstractControllerTest {
 | 
			
		||||
        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.setTenantId(tenantId);
 | 
			
		||||
        notificationTemplate.setName("Notification template for testing");
 | 
			
		||||
        NotificationTemplateConfig config = new NotificationTemplateConfig();
 | 
			
		||||
        NotificationTextTemplate textTemplate = new NotificationTextTemplate();
 | 
			
		||||
        textTemplate.setBody(text);
 | 
			
		||||
        config.setDefaultTextTemplate(textTemplate);
 | 
			
		||||
        DeliveryMethodNotificationTemplate defaultTemplate = new DeliveryMethodNotificationTemplate();
 | 
			
		||||
        defaultTemplate.setBody(text);
 | 
			
		||||
        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);
 | 
			
		||||
        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 {
 | 
			
		||||
        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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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.JsonSubTypes;
 | 
			
		||||
import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
 | 
			
		||||
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 = NotificationDeliveryMethodConfig.class)
 | 
			
		||||
//@JsonSubTypes({
 | 
			
		||||
////        @JsonSubTypes.Type(name = "SLACK", value = SlackNotificationDeliveryMethodConfig.class),
 | 
			
		||||
//})
 | 
			
		||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "method", include = JsonTypeInfo.As.EXISTING_PROPERTY,
 | 
			
		||||
        visible = true, defaultImpl = NotificationDeliveryMethodConfig.class)
 | 
			
		||||
@JsonSubTypes({
 | 
			
		||||
        @Type(name = "SLACK", value = SlackNotificationDeliveryMethodConfig.class),
 | 
			
		||||
})
 | 
			
		||||
@Data
 | 
			
		||||
public class NotificationDeliveryMethodConfig {
 | 
			
		||||
 | 
			
		||||
    private boolean enabled;
 | 
			
		||||
 | 
			
		||||
    public NotificationDeliveryMethod getMethod() {
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
    private NotificationDeliveryMethod method;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import lombok.EqualsAndHashCode;
 | 
			
		||||
 | 
			
		||||
@Data
 | 
			
		||||
public class NotificationTextTemplate {
 | 
			
		||||
@EqualsAndHashCode(callSuper = true)
 | 
			
		||||
public class EmailDeliveryMethodNotificationTemplate extends DeliveryMethodNotificationTemplate {
 | 
			
		||||
 | 
			
		||||
    private String body;
 | 
			
		||||
    private String subject;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -23,7 +23,7 @@ import java.util.Map;
 | 
			
		||||
@Data
 | 
			
		||||
public class NotificationTemplateConfig {
 | 
			
		||||
 | 
			
		||||
    private NotificationTextTemplate defaultTextTemplate;
 | 
			
		||||
    private Map<NotificationDeliveryMethod, NotificationTextTemplate> textTemplates;
 | 
			
		||||
    private DeliveryMethodNotificationTemplate defaultTemplate;
 | 
			
		||||
    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>
 | 
			
		||||
        <exp4j.version>0.4.8</exp4j.version>
 | 
			
		||||
        <aerogear-otp.version>1.0.0</aerogear-otp.version>
 | 
			
		||||
        <slack-api.version>1.12.1</slack-api.version>
 | 
			
		||||
    </properties>
 | 
			
		||||
 | 
			
		||||
    <modules>
 | 
			
		||||
@ -1945,6 +1946,11 @@
 | 
			
		||||
                <artifactId>aerogear-otp-java</artifactId>
 | 
			
		||||
                <version>${aerogear-otp.version}</version>
 | 
			
		||||
            </dependency>
 | 
			
		||||
            <dependency>
 | 
			
		||||
                <groupId>com.slack.api</groupId>
 | 
			
		||||
                <artifactId>slack-api-client</artifactId>
 | 
			
		||||
                <version>${slack-api.version}</version>
 | 
			
		||||
            </dependency>
 | 
			
		||||
            <dependency>
 | 
			
		||||
                <groupId>org.eclipse.jgit</groupId>
 | 
			
		||||
                <artifactId>org.eclipse.jgit</artifactId>
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user