Merge pull request #12062 from thingsboard/feature/system-notifications

System notification type; improvements for notification center
This commit is contained in:
Andrew Shvayka 2024-11-20 13:59:05 +01:00 committed by GitHub
commit eb54dd50e8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 247 additions and 107 deletions

View File

@ -90,6 +90,7 @@ import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.HasId;
import org.thingsboard.server.common.data.id.MobileAppBundleId;
import org.thingsboard.server.common.data.id.MobileAppId;
import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.id.OAuth2ClientId;
import org.thingsboard.server.common.data.id.OtaPackageId;
import org.thingsboard.server.common.data.id.QueueId;
@ -105,6 +106,7 @@ import org.thingsboard.server.common.data.id.WidgetTypeId;
import org.thingsboard.server.common.data.id.WidgetsBundleId;
import org.thingsboard.server.common.data.mobile.app.MobileApp;
import org.thingsboard.server.common.data.mobile.bundle.MobileAppBundle;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.oauth2.OAuth2Client;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.page.SortOrder;
@ -141,6 +143,7 @@ import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.mobile.MobileAppBundleService;
import org.thingsboard.server.dao.mobile.MobileAppService;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.notification.NotificationTargetService;
import org.thingsboard.server.dao.oauth2.OAuth2ClientService;
import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService;
import org.thingsboard.server.dao.ota.OtaPackageService;
@ -355,6 +358,9 @@ public abstract class BaseController {
@Autowired
protected TbServiceInfoProvider serviceInfoProvider;
@Autowired
protected NotificationTargetService notificationTargetService;
@Value("${server.log_controller_error_stack_trace}")
@Getter
private boolean logControllerErrorStackTrace;
@ -852,6 +858,10 @@ public abstract class BaseController {
return checkEntityId(mobileAppBundleId, mobileAppBundleService::findMobileAppBundleById, operation);
}
NotificationTarget checkNotificationTargetId(NotificationTargetId notificationTargetId, Operation operation) throws ThingsboardException {
return checkEntityId(notificationTargetId, notificationTargetService::findNotificationTargetById, operation);
}
protected <I extends EntityId> I emptyId(EntityType entityType) {
return (I) EntityIdFactory.getByTypeAndUuid(entityType, ModelConstants.NULL_UUID);
}

View File

@ -266,6 +266,12 @@ public class NotificationController extends BaseController {
}
notificationRequest.setTenantId(user.getTenantId());
checkEntity(notificationRequest.getId(), notificationRequest, NOTIFICATION);
List<NotificationTargetId> targets = notificationRequest.getTargets().stream()
.map(NotificationTargetId::new)
.toList();
for (NotificationTargetId targetId : targets) {
checkNotificationTargetId(targetId, Operation.READ);
}
notificationRequest.setOriginatorEntityId(user.getId());
notificationRequest.setInfo(null);
@ -316,6 +322,8 @@ public class NotificationController extends BaseController {
Map<String, Integer> recipientsCountByTarget = new LinkedHashMap<>();
Map<NotificationTargetType, NotificationRecipient> firstRecipient = new HashMap<>();
for (NotificationTarget target : targets) {
checkEntity(getCurrentUser(), target, Operation.READ);
int recipientsCount;
List<NotificationRecipient> recipientsPart;
NotificationTargetType targetType = target.getConfiguration().getType();

View File

@ -127,7 +127,7 @@ public class NotificationTargetController extends BaseController {
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public NotificationTarget getNotificationTargetById(@PathVariable UUID id) throws ThingsboardException {
NotificationTargetId notificationTargetId = new NotificationTargetId(id);
return checkEntityId(notificationTargetId, notificationTargetService::findNotificationTargetById, Operation.READ);
return checkNotificationTargetId(notificationTargetId, Operation.READ);
}
@ApiOperation(value = "Get recipients for notification target config (getRecipientsForNotificationTargetConfig)",
@ -214,7 +214,7 @@ public class NotificationTargetController extends BaseController {
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public void deleteNotificationTargetById(@PathVariable UUID id) throws Exception {
NotificationTargetId notificationTargetId = new NotificationTargetId(id);
NotificationTarget notificationTarget = checkEntityId(notificationTargetId, notificationTargetService::findNotificationTargetById, Operation.DELETE);
NotificationTarget notificationTarget = checkNotificationTargetId(notificationTargetId, Operation.DELETE);
doDeleteAndLog(EntityType.NOTIFICATION_TARGET, notificationTarget, notificationTargetService::deleteNotificationTargetById);
}

View File

@ -40,7 +40,9 @@ 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.NotificationType;
import org.thingsboard.server.common.data.notification.info.GeneralNotificationInfo;
import org.thingsboard.server.common.data.notification.info.NotificationInfo;
import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.settings.UserNotificationSettings;
@ -217,6 +219,21 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
}
}
@Override
public void sendSystemNotification(TenantId tenantId, NotificationTargetId targetId, NotificationType type, NotificationInfo info) {
log.debug("[{}] Sending {} system notification to {}: {}", tenantId, type, targetId, info);
NotificationTemplate notificationTemplate = notificationTemplateService.findTenantOrSystemNotificationTemplate(tenantId, type)
.orElseThrow(() -> new IllegalArgumentException("No notification template found for type " + type));
NotificationRequest notificationRequest = NotificationRequest.builder()
.tenantId(TenantId.SYS_TENANT_ID)
.targets(List.of(targetId.getId()))
.templateId(notificationTemplate.getId())
.info(info)
.originatorEntityId(TenantId.SYS_TENANT_ID)
.build();
processNotificationRequest(TenantId.SYS_TENANT_ID, notificationRequest, null);
}
private void processNotificationRequestAsync(NotificationProcessingContext ctx, List<NotificationTarget> targets, FutureCallback<NotificationRequestStats> callback) {
notificationExecutor.submit(() -> {
long startTs = System.currentTimeMillis();
@ -271,7 +288,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
}, 256);
} else {
recipients = new PageDataIterable<>(pageLink -> {
return notificationTargetService.findRecipientsForNotificationTargetConfig(ctx.getTenantId(), targetConfig, pageLink);
return notificationTargetService.findRecipientsForNotificationTargetConfig(target.getTenantId(), targetConfig, pageLink);
}, 256);
}
}

View File

@ -23,6 +23,7 @@ import com.google.common.util.concurrent.ListeningExecutorService;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.awaitility.Awaitility;
import org.hamcrest.Matcher;
import org.hibernate.exception.ConstraintViolationException;
@ -99,6 +100,21 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.TenantProfileId;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.targets.platform.UserListFilter;
import org.thingsboard.server.common.data.notification.targets.platform.UsersFilter;
import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.EmailDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.HasSubject;
import org.thingsboard.server.common.data.notification.template.MobileAppDeliveryMethodNotificationTemplate;
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.SmsDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.WebDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.oauth2.MapperType;
import org.thingsboard.server.common.data.oauth2.OAuth2Client;
import org.thingsboard.server.common.data.oauth2.OAuth2CustomMapperConfig;
@ -116,6 +132,7 @@ import org.thingsboard.server.common.data.tenant.profile.TenantProfileData;
import org.thingsboard.server.common.msg.session.FeatureType;
import org.thingsboard.server.config.ThingsboardSecurityConfiguration;
import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.device.ClaimDevicesService;
import org.thingsboard.server.dao.tenant.TenantProfileService;
@ -136,6 +153,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@ -1162,4 +1180,76 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
return oAuth2Client;
}
protected NotificationTarget createNotificationTarget(UserId... usersIds) {
UserListFilter filter = new UserListFilter();
filter.setUsersIds(DaoUtil.toUUIDs(List.of(usersIds)));
return createNotificationTarget(filter);
}
protected NotificationTarget createNotificationTarget(UsersFilter usersFilter) {
NotificationTarget notificationTarget = new NotificationTarget();
notificationTarget.setName(usersFilter.toString() + RandomStringUtils.randomNumeric(5));
PlatformUsersNotificationTargetConfig targetConfig = new PlatformUsersNotificationTargetConfig();
targetConfig.setUsersFilter(usersFilter);
notificationTarget.setConfiguration(targetConfig);
return saveNotificationTarget(notificationTarget);
}
protected NotificationTarget saveNotificationTarget(NotificationTarget notificationTarget) {
return doPost("/api/notification/target", notificationTarget, NotificationTarget.class);
}
protected NotificationTemplate createNotificationTemplate(NotificationType notificationType, String subject,
String text, NotificationDeliveryMethod... deliveryMethods) {
NotificationTemplate notificationTemplate = new NotificationTemplate();
notificationTemplate.setTenantId(tenantId);
notificationTemplate.setName("Notification template: " + text);
notificationTemplate.setNotificationType(notificationType);
NotificationTemplateConfig config = new NotificationTemplateConfig();
config.setDeliveryMethodsTemplates(new HashMap<>());
for (NotificationDeliveryMethod deliveryMethod : deliveryMethods) {
DeliveryMethodNotificationTemplate deliveryMethodNotificationTemplate;
switch (deliveryMethod) {
case WEB: {
deliveryMethodNotificationTemplate = new WebDeliveryMethodNotificationTemplate();
break;
}
case EMAIL: {
deliveryMethodNotificationTemplate = new EmailDeliveryMethodNotificationTemplate();
break;
}
case SMS: {
deliveryMethodNotificationTemplate = new SmsDeliveryMethodNotificationTemplate();
break;
}
case MOBILE_APP:
deliveryMethodNotificationTemplate = new MobileAppDeliveryMethodNotificationTemplate();
break;
default:
throw new IllegalArgumentException("Unsupported delivery method " + deliveryMethod);
}
deliveryMethodNotificationTemplate.setEnabled(true);
deliveryMethodNotificationTemplate.setBody(text);
if (deliveryMethodNotificationTemplate instanceof HasSubject) {
((HasSubject) deliveryMethodNotificationTemplate).setSubject(subject);
}
config.getDeliveryMethodsTemplates().put(deliveryMethod, deliveryMethodNotificationTemplate);
}
notificationTemplate.setConfiguration(config);
return saveNotificationTemplate(notificationTemplate);
}
protected NotificationTemplate saveNotificationTemplate(NotificationTemplate notificationTemplate) {
return doPost("/api/notification/template", notificationTemplate, NotificationTemplate.class);
}
protected List<Notification> getMyNotifications(boolean unreadOnly, int limit) throws Exception {
return getMyNotifications(NotificationDeliveryMethod.WEB, unreadOnly, limit);
}
protected List<Notification> getMyNotifications(NotificationDeliveryMethod deliveryMethod, boolean unreadOnly, int limit) throws Exception {
return doGetTypedWithPageLink("/api/notifications?unreadOnly={unreadOnly}&deliveryMethod={deliveryMethod}&", new TypeReference<PageData<Notification>>() {},
new PageLink(limit, 0), unreadOnly, deliveryMethod).getData();
}
}

View File

@ -274,14 +274,6 @@ public class NotificationEdgeTest extends AbstractEdgeTest {
return saveNotificationRule(notificationRule);
}
private NotificationTemplate saveNotificationTemplate(NotificationTemplate notificationTemplate) {
return doPost("/api/notification/template", notificationTemplate, NotificationTemplate.class);
}
private NotificationTarget saveNotificationTarget(NotificationTarget notificationTarget) {
return doPost("/api/notification/target", notificationTarget, NotificationTarget.class);
}
private NotificationRule saveNotificationRule(NotificationRule notificationRule) {
return doPost("/api/notification/rule", notificationRule, NotificationRule.class);
}

View File

@ -30,8 +30,6 @@ import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.id.NotificationTemplateId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.id.UserId;
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;
@ -44,18 +42,7 @@ import org.thingsboard.server.common.data.notification.rule.NotificationRuleInfo
import org.thingsboard.server.common.data.notification.rule.trigger.config.NotificationRuleTriggerConfig;
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.platform.PlatformUsersNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.targets.platform.UserListFilter;
import org.thingsboard.server.common.data.notification.targets.platform.UsersFilter;
import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.EmailDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.HasSubject;
import org.thingsboard.server.common.data.notification.template.MobileAppDeliveryMethodNotificationTemplate;
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.SmsDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.WebDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority;
@ -71,7 +58,6 @@ import org.thingsboard.server.dao.sqlts.insert.sql.SqlPartitioningRepository;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@ -120,25 +106,6 @@ public abstract class AbstractNotificationApiTest extends AbstractControllerTest
notificationSettingsService.deleteNotificationSettings(TenantId.SYS_TENANT_ID);
}
protected NotificationTarget createNotificationTarget(UserId... usersIds) {
UserListFilter filter = new UserListFilter();
filter.setUsersIds(DaoUtil.toUUIDs(List.of(usersIds)));
return createNotificationTarget(filter);
}
protected NotificationTarget createNotificationTarget(UsersFilter usersFilter) {
NotificationTarget notificationTarget = new NotificationTarget();
notificationTarget.setName(usersFilter.toString());
PlatformUsersNotificationTargetConfig targetConfig = new PlatformUsersNotificationTargetConfig();
targetConfig.setUsersFilter(usersFilter);
notificationTarget.setConfiguration(targetConfig);
return saveNotificationTarget(notificationTarget);
}
protected NotificationTarget saveNotificationTarget(NotificationTarget notificationTarget) {
return doPost("/api/notification/target", notificationTarget, NotificationTarget.class);
}
protected NotificationRequest submitNotificationRequest(NotificationTargetId targetId, String text, NotificationDeliveryMethod... deliveryMethods) {
return submitNotificationRequest(targetId, text, 0, deliveryMethods);
}
@ -178,50 +145,6 @@ public abstract class AbstractNotificationApiTest extends AbstractControllerTest
return findNotificationRequest(notificationRequestId).getStats();
}
protected NotificationTemplate createNotificationTemplate(NotificationType notificationType, String subject,
String text, NotificationDeliveryMethod... deliveryMethods) {
NotificationTemplate notificationTemplate = new NotificationTemplate();
notificationTemplate.setTenantId(tenantId);
notificationTemplate.setName("Notification template: " + text);
notificationTemplate.setNotificationType(notificationType);
NotificationTemplateConfig config = new NotificationTemplateConfig();
config.setDeliveryMethodsTemplates(new HashMap<>());
for (NotificationDeliveryMethod deliveryMethod : deliveryMethods) {
DeliveryMethodNotificationTemplate deliveryMethodNotificationTemplate;
switch (deliveryMethod) {
case WEB: {
deliveryMethodNotificationTemplate = new WebDeliveryMethodNotificationTemplate();
break;
}
case EMAIL: {
deliveryMethodNotificationTemplate = new EmailDeliveryMethodNotificationTemplate();
break;
}
case SMS: {
deliveryMethodNotificationTemplate = new SmsDeliveryMethodNotificationTemplate();
break;
}
case MOBILE_APP:
deliveryMethodNotificationTemplate = new MobileAppDeliveryMethodNotificationTemplate();
break;
default:
throw new IllegalArgumentException("Unsupported delivery method " + deliveryMethod);
}
deliveryMethodNotificationTemplate.setEnabled(true);
deliveryMethodNotificationTemplate.setBody(text);
if (deliveryMethodNotificationTemplate instanceof HasSubject) {
((HasSubject) deliveryMethodNotificationTemplate).setSubject(subject);
}
config.getDeliveryMethodsTemplates().put(deliveryMethod, deliveryMethodNotificationTemplate);
}
notificationTemplate.setConfiguration(config);
return saveNotificationTemplate(notificationTemplate);
}
protected NotificationTemplate saveNotificationTemplate(NotificationTemplate notificationTemplate) {
return doPost("/api/notification/template", notificationTemplate, NotificationTemplate.class);
}
protected void saveNotificationSettings(NotificationSettings notificationSettings) throws Exception {
doPost("/api/notification/settings", notificationSettings).andExpect(status().isOk());
}
@ -258,15 +181,6 @@ public abstract class AbstractNotificationApiTest extends AbstractControllerTest
doDelete("/api/notification/request/" + id);
}
protected List<Notification> getMyNotifications(boolean unreadOnly, int limit) throws Exception {
return getMyNotifications(NotificationDeliveryMethod.WEB, unreadOnly, limit);
}
protected List<Notification> getMyNotifications(NotificationDeliveryMethod deliveryMethod, boolean unreadOnly, int limit) throws Exception {
return doGetTypedWithPageLink("/api/notifications?unreadOnly={unreadOnly}&deliveryMethod={deliveryMethod}&", new TypeReference<PageData<Notification>>() {},
new PageLink(limit, 0), unreadOnly, deliveryMethod).getData();
}
protected NotificationRule createNotificationRule(NotificationRuleTriggerConfig triggerConfig, String subject, String text, NotificationTargetId... targets) {
return createNotificationRule(triggerConfig, subject, text, List.of(targets), NotificationDeliveryMethod.WEB);
}

View File

@ -15,11 +15,15 @@
*/
package org.thingsboard.server.dao.notification;
import com.fasterxml.jackson.databind.JsonNode;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.settings.UserNotificationSettings;
import java.util.Map;
public interface NotificationSettingsService {
void saveNotificationSettings(TenantId tenantId, NotificationSettings settings);
@ -36,4 +40,6 @@ public interface NotificationSettingsService {
void updateDefaultNotificationConfigs(TenantId tenantId);
void moveMailTemplatesToNotificationCenter(TenantId tenantId, JsonNode mailTemplates, Map<String, NotificationType> mailTemplatesNames);
}

View File

@ -22,7 +22,9 @@ import org.thingsboard.server.common.data.notification.template.NotificationTemp
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
public interface NotificationTemplateService {
@ -32,7 +34,11 @@ public interface NotificationTemplateService {
PageData<NotificationTemplate> findNotificationTemplatesByTenantIdAndNotificationTypes(TenantId tenantId, List<NotificationType> notificationTypes, PageLink pageLink);
int countNotificationTemplatesByTenantIdAndNotificationTypes(TenantId tenantId, List<NotificationType> notificationTypes);
Optional<NotificationTemplate> findTenantOrSystemNotificationTemplate(TenantId tenantId, NotificationType notificationType);
Optional<NotificationTemplate> findNotificationTemplateByTenantIdAndType(TenantId tenantId, NotificationType notificationType);
int countNotificationTemplatesByTenantIdAndNotificationTypes(TenantId tenantId, Collection<NotificationType> notificationTypes);
void deleteNotificationTemplateById(TenantId tenantId, NotificationTemplateId id);

View File

@ -15,6 +15,12 @@
*/
package org.thingsboard.server.common.data.notification;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor
public enum NotificationType {
GENERAL,
@ -31,6 +37,9 @@ public enum NotificationType {
RATE_LIMITS,
EDGE_CONNECTION,
EDGE_COMMUNICATION_FAILURE,
TASK_PROCESSING_FAILURE
TASK_PROCESSING_FAILURE;
@Getter
private boolean system; // for future use and compatibility with PE
}

View File

@ -15,8 +15,11 @@
*/
package org.thingsboard.server.dao.notification;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@ -41,7 +44,9 @@ import org.thingsboard.server.common.data.notification.targets.platform.SystemAd
import org.thingsboard.server.common.data.notification.targets.platform.TenantAdministratorsFilter;
import org.thingsboard.server.common.data.notification.targets.platform.UsersFilter;
import org.thingsboard.server.common.data.notification.targets.platform.UsersFilterType;
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.page.PageLink;
import org.thingsboard.server.common.data.settings.UserSettings;
import org.thingsboard.server.common.data.settings.UserSettingsType;
@ -249,6 +254,52 @@ public class DefaultNotificationSettingsService implements NotificationSettingsS
}
}
@Override
public void moveMailTemplatesToNotificationCenter(TenantId tenantId, JsonNode mailTemplates, Map<String, NotificationType> mailTemplatesNames) {
mailTemplatesNames.forEach((mailTemplateName, notificationType) -> {
moveMailTemplateToNotificationCenter(tenantId, mailTemplates, mailTemplateName, notificationType);
});
}
private void moveMailTemplateToNotificationCenter(TenantId tenantId, JsonNode mailTemplates, String mailTemplateName, NotificationType notificationType) {
JsonNode mailTemplate = mailTemplates.get(mailTemplateName);
if (mailTemplate == null || mailTemplate.isNull() || !mailTemplate.has("subject") || !mailTemplate.has("body")) {
return;
}
String subject = mailTemplate.get("subject").asText();
String body = mailTemplate.get("body").asText();
body = body.replace("targetEmail", "recipientEmail");
NotificationTemplate notificationTemplate = null;
if (tenantId.isSysTenantId()) {
// updating system notification template, not touching tenants' templates
notificationTemplate = notificationTemplateService.findNotificationTemplateByTenantIdAndType(tenantId, notificationType).orElse(null);
}
if (notificationTemplate == null) {
log.debug("[{}] Creating {} template", tenantId, notificationType);
notificationTemplate = new NotificationTemplate();
} else {
log.debug("[{}] Updating {} template", tenantId, notificationType);
}
String name = StringUtils.capitalize(notificationType.name().toLowerCase().replaceAll("_", " ")) + " notification";
notificationTemplate.setName(name);
notificationTemplate.setTenantId(tenantId);
notificationTemplate.setNotificationType(notificationType);
NotificationTemplateConfig notificationTemplateConfig = new NotificationTemplateConfig();
EmailDeliveryMethodNotificationTemplate emailNotificationTemplate = new EmailDeliveryMethodNotificationTemplate();
emailNotificationTemplate.setEnabled(true);
emailNotificationTemplate.setSubject(subject);
emailNotificationTemplate.setBody(body);
notificationTemplateConfig.setDeliveryMethodsTemplates(Map.of(NotificationDeliveryMethod.EMAIL, emailNotificationTemplate));
notificationTemplate.setConfiguration(notificationTemplateConfig);
notificationTemplateService.saveNotificationTemplate(tenantId, notificationTemplate);
((ObjectNode) mailTemplates).remove(mailTemplateName);
}
private boolean isNotificationConfigured(TenantId tenantId, NotificationType... notificationTypes) {
return notificationTemplateService.countNotificationTemplatesByTenantIdAndNotificationTypes(tenantId, List.of(notificationTypes)) > 0;
}

View File

@ -109,7 +109,7 @@ public class DefaultNotificationTargetService extends AbstractEntityService impl
NotificationTarget notificationTarget = findNotificationTargetById(tenantId, targetId);
Objects.requireNonNull(notificationTarget, "Notification target [" + targetId + "] not found");
NotificationTargetConfig configuration = notificationTarget.getConfiguration();
return findRecipientsForNotificationTargetConfig(tenantId, (PlatformUsersNotificationTargetConfig) configuration, pageLink);
return findRecipientsForNotificationTargetConfig(notificationTarget.getTenantId(), (PlatformUsersNotificationTargetConfig) configuration, pageLink);
}
@Override

View File

@ -32,6 +32,7 @@ import org.thingsboard.server.dao.entity.EntityDaoService;
import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent;
import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@ -50,11 +51,19 @@ public class DefaultNotificationTemplateService extends AbstractEntityService im
@Override
public NotificationTemplate saveNotificationTemplate(TenantId tenantId, NotificationTemplate notificationTemplate) {
NotificationType notificationType = notificationTemplate.getNotificationType();
if (notificationTemplate.getId() != null) {
NotificationTemplate oldNotificationTemplate = findNotificationTemplateById(tenantId, notificationTemplate.getId());
if (notificationTemplate.getNotificationType() != oldNotificationTemplate.getNotificationType()) {
if (notificationType != oldNotificationTemplate.getNotificationType()) {
throw new IllegalArgumentException("Notification type cannot be updated");
}
} else {
if (notificationType.isSystem()) {
int systemTemplatesCount = countNotificationTemplatesByTenantIdAndNotificationTypes(tenantId, List.of(notificationType));
if (systemTemplatesCount > 0) {
throw new IllegalArgumentException("There can only be one notification template of this type");
}
}
}
try {
NotificationTemplate savedTemplate = notificationTemplateDao.saveAndFlush(tenantId, notificationTemplate);
@ -75,7 +84,19 @@ public class DefaultNotificationTemplateService extends AbstractEntityService im
}
@Override
public int countNotificationTemplatesByTenantIdAndNotificationTypes(TenantId tenantId, List<NotificationType> notificationTypes) {
public Optional<NotificationTemplate> findTenantOrSystemNotificationTemplate(TenantId tenantId, NotificationType notificationType) {
return findNotificationTemplateByTenantIdAndType(tenantId, notificationType)
.or(() -> findNotificationTemplateByTenantIdAndType(TenantId.SYS_TENANT_ID, notificationType));
}
@Override
public Optional<NotificationTemplate> findNotificationTemplateByTenantIdAndType(TenantId tenantId, NotificationType notificationType) {
return findNotificationTemplatesByTenantIdAndNotificationTypes(tenantId, List.of(notificationType), new PageLink(1)).getData()
.stream().findFirst();
}
@Override
public int countNotificationTemplatesByTenantIdAndNotificationTypes(TenantId tenantId, Collection<NotificationType> notificationTypes) {
return notificationTemplateDao.countByTenantIdAndNotificationTypes(tenantId, notificationTypes);
}
@ -86,8 +107,16 @@ public class DefaultNotificationTemplateService extends AbstractEntityService im
@Override
public void deleteEntity(TenantId tenantId, EntityId id, boolean force) {
if (!force && notificationRequestDao.existsByTenantIdAndStatusAndTemplateId(tenantId, NotificationRequestStatus.SCHEDULED, (NotificationTemplateId) id)) {
throw new IllegalArgumentException("Notification template is referenced by scheduled notification request");
if (!force) {
if (notificationRequestDao.existsByTenantIdAndStatusAndTemplateId(tenantId, NotificationRequestStatus.SCHEDULED, (NotificationTemplateId) id)) {
throw new IllegalArgumentException("Notification template is referenced by scheduled notification request");
}
if (tenantId.isSysTenantId()) {
NotificationTemplate notificationTemplate = findNotificationTemplateById(tenantId, (NotificationTemplateId) id);
if (notificationTemplate.getNotificationType().isSystem()) {
throw new IllegalArgumentException("System notification template cannot be deleted");
}
}
}
try {
notificationTemplateDao.removeById(tenantId, id.getId());

View File

@ -24,13 +24,14 @@ import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.ExportableEntityDao;
import java.util.Collection;
import java.util.List;
public interface NotificationTemplateDao extends Dao<NotificationTemplate>, ExportableEntityDao<NotificationTemplateId, NotificationTemplate> {
PageData<NotificationTemplate> findByTenantIdAndNotificationTypesAndPageLink(TenantId tenantId, List<NotificationType> notificationTypes, PageLink pageLink);
int countByTenantIdAndNotificationTypes(TenantId tenantId, List<NotificationType> notificationTypes);
int countByTenantIdAndNotificationTypes(TenantId tenantId, Collection<NotificationType> notificationTypes);
void removeByTenantId(TenantId tenantId);

View File

@ -31,6 +31,7 @@ import org.thingsboard.server.dao.notification.NotificationTemplateDao;
import org.thingsboard.server.dao.sql.JpaAbstractDao;
import org.thingsboard.server.dao.util.SqlDao;
import java.util.Collection;
import java.util.List;
import java.util.UUID;
@ -53,7 +54,7 @@ public class JpaNotificationTemplateDao extends JpaAbstractDao<NotificationTempl
}
@Override
public int countByTenantIdAndNotificationTypes(TenantId tenantId, List<NotificationType> notificationTypes) {
public int countByTenantIdAndNotificationTypes(TenantId tenantId, Collection<NotificationType> notificationTypes) {
return notificationTemplateRepository.countByTenantIdAndNotificationTypes(tenantId.getId(), notificationTypes);
}

View File

@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.dao.ExportableEntityRepository;
import org.thingsboard.server.dao.model.sql.NotificationTemplateEntity;
import java.util.Collection;
import java.util.List;
import java.util.UUID;
@ -45,7 +46,7 @@ public interface NotificationTemplateRepository extends JpaRepository<Notificati
@Query("SELECT count(t) FROM NotificationTemplateEntity t WHERE t.tenantId = :tenantId AND " +
"t.notificationType IN :notificationTypes")
int countByTenantIdAndNotificationTypes(@Param("tenantId") UUID tenantId,
@Param("notificationTypes") List<NotificationType> notificationTypes);
@Param("notificationTypes") Collection<NotificationType> notificationTypes);
@Transactional
@Modifying

View File

@ -18,12 +18,15 @@ package org.thingsboard.rule.engine.api;
import com.google.common.util.concurrent.FutureCallback;
import org.thingsboard.server.common.data.id.NotificationId;
import org.thingsboard.server.common.data.id.NotificationRequestId;
import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationRequest;
import org.thingsboard.server.common.data.notification.NotificationRequestStats;
import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.common.data.notification.info.GeneralNotificationInfo;
import org.thingsboard.server.common.data.notification.info.NotificationInfo;
import org.thingsboard.server.common.data.notification.targets.platform.UsersFilter;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
@ -35,6 +38,8 @@ public interface NotificationCenter {
void sendGeneralWebNotification(TenantId tenantId, UsersFilter recipients, NotificationTemplate template, GeneralNotificationInfo info);
void sendSystemNotification(TenantId tenantId, NotificationTargetId targetId, NotificationType type, NotificationInfo info); // for future use and compatibility with PE
void deleteNotificationRequest(TenantId tenantId, NotificationRequestId notificationRequestId);
void markNotificationAsRead(TenantId tenantId, UserId recipientId, NotificationId notificationId);