Browse Source

Alarm related notification rules

pull/7911/head
ViacheslavKlimov 4 years ago
parent
commit
4a20adc62e
  1. 4
      application/src/main/data/upgrade/3.4.4/schema_update.sql
  2. 53
      application/src/main/java/org/thingsboard/server/service/apiusage/DefaultApiLimitService.java
  3. 3
      application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java
  4. 3
      application/src/main/java/org/thingsboard/server/service/entitiy/TbNotificationEntityService.java
  5. 61
      application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessingService.java
  6. 14
      application/src/main/java/org/thingsboard/server/service/notification/rule/NotificationRuleProcessingService.java
  7. 79
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmAssignmentTriggerProcessor.java
  8. 24
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmCommentTriggerProcessor.java
  9. 81
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmTriggerProcessor.java
  10. 1
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/DeviceInactivityTriggerProcessor.java
  11. 62
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EntitiesLimitTriggerProcessor.java
  12. 45
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/NewPlatformVersionTriggerProcessor.java
  13. 2
      application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/BackupCodeTwoFaProvider.java
  14. 4
      application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java
  15. 6
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java
  16. 5
      application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitVersionControlQueueService.java
  17. 4
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java
  18. 11
      application/src/main/java/org/thingsboard/server/service/update/DefaultUpdateService.java
  19. 5
      application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java
  20. 13
      common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmApiCallResult.java
  21. 25
      common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/ApiLimitService.java
  22. 3
      common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationProcessingContext.java
  23. 5
      common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationType.java
  24. 78
      common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmAssignmentNotificationInfo.java
  25. 19
      common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmCommentNotificationInfo.java
  26. 17
      common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmNotificationInfo.java
  27. 7
      common/data/src/main/java/org/thingsboard/server/common/data/notification/info/DeviceInactivityNotificationInfo.java
  28. 45
      common/data/src/main/java/org/thingsboard/server/common/data/notification/info/EntitiesLimitNotificationInfo.java
  29. 4
      common/data/src/main/java/org/thingsboard/server/common/data/notification/info/EntityActionNotificationInfo.java
  30. 42
      common/data/src/main/java/org/thingsboard/server/common/data/notification/info/NewPlatformVersionNotificationInfo.java
  31. 6
      common/data/src/main/java/org/thingsboard/server/common/data/notification/info/RuleEngineComponentLifecycleEventNotificationInfo.java
  32. 1
      common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java
  33. 37
      common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/AlarmAssignmentNotificationRuleTriggerConfig.java
  34. 1
      common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/AlarmCommentNotificationRuleTriggerConfig.java
  35. 8
      common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/AlarmNotificationRuleTriggerConfig.java
  36. 36
      common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/EntitiesLimitNotificationRuleTriggerConfig.java
  37. 28
      common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NewPlatformVersionNotificationRuleTriggerConfig.java
  38. 5
      common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NotificationRuleTriggerConfig.java
  39. 5
      common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NotificationRuleTriggerType.java
  40. 20
      common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java
  41. 22
      common/data/src/main/java/org/thingsboard/server/common/data/util/CollectionsUtil.java
  42. 2
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/model/LwM2MModelConfig.java
  43. 2
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java
  44. 2
      common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultClusterVersionControlService.java
  45. 2
      dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java
  46. 3
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  47. 19
      dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java
  48. 11
      dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetDataValidator.java
  49. 13
      dao/src/main/java/org/thingsboard/server/dao/service/validator/CustomerDataValidator.java
  50. 12
      dao/src/main/java/org/thingsboard/server/dao/service/validator/DashboardDataValidator.java
  51. 12
      dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceDataValidator.java
  52. 9
      dao/src/main/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidator.java
  53. 12
      dao/src/main/java/org/thingsboard/server/dao/service/validator/UserDataValidator.java
  54. 15
      dao/src/main/resources/sql/schema-entities-idx.sql
  55. 4
      dao/src/main/resources/sql/schema-entities.sql
  56. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java

4
application/src/main/data/upgrade/3.4.4/schema_update.sql

@ -157,7 +157,7 @@ CREATE TABLE IF NOT EXISTS notification (
recipient_id UUID NOT NULL CONSTRAINT fk_notification_recipient_id REFERENCES tb_user(id) ON DELETE CASCADE,
type VARCHAR(50) NOT NULL,
subject VARCHAR(255),
text VARCHAR(1000) NOT NULL,
body VARCHAR(1000) NOT NULL,
additional_config VARCHAR(1000),
status VARCHAR(32)
) PARTITION BY RANGE (created_time);
@ -347,7 +347,7 @@ BEGIN
UPDATE alarm a SET acknowledged = true, ack_ts = a_ts WHERE a.id = a_id AND a.tenant_id = t_id;
END IF;
SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id;
RETURN json_build_object('success', true, 'modified', modified, 'alarm', row_to_json(result))::text;
RETURN json_build_object('success', true, 'modified', modified, 'alarm', row_to_json(result), 'old', row_to_json(existing))::text;
END
$$;

53
application/src/main/java/org/thingsboard/server/service/apiusage/DefaultApiLimitService.java

@ -0,0 +1,53 @@
/**
* 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.service.apiusage;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.query.EntityCountQuery;
import org.thingsboard.server.common.data.query.EntityTypeFilter;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
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;
@Service
@RequiredArgsConstructor
public class DefaultApiLimitService implements ApiLimitService {
private final EntityService entityService;
private final TbTenantProfileCache tenantProfileCache;
private final NotificationRuleProcessingService notificationRuleProcessingService;
@Override
public boolean checkEntitiesLimit(TenantId tenantId, EntityType entityType) {
DefaultTenantProfileConfiguration profileConfiguration = tenantProfileCache.get(tenantId).getDefaultProfileConfiguration();
long limit = profileConfiguration.getEntitiesLimit(entityType);
if (limit > 0) {
EntityTypeFilter filter = new EntityTypeFilter();
filter.setEntityType(entityType);
long currentCount = entityService.countEntitiesByQuery(tenantId, null, new EntityCountQuery(filter));
notificationRuleProcessingService.process(tenantId, entityType, limit, currentCount);
return currentCount < limit;
} else {
return true;
}
}
}

3
application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java

@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
@ -221,7 +222,7 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS
}
@Override
public void notifyCreateOrUpdateAlarm(Alarm alarm, ActionType actionType, User user, Object... additionalInfo) {
public void notifyCreateOrUpdateAlarm(AlarmInfo alarm, ActionType actionType, User user, Object... additionalInfo) {
logEntityAction(alarm.getTenantId(), alarm.getOriginator(), alarm, alarm.getCustomerId(), actionType, user, additionalInfo);
sendEntityNotificationMsg(alarm.getTenantId(), alarm.getId(), edgeTypeByActionType(actionType));
}

3
application/src/main/java/org/thingsboard/server/service/entitiy/TbNotificationEntityService.java

@ -21,6 +21,7 @@ import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
@ -100,7 +101,7 @@ public interface TbNotificationEntityService {
void notifyCreateOrUpdateOrDeleteEdge(TenantId tenantId, EdgeId edgeId, CustomerId customerId, Edge edge, ActionType actionType,
User user, Object... additionalInfo);
void notifyCreateOrUpdateAlarm(Alarm alarm, ActionType actionType, User user, Object... additionalInfo);
void notifyCreateOrUpdateAlarm(AlarmInfo alarm, ActionType actionType, User user, Object... additionalInfo);
void notifyAlarmComment(Alarm alarm, AlarmComment alarmComment, ActionType actionType, User user);

61
application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessingService.java

@ -23,7 +23,7 @@ import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.NotificationCenter;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.UpdateMessage;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.NotificationRequestId;
import org.thingsboard.server.common.data.id.NotificationRuleId;
@ -39,11 +39,12 @@ 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.NotificationRuleService;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.service.executors.NotificationExecutorService;
import org.thingsboard.server.service.notification.rule.trigger.AlarmTriggerProcessor.AlarmTriggerObject;
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;
@ -84,12 +85,8 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
}
@Override
public void process(TenantId tenantId, Alarm alarm, boolean deleted) {
AlarmTriggerObject triggerObject = AlarmTriggerObject.builder()
.alarm(alarm)
.deleted(deleted)
.build();
processTrigger(tenantId, NotificationRuleTriggerType.ALARM, alarm.getId(), triggerObject);
public void process(TenantId tenantId, AlarmApiCallResult alarmUpdate) {
processTrigger(tenantId, NotificationRuleTriggerType.ALARM, alarmUpdate.getAlarm().getId(), alarmUpdate);
}
@Override
@ -105,11 +102,43 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
processTrigger(tenantId, NotificationRuleTriggerType.RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT, componentId, triggerObject);
}
@Override
public void process(UpdateMessage platformUpdateMessage) {
// if (!partitionService.resolve(ServiceType.TB_CORE, TenantId.SYS_TENANT_ID, TenantId.SYS_TENANT_ID).isMyPartition()) {
// return;
// }
// // todo: don't send repetitive notification after platform restart?
//
// processTrigger(TenantId.SYS_TENANT_ID, NotificationRuleTriggerType.NEW_PLATFORM_VERSION, TenantId.SYS_TENANT_ID, platformUpdateMessage);
}
@Override
public void process(TenantId tenantId, EntityType entityType, long limit, long currentCount) {
// EntitiesLimitTriggerObject triggerObject = EntitiesLimitTriggerObject.builder()
// .entityType(entityType)
// .limit(limit)
// .currentCount(currentCount)
// .build();
}
@Override
public void process(ComponentLifecycleMsg componentLifecycleMsg) {
// EntityId entityId = componentLifecycleMsg.getEntityId();
// switch (entityId.getEntityType()) {
// case TENANT:
//
// }
}
private void processTrigger(TenantId tenantId, NotificationRuleTriggerType triggerType, EntityId originatorEntityId, Object triggerObject) {
List<NotificationRule> rules = notificationRuleService.findNotificationRulesByTenantIdAndTriggerType(tenantId, triggerType);
for (NotificationRule rule : rules) {
notificationExecutor.submit(() -> {
processNotificationRule(rule, originatorEntityId, triggerObject);
try {
processNotificationRule(rule, originatorEntityId, triggerObject);
} catch (Throwable e) {
log.error("Failed to process notification rule {} for trigger type {} with trigger object {}", rule.getId(), rule.getTriggerType(), triggerObject, e);
}
});
}
}
@ -139,14 +168,12 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
return;
}
if (!matchesFilter(triggerObject, triggerConfig)) {
return;
if (matchesFilter(triggerObject, triggerConfig)) {
NotificationInfo notificationInfo = constructNotificationInfo(triggerObject, triggerConfig);
rule.getRecipientsConfig().getTargetsTable().forEach((delay, targets) -> {
submitNotificationRequest(targets, rule, originatorEntityId, notificationInfo, delay);
});
}
NotificationInfo notificationInfo = constructNotificationInfo(triggerObject, triggerConfig);
rule.getRecipientsConfig().getTargetsTable().forEach((delay, targets) -> {
submitNotificationRequest(targets, rule, originatorEntityId, notificationInfo, delay);
});
}
private boolean matchesFilter(Object triggerObject, NotificationRuleTriggerConfig triggerConfig) {

14
application/src/main/java/org/thingsboard/server/service/notification/rule/NotificationRuleProcessingService.java

@ -15,20 +15,30 @@
*/
package org.thingsboard.server.service.notification.rule;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.UpdateMessage;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
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.dao.alarm.AlarmApiCallResult;
public interface NotificationRuleProcessingService {
void process(TenantId tenantId, TbMsg ruleEngineMsg);
void process(TenantId tenantId, Alarm alarm, boolean deleted);
// for handling internal component lifecycle events that are not getting to rule chain
void process(ComponentLifecycleMsg componentLifecycleMsg);
void process(TenantId tenantId, AlarmApiCallResult alarmUpdate);
void process(TenantId tenantId, RuleChainId ruleChainId, String ruleChainName,
EntityId componentId, String componentName, ComponentLifecycleEvent eventType, Exception error);
void process(UpdateMessage platformUpdateMessage);
void process(TenantId tenantId, EntityType entityType, long limit, long currentCount);
}

79
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmAssignmentTriggerProcessor.java

@ -0,0 +1,79 @@
/**
* 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.service.notification.rule.trigger;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmAssignee;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmStatusFilter;
import org.thingsboard.server.common.data.notification.info.AlarmAssignmentNotificationInfo;
import org.thingsboard.server.common.data.notification.info.NotificationInfo;
import org.thingsboard.server.common.data.notification.rule.trigger.AlarmAssignmentNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.msg.TbMsg;
import java.util.Set;
import static org.apache.commons.collections.CollectionUtils.isEmpty;
@Service
public class AlarmAssignmentTriggerProcessor implements RuleEngineMsgNotificationRuleTriggerProcessor<AlarmAssignmentNotificationRuleTriggerConfig> {
@Override
public boolean matchesFilter(TbMsg ruleEngineMsg, AlarmAssignmentNotificationRuleTriggerConfig triggerConfig) {
if (ruleEngineMsg.getType().equals(DataConstants.ALARM_UNASSIGN) && !triggerConfig.isNotifyOnUnassign()) {
return false;
}
Alarm alarm = JacksonUtil.fromString(ruleEngineMsg.getData(), Alarm.class);
return (isEmpty(triggerConfig.getAlarmTypes()) || triggerConfig.getAlarmTypes().contains(alarm.getType())) &&
(isEmpty(triggerConfig.getAlarmSeverities()) || triggerConfig.getAlarmSeverities().contains(alarm.getSeverity())) &&
(isEmpty(triggerConfig.getAlarmStatuses()) || AlarmStatusFilter.from(triggerConfig.getAlarmStatuses()).matches(alarm));
}
@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()
.assigneeFirstName(assignee != null ? assignee.getFirstName() : null)
.assigneeLastName(assignee != null ? assignee.getLastName() : null)
.assigneeEmail(assignee != null ? assignee.getEmail() : null)
.userName(ruleEngineMsg.getMetaData().getValue("userName"))
.alarmId(alarmInfo.getUuidId())
.alarmType(alarmInfo.getType())
.alarmOriginator(alarmInfo.getOriginator())
.alarmOriginatorName(alarmInfo.getOriginatorName())
.alarmSeverity(alarmInfo.getSeverity())
.alarmStatus(alarmInfo.getStatus())
.alarmCustomerId(alarmInfo.getCustomerId())
.build();
}
@Override
public NotificationRuleTriggerType getTriggerType() {
return NotificationRuleTriggerType.ALARM_ASSIGNMENT;
}
@Override
public Set<String> getSupportedMsgTypes() {
return Set.of(DataConstants.ALARM_ASSIGN, DataConstants.ALARM_UNASSIGN);
}
}

24
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmCommentTriggerProcessor.java

@ -20,6 +20,8 @@ import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.alarm.AlarmCommentType;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmStatusFilter;
import org.thingsboard.server.common.data.notification.info.AlarmCommentNotificationInfo;
import org.thingsboard.server.common.data.notification.info.NotificationInfo;
@ -39,6 +41,12 @@ public class AlarmCommentTriggerProcessor implements RuleEngineMsgNotificationRu
if (ruleEngineMsg.getMetaData().getValue("comment") == null) {
return false;
}
if (triggerConfig.isOnlyUserComments()) {
AlarmComment comment = JacksonUtil.fromString(ruleEngineMsg.getMetaData().getValue("comment"), AlarmComment.class);
if (comment.getType() == AlarmCommentType.SYSTEM) {
return false;
}
}
Alarm alarm = JacksonUtil.fromString(ruleEngineMsg.getData(), Alarm.class);
return (isEmpty(triggerConfig.getAlarmTypes()) || triggerConfig.getAlarmTypes().contains(alarm.getType())) &&
(isEmpty(triggerConfig.getAlarmSeverities()) || triggerConfig.getAlarmSeverities().contains(alarm.getSeverity())) &&
@ -47,15 +55,19 @@ 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);
Alarm alarm = JacksonUtil.fromString(ruleEngineMsg.getData(), Alarm.class);
AlarmInfo alarmInfo = JacksonUtil.fromString(ruleEngineMsg.getData(), AlarmInfo.class);
return AlarmCommentNotificationInfo.builder()
.comment(comment.getComment().get("text").asText())
.alarmId(alarm.getUuidId())
.alarmType(alarm.getType())
.alarmOriginator(alarm.getOriginator())
.alarmSeverity(alarm.getSeverity())
.alarmStatus(alarm.getStatus())
.userName(ruleEngineMsg.getMetaData().getValue("userName"))
.alarmId(alarmInfo.getUuidId())
.alarmType(alarmInfo.getType())
.alarmOriginator(alarmInfo.getOriginator())
.alarmOriginatorName(alarmInfo.getOriginatorName())
.alarmSeverity(alarmInfo.getSeverity())
.alarmStatus(alarmInfo.getStatus())
.alarmCustomerId(alarmInfo.getCustomerId())
.build();
}

81
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmTriggerProcessor.java

@ -15,37 +15,63 @@
*/
package org.thingsboard.server.service.notification.rule.trigger;
import lombok.Builder;
import lombok.Data;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmStatusFilter;
import org.thingsboard.server.common.data.notification.info.AlarmNotificationInfo;
import org.thingsboard.server.common.data.notification.info.NotificationInfo;
import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig.AlarmAction;
import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig.ClearRule;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.service.notification.rule.trigger.AlarmTriggerProcessor.AlarmTriggerObject;
import org.thingsboard.server.dao.alarm.AlarmApiCallResult;
import static org.apache.commons.collections.CollectionUtils.isEmpty;
import static org.apache.commons.collections.CollectionUtils.isNotEmpty;
@Service
public class AlarmTriggerProcessor implements NotificationRuleTriggerProcessor<AlarmTriggerObject, AlarmNotificationRuleTriggerConfig> {
public class AlarmTriggerProcessor implements NotificationRuleTriggerProcessor<AlarmApiCallResult, AlarmNotificationRuleTriggerConfig> {
@Override
public boolean matchesFilter(AlarmTriggerObject triggerObject, AlarmNotificationRuleTriggerConfig triggerConfig) {
Alarm alarm = triggerObject.getAlarm();
return (isEmpty(triggerConfig.getAlarmTypes()) || triggerConfig.getAlarmTypes().contains(alarm.getType())) &&
(isEmpty(triggerConfig.getAlarmSeverities()) || triggerConfig.getAlarmSeverities().contains(alarm.getSeverity()));
public boolean matchesFilter(AlarmApiCallResult alarmUpdate, AlarmNotificationRuleTriggerConfig triggerConfig) {
Alarm alarm = alarmUpdate.getAlarm();
if (!typeMatches(alarm, triggerConfig)) {
return false;
}
if (alarmUpdate.isCreated()) {
if (triggerConfig.getNotifyOn().contains(AlarmAction.CREATED)) {
return severityMatches(alarm, triggerConfig);
}
} else if (alarmUpdate.isSeverityChanged()) {
if (triggerConfig.getNotifyOn().contains(AlarmAction.SEVERITY_CHANGED)) {
return severityMatches(alarmUpdate.getOld(), triggerConfig) || severityMatches(alarm, triggerConfig);
} else {
// if we haven't yet sent notification about the alarm
return !severityMatches(alarmUpdate.getOld(), triggerConfig) && severityMatches(alarm, triggerConfig);
}
} else if (alarmUpdate.isAcknowledged()) {
if (triggerConfig.getNotifyOn().contains(AlarmAction.ACKNOWLEDGED)) {
return severityMatches(alarm, triggerConfig);
}
} else if (alarmUpdate.isCleared()) {
if (triggerConfig.getNotifyOn().contains(AlarmAction.CLEARED)) {
return severityMatches(alarm, triggerConfig);
}
}
return false;
}
@Override
public boolean matchesClearRule(AlarmTriggerObject triggerObject, AlarmNotificationRuleTriggerConfig triggerConfig) {
if (triggerObject.isDeleted()) {
public boolean matchesClearRule(AlarmApiCallResult alarmUpdate, AlarmNotificationRuleTriggerConfig triggerConfig) {
Alarm alarm = alarmUpdate.getAlarm();
if (!typeMatches(alarm, triggerConfig)) {
return false;
}
if (alarmUpdate.isDeleted()) {
return true;
}
Alarm alarm = triggerObject.getAlarm();
ClearRule clearRule = triggerConfig.getClearRule();
if (clearRule != null) {
if (isNotEmpty(clearRule.getAlarmStatuses())) {
@ -55,16 +81,26 @@ public class AlarmTriggerProcessor implements NotificationRuleTriggerProcessor<A
return false;
}
private boolean severityMatches(Alarm alarm, AlarmNotificationRuleTriggerConfig triggerConfig) {
return isEmpty(triggerConfig.getAlarmSeverities()) || triggerConfig.getAlarmSeverities().contains(alarm.getSeverity());
}
private boolean typeMatches(Alarm alarm, AlarmNotificationRuleTriggerConfig triggerConfig) {
return isEmpty(triggerConfig.getAlarmTypes()) || triggerConfig.getAlarmTypes().contains(alarm.getType());
}
@Override
public NotificationInfo constructNotificationInfo(AlarmTriggerObject triggerObject, AlarmNotificationRuleTriggerConfig triggerConfig) {
Alarm alarm = triggerObject.getAlarm();
public NotificationInfo constructNotificationInfo(AlarmApiCallResult alarmUpdate, AlarmNotificationRuleTriggerConfig triggerConfig) {
// TODO: readable action
AlarmInfo alarmInfo = alarmUpdate.getAlarm();
return AlarmNotificationInfo.builder()
.alarmId(alarm.getUuidId())
.alarmType(alarm.getType())
.alarmOriginator(alarm.getOriginator())
.alarmSeverity(alarm.getSeverity())
.alarmStatus(alarm.getStatus())
.alarmCustomerId(alarm.getCustomerId())
.alarmId(alarmInfo.getUuidId())
.alarmType(alarmInfo.getType())
.alarmOriginator(alarmInfo.getOriginator())
.alarmOriginatorName(alarmInfo.getOriginatorName())
.alarmSeverity(alarmInfo.getSeverity())
.alarmStatus(alarmInfo.getStatus())
.alarmCustomerId(alarmInfo.getCustomerId())
.build();
}
@ -73,11 +109,4 @@ public class AlarmTriggerProcessor implements NotificationRuleTriggerProcessor<A
return NotificationRuleTriggerType.ALARM;
}
@Data
@Builder
public static class AlarmTriggerObject {
private final Alarm alarm;
private final boolean deleted;
}
}

1
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/DeviceInactivityTriggerProcessor.java

@ -56,6 +56,7 @@ public class DeviceInactivityTriggerProcessor implements RuleEngineMsgNotificati
.deviceId(ruleEngineMsg.getOriginator().getId())
.deviceName(ruleEngineMsg.getMetaData().getValue("deviceName"))
.deviceType(ruleEngineMsg.getMetaData().getValue("deviceType"))
.deviceLabel(ruleEngineMsg.getMetaData().getValue("deviceLabel"))
.deviceCustomerId(ruleEngineMsg.getCustomerId())
.build();
}

62
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EntitiesLimitTriggerProcessor.java

@ -0,0 +1,62 @@
/**
* 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.service.notification.rule.trigger;
import lombok.Builder;
import lombok.Data;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
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.service.notification.rule.trigger.EntitiesLimitTriggerProcessor.EntitiesLimitTriggerObject;
import static org.apache.commons.collections.CollectionUtils.isNotEmpty;
@Service
public class EntitiesLimitTriggerProcessor implements NotificationRuleTriggerProcessor<EntitiesLimitTriggerObject, EntitiesLimitNotificationRuleTriggerConfig> {
@Override
public boolean matchesFilter(EntitiesLimitTriggerObject triggerObject, EntitiesLimitNotificationRuleTriggerConfig triggerConfig) {
if (isNotEmpty(triggerConfig.getEntityTypes()) && !triggerConfig.getEntityTypes().contains(triggerObject.getEntityType())) {
return false;
}
return ((float) triggerObject.getCurrentCount() / triggerObject.getLimit()) >= triggerConfig.getThreshold();
}
@Override
public NotificationInfo constructNotificationInfo(EntitiesLimitTriggerObject triggerObject, EntitiesLimitNotificationRuleTriggerConfig triggerConfig) {
return EntitiesLimitNotificationInfo.builder()
.entityType(triggerObject.getEntityType())
.threshold((int) (triggerConfig.getThreshold() * 100))
.build();
}
@Override
public NotificationRuleTriggerType getTriggerType() {
return NotificationRuleTriggerType.ENTITIES_LIMIT;
}
@Data
@Builder
public static class EntitiesLimitTriggerObject {
private final EntityType entityType;
private final long limit;
private final long currentCount;
}
}

45
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/NewPlatformVersionTriggerProcessor.java

@ -0,0 +1,45 @@
/**
* 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.service.notification.rule.trigger;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.UpdateMessage;
import org.thingsboard.server.common.data.notification.info.NewPlatformVersionNotificationInfo;
import org.thingsboard.server.common.data.notification.info.NotificationInfo;
import org.thingsboard.server.common.data.notification.rule.trigger.NewPlatformVersionNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
@Service
public class NewPlatformVersionTriggerProcessor implements NotificationRuleTriggerProcessor<UpdateMessage, NewPlatformVersionNotificationRuleTriggerConfig> {
@Override
public boolean matchesFilter(UpdateMessage triggerObject, NewPlatformVersionNotificationRuleTriggerConfig triggerConfig) {
return triggerObject.isUpdateAvailable();
}
@Override
public NotificationInfo constructNotificationInfo(UpdateMessage updateMessage, NewPlatformVersionNotificationRuleTriggerConfig triggerConfig) {
return NewPlatformVersionNotificationInfo.builder()
.message(updateMessage.getMessage())
.build();
}
@Override
public NotificationRuleTriggerType getTriggerType() {
return NotificationRuleTriggerType.NEW_PLATFORM_VERSION;
}
}

2
application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/BackupCodeTwoFaProvider.java

@ -18,7 +18,7 @@ package org.thingsboard.server.service.security.auth.mfa.provider.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.CollectionsUtil;
import org.thingsboard.server.common.data.util.CollectionsUtil;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.security.model.mfa.account.BackupCodeTwoFaAccountConfig;

4
application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java

@ -139,6 +139,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev
private static final List<EntityKey> PERSISTENT_ENTITY_FIELDS = Arrays.asList(
new EntityKey(EntityKeyType.ENTITY_FIELD, "name"),
new EntityKey(EntityKeyType.ENTITY_FIELD, "type"),
new EntityKey(EntityKeyType.ENTITY_FIELD, "label"),
new EntityKey(EntityKeyType.ENTITY_FIELD, "createdTime"));
private final TenantService tenantService;
@ -329,6 +330,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev
DeviceStateData stateData = getOrFetchDeviceStateData(device.getId());
TbMsgMetaData md = new TbMsgMetaData();
md.putValue("deviceName", device.getName());
md.putValue("deviceLabel", device.getLabel());
md.putValue("deviceType", device.getType());
stateData.setMetaData(md);
callback.onSuccess();
@ -581,6 +583,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev
.build();
TbMsgMetaData md = new TbMsgMetaData();
md.putValue("deviceName", device.getName());
md.putValue("deviceLabel", device.getLabel());
md.putValue("deviceType", device.getType());
DeviceStateData deviceStateData = DeviceStateData.builder()
.customerId(device.getCustomerId())
@ -660,6 +663,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev
.build();
TbMsgMetaData md = new TbMsgMetaData();
md.putValue("deviceName", getEntryValue(ed, EntityKeyType.ENTITY_FIELD, "name", ""));
md.putValue("deviceLabel", getEntryValue(ed, EntityKeyType.ENTITY_FIELD, "label", ""));
md.putValue("deviceType", getEntryValue(ed, EntityKeyType.ENTITY_FIELD, "type", ""));
return DeviceStateData.builder()
.customerId(deviceIdInfo.getCustomerId())

6
application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java

@ -25,7 +25,6 @@ import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
@ -42,7 +41,6 @@ import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.common.data.alarm.AlarmAssigneeUpdate;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.gen.transport.TransportProtos.LocalSubscriptionServiceMsgProto;
@ -60,7 +58,6 @@ import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.notification.rule.NotificationRuleProcessingService;
import org.thingsboard.server.service.state.DefaultDeviceStateService;
import org.thingsboard.server.service.state.DeviceStateService;
import org.thingsboard.server.service.ws.notification.sub.NotificationRequestUpdate;
@ -100,7 +97,6 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
private final TbLocalSubscriptionService localSubscriptionService;
private final DeviceStateService deviceStateService;
private final TbClusterService clusterService;
private final NotificationRuleProcessingService notificationRuleProcessingService;
private final Map<EntityId, Set<TbSubscription>> subscriptionsByEntityId = new ConcurrentHashMap<>();
private final Map<String, Map<Integer, TbSubscription>> subscriptionsByWsSessionId = new ConcurrentHashMap<>();
@ -296,7 +292,6 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
s -> alarm.getCreatedTime() >= s.getTs() || alarm.getAssignTs() >= s.getTs(),
alarm, false
);
notificationRuleProcessingService.process(tenantId, alarm, false);
callback.onSuccess();
}
@ -313,7 +308,6 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
s -> alarm.getCreatedTime() >= s.getTs(),
alarm, true
);
notificationRuleProcessingService.process(tenantId, alarm, true);
callback.onSuccess();
}

5
application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitVersionControlQueueService.java

@ -26,7 +26,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.CollectionsUtil;
import org.thingsboard.server.common.data.util.CollectionsUtil;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.EntityType;
@ -64,7 +64,6 @@ import org.thingsboard.server.queue.util.DataDecodingEncodingService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.sync.vc.data.ClearRepositoryGitRequest;
import org.thingsboard.server.service.sync.vc.data.CommitGitRequest;
import org.thingsboard.server.service.sync.vc.data.ContentsDiffGitRequest;
import org.thingsboard.server.service.sync.vc.data.EntitiesContentGitRequest;
import org.thingsboard.server.service.sync.vc.data.EntityContentGitRequest;
import org.thingsboard.server.service.sync.vc.data.ListBranchesGitRequest;
@ -78,11 +77,9 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.TreeMap;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

4
application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java

@ -52,6 +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.service.subscription.TbSubscriptionUtils;
import java.util.Collection;
@ -68,6 +69,7 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
private final TbAlarmCommentService alarmCommentService;
private final TbApiUsageReportClient apiUsageClient;
private final TbApiUsageStateService apiUsageStateService;
private final NotificationRuleProcessingService notificationRuleProcessingService;
@Override
protected String getExecutorPrefix() {
@ -232,6 +234,7 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
return TbSubscriptionUtils.toAlarmUpdateProto(tenantId, entityId, alarm);
});
}
notificationRuleProcessingService.process(tenantId, result);
});
}
@ -246,6 +249,7 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
return TbSubscriptionUtils.toAlarmDeletedProto(tenantId, entityId, alarm);
});
}
notificationRuleProcessingService.process(tenantId, result);
});
}

11
application/src/main/java/org/thingsboard/server/service/update/DefaultUpdateService.java

@ -19,12 +19,14 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
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 javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@ -53,6 +55,9 @@ public class DefaultUpdateService implements UpdateService {
@Value("${updates.enabled}")
private boolean updatesEnabled;
@Autowired
private NotificationRuleProcessingService notificationRuleProcessingService;
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1, ThingsBoardThreadFactory.forName("tb-update-service"));
private ScheduledFuture checkUpdatesFuture = null;
@ -121,11 +126,15 @@ public class DefaultUpdateService implements UpdateService {
request.put(PLATFORM_PARAM, platform);
request.put(VERSION_PARAM, version);
request.put(INSTANCE_ID_PARAM, instanceId.toString());
JsonNode response = restClient.postForObject(UPDATE_SERVER_BASE_URL+"/api/thingsboard/updates", request, JsonNode.class);
JsonNode response = restClient.postForObject(UPDATE_SERVER_BASE_URL + "/api/thingsboard/updates", request, JsonNode.class);
UpdateMessage prevUpdateMessage = updateMessage;
updateMessage = new UpdateMessage(
response.get("message").asText(),
response.get("updateAvailable").asBoolean()
);
if (updateMessage.isUpdateAvailable() && !updateMessage.equals(prevUpdateMessage)) {
notificationRuleProcessingService.process(updateMessage);
}
} catch (Exception e) {
log.trace(e.getMessage());
}

5
application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java

@ -56,6 +56,7 @@ import org.thingsboard.server.common.data.notification.rule.EscalatedNotificatio
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.AlarmNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig.AlarmAction;
import org.thingsboard.server.common.data.notification.rule.trigger.EntityActionNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.data.notification.rule.trigger.RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig;
@ -180,6 +181,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
AlarmNotificationRuleTriggerConfig triggerConfig = new AlarmNotificationRuleTriggerConfig();
triggerConfig.setAlarmTypes(null);
triggerConfig.setAlarmSeverities(null);
triggerConfig.setNotifyOn(Set.of(AlarmAction.CREATED, AlarmAction.SEVERITY_CHANGED, AlarmAction.ACKNOWLEDGED, AlarmAction.CLEARED));
notificationRule.setTriggerConfig(triggerConfig);
EscalatedNotificationRuleRecipientsConfig recipientsConfig = new EscalatedNotificationRuleRecipientsConfig();
@ -251,8 +253,6 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
wsClient.close();
});
// TODO: test severity changes
}
@Test
@ -273,6 +273,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
AlarmNotificationRuleTriggerConfig triggerConfig = new AlarmNotificationRuleTriggerConfig();
triggerConfig.setAlarmTypes(Set.of(alarmType));
triggerConfig.setAlarmSeverities(null);
triggerConfig.setNotifyOn(Set.of(AlarmAction.CREATED, AlarmAction.SEVERITY_CHANGED, AlarmAction.ACKNOWLEDGED));
AlarmNotificationRuleTriggerConfig.ClearRule clearRule = new AlarmNotificationRuleTriggerConfig.ClearRule();
clearRule.setAlarmStatuses(Set.of(AlarmSearchStatus.CLEARED, AlarmSearchStatus.UNACK));

13
common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmApiCallResult.java

@ -32,16 +32,18 @@ public class AlarmApiCallResult {
private final boolean created;
private final boolean modified;
private final boolean cleared;
private final boolean deleted;
private final AlarmInfo alarm;
private final Alarm old;
private final List<EntityId> propagatedEntitiesList;
@Builder
private AlarmApiCallResult(boolean successful, boolean created, boolean modified, boolean cleared, AlarmInfo alarm, Alarm old, List<EntityId> propagatedEntitiesList) {
private AlarmApiCallResult(boolean successful, boolean created, boolean modified, boolean cleared, boolean deleted, AlarmInfo alarm, Alarm old, List<EntityId> propagatedEntitiesList) {
this.successful = successful;
this.created = created;
this.modified = modified;
this.cleared = cleared;
this.deleted = deleted;
this.alarm = alarm;
this.old = old;
this.propagatedEntitiesList = propagatedEntitiesList;
@ -52,6 +54,7 @@ public class AlarmApiCallResult {
this.created = other.created;
this.modified = other.modified;
this.cleared = other.cleared;
this.deleted = other.deleted;
this.alarm = other.alarm;
this.old = other.old;
this.propagatedEntitiesList = propagatedEntitiesList;
@ -65,6 +68,14 @@ public class AlarmApiCallResult {
}
}
public boolean isAcknowledged() {
if (alarm == null || old == null) {
return false;
} else {
return alarm.isAcknowledged() != old.isAcknowledged();
}
}
public AlarmSeverity getOldSeverity() {
return isSeverityChanged() ? old.getSeverity() : null;
}

25
common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/ApiLimitService.java

@ -0,0 +1,25 @@
/**
* 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.dao.usagerecord;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.TenantId;
public interface ApiLimitService {
boolean checkEntitiesLimit(TenantId tenantId, EntityType entityType);
}

3
common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationProcessingContext.java

@ -129,7 +129,8 @@ public class NotificationProcessingContext {
String result = template;
for (Map<String, String> context : contexts) {
for (Map.Entry<String, String> kv : context.entrySet()) {
result = result.replace("${" + kv.getKey() + '}', kv.getValue());
String value = Strings.nullToEmpty(kv.getValue());
result = result.replace("${" + kv.getKey() + '}', value);
}
}
return result;

5
common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationType.java

@ -22,6 +22,9 @@ public enum NotificationType {
DEVICE_INACTIVITY,
ENTITY_ACTION,
ALARM_COMMENT,
RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT
RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT,
ALARM_ASSIGNMENT,
NEW_PLATFORM_VERSION,
ENTITIES_LIMIT
}

78
common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmAssignmentNotificationInfo.java

@ -0,0 +1,78 @@
/**
* 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.info;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
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 java.util.Map;
import java.util.UUID;
import static org.thingsboard.server.common.data.util.CollectionsUtil.mapOf;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class AlarmAssignmentNotificationInfo implements RuleOriginatedNotificationInfo {
private String assigneeFirstName;
private String assigneeLastName;
private String assigneeEmail;
private String userName;
private String alarmType;
private UUID alarmId;
private EntityId alarmOriginator;
private String alarmOriginatorName;
private AlarmSeverity alarmSeverity;
private AlarmStatus alarmStatus;
private CustomerId alarmCustomerId;
@Override
public Map<String, String> getTemplateData() {
return mapOf(
"assigneeFirstName", assigneeFirstName,
"assigneeLastName", assigneeLastName,
"assigneeEmail", assigneeEmail,
"userName", userName,
"alarmType", alarmType,
"alarmId", alarmId.toString(),
"alarmSeverity", alarmSeverity.toString(),
"alarmStatus", alarmStatus.toString(),
"alarmOriginatorEntityType", alarmOriginator.getEntityType().toString(),
"alarmOriginatorId", alarmOriginator.getId().toString(),
"alarmOriginatorName", alarmOriginatorName
);
}
@Override
public CustomerId getOriginatorEntityCustomerId() {
return alarmCustomerId;
}
@Override
public EntityId getStateEntityId() {
return alarmOriginator;
}
}

19
common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmCommentNotificationInfo.java

@ -21,37 +21,50 @@ import lombok.Data;
import lombok.NoArgsConstructor;
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 java.util.Map;
import java.util.UUID;
import static org.thingsboard.server.common.data.util.CollectionsUtil.mapOf;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class AlarmCommentNotificationInfo implements NotificationInfo {
public class AlarmCommentNotificationInfo implements RuleOriginatedNotificationInfo {
private String comment;
private String userName;
private String alarmType;
private UUID alarmId;
private EntityId alarmOriginator;
private String alarmOriginatorName;
private AlarmSeverity alarmSeverity;
private AlarmStatus alarmStatus;
private CustomerId alarmCustomerId;
@Override
public Map<String, String> getTemplateData() {
return Map.of(
return mapOf(
"comment", comment,
"userName", userName,
"alarmType", alarmType,
"alarmId", alarmId.toString(),
"alarmSeverity", alarmSeverity.toString(),
"alarmStatus", alarmStatus.toString(),
"alarmOriginatorEntityType", alarmOriginator.getEntityType().toString(),
"alarmOriginatorId", alarmOriginator.getId().toString()
"alarmOriginatorId", alarmOriginator.getId().toString(),
"alarmOriginatorName", alarmOriginatorName
);
}
@Override
public CustomerId getOriginatorEntityCustomerId() {
return alarmCustomerId;
}
@Override
public EntityId getStateEntityId() {
return alarmOriginator;

17
common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmNotificationInfo.java

@ -27,6 +27,8 @@ import org.thingsboard.server.common.data.id.EntityId;
import java.util.Map;
import java.util.UUID;
import static org.thingsboard.server.common.data.util.CollectionsUtil.mapOf;
@Data
@NoArgsConstructor
@AllArgsConstructor
@ -36,27 +38,30 @@ public class AlarmNotificationInfo implements RuleOriginatedNotificationInfo {
private String alarmType;
private UUID alarmId;
private EntityId alarmOriginator;
private String alarmOriginatorName;
private AlarmSeverity alarmSeverity;
private AlarmStatus alarmStatus;
private CustomerId alarmCustomerId;
@Override
public CustomerId getOriginatorEntityCustomerId() {
return alarmCustomerId;
}
@Override
public Map<String, String> getTemplateData() {
return Map.of(
// TODO: readable status change
return mapOf(
"alarmType", alarmType,
"alarmId", alarmId.toString(),
"alarmSeverity", alarmSeverity.toString(),
"alarmStatus", alarmStatus.toString(),
"alarmOriginatorEntityType", alarmOriginator.getEntityType().toString(),
"alarmOriginatorName", alarmOriginatorName,
"alarmOriginatorId", alarmOriginator.getId().toString()
);
}
@Override
public CustomerId getOriginatorEntityCustomerId() {
return alarmCustomerId;
}
@Override
public EntityId getStateEntityId() {
return alarmOriginator;

7
common/data/src/main/java/org/thingsboard/server/common/data/notification/info/DeviceInactivityNotificationInfo.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.common.data.notification.info;
import com.google.common.base.Strings;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@ -26,6 +27,8 @@ import org.thingsboard.server.common.data.id.EntityId;
import java.util.Map;
import java.util.UUID;
import static org.thingsboard.server.common.data.util.CollectionsUtil.mapOf;
@Data
@NoArgsConstructor
@AllArgsConstructor
@ -34,6 +37,7 @@ public class DeviceInactivityNotificationInfo implements RuleOriginatedNotificat
private UUID deviceId;
private String deviceName;
private String deviceLabel;
private String deviceType;
private CustomerId deviceCustomerId;
@ -44,9 +48,10 @@ public class DeviceInactivityNotificationInfo implements RuleOriginatedNotificat
@Override
public Map<String, String> getTemplateData() {
return Map.of(
return mapOf(
"deviceId", deviceId.toString(),
"deviceName", deviceName,
"deviceLabel", deviceLabel,
"deviceType", deviceType
);
}

45
common/data/src/main/java/org/thingsboard/server/common/data/notification/info/EntitiesLimitNotificationInfo.java

@ -0,0 +1,45 @@
/**
* 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.info;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.EntityType;
import java.util.Map;
import static org.thingsboard.server.common.data.util.CollectionsUtil.mapOf;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class EntitiesLimitNotificationInfo implements NotificationInfo {
private EntityType entityType;
private int threshold;
@Override
public Map<String, String> getTemplateData() {
// FIXME: readable entity type name, e.g. 'Devices'
return mapOf(
"entityType", entityType.name(),
"threshold", String.valueOf(threshold)
);
}
}

4
common/data/src/main/java/org/thingsboard/server/common/data/notification/info/EntityActionNotificationInfo.java

@ -26,6 +26,8 @@ import org.thingsboard.server.common.data.id.EntityId;
import java.util.Map;
import java.util.UUID;
import static org.thingsboard.server.common.data.util.CollectionsUtil.mapOf;
@Data
@NoArgsConstructor
@AllArgsConstructor
@ -46,7 +48,7 @@ public class EntityActionNotificationInfo implements RuleOriginatedNotificationI
@Override
public Map<String, String> getTemplateData() {
return Map.of(
return mapOf(
"entityType", entityId.getEntityType().name(),
"entityId", entityId.toString(),
"entityName", entityName,

42
common/data/src/main/java/org/thingsboard/server/common/data/notification/info/NewPlatformVersionNotificationInfo.java

@ -0,0 +1,42 @@
/**
* 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.info;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Map;
import static org.thingsboard.server.common.data.util.CollectionsUtil.mapOf;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class NewPlatformVersionNotificationInfo implements NotificationInfo {
private String message;
@Override
public Map<String, String> getTemplateData() {
return mapOf(
"message", message
);
}
}

6
common/data/src/main/java/org/thingsboard/server/common/data/notification/info/RuleEngineComponentLifecycleEventNotificationInfo.java

@ -15,18 +15,18 @@
*/
package org.thingsboard.server.common.data.notification.info;
import com.google.common.base.Strings;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import java.util.Map;
import static org.thingsboard.server.common.data.util.CollectionsUtil.mapOf;
@Data
@AllArgsConstructor
@NoArgsConstructor
@ -42,7 +42,7 @@ public class RuleEngineComponentLifecycleEventNotificationInfo implements Notifi
@Override
public Map<String, String> getTemplateData() {
return Map.of(
return mapOf(
"ruleChainId", ruleChainId.toString(),
"ruleChainName", ruleChainName,
"componentId", componentId.toString(),

1
common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java

@ -47,6 +47,7 @@ public class NotificationRule extends BaseData<NotificationRuleId> implements Ha
@NotNull
private NotificationRuleTriggerType triggerType;
@NotNull
@Valid
private NotificationRuleTriggerConfig triggerConfig;
@NotNull
@Valid

37
common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/AlarmAssignmentNotificationRuleTriggerConfig.java

@ -0,0 +1,37 @@
/**
* 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.rule.trigger;
import lombok.Data;
import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import java.util.Set;
@Data
public class AlarmAssignmentNotificationRuleTriggerConfig implements NotificationRuleTriggerConfig {
private Set<String> alarmTypes;
private Set<AlarmSeverity> alarmSeverities;
private Set<AlarmSearchStatus> alarmStatuses;
private boolean notifyOnUnassign;
@Override
public NotificationRuleTriggerType getTriggerType() {
return NotificationRuleTriggerType.ALARM_ASSIGNMENT;
}
}

1
common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/AlarmCommentNotificationRuleTriggerConfig.java

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

8
common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/AlarmNotificationRuleTriggerConfig.java

@ -19,6 +19,7 @@ import lombok.Data;
import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import javax.validation.constraints.NotEmpty;
import java.util.Set;
@Data
@ -26,6 +27,9 @@ public class AlarmNotificationRuleTriggerConfig implements NotificationRuleTrigg
private Set<String> alarmTypes;
private Set<AlarmSeverity> alarmSeverities;
@NotEmpty
private Set<AlarmAction> notifyOn;
private ClearRule clearRule;
@Override
@ -38,4 +42,8 @@ public class AlarmNotificationRuleTriggerConfig implements NotificationRuleTrigg
private Set<AlarmSearchStatus> alarmStatuses;
}
public enum AlarmAction {
CREATED, SEVERITY_CHANGED, ACKNOWLEDGED, CLEARED
}
}

36
common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/EntitiesLimitNotificationRuleTriggerConfig.java

@ -0,0 +1,36 @@
/**
* 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.rule.trigger;
import lombok.Data;
import org.thingsboard.server.common.data.EntityType;
import javax.validation.constraints.Max;
import java.util.Set;
@Data
public class EntitiesLimitNotificationRuleTriggerConfig implements NotificationRuleTriggerConfig {
private Set<EntityType> entityTypes;
@Max(1)
private float threshold; // in percents,
@Override
public NotificationRuleTriggerType getTriggerType() {
return NotificationRuleTriggerType.ENTITIES_LIMIT;
}
}

28
common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NewPlatformVersionNotificationRuleTriggerConfig.java

@ -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.rule.trigger;
import lombok.Data;
@Data
public class NewPlatformVersionNotificationRuleTriggerConfig implements NotificationRuleTriggerConfig {
@Override
public NotificationRuleTriggerType getTriggerType() {
return NotificationRuleTriggerType.NEW_PLATFORM_VERSION;
}
}

5
common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NotificationRuleTriggerConfig.java

@ -27,7 +27,10 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
@Type(value = DeviceInactivityNotificationRuleTriggerConfig.class, name = "DEVICE_INACTIVITY"),
@Type(value = EntityActionNotificationRuleTriggerConfig.class, name = "ENTITY_ACTION"),
@Type(value = AlarmCommentNotificationRuleTriggerConfig.class, name = "ALARM_COMMENT"),
@Type(value = RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig.class, name = "RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT")
@Type(value = RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig.class, name = "RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT"),
@Type(value = AlarmAssignmentNotificationRuleTriggerConfig.class, name = "ALARM_ASSIGNMENT"),
@Type(value = NewPlatformVersionNotificationRuleTriggerConfig.class, name = "NEW_PLATFORM_VERSION"),
@Type(value = EntitiesLimitNotificationRuleTriggerConfig.class, name = "ENTITIES_LIMIT")
})
public interface NotificationRuleTriggerConfig {

5
common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NotificationRuleTriggerType.java

@ -21,6 +21,9 @@ public enum NotificationRuleTriggerType {
ALARM_COMMENT,
DEVICE_INACTIVITY,
ENTITY_ACTION,
RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT
RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT,
ALARM_ASSIGNMENT,
NEW_PLATFORM_VERSION,
ENTITIES_LIMIT
}

20
common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java

@ -20,6 +20,7 @@ import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.TenantProfileType;
@AllArgsConstructor
@ -107,6 +108,25 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura
return (long) (getProfileThreshold(key) * (warnThreshold > 0.0 ? warnThreshold : 0.8));
}
public long getEntitiesLimit(EntityType entityType) {
switch (entityType) {
case DEVICE:
return maxDevices;
case ASSET:
return maxAssets;
case CUSTOMER:
return maxCustomers;
case USER:
return maxUsers;
case DASHBOARD:
return maxDashboards;
case RULE_CHAIN:
return maxRuleChains;
default:
return 0;
}
}
@Override
public TenantProfileType getType() {
return TenantProfileType.DEFAULT;

22
common/util/src/main/java/org/thingsboard/common/util/CollectionsUtil.java → common/data/src/main/java/org/thingsboard/server/common/data/util/CollectionsUtil.java

@ -13,9 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.common.util;
package org.thingsboard.server.common.data.util;
import com.google.common.collect.ImmutableMap;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
@ -47,4 +52,19 @@ public class CollectionsUtil {
return count;
}
@SuppressWarnings("unchecked")
public static <K, V> Map<K, V> mapOf(Object... kvs) {
Map<K, V> map = new HashMap<>();
for (int i = 0; i < kvs.length; i += 2) {
K key = (K) kvs[i];
V value = (V) kvs[i + 1];
map.put(key, value);
}
return map;
}
public static <K, V> Map<K, V> unmodifiableMapOf(Object... kvs) {
return Collections.unmodifiableMap(mapOf(kvs));
}
}

2
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/model/LwM2MModelConfig.java

@ -27,7 +27,7 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import static org.thingsboard.common.util.CollectionsUtil.diffSets;
import static org.thingsboard.server.common.data.util.CollectionsUtil.diffSets;
@Data
@NoArgsConstructor

2
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java

@ -103,7 +103,7 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static org.thingsboard.common.util.CollectionsUtil.diffSets;
import static org.thingsboard.server.common.data.util.CollectionsUtil.diffSets;
import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH;
import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_3_VER_ID;
import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_DELIVERY_METHOD;

2
common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultClusterVersionControlService.java

@ -29,7 +29,7 @@ import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.CollectionsUtil;
import org.thingsboard.server.common.data.util.CollectionsUtil;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils;

2
dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java

@ -185,7 +185,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
} else {
deleteEntityRelations(tenantId, alarm.getId());
alarmDao.removeById(tenantId, alarm.getUuidId());
return AlarmApiCallResult.builder().alarm(alarm).successful(true).build();
return AlarmApiCallResult.builder().alarm(alarm).deleted(true).successful(true).build();
}
}

3
dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java

@ -680,7 +680,6 @@ public class ModelConstants {
* */
public static final String NOTIFICATION_TARGET_TABLE_NAME = "notification_target";
public static final String NOTIFICATION_TARGET_TYPE_PROPERTY = "type";
public static final String NOTIFICATION_TARGET_CONFIGURATION_PROPERTY = "configuration";
public static final String NOTIFICATION_TABLE_NAME = "notification";
@ -688,7 +687,7 @@ public class ModelConstants {
public static final String NOTIFICATION_RECIPIENT_ID_PROPERTY = "recipient_id";
public static final String NOTIFICATION_TYPE_PROPERTY = "type";
public static final String NOTIFICATION_SUBJECT_PROPERTY = "subject";
public static final String NOTIFICATION_TEXT_PROPERTY = "text";
public static final String NOTIFICATION_TEXT_PROPERTY = "body";
public static final String NOTIFICATION_ADDITIONAL_CONFIG_PROPERTY = "additional_config";
public static final String NOTIFICATION_STATUS_PROPERTY = "status";

19
dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java

@ -17,13 +17,15 @@ package org.thingsboard.server.dao.service;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.TenantEntityDao;
import org.thingsboard.server.dao.TenantEntityWithDataDao;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.usagerecord.ApiLimitService;
import java.util.HashSet;
import java.util.Iterator;
@ -32,6 +34,8 @@ import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static org.apache.commons.lang3.StringUtils.capitalize;
@Slf4j
public abstract class DataValidator<D extends BaseData<?>> {
private static final Pattern EMAIL_PATTERN =
@ -42,6 +46,9 @@ public abstract class DataValidator<D extends BaseData<?>> {
private static final String NAME = "name";
private static final String TOPIC = "topic";
@Autowired @Lazy
private ApiLimitService apiLimitService;
// Returns old instance of the same object that is fetched during validation.
public D validate(D data, Function<D, TenantId> tenantIdFunction) {
try {
@ -97,15 +104,9 @@ public abstract class DataValidator<D extends BaseData<?>> {
}
protected void validateNumberOfEntitiesPerTenant(TenantId tenantId,
TenantEntityDao tenantEntityDao,
long maxEntities,
EntityType entityType) {
if (maxEntities > 0) {
long currentEntitiesCount = tenantEntityDao.countByTenantId(tenantId);
if (currentEntitiesCount >= maxEntities) {
throw new DataValidationException(String.format("Can't create more then %d %ss!",
maxEntities, entityType.name().toLowerCase().replaceAll("_", " ")));
}
if (!apiLimitService.checkEntitiesLimit(tenantId, entityType)) {
throw new DataValidationException(String.format("%ss limit reached", capitalize(entityType.name().toLowerCase().replaceAll("_", " "))));
}
}

11
dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetDataValidator.java

@ -24,13 +24,11 @@ import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.dao.asset.AssetDao;
import org.thingsboard.server.dao.asset.BaseAssetService;
import org.thingsboard.server.dao.customer.CustomerDao;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantService;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
@ -48,17 +46,10 @@ public class AssetDataValidator extends DataValidator<Asset> {
@Autowired
private CustomerDao customerDao;
@Autowired
@Lazy
private TbTenantProfileCache tenantProfileCache;
@Override
protected void validateCreate(TenantId tenantId, Asset asset) {
DefaultTenantProfileConfiguration profileConfiguration =
(DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
if (!BaseAssetService.TB_SERVICE_QUEUE.equals(asset.getType())) {
long maxAssets = profileConfiguration.getMaxAssets();
validateNumberOfEntitiesPerTenant(tenantId, assetDao, maxAssets, EntityType.ASSET);
validateNumberOfEntitiesPerTenant(tenantId, EntityType.ASSET);
}
}

13
dao/src/main/java/org/thingsboard/server/dao/service/validator/CustomerDataValidator.java

@ -16,18 +16,15 @@
package org.thingsboard.server.dao.service.validator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.dao.customer.CustomerDao;
import org.thingsboard.server.dao.customer.CustomerServiceImpl;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantService;
import java.util.Optional;
@ -41,17 +38,9 @@ public class CustomerDataValidator extends DataValidator<Customer> {
@Autowired
private TenantService tenantService;
@Autowired
@Lazy
private TbTenantProfileCache tenantProfileCache;
@Override
protected void validateCreate(TenantId tenantId, Customer customer) {
DefaultTenantProfileConfiguration profileConfiguration =
(DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
long maxCustomers = profileConfiguration.getMaxCustomers();
validateNumberOfEntitiesPerTenant(tenantId, customerDao, maxCustomers, EntityType.CUSTOMER);
validateNumberOfEntitiesPerTenant(tenantId, EntityType.CUSTOMER);
customerDao.findCustomersByTenantIdAndTitle(customer.getTenantId().getId(), customer.getTitle()).ifPresent(
c -> {
throw new DataValidationException("Customer with such title already exists!");

12
dao/src/main/java/org/thingsboard/server/dao/service/validator/DashboardDataValidator.java

@ -16,17 +16,14 @@
package org.thingsboard.server.dao.service.validator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.dao.dashboard.DashboardDao;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantService;
@Component
@ -38,16 +35,9 @@ public class DashboardDataValidator extends DataValidator<Dashboard> {
@Autowired
private TenantService tenantService;
@Autowired
@Lazy
private TbTenantProfileCache tenantProfileCache;
@Override
protected void validateCreate(TenantId tenantId, Dashboard data) {
DefaultTenantProfileConfiguration profileConfiguration =
(DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
long maxDashboards = profileConfiguration.getMaxDashboards();
validateNumberOfEntitiesPerTenant(tenantId, dashboardDao, maxDashboards, EntityType.DASHBOARD);
validateNumberOfEntitiesPerTenant(tenantId, EntityType.DASHBOARD);
}
@Override

12
dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceDataValidator.java

@ -16,7 +16,6 @@
package org.thingsboard.server.dao.service.validator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
@ -25,11 +24,9 @@ import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.device.data.DeviceTransportConfiguration;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.dao.customer.CustomerDao;
import org.thingsboard.server.dao.device.DeviceDao;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantService;
import java.util.Optional;
@ -48,16 +45,9 @@ public class DeviceDataValidator extends AbstractHasOtaPackageValidator<Device>
@Autowired
private CustomerDao customerDao;
@Autowired
@Lazy
private TbTenantProfileCache tenantProfileCache;
@Override
protected void validateCreate(TenantId tenantId, Device device) {
DefaultTenantProfileConfiguration profileConfiguration =
(DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
long maxDevices = profileConfiguration.getMaxDevices();
validateNumberOfEntitiesPerTenant(tenantId, deviceDao, maxDevices, EntityType.DEVICE);
validateNumberOfEntitiesPerTenant(tenantId, EntityType.DEVICE);
}
@Override

9
dao/src/main/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidator.java

@ -60,16 +60,9 @@ public class RuleChainDataValidator extends DataValidator<RuleChain> {
@Autowired
private TenantService tenantService;
@Autowired
@Lazy
private TbTenantProfileCache tenantProfileCache;
@Override
protected void validateCreate(TenantId tenantId, RuleChain data) {
DefaultTenantProfileConfiguration profileConfiguration =
(DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
long maxRuleChains = profileConfiguration.getMaxRuleChains();
validateNumberOfEntitiesPerTenant(tenantId, ruleChainDao, maxRuleChains, EntityType.RULE_CHAIN);
validateNumberOfEntitiesPerTenant(tenantId, EntityType.RULE_CHAIN);
}
@Override

12
dao/src/main/java/org/thingsboard/server/dao/service/validator/UserDataValidator.java

@ -19,19 +19,16 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils;
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.security.Authority;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.dao.customer.CustomerDao;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.user.UserDao;
import org.thingsboard.server.dao.user.UserService;
@ -49,10 +46,6 @@ public class UserDataValidator extends DataValidator<User> {
@Autowired
private CustomerDao customerDao;
@Autowired
@Lazy
private TbTenantProfileCache tenantProfileCache;
@Autowired
@Lazy
private TenantService tenantService;
@ -60,10 +53,7 @@ public class UserDataValidator extends DataValidator<User> {
@Override
protected void validateCreate(TenantId tenantId, User user) {
if (!user.getTenantId().getId().equals(ModelConstants.NULL_UUID)) {
DefaultTenantProfileConfiguration profileConfiguration =
(DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
long maxUsers = profileConfiguration.getMaxUsers();
validateNumberOfEntitiesPerTenant(tenantId, userDao, maxUsers, EntityType.USER);
validateNumberOfEntitiesPerTenant(tenantId, EntityType.USER);
}
}

15
dao/src/main/resources/sql/schema-entities-idx.sql

@ -94,14 +94,17 @@ CREATE INDEX IF NOT EXISTS idx_notification_target_tenant_id_created_time ON not
CREATE INDEX IF NOT EXISTS idx_notification_template_tenant_id_created_time ON notification_template(tenant_id, created_time DESC);
CREATE INDEX IF NOT EXISTS idx_notification_rule_tenant_id_created_time ON notification_rule(tenant_id, created_time DESC);
CREATE INDEX IF NOT EXISTS idx_notification_rule_tenant_id_trigger_type_created_time ON notification_rule(tenant_id, trigger_type, created_time DESC);
CREATE INDEX IF NOT EXISTS idx_notification_request_tenant_id_originator_type_created_time ON notification_request(tenant_id, originator_entity_type, created_time DESC);
CREATE INDEX IF NOT EXISTS idx_notification_request_tenant_id_user_created_time ON notification_request(tenant_id, created_time DESC)
WHERE originator_entity_type = 'USER';
CREATE INDEX IF NOT EXISTS idx_notification_request_rule_id_originator_entity_id ON notification_request(rule_id, originator_entity_id);
CREATE INDEX IF NOT EXISTS idx_notification_request_rule_id_originator_entity_id ON notification_request(rule_id, originator_entity_id)
WHERE originator_entity_type = 'ALARM';
CREATE INDEX IF NOT EXISTS idx_notification_request_status ON notification_request(status);
CREATE INDEX IF NOT EXISTS idx_notification_request_status ON notification_request(status)
WHERE status = 'SCHEDULED';
CREATE INDEX IF NOT EXISTS idx_notification_id_recipient_id ON notification(id, recipient_id);
CREATE INDEX IF NOT EXISTS idx_notification_id ON notification(id);
CREATE INDEX IF NOT EXISTS idx_notification_recipient_id_status_created_time ON notification(recipient_id, status, created_time DESC);
CREATE INDEX IF NOT EXISTS idx_notification_recipient_id_created_time ON notification(recipient_id, created_time DESC);

4
dao/src/main/resources/sql/schema-entities.sql

@ -844,7 +844,7 @@ CREATE TABLE IF NOT EXISTS notification (
recipient_id UUID NOT NULL CONSTRAINT fk_notification_recipient_id REFERENCES tb_user(id) ON DELETE CASCADE,
type VARCHAR(50) NOT NULL,
subject VARCHAR(255),
text VARCHAR(1000) NOT NULL,
body VARCHAR(1000) NOT NULL,
additional_config VARCHAR(1000),
status VARCHAR(32)
) PARTITION BY RANGE (created_time);
@ -1021,7 +1021,7 @@ BEGIN
UPDATE alarm a SET acknowledged = true, ack_ts = a_ts WHERE a.id = a_id AND a.tenant_id = t_id;
END IF;
SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id;
RETURN json_build_object('success', true, 'modified', modified, 'alarm', row_to_json(result))::text;
RETURN json_build_object('success', true, 'modified', modified, 'alarm', row_to_json(result), 'old', row_to_json(existing))::text;
END
$$;

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java

@ -22,7 +22,7 @@ import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.thingsboard.common.util.CollectionsUtil;
import org.thingsboard.server.common.data.util.CollectionsUtil;
import org.thingsboard.common.util.DonAsynchron;
import org.thingsboard.rule.engine.api.EmptyNodeConfiguration;
import org.thingsboard.rule.engine.api.RuleNode;

Loading…
Cancel
Save