Notification system fixes, new notification targets

This commit is contained in:
ViacheslavKlimov 2023-03-23 18:27:45 +02:00
parent b10e162b19
commit 197207035b
19 changed files with 143 additions and 26 deletions

View File

@ -32,6 +32,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.NotificationRuleId;
import org.thingsboard.server.common.data.notification.rule.NotificationRule;
import org.thingsboard.server.common.data.notification.rule.NotificationRuleInfo;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
@ -56,9 +57,16 @@ public class NotificationRuleController extends BaseController {
@PostMapping("/rule")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public NotificationRule saveNotificationRule(@RequestBody @Valid NotificationRule notificationRule) throws Exception {
notificationRule.setTenantId(getTenantId());
public NotificationRule saveNotificationRule(@RequestBody @Valid NotificationRule notificationRule,
@AuthenticationPrincipal SecurityUser user) throws Exception {
notificationRule.setTenantId(user.getTenantId());
checkEntity(notificationRule.getId(), notificationRule, NOTIFICATION);
NotificationRuleTriggerType triggerType = notificationRule.getTriggerType();
if ((user.isTenantAdmin() && !triggerType.isTenantLevel()) || (user.isSystemAdmin() && triggerType.isTenantLevel())) {
throw new IllegalArgumentException("Trigger type " + triggerType + " is not available");
}
return doSaveAndLog(EntityType.NOTIFICATION_RULE, notificationRule, notificationRuleService::saveNotificationRule);
}

View File

@ -191,6 +191,8 @@ public class NotificationTargetController extends BaseController {
throw new AccessDeniedException("");
}
break;
case SYSTEM_ADMINISTRATORS:
throw new AccessDeniedException("");
}
}

View File

@ -35,6 +35,9 @@ public class DefaultRateLimitService implements RateLimitService {
@Override
public boolean checkRateLimit(TenantId tenantId, LimitedApi api) {
if (tenantId.isSysTenantId()) {
return true;
}
String rateLimitConfig = tenantProfileCache.get(tenantId).getProfileConfiguration()
.map(api::getLimitConfig).orElse(null);

View File

@ -123,8 +123,8 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
}
if (notificationTemplate == null) throw new IllegalArgumentException("Template is missing");
List<NotificationTarget> targets = notificationTargetService.findNotificationTargetsByTenantIdAndIds(tenantId,
notificationRequest.getTargets().stream().map(NotificationTargetId::new).collect(Collectors.toList()));
List<NotificationTarget> targets = notificationRequest.getTargets().stream().map(NotificationTargetId::new)
.map(id -> notificationTargetService.findNotificationTargetById(tenantId, id)).collect(Collectors.toList());
Set<NotificationDeliveryMethod> availableDeliveryMethods = getAvailableDeliveryMethods(tenantId);
notificationTemplate.getConfiguration().getDeliveryMethodsTemplates().forEach((deliveryMethod, template) -> {

View File

@ -73,11 +73,12 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
@Override
public void process(TenantId tenantId, NotificationRuleTrigger trigger) {
List<NotificationRule> rules = notificationRuleService.findNotificationRulesByTenantIdAndTriggerType(tenantId, trigger.getType());
List<NotificationRule> rules = notificationRuleService.findNotificationRulesByTenantIdAndTriggerType(
trigger.getType().isTenantLevel() ? tenantId : TenantId.SYS_TENANT_ID, trigger.getType());
for (NotificationRule rule : rules) {
notificationExecutor.submit(() -> {
try {
processNotificationRule(rule, trigger);
processNotificationRule(tenantId, rule, trigger);
} catch (Throwable e) {
log.error("Failed to process notification rule {} for trigger type {} with trigger object {}", rule.getId(), rule.getTriggerType(), trigger, e);
}
@ -97,12 +98,12 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
.build());
}
private void processNotificationRule(NotificationRule rule, NotificationRuleTrigger trigger) {
private void processNotificationRule(TenantId tenantId, NotificationRule rule, NotificationRuleTrigger trigger) {
NotificationRuleTriggerConfig triggerConfig = rule.getTriggerConfig();
log.debug("Processing notification rule '{}' for trigger type {}", rule.getName(), rule.getTriggerType());
if (matchesClearRule(trigger, triggerConfig)) {
List<NotificationRequest> notificationRequests = notificationRequestService.findNotificationRequestsByRuleIdAndOriginatorEntityId(rule.getTenantId(), rule.getId(), trigger.getOriginatorEntityId());
List<NotificationRequest> notificationRequests = notificationRequestService.findNotificationRequestsByRuleIdAndOriginatorEntityId(tenantId, rule.getId(), trigger.getOriginatorEntityId());
if (notificationRequests.isEmpty()) {
return;
}
@ -112,11 +113,11 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
.flatMap(notificationRequest -> notificationRequest.getTargets().stream())
.distinct().collect(Collectors.toList());
NotificationInfo notificationInfo = constructNotificationInfo(trigger, triggerConfig);
submitNotificationRequest(targets, rule, trigger.getOriginatorEntityId(), notificationInfo, 0);
submitNotificationRequest(tenantId, targets, rule, trigger.getOriginatorEntityId(), notificationInfo, 0);
notificationRequests.forEach(notificationRequest -> {
if (notificationRequest.isScheduled()) {
notificationCenter.deleteNotificationRequest(rule.getTenantId(), notificationRequest.getId());
notificationCenter.deleteNotificationRequest(tenantId, notificationRequest.getId());
}
});
return;
@ -125,7 +126,7 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
if (matchesFilter(trigger, triggerConfig)) {
NotificationInfo notificationInfo = constructNotificationInfo(trigger, triggerConfig);
rule.getRecipientsConfig().getTargetsTable().forEach((delay, targets) -> {
submitNotificationRequest(targets, rule, trigger.getOriginatorEntityId(), notificationInfo, delay);
submitNotificationRequest(tenantId, targets, rule, trigger.getOriginatorEntityId(), notificationInfo, delay);
});
}
}
@ -142,14 +143,14 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
return triggerProcessors.get(triggerConfig.getTriggerType()).constructNotificationInfo(trigger, triggerConfig);
}
private void submitNotificationRequest(List<UUID> targets, NotificationRule rule,
private void submitNotificationRequest(TenantId tenantId, List<UUID> targets, NotificationRule rule,
EntityId originatorEntityId, NotificationInfo notificationInfo, int delayInSec) {
NotificationRequestConfig config = new NotificationRequestConfig();
if (delayInSec > 0) {
config.setSendingDelayInSec(delayInSec);
}
NotificationRequest notificationRequest = NotificationRequest.builder()
.tenantId(rule.getTenantId())
.tenantId(tenantId)
.targets(targets)
.templateId(rule.getTemplateId())
.additionalConfig(config)
@ -160,7 +161,7 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
notificationExecutor.submit(() -> {
try {
log.debug("Submitting notification request for rule '{}' with delay of {} sec to targets {}", rule.getName(), delayInSec, targets);
notificationCenter.processNotificationRequest(rule.getTenantId(), notificationRequest);
notificationCenter.processNotificationRequest(tenantId, notificationRequest);
} catch (Exception e) {
log.error("Failed to process notification request for rule {}", rule.getId(), e);
}

View File

@ -15,18 +15,23 @@
*/
package org.thingsboard.server.service.notification.rule.trigger;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.notification.info.EntitiesLimitNotificationInfo;
import org.thingsboard.server.common.data.notification.info.NotificationInfo;
import org.thingsboard.server.common.data.notification.rule.trigger.EntitiesLimitNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.dao.notification.trigger.EntitiesLimitTrigger;
import org.thingsboard.server.dao.tenant.TenantService;
import static org.apache.commons.collections.CollectionUtils.isNotEmpty;
@Service
@RequiredArgsConstructor
public class EntitiesLimitTriggerProcessor implements NotificationRuleTriggerProcessor<EntitiesLimitTrigger, EntitiesLimitNotificationRuleTriggerConfig> {
private final TenantService tenantService;
@Override
public boolean matchesFilter(EntitiesLimitTrigger trigger, EntitiesLimitNotificationRuleTriggerConfig triggerConfig) {
if (isNotEmpty(triggerConfig.getEntityTypes()) && !triggerConfig.getEntityTypes().contains(trigger.getEntityType())) {
@ -42,6 +47,8 @@ public class EntitiesLimitTriggerProcessor implements NotificationRuleTriggerPro
.currentCount(trigger.getCurrentCount())
.limit(trigger.getLimit())
.percents((int) (((float)trigger.getCurrentCount() / trigger.getLimit()) * 100))
.tenantId(trigger.getTenantId())
.tenantName(tenantService.findTenantById(trigger.getTenantId()).getName())
.build();
}

View File

@ -62,7 +62,7 @@ public class EntityActionTriggerProcessor implements RuleEngineMsgNotificationRu
msgType.equals(DataConstants.ENTITY_UPDATED) ? ActionType.UPDATED :
msgType.equals(DataConstants.ENTITY_DELETED) ? ActionType.DELETED : null;
return EntityActionNotificationInfo.builder()
.entityId(actionType != ActionType.DELETED ? msg.getOriginator() : null)
.entityId(msg.getOriginator())
.entityName(msg.getMetaData().getValue("entityName"))
.actionType(actionType)
.originatorUserId(UUID.fromString(msg.getMetaData().getValue("userId")))

View File

@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.notification.rule.trigger.Notification
@Builder
public class EntitiesLimitTrigger implements NotificationRuleTrigger {
private final TenantId tenantId;
private final EntityType entityType;
private final long currentCount;
private final long limit;

View File

@ -63,6 +63,8 @@ public interface UserService extends EntityDaoService {
PageData<User> findTenantAdmins(TenantId tenantId, PageLink pageLink);
PageData<User> findSysAdmins(PageLink pageLink);
PageData<User> findAllTenantAdmins(PageLink pageLink);
PageData<User> findTenantAdminsByTenantsIds(List<TenantId> tenantsIds, PageLink pageLink);

View File

@ -20,6 +20,7 @@ import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.TenantId;
import java.util.Map;
@ -35,6 +36,8 @@ public class EntitiesLimitNotificationInfo implements NotificationInfo {
private long currentCount;
private long limit;
private int percents;
private TenantId tenantId;
private String tenantName;
@Override
public Map<String, String> getTemplateData() {
@ -42,7 +45,9 @@ public class EntitiesLimitNotificationInfo implements NotificationInfo {
"entityType", entityType.getNormalName(),
"currentCount", String.valueOf(currentCount),
"limit", String.valueOf(limit),
"percents", String.valueOf(percents)
"percents", String.valueOf(percents),
"tenantId", tenantId.toString(),
"tenantName", tenantName
);
}

View File

@ -15,6 +15,9 @@
*/
package org.thingsboard.server.common.data.notification.rule.trigger;
import lombok.Getter;
@Getter
public enum NotificationRuleTriggerType {
ALARM,
@ -23,7 +26,17 @@ public enum NotificationRuleTriggerType {
ENTITY_ACTION,
RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT,
ALARM_ASSIGNMENT,
NEW_PLATFORM_VERSION,
ENTITIES_LIMIT
NEW_PLATFORM_VERSION(false),
ENTITIES_LIMIT(false);
private final boolean tenantLevel;
NotificationRuleTriggerType(boolean tenantLevel) {
this.tenantLevel = tenantLevel;
}
NotificationRuleTriggerType() {
this(true);
}
}

View File

@ -0,0 +1,28 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.notification.targets.platform;
import lombok.Data;
@Data
public class AffectedTenantAdministratorsFilter implements UsersFilter {
@Override
public UsersFilterType getType() {
return UsersFilterType.AFFECTED_TENANT_ADMINISTRATORS;
}
}

View File

@ -0,0 +1,28 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.notification.targets.platform;
import lombok.Data;
@Data
public class SystemAdministratorsFilter implements UsersFilter {
@Override
public UsersFilterType getType() {
return UsersFilterType.SYSTEM_ADMINISTRATORS;
}
}

View File

@ -27,6 +27,8 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
@Type(value = UserListFilter.class, name = "USER_LIST"),
@Type(value = CustomerUsersFilter.class, name = "CUSTOMER_USERS"),
@Type(value = TenantAdministratorsFilter.class, name = "TENANT_ADMINISTRATORS"),
@Type(value = AffectedTenantAdministratorsFilter.class, name = "AFFECTED_TENANT_ADMINISTRATORS"),
@Type(value = SystemAdministratorsFilter.class, name = "SYSTEM_ADMINISTRATORS"),
@Type(value = AllUsersFilter.class, name = "ALL_USERS"),
@Type(value = OriginatorEntityOwnerUsersFilter.class, name = "ORIGINATOR_ENTITY_OWNER_USERS"),
@Type(value = AffectedUserFilter.class, name = "AFFECTED_USER")

View File

@ -27,6 +27,8 @@ public enum UsersFilterType {
USER_LIST,
CUSTOMER_USERS,
TENANT_ADMINISTRATORS,
AFFECTED_TENANT_ADMINISTRATORS(true),
SYSTEM_ADMINISTRATORS,
ALL_USERS,
ORIGINATOR_ENTITY_OWNER_USERS(true),
AFFECTED_USER(true);

View File

@ -48,10 +48,12 @@ import org.thingsboard.server.common.data.notification.rule.trigger.Notification
import org.thingsboard.server.common.data.notification.rule.trigger.RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig;
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.AffectedTenantAdministratorsFilter;
import org.thingsboard.server.common.data.notification.targets.platform.AffectedUserFilter;
import org.thingsboard.server.common.data.notification.targets.platform.AllUsersFilter;
import org.thingsboard.server.common.data.notification.targets.platform.OriginatorEntityOwnerUsersFilter;
import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.targets.platform.SystemAdministratorsFilter;
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.template.NotificationTemplate;
@ -119,14 +121,17 @@ public class DefaultNotificationSettingsService implements NotificationSettingsS
"Maintenance work is scheduled for tomorrow (7:00 a.m. - 9:00 a.m. UTC)");
if (tenantId.isSysTenantId()) {
NotificationTarget sysAdmins = createTarget(tenantId, "System administrators", new SystemAdministratorsFilter(), "All system administrators");
NotificationTarget affectedTenantAdmins = createTarget(tenantId, "Affected tenant's administrators", new AffectedTenantAdministratorsFilter(), "");
NotificationTemplate entitiesLimitNotificationTemplate = createTemplate(tenantId, "Entities limit notification", NotificationType.ENTITIES_LIMIT,
"${entityType}s limit will be reached soon",
"${entityType}s limit will be reached soon for tenant ${tenantName}",
"${entityType}s usage: ${currentCount}/${limit} (${percents}%)");
EntitiesLimitNotificationRuleTriggerConfig entitiesLimitRuleTriggerConfig = new EntitiesLimitNotificationRuleTriggerConfig();
entitiesLimitRuleTriggerConfig.setEntityTypes(null);
entitiesLimitRuleTriggerConfig.setThreshold(0.8f);
createRule(tenantId, "Entities limit", entitiesLimitNotificationTemplate.getId(), entitiesLimitRuleTriggerConfig,
List.of(tenantAdmins.getId()), "Send notification to tenant admins when count of entities of some type reached 80% threshold of the limit");
List.of(affectedTenantAdmins.getId(), sysAdmins.getId()), "Send notification to tenant admins when count of entities of some type reached 80% threshold of the limit");
return;
}

View File

@ -142,6 +142,10 @@ public class DefaultNotificationTargetService extends AbstractEntityService impl
}
}
}
case AFFECTED_TENANT_ADMINISTRATORS:
return userService.findTenantAdmins(tenantId, pageLink);
case SYSTEM_ADMINISTRATORS:
return userService.findSysAdmins(pageLink);
case ALL_USERS: {
if (!tenantId.equals(TenantId.SYS_TENANT_ID)) {
return userService.findUsersByTenantId(tenantId, pageLink);

View File

@ -49,6 +49,7 @@ public class DefaultApiLimitService implements ApiLimitService {
long currentCount = entityService.countEntitiesByQuery(tenantId, new CustomerId(EntityId.NULL_UUID), new EntityCountQuery(filter));
if (notificationRuleProcessingService != null) {
notificationRuleProcessingService.process(tenantId, EntitiesLimitTrigger.builder()
.tenantId(tenantId)
.entityType(entityType)
.currentCount(currentCount)
.limit(limit)

View File

@ -252,6 +252,11 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic
return userDao.findTenantAdmins(tenantId.getId(), pageLink);
}
@Override
public PageData<User> findSysAdmins(PageLink pageLink) {
return userDao.findAllByAuthority(Authority.SYS_ADMIN, pageLink);
}
@Override
public PageData<User> findAllTenantAdmins(PageLink pageLink) {
return userDao.findAllByAuthority(Authority.TENANT_ADMIN, pageLink);