Notification rules improvements; new platform users filters

This commit is contained in:
ViacheslavKlimov 2023-03-17 14:03:03 +02:00
parent 1e7c1a8a5a
commit 3b8ed5fae6
39 changed files with 273 additions and 62 deletions

View File

@ -95,7 +95,7 @@ import org.thingsboard.server.service.executors.ExternalCallExecutorService;
import org.thingsboard.server.service.executors.NotificationExecutorService;
import org.thingsboard.server.service.executors.SharedEventLoopGroupService;
import org.thingsboard.server.service.mail.MailExecutorService;
import org.thingsboard.server.service.notification.rule.NotificationRuleProcessingService;
import org.thingsboard.server.dao.notification.NotificationRuleProcessingService;
import org.thingsboard.server.service.profile.TbAssetProfileCache;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService;

View File

@ -18,6 +18,8 @@ package org.thingsboard.server.controller;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.DeleteMapping;
@ -39,6 +41,7 @@ import org.thingsboard.server.common.data.notification.targets.NotificationTarge
import org.thingsboard.server.common.data.notification.targets.NotificationTargetType;
import org.thingsboard.server.common.data.notification.targets.platform.CustomerUsersFilter;
import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.targets.platform.TenantAdministratorsFilter;
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.targets.platform.UsersFilterType;
@ -159,13 +162,22 @@ public class NotificationTargetController extends BaseController {
}
// generic permission for users
UsersFilter usersFilter = ((PlatformUsersNotificationTargetConfig) targetConfig).getUsersFilter();
if (usersFilter.getType() == UsersFilterType.USER_LIST) {
switch (usersFilter.getType()) {
case USER_LIST:
for (UUID recipientId : ((UserListFilter) usersFilter).getUsersIds()) {
checkUserId(new UserId(recipientId), Operation.READ);
}
} else if (usersFilter.getType() == UsersFilterType.CUSTOMER_USERS) {
break;
case CUSTOMER_USERS:
CustomerId customerId = new CustomerId(((CustomerUsersFilter) usersFilter).getCustomerId());
checkEntityId(customerId, Operation.READ);
break;
case TENANT_ADMINISTRATORS:
if (CollectionUtils.isNotEmpty(((TenantAdministratorsFilter) usersFilter).getTenantsIds()) ||
CollectionUtils.isNotEmpty(((TenantAdministratorsFilter) usersFilter).getTenantProfilesIds())) {
throw new AccessDeniedException("");
}
break;
}
}

View File

@ -19,7 +19,6 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
@ -58,9 +57,9 @@ import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.common.data.security.UserSettings;
import org.thingsboard.server.common.data.security.event.UserCredentialsInvalidationEvent;
import org.thingsboard.server.common.data.security.model.JwtPair;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.user.TbUserService;
import org.thingsboard.server.common.data.security.model.JwtPair;
import org.thingsboard.server.service.query.EntityQueryService;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.UserPrincipal;
@ -70,7 +69,6 @@ import org.thingsboard.server.service.security.permission.Resource;
import org.thingsboard.server.service.security.system.SystemSecurityService;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@ -109,7 +107,6 @@ public class UserController extends BaseController {
public static final String ACTIVATE_URL_PATTERN = "%s/api/noauth/activate?activateToken=%s";
@Value("${security.user_token_access_enabled}")
@Getter
private boolean userTokenAccessEnabled;
private final MailService mailService;

View File

@ -22,7 +22,7 @@ import org.thingsboard.common.util.AbstractListeningExecutor;
@Component
public class NotificationExecutorService extends AbstractListeningExecutor {
@Value("${notification_system.thread_pool_size:30}")
@Value("${notification_system.thread_pool_size:10}")
private int threadPoolSize;
@Override

View File

@ -39,9 +39,12 @@ 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.RuleOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.targets.NotificationRecipient;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.targets.platform.UsersFilterType;
import org.thingsboard.server.common.data.notification.targets.slack.SlackNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
@ -56,6 +59,7 @@ import org.thingsboard.server.dao.notification.NotificationService;
import org.thingsboard.server.dao.notification.NotificationSettingsService;
import org.thingsboard.server.dao.notification.NotificationTargetService;
import org.thingsboard.server.dao.notification.NotificationTemplateService;
import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.NotificationsTopicService;
@ -89,6 +93,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
private final NotificationService notificationService;
private final NotificationTemplateService notificationTemplateService;
private final NotificationSettingsService notificationSettingsService;
private final UserService userService;
private final NotificationExecutorService notificationExecutor;
private final DbCallbackExecutorService dbCallbackExecutorService;
private final NotificationsTopicService notificationsTopicService;
@ -186,9 +191,21 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
Iterable<? extends NotificationRecipient> recipients;
switch (target.getConfiguration().getType()) {
case PLATFORM_USERS: {
PlatformUsersNotificationTargetConfig platformUsersTargetConfig = (PlatformUsersNotificationTargetConfig) target.getConfiguration();
if (platformUsersTargetConfig.getUsersFilter().getType() == UsersFilterType.ACTION_TARGET_USER) {
if (ctx.getRequest().getInfo() instanceof RuleOriginatedNotificationInfo) {
UserId targetUserId = ((RuleOriginatedNotificationInfo) ctx.getRequest().getInfo()).getTargetUserId();
if (targetUserId != null) {
recipients = List.of(userService.findUserById(ctx.getTenantId(), targetUserId));
break;
}
}
recipients = Collections.emptyList();
} else {
recipients = new PageDataIterable<>(pageLink -> {
return notificationTargetService.findRecipientsForNotificationTargetConfig(ctx.getTenantId(), ctx.getCustomerId(), target.getConfiguration(), pageLink);
}, 200);
return notificationTargetService.findRecipientsForNotificationTargetConfig(ctx.getTenantId(), ctx.getCustomerId(), platformUsersTargetConfig, pageLink);
}, 500);
}
break;
}
case SLACK: {

View File

@ -39,12 +39,11 @@ import org.thingsboard.server.common.data.notification.rule.trigger.Notification
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.dao.alarm.AlarmApiCallResult;
import org.thingsboard.server.dao.notification.NotificationRequestService;
import org.thingsboard.server.dao.notification.NotificationRuleProcessingService;
import org.thingsboard.server.dao.notification.NotificationRuleService;
import org.thingsboard.server.service.executors.NotificationExecutorService;
import org.thingsboard.server.service.notification.rule.trigger.EntitiesLimitTriggerProcessor.EntitiesLimitTriggerObject;
import org.thingsboard.server.service.notification.rule.trigger.NotificationRuleTriggerProcessor;
import org.thingsboard.server.service.notification.rule.trigger.RuleEngineComponentLifecycleEventTriggerProcessor.RuleEngineComponentLifecycleEventTriggerObject;
import org.thingsboard.server.service.notification.rule.trigger.RuleEngineMsgNotificationRuleTriggerProcessor;

View File

@ -37,7 +37,8 @@ public class AlarmAssignmentTriggerProcessor implements RuleEngineMsgNotificatio
@Override
public boolean matchesFilter(TbMsg ruleEngineMsg, AlarmAssignmentNotificationRuleTriggerConfig triggerConfig) {
if (ruleEngineMsg.getType().equals(DataConstants.ALARM_UNASSIGN) && !triggerConfig.isNotifyOnUnassign()) {
if ((ruleEngineMsg.getType().equals(DataConstants.ALARM_ASSIGN) && !triggerConfig.isNotifyOnAssign())
|| (ruleEngineMsg.getType().equals(DataConstants.ALARM_UNASSIGN) && !triggerConfig.isNotifyOnUnassign())) {
return false;
}
Alarm alarm = JacksonUtil.fromString(ruleEngineMsg.getData(), Alarm.class);
@ -48,13 +49,14 @@ public class AlarmAssignmentTriggerProcessor implements RuleEngineMsgNotificatio
@Override
public NotificationInfo constructNotificationInfo(TbMsg ruleEngineMsg, AlarmAssignmentNotificationRuleTriggerConfig triggerConfig) {
// TODO: readable action
AlarmInfo alarmInfo = JacksonUtil.fromString(ruleEngineMsg.getData(), AlarmInfo.class);
AlarmAssignee assignee = alarmInfo.getAssignee();
return AlarmAssignmentNotificationInfo.builder()
.action(ruleEngineMsg.getType().equals(DataConstants.ALARM_ASSIGN) ? "assigned" : "unassigned")
.assigneeFirstName(assignee != null ? assignee.getFirstName() : null)
.assigneeLastName(assignee != null ? assignee.getLastName() : null)
.assigneeEmail(assignee != null ? assignee.getEmail() : null)
.assigneeId(assignee != null ? assignee.getId() : null)
.userName(ruleEngineMsg.getMetaData().getValue("userName"))
.alarmId(alarmInfo.getUuidId())
.alarmType(alarmInfo.getType())

View File

@ -41,6 +41,9 @@ public class AlarmCommentTriggerProcessor implements RuleEngineMsgNotificationRu
if (ruleEngineMsg.getMetaData().getValue("comment") == null) {
return false;
}
if (ruleEngineMsg.getType().equals(DataConstants.COMMENT_UPDATED) && !triggerConfig.isNotifyOnCommentUpdate()) {
return false;
}
if (triggerConfig.isOnlyUserComments()) {
AlarmComment comment = JacksonUtil.fromString(ruleEngineMsg.getMetaData().getValue("comment"), AlarmComment.class);
if (comment.getType() == AlarmCommentType.SYSTEM) {
@ -55,11 +58,11 @@ public class AlarmCommentTriggerProcessor implements RuleEngineMsgNotificationRu
@Override
public NotificationInfo constructNotificationInfo(TbMsg ruleEngineMsg, AlarmCommentNotificationRuleTriggerConfig triggerConfig) {
// TODO: readable action
AlarmComment comment = JacksonUtil.fromString(ruleEngineMsg.getMetaData().getValue("comment"), AlarmComment.class);
AlarmInfo alarmInfo = JacksonUtil.fromString(ruleEngineMsg.getData(), AlarmInfo.class);
return AlarmCommentNotificationInfo.builder()
.comment(comment.getComment().get("text").asText())
.action(ruleEngineMsg.getType().equals(DataConstants.COMMENT_CREATED) ? "created" : "updated")
.userName(ruleEngineMsg.getMetaData().getValue("userName"))
.alarmId(alarmInfo.getUuidId())
.alarmType(alarmInfo.getType())

View File

@ -91,11 +91,15 @@ public class AlarmTriggerProcessor implements NotificationRuleTriggerProcessor<A
@Override
public NotificationInfo constructNotificationInfo(AlarmApiCallResult alarmUpdate, AlarmNotificationRuleTriggerConfig triggerConfig) {
// TODO: readable action
AlarmInfo alarmInfo = alarmUpdate.getAlarm();
return AlarmNotificationInfo.builder()
.alarmId(alarmInfo.getUuidId())
.alarmType(alarmInfo.getType())
.action(alarmUpdate.isCreated() ? "created" :
alarmUpdate.isSeverityChanged() ? "severity changed" :
alarmUpdate.isAcknowledged() ? "acknowledged" :
alarmUpdate.isCleared() ? "cleared" :
alarmUpdate.isDeleted() ? "deleted" : null)
.alarmOriginator(alarmInfo.getOriginator())
.alarmOriginatorName(alarmInfo.getOriginatorName())
.alarmSeverity(alarmInfo.getSeverity())

View File

@ -69,7 +69,6 @@ import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
import org.thingsboard.server.service.edge.EdgeNotificationService;
import org.thingsboard.server.service.notification.NotificationSchedulerService;
import org.thingsboard.server.service.notification.rule.NotificationRuleProcessingService;
import org.thingsboard.server.service.ota.OtaPackageStateService;
import org.thingsboard.server.service.profile.TbAssetProfileCache;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
@ -592,7 +591,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
notificationSchedulerService.scheduleNotificationRequest(tenantId, notificationRequestId, msg.getTs());
callback.onSuccess();
} catch (Exception e) {
callback.onFailure(new RuntimeException("Failed to scheduler notification request", e));
callback.onFailure(new RuntimeException("Failed to schedule notification request", e));
}
}

View File

@ -52,7 +52,6 @@ import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.queue.provider.TbRuleEngineQueueFactory;
import org.thingsboard.server.queue.util.TbRuleEngineComponent;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
import org.thingsboard.server.service.notification.rule.NotificationRuleProcessingService;
import org.thingsboard.server.service.profile.TbAssetProfileCache;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.queue.processing.AbstractConsumerService;

View File

@ -30,12 +30,12 @@ import com.slack.api.model.ConversationType;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.notification.targets.slack.SlackConversation;
import org.thingsboard.rule.engine.api.slack.SlackService;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig;
import org.thingsboard.server.common.data.notification.targets.slack.SlackConversation;
import org.thingsboard.server.common.data.notification.targets.slack.SlackConversationType;
import org.thingsboard.server.common.data.util.ThrowingBiFunction;
import org.thingsboard.server.dao.notification.NotificationSettingsService;
@ -140,10 +140,9 @@ public class DefaultSlackService implements SlackService {
String error = response.getError();
if (error == null) {
error = "unknown error";
}
if (error.contains("missing_scope")) {
} else if (error.contains("missing_scope")) {
String neededScope = response.getNeeded();
throw new RuntimeException("Bot token scope '" + neededScope + "' is needed");
error = "bot token scope '" + neededScope + "' is needed";
}
throw new RuntimeException("Failed to send message via Slack: " + error);
}

View File

@ -52,7 +52,7 @@ import org.thingsboard.server.dao.alarm.AlarmOperationResult;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
import org.thingsboard.server.service.entitiy.alarm.TbAlarmCommentService;
import org.thingsboard.server.service.notification.rule.NotificationRuleProcessingService;
import org.thingsboard.server.dao.notification.NotificationRuleProcessingService;
import org.thingsboard.server.service.subscription.TbSubscriptionUtils;
import java.util.Collection;

View File

@ -26,7 +26,7 @@ import org.springframework.web.client.RestTemplate;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.UpdateMessage;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.notification.rule.NotificationRuleProcessingService;
import org.thingsboard.server.dao.notification.NotificationRuleProcessingService;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

View File

@ -1216,7 +1216,7 @@ vc:
repositories-folder: "${TB_VC_GIT_REPOSITORIES_FOLDER:${java.io.tmpdir}/repositories}"
notification_system:
thread_pool_size: "${TB_NOTIFICATION_SYSTEM_THREAD_POOL_SIZE:30}"
thread_pool_size: "${TB_NOTIFICATION_SYSTEM_THREAD_POOL_SIZE:10}"
management:
endpoints:

View File

@ -145,7 +145,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
getWsClient().waitForUpdate(true);
Notification notification = getWsClient().getLastDataUpdate().getUpdate();
assertThat(notification.getSubject()).isEqualTo("added: DEVICE [" + device.getId() + "]");
assertThat(notification.getSubject()).isEqualTo("added: Device [" + device.getId() + "]");
assertThat(notification.getText()).isEqualTo("User: " + TENANT_ADMIN_EMAIL);
@ -155,7 +155,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
getWsClient().waitForUpdate(true);
notification = getWsClient().getLastDataUpdate().getUpdate();
assertThat(notification.getSubject()).isEqualTo("updated: DEVICE [" + device.getId() + "]");
assertThat(notification.getSubject()).isEqualTo("updated: Device [" + device.getId() + "]");
getWsClient().registerWaitForUpdate();
@ -163,7 +163,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
getWsClient().waitForUpdate(true);
notification = getWsClient().getLastDataUpdate().getUpdate();
assertThat(notification.getSubject()).isEqualTo("deleted: DEVICE [" + device.getId() + "]");
assertThat(notification.getSubject()).isEqualTo("deleted: Device [" + device.getId() + "]");
}
@Test

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.notification.rule;
package org.thingsboard.server.dao.notification;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.UpdateMessage;

View File

@ -19,6 +19,7 @@ import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.TenantProfileId;
import org.thingsboard.server.common.data.id.UserCredentialsId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.PageData;
@ -26,6 +27,8 @@ import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.dao.entity.EntityDaoService;
import java.util.List;
public interface UserService extends EntityDaoService {
User findUserById(TenantId tenantId, UserId userId);
@ -60,7 +63,13 @@ public interface UserService extends EntityDaoService {
PageData<User> findTenantAdmins(TenantId tenantId, PageLink pageLink);
PageData<User> findAllUsers(TenantId tenantId, PageLink pageLink);
PageData<User> findAllTenantAdmins(PageLink pageLink);
PageData<User> findTenantAdminsByTenantsIds(List<TenantId> tenantsIds, PageLink pageLink);
PageData<User> findTenantAdminsByTenantProfilesIds(List<TenantProfileId> tenantProfilesIds, PageLink pageLink);
PageData<User> findAllUsers(PageLink pageLink);
void deleteTenantAdmins(TenantId tenantId);

View File

@ -15,6 +15,8 @@
*/
package org.thingsboard.server.common.data;
import org.apache.commons.lang3.StringUtils;
/**
* @author Andrew Shvayka
*/
@ -44,5 +46,10 @@ public enum EntityType {
NOTIFICATION_TEMPLATE,
NOTIFICATION_REQUEST,
NOTIFICATION,
NOTIFICATION_RULE
NOTIFICATION_RULE;
public String normalName() {
return StringUtils.capitalize(name().toLowerCase().replaceAll("_", " "));
}
}

View File

@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.UserId;
import java.util.Map;
import java.util.UUID;
@ -35,9 +36,11 @@ import static org.thingsboard.server.common.data.util.CollectionsUtil.mapOf;
@Builder
public class AlarmAssignmentNotificationInfo implements RuleOriginatedNotificationInfo {
private String action;
private String assigneeFirstName;
private String assigneeLastName;
private String assigneeEmail;
private UserId assigneeId;
private String userName;
private String alarmType;
@ -51,6 +54,7 @@ public class AlarmAssignmentNotificationInfo implements RuleOriginatedNotificati
@Override
public Map<String, String> getTemplateData() {
return mapOf(
"action", action,
"assigneeFirstName", assigneeFirstName,
"assigneeLastName", assigneeLastName,
"assigneeEmail", assigneeEmail,
@ -59,7 +63,7 @@ public class AlarmAssignmentNotificationInfo implements RuleOriginatedNotificati
"alarmId", alarmId.toString(),
"alarmSeverity", alarmSeverity.toString(),
"alarmStatus", alarmStatus.toString(),
"alarmOriginatorEntityType", alarmOriginator.getEntityType().toString(),
"alarmOriginatorEntityType", alarmOriginator.getEntityType().normalName(),
"alarmOriginatorId", alarmOriginator.getId().toString(),
"alarmOriginatorName", alarmOriginatorName
);
@ -70,6 +74,11 @@ public class AlarmAssignmentNotificationInfo implements RuleOriginatedNotificati
return alarmCustomerId;
}
@Override
public UserId getTargetUserId() {
return assigneeId;
}
@Override
public EntityId getStateEntityId() {
return alarmOriginator;

View File

@ -36,6 +36,7 @@ import static org.thingsboard.server.common.data.util.CollectionsUtil.mapOf;
public class AlarmCommentNotificationInfo implements RuleOriginatedNotificationInfo {
private String comment;
private String action;
private String userName;
private String alarmType;
private UUID alarmId;
@ -49,12 +50,13 @@ public class AlarmCommentNotificationInfo implements RuleOriginatedNotificationI
public Map<String, String> getTemplateData() {
return mapOf(
"comment", comment,
"action", action,
"userName", userName,
"alarmType", alarmType,
"alarmId", alarmId.toString(),
"alarmSeverity", alarmSeverity.toString(),
"alarmStatus", alarmStatus.toString(),
"alarmOriginatorEntityType", alarmOriginator.getEntityType().toString(),
"alarmOriginatorEntityType", alarmOriginator.getEntityType().normalName(),
"alarmOriginatorId", alarmOriginator.getId().toString(),
"alarmOriginatorName", alarmOriginatorName
);

View File

@ -36,6 +36,7 @@ import static org.thingsboard.server.common.data.util.CollectionsUtil.mapOf;
public class AlarmNotificationInfo implements RuleOriginatedNotificationInfo {
private String alarmType;
private String action;
private UUID alarmId;
private EntityId alarmOriginator;
private String alarmOriginatorName;
@ -45,13 +46,13 @@ public class AlarmNotificationInfo implements RuleOriginatedNotificationInfo {
@Override
public Map<String, String> getTemplateData() {
// TODO: readable status change
return mapOf(
"alarmType", alarmType,
"action", action,
"alarmId", alarmId.toString(),
"alarmSeverity", alarmSeverity.toString(),
"alarmStatus", alarmStatus.toString(),
"alarmOriginatorEntityType", alarmOriginator.getEntityType().toString(),
"alarmOriginatorEntityType", alarmOriginator.getEntityType().normalName(),
"alarmOriginatorName", alarmOriginatorName,
"alarmOriginatorId", alarmOriginator.getId().toString()
);

View File

@ -36,9 +36,8 @@ public class EntitiesLimitNotificationInfo implements NotificationInfo {
@Override
public Map<String, String> getTemplateData() {
// FIXME: readable entity type name, e.g. 'Devices'
return mapOf(
"entityType", entityType.name(),
"entityType", entityType.normalName(),
"threshold", String.valueOf(threshold)
);
}

View File

@ -49,7 +49,7 @@ public class EntityActionNotificationInfo implements RuleOriginatedNotificationI
@Override
public Map<String, String> getTemplateData() {
return mapOf(
"entityType", entityId.getEntityType().name(),
"entityType", entityId.getEntityType().normalName(),
"entityId", entityId.toString(),
"entityName", entityName,
"actionType", actionType.name().toLowerCase(),

View File

@ -46,9 +46,9 @@ public class RuleEngineComponentLifecycleEventNotificationInfo implements Notifi
"ruleChainId", ruleChainId.toString(),
"ruleChainName", ruleChainName,
"componentId", componentId.toString(),
"componentType", componentId.getEntityType().name(),
"componentType", componentId.getEntityType().normalName(),
"componentName", componentName,
"eventType", eventType.name(),
"eventType", eventType.name().toLowerCase(),
"error", error
);
}

View File

@ -16,9 +16,14 @@
package org.thingsboard.server.common.data.notification.info;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.UserId;
public interface RuleOriginatedNotificationInfo extends NotificationInfo {
CustomerId getOriginatorEntityCustomerId();
default UserId getTargetUserId() {
return null;
}
}

View File

@ -27,6 +27,7 @@ public class AlarmAssignmentNotificationRuleTriggerConfig implements Notificatio
private Set<String> alarmTypes;
private Set<AlarmSeverity> alarmSeverities;
private Set<AlarmSearchStatus> alarmStatuses;
private boolean notifyOnAssign;
private boolean notifyOnUnassign;
@Override

View File

@ -28,6 +28,7 @@ public class AlarmCommentNotificationRuleTriggerConfig implements NotificationRu
private Set<AlarmSeverity> alarmSeverities;
private Set<AlarmSearchStatus> alarmStatuses;
private boolean onlyUserComments;
private boolean notifyOnCommentUpdate;
@Override
public NotificationRuleTriggerType getTriggerType() {

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 ActionTargetUserFilter implements UsersFilter {
@Override
public UsersFilterType getType() {
return UsersFilterType.ACTION_TARGET_USER;
}
}

View File

@ -0,0 +1,34 @@
/**
* 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;
import java.util.Set;
import java.util.UUID;
@Data
public class TenantAdministratorsFilter implements UsersFilter {
private Set<UUID> tenantsIds;
private Set<UUID> tenantProfilesIds;
@Override
public UsersFilterType getType() {
return UsersFilterType.TENANT_ADMINISTRATORS;
}
}

View File

@ -26,8 +26,10 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
@JsonSubTypes({
@Type(value = UserListFilter.class, name = "USER_LIST"),
@Type(value = CustomerUsersFilter.class, name = "CUSTOMER_USERS"),
@Type(value = TenantAdministratorsFilter.class, name = "TENANT_ADMINISTRATORS"),
@Type(value = AllUsersFilter.class, name = "ALL_USERS"),
@Type(value = OriginatorEntityOwnerUsersFilter.class, name = "ORIGINATOR_ENTITY_OWNER_USERS") // for usage in notification rules
@Type(value = OriginatorEntityOwnerUsersFilter.class, name = "ORIGINATOR_ENTITY_OWNER_USERS"),
@Type(value = ActionTargetUserFilter.class, name = "ACTION_TARGET_USER")
})
public interface UsersFilter {

View File

@ -16,8 +16,12 @@
package org.thingsboard.server.common.data.notification.targets.platform;
public enum UsersFilterType {
USER_LIST,
CUSTOMER_USERS,
TENANT_ADMINISTRATORS,
ALL_USERS,
ORIGINATOR_ENTITY_OWNER_USERS
ORIGINATOR_ENTITY_OWNER_USERS,
ACTION_TARGET_USER
}

View File

@ -25,13 +25,14 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.HasId;
import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.TenantProfileId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.notification.NotificationRequestStatus;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.NotificationTargetConfig;
import org.thingsboard.server.common.data.notification.targets.platform.CustomerUsersFilter;
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.TenantAdministratorsFilter;
import org.thingsboard.server.common.data.notification.targets.platform.UserListFilter;
import org.thingsboard.server.common.data.notification.targets.platform.UsersFilter;
import org.thingsboard.server.common.data.page.PageData;
@ -46,6 +47,8 @@ import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import static org.apache.commons.collections.CollectionUtils.isNotEmpty;
@Service
@Slf4j
@RequiredArgsConstructor
@ -117,15 +120,30 @@ public class DefaultNotificationTargetService extends AbstractEntityService impl
CustomerUsersFilter filter = (CustomerUsersFilter) usersFilter;
return userService.findCustomerUsers(tenantId, new CustomerId(filter.getCustomerId()), pageLink);
}
case TENANT_ADMINISTRATORS: {
TenantAdministratorsFilter filter = (TenantAdministratorsFilter) usersFilter;
if (!tenantId.equals(TenantId.SYS_TENANT_ID)) {
return userService.findTenantAdmins(tenantId, pageLink);
} else {
if (isNotEmpty(filter.getTenantsIds())) {
return userService.findTenantAdminsByTenantsIds(filter.getTenantsIds().stream()
.map(TenantId::fromUUID).collect(Collectors.toList()), pageLink);
} else if (isNotEmpty(filter.getTenantProfilesIds())) {
return userService.findTenantAdminsByTenantProfilesIds(filter.getTenantProfilesIds().stream()
.map(TenantProfileId::new).collect(Collectors.toList()), pageLink);
} else {
return userService.findAllTenantAdmins(pageLink);
}
}
}
case ALL_USERS: {
if (!tenantId.equals(TenantId.SYS_TENANT_ID)) {
return userService.findUsersByTenantId(tenantId, pageLink);
} else {
return userService.findAllUsers(TenantId.SYS_TENANT_ID, pageLink);
return userService.findAllUsers(pageLink);
}
}
case ORIGINATOR_ENTITY_OWNER_USERS: {
OriginatorEntityOwnerUsersFilter filter = (OriginatorEntityOwnerUsersFilter) usersFilter;
if (customerId != null && !customerId.isNullUid()) {
return userService.findCustomerUsers(tenantId, customerId, pageLink);
} else {

View File

@ -106,7 +106,7 @@ public abstract class DataValidator<D extends BaseData<?>> {
protected void validateNumberOfEntitiesPerTenant(TenantId tenantId,
EntityType entityType) {
if (!apiLimitService.checkEntitiesLimit(tenantId, entityType)) {
throw new DataValidationException(String.format("%ss limit reached", capitalize(entityType.name().toLowerCase().replaceAll("_", " "))));
throw new DataValidationException(entityType.normalName() + "s limit reached");
}
}

View File

@ -21,6 +21,7 @@ import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.TenantProfileId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority;
@ -30,6 +31,7 @@ import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao;
import org.thingsboard.server.dao.user.UserDao;
import org.thingsboard.server.dao.util.SqlDao;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
@ -101,10 +103,26 @@ public class JpaUserDao extends JpaAbstractSearchTextDao<UserEntity, User> imple
}
@Override
public PageData<User> findAll(TenantId tenantId, PageLink pageLink) {
public PageData<User> findAll(PageLink pageLink) {
return DaoUtil.toPageData(userRepository.findAll(DaoUtil.toPageable(pageLink)));
}
@Override
public PageData<User> findAllByAuthority(Authority authority, PageLink pageLink) {
return DaoUtil.toPageData(userRepository.findAllByAuthority(authority, DaoUtil.toPageable(pageLink)));
}
@Override
public PageData<User> findByAuthorityAndTenantsIds(Authority authority, List<TenantId> tenantsIds, PageLink pageLink) {
return DaoUtil.toPageData(userRepository.findByAuthorityAndTenantIdIn(authority, DaoUtil.toUUIDs(tenantsIds), DaoUtil.toPageable(pageLink)));
}
@Override
public PageData<User> findByAuthorityAndTenantProfilesIds(Authority authority, List<TenantProfileId> tenantProfilesIds, PageLink pageLink) {
return DaoUtil.toPageData(userRepository.findByAuthorityAndTenantProfilesIds(authority, DaoUtil.toUUIDs(tenantProfilesIds),
DaoUtil.toPageable(pageLink)));
}
@Override
public Long countByTenantId(TenantId tenantId) {
return userRepository.countByTenantId(tenantId.getId());

View File

@ -20,10 +20,10 @@ import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.model.sql.UserEntity;
import java.util.Collection;
import java.util.UUID;
/**
@ -50,6 +50,17 @@ public interface UserRepository extends JpaRepository<UserEntity, UUID> {
@Param("searchText") String searchText,
Pageable pageable);
Page<UserEntity> findAllByAuthority(Authority authority, Pageable pageable);
Page<UserEntity> findByAuthorityAndTenantIdIn(Authority authority, Collection<UUID> tenantsIds, Pageable pageable);
@Query("SELECT u FROM UserEntity u INNER JOIN TenantEntity t ON u.tenantId = t.id AND u.authority = :authority " +
"INNER JOIN TenantProfileEntity p ON t.tenantProfileId = p.id " +
"WHERE p.id IN :profiles")
Page<UserEntity> findByAuthorityAndTenantProfilesIds(@Param("authority") Authority authority,
@Param("profiles") Collection<UUID> tenantProfilesIds,
Pageable pageable);
Long countByTenantId(UUID tenantId);
}

View File

@ -13,9 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.apiusage;
package org.thingsboard.server.dao.usagerecord;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.TenantId;
@ -25,7 +26,7 @@ import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileCon
import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.usagerecord.ApiLimitService;
import org.thingsboard.server.service.notification.rule.NotificationRuleProcessingService;
import org.thingsboard.server.dao.notification.NotificationRuleProcessingService;
@Service
@RequiredArgsConstructor
@ -33,7 +34,8 @@ public class DefaultApiLimitService implements ApiLimitService {
private final EntityService entityService;
private final TbTenantProfileCache tenantProfileCache;
private final NotificationRuleProcessingService notificationRuleProcessingService;
@Autowired(required = false)
private NotificationRuleProcessingService notificationRuleProcessingService;
@Override
public boolean checkEntitiesLimit(TenantId tenantId, EntityType entityType) {
@ -43,7 +45,9 @@ public class DefaultApiLimitService implements ApiLimitService {
EntityTypeFilter filter = new EntityTypeFilter();
filter.setEntityType(entityType);
long currentCount = entityService.countEntitiesByQuery(tenantId, null, new EntityCountQuery(filter));
if (notificationRuleProcessingService != null) {
notificationRuleProcessingService.process(tenantId, entityType, limit, currentCount);
}
return currentCount < limit;
} else {
return true;

View File

@ -17,11 +17,14 @@ package org.thingsboard.server.dao.user;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.TenantProfileId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.TenantEntityDao;
import java.util.List;
import java.util.UUID;
public interface UserDao extends Dao<User>, TenantEntityDao {
@ -79,6 +82,12 @@ public interface UserDao extends Dao<User>, TenantEntityDao {
*/
PageData<User> findCustomerUsers(UUID tenantId, UUID customerId, PageLink pageLink);
PageData<User> findAll(TenantId tenantId, PageLink pageLink);
PageData<User> findAll(PageLink pageLink);
PageData<User> findAllByAuthority(Authority authority, PageLink pageLink);
PageData<User> findByAuthorityAndTenantsIds(Authority authority, List<TenantId> tenantsIds, PageLink pageLink);
PageData<User> findByAuthorityAndTenantProfilesIds(Authority authority, List<TenantProfileId> tenantProfilesIds, PageLink pageLink);
}

View File

@ -34,10 +34,12 @@ import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.HasId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.TenantProfileId;
import org.thingsboard.server.common.data.id.UserCredentialsId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.common.data.security.event.UserCredentialsInvalidationEvent;
import org.thingsboard.server.dao.entity.AbstractEntityService;
@ -46,6 +48,7 @@ import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@ -250,8 +253,23 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic
}
@Override
public PageData<User> findAllUsers(TenantId tenantId, PageLink pageLink) {
return userDao.findAll(tenantId, pageLink);
public PageData<User> findAllTenantAdmins(PageLink pageLink) {
return userDao.findAllByAuthority(Authority.TENANT_ADMIN, pageLink);
}
@Override
public PageData<User> findTenantAdminsByTenantsIds(List<TenantId> tenantsIds, PageLink pageLink) {
return userDao.findByAuthorityAndTenantsIds(Authority.TENANT_ADMIN, tenantsIds, pageLink);
}
@Override
public PageData<User> findTenantAdminsByTenantProfilesIds(List<TenantProfileId> tenantProfilesIds, PageLink pageLink) {
return userDao.findByAuthorityAndTenantProfilesIds(Authority.TENANT_ADMIN, tenantProfilesIds, pageLink);
}
@Override
public PageData<User> findAllUsers(PageLink pageLink) {
return userDao.findAll(pageLink);
}
@Override