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, recipient_id UUID NOT NULL CONSTRAINT fk_notification_recipient_id REFERENCES tb_user(id) ON DELETE CASCADE,
type VARCHAR(50) NOT NULL, type VARCHAR(50) NOT NULL,
subject VARCHAR(255), subject VARCHAR(255),
text VARCHAR(1000) NOT NULL, body VARCHAR(1000) NOT NULL,
additional_config VARCHAR(1000), additional_config VARCHAR(1000),
status VARCHAR(32) status VARCHAR(32)
) PARTITION BY RANGE (created_time); ) 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; UPDATE alarm a SET acknowledged = true, ack_ts = a_ts WHERE a.id = a_id AND a.tenant_id = t_id;
END IF; END IF;
SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id; 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 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.User;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmComment; 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.audit.ActionType;
import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventActionType;
@ -221,7 +222,7 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS
} }
@Override @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); logEntityAction(alarm.getTenantId(), alarm.getOriginator(), alarm, alarm.getCustomerId(), actionType, user, additionalInfo);
sendEntityNotificationMsg(alarm.getTenantId(), alarm.getId(), edgeTypeByActionType(actionType)); 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.User;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmComment; 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.audit.ActionType;
import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEventActionType; 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, void notifyCreateOrUpdateOrDeleteEdge(TenantId tenantId, EdgeId edgeId, CustomerId customerId, Edge edge, ActionType actionType,
User user, Object... additionalInfo); 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); 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.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.NotificationCenter; import org.thingsboard.rule.engine.api.NotificationCenter;
import org.thingsboard.server.common.data.EntityType; 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.EntityId;
import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.NotificationRequestId;
import org.thingsboard.server.common.data.id.NotificationRuleId; 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.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; 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.NotificationRequestService;
import org.thingsboard.server.dao.notification.NotificationRuleService; 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.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.NotificationRuleTriggerProcessor;
import org.thingsboard.server.service.notification.rule.trigger.RuleEngineComponentLifecycleEventTriggerProcessor.RuleEngineComponentLifecycleEventTriggerObject; import org.thingsboard.server.service.notification.rule.trigger.RuleEngineComponentLifecycleEventTriggerProcessor.RuleEngineComponentLifecycleEventTriggerObject;
import org.thingsboard.server.service.notification.rule.trigger.RuleEngineMsgNotificationRuleTriggerProcessor; import org.thingsboard.server.service.notification.rule.trigger.RuleEngineMsgNotificationRuleTriggerProcessor;
@ -84,12 +85,8 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
} }
@Override @Override
public void process(TenantId tenantId, Alarm alarm, boolean deleted) { public void process(TenantId tenantId, AlarmApiCallResult alarmUpdate) {
AlarmTriggerObject triggerObject = AlarmTriggerObject.builder() processTrigger(tenantId, NotificationRuleTriggerType.ALARM, alarmUpdate.getAlarm().getId(), alarmUpdate);
.alarm(alarm)
.deleted(deleted)
.build();
processTrigger(tenantId, NotificationRuleTriggerType.ALARM, alarm.getId(), triggerObject);
} }
@Override @Override
@ -105,11 +102,43 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
processTrigger(tenantId, NotificationRuleTriggerType.RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT, componentId, triggerObject); 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) { private void processTrigger(TenantId tenantId, NotificationRuleTriggerType triggerType, EntityId originatorEntityId, Object triggerObject) {
List<NotificationRule> rules = notificationRuleService.findNotificationRulesByTenantIdAndTriggerType(tenantId, triggerType); List<NotificationRule> rules = notificationRuleService.findNotificationRulesByTenantIdAndTriggerType(tenantId, triggerType);
for (NotificationRule rule : rules) { for (NotificationRule rule : rules) {
notificationExecutor.submit(() -> { 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; return;
} }
if (!matchesFilter(triggerObject, triggerConfig)) { if (matchesFilter(triggerObject, triggerConfig)) {
return; 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) { 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; 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.EntityId;
import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.TbMsg; 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 { public interface NotificationRuleProcessingService {
void process(TenantId tenantId, TbMsg ruleEngineMsg); 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, void process(TenantId tenantId, RuleChainId ruleChainId, String ruleChainName,
EntityId componentId, String componentName, ComponentLifecycleEvent eventType, Exception error); 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.DataConstants;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmComment; 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.alarm.AlarmStatusFilter;
import org.thingsboard.server.common.data.notification.info.AlarmCommentNotificationInfo; import org.thingsboard.server.common.data.notification.info.AlarmCommentNotificationInfo;
import org.thingsboard.server.common.data.notification.info.NotificationInfo; import org.thingsboard.server.common.data.notification.info.NotificationInfo;
@ -39,6 +41,12 @@ public class AlarmCommentTriggerProcessor implements RuleEngineMsgNotificationRu
if (ruleEngineMsg.getMetaData().getValue("comment") == null) { if (ruleEngineMsg.getMetaData().getValue("comment") == null) {
return false; 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); Alarm alarm = JacksonUtil.fromString(ruleEngineMsg.getData(), Alarm.class);
return (isEmpty(triggerConfig.getAlarmTypes()) || triggerConfig.getAlarmTypes().contains(alarm.getType())) && return (isEmpty(triggerConfig.getAlarmTypes()) || triggerConfig.getAlarmTypes().contains(alarm.getType())) &&
(isEmpty(triggerConfig.getAlarmSeverities()) || triggerConfig.getAlarmSeverities().contains(alarm.getSeverity())) && (isEmpty(triggerConfig.getAlarmSeverities()) || triggerConfig.getAlarmSeverities().contains(alarm.getSeverity())) &&
@ -47,15 +55,19 @@ public class AlarmCommentTriggerProcessor implements RuleEngineMsgNotificationRu
@Override @Override
public NotificationInfo constructNotificationInfo(TbMsg ruleEngineMsg, AlarmCommentNotificationRuleTriggerConfig triggerConfig) { public NotificationInfo constructNotificationInfo(TbMsg ruleEngineMsg, AlarmCommentNotificationRuleTriggerConfig triggerConfig) {
// TODO: readable action
AlarmComment comment = JacksonUtil.fromString(ruleEngineMsg.getMetaData().getValue("comment"), AlarmComment.class); 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() return AlarmCommentNotificationInfo.builder()
.comment(comment.getComment().get("text").asText()) .comment(comment.getComment().get("text").asText())
.alarmId(alarm.getUuidId()) .userName(ruleEngineMsg.getMetaData().getValue("userName"))
.alarmType(alarm.getType()) .alarmId(alarmInfo.getUuidId())
.alarmOriginator(alarm.getOriginator()) .alarmType(alarmInfo.getType())
.alarmSeverity(alarm.getSeverity()) .alarmOriginator(alarmInfo.getOriginator())
.alarmStatus(alarm.getStatus()) .alarmOriginatorName(alarmInfo.getOriginatorName())
.alarmSeverity(alarmInfo.getSeverity())
.alarmStatus(alarmInfo.getStatus())
.alarmCustomerId(alarmInfo.getCustomerId())
.build(); .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; package org.thingsboard.server.service.notification.rule.trigger;
import lombok.Builder;
import lombok.Data;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.alarm.Alarm; 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.alarm.AlarmStatusFilter;
import org.thingsboard.server.common.data.notification.info.AlarmNotificationInfo; 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.info.NotificationInfo;
import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig; 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.AlarmNotificationRuleTriggerConfig.ClearRule;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; 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.isEmpty;
import static org.apache.commons.collections.CollectionUtils.isNotEmpty; import static org.apache.commons.collections.CollectionUtils.isNotEmpty;
@Service @Service
public class AlarmTriggerProcessor implements NotificationRuleTriggerProcessor<AlarmTriggerObject, AlarmNotificationRuleTriggerConfig> { public class AlarmTriggerProcessor implements NotificationRuleTriggerProcessor<AlarmApiCallResult, AlarmNotificationRuleTriggerConfig> {
@Override @Override
public boolean matchesFilter(AlarmTriggerObject triggerObject, AlarmNotificationRuleTriggerConfig triggerConfig) { public boolean matchesFilter(AlarmApiCallResult alarmUpdate, AlarmNotificationRuleTriggerConfig triggerConfig) {
Alarm alarm = triggerObject.getAlarm(); Alarm alarm = alarmUpdate.getAlarm();
return (isEmpty(triggerConfig.getAlarmTypes()) || triggerConfig.getAlarmTypes().contains(alarm.getType())) && if (!typeMatches(alarm, triggerConfig)) {
(isEmpty(triggerConfig.getAlarmSeverities()) || triggerConfig.getAlarmSeverities().contains(alarm.getSeverity())); 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 @Override
public boolean matchesClearRule(AlarmTriggerObject triggerObject, AlarmNotificationRuleTriggerConfig triggerConfig) { public boolean matchesClearRule(AlarmApiCallResult alarmUpdate, AlarmNotificationRuleTriggerConfig triggerConfig) {
if (triggerObject.isDeleted()) { Alarm alarm = alarmUpdate.getAlarm();
if (!typeMatches(alarm, triggerConfig)) {
return false;
}
if (alarmUpdate.isDeleted()) {
return true; return true;
} }
Alarm alarm = triggerObject.getAlarm();
ClearRule clearRule = triggerConfig.getClearRule(); ClearRule clearRule = triggerConfig.getClearRule();
if (clearRule != null) { if (clearRule != null) {
if (isNotEmpty(clearRule.getAlarmStatuses())) { if (isNotEmpty(clearRule.getAlarmStatuses())) {
@ -55,16 +81,26 @@ public class AlarmTriggerProcessor implements NotificationRuleTriggerProcessor<A
return false; 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 @Override
public NotificationInfo constructNotificationInfo(AlarmTriggerObject triggerObject, AlarmNotificationRuleTriggerConfig triggerConfig) { public NotificationInfo constructNotificationInfo(AlarmApiCallResult alarmUpdate, AlarmNotificationRuleTriggerConfig triggerConfig) {
Alarm alarm = triggerObject.getAlarm(); // TODO: readable action
AlarmInfo alarmInfo = alarmUpdate.getAlarm();
return AlarmNotificationInfo.builder() return AlarmNotificationInfo.builder()
.alarmId(alarm.getUuidId()) .alarmId(alarmInfo.getUuidId())
.alarmType(alarm.getType()) .alarmType(alarmInfo.getType())
.alarmOriginator(alarm.getOriginator()) .alarmOriginator(alarmInfo.getOriginator())
.alarmSeverity(alarm.getSeverity()) .alarmOriginatorName(alarmInfo.getOriginatorName())
.alarmStatus(alarm.getStatus()) .alarmSeverity(alarmInfo.getSeverity())
.alarmCustomerId(alarm.getCustomerId()) .alarmStatus(alarmInfo.getStatus())
.alarmCustomerId(alarmInfo.getCustomerId())
.build(); .build();
} }
@ -73,11 +109,4 @@ public class AlarmTriggerProcessor implements NotificationRuleTriggerProcessor<A
return NotificationRuleTriggerType.ALARM; 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()) .deviceId(ruleEngineMsg.getOriginator().getId())
.deviceName(ruleEngineMsg.getMetaData().getValue("deviceName")) .deviceName(ruleEngineMsg.getMetaData().getValue("deviceName"))
.deviceType(ruleEngineMsg.getMetaData().getValue("deviceType")) .deviceType(ruleEngineMsg.getMetaData().getValue("deviceType"))
.deviceLabel(ruleEngineMsg.getMetaData().getValue("deviceLabel"))
.deviceCustomerId(ruleEngineMsg.getCustomerId()) .deviceCustomerId(ruleEngineMsg.getCustomerId())
.build(); .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.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; 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.StringUtils;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.security.model.mfa.account.BackupCodeTwoFaAccountConfig; 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( private static final List<EntityKey> PERSISTENT_ENTITY_FIELDS = Arrays.asList(
new EntityKey(EntityKeyType.ENTITY_FIELD, "name"), new EntityKey(EntityKeyType.ENTITY_FIELD, "name"),
new EntityKey(EntityKeyType.ENTITY_FIELD, "type"), new EntityKey(EntityKeyType.ENTITY_FIELD, "type"),
new EntityKey(EntityKeyType.ENTITY_FIELD, "label"),
new EntityKey(EntityKeyType.ENTITY_FIELD, "createdTime")); new EntityKey(EntityKeyType.ENTITY_FIELD, "createdTime"));
private final TenantService tenantService; private final TenantService tenantService;
@ -329,6 +330,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev
DeviceStateData stateData = getOrFetchDeviceStateData(device.getId()); DeviceStateData stateData = getOrFetchDeviceStateData(device.getId());
TbMsgMetaData md = new TbMsgMetaData(); TbMsgMetaData md = new TbMsgMetaData();
md.putValue("deviceName", device.getName()); md.putValue("deviceName", device.getName());
md.putValue("deviceLabel", device.getLabel());
md.putValue("deviceType", device.getType()); md.putValue("deviceType", device.getType());
stateData.setMetaData(md); stateData.setMetaData(md);
callback.onSuccess(); callback.onSuccess();
@ -581,6 +583,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev
.build(); .build();
TbMsgMetaData md = new TbMsgMetaData(); TbMsgMetaData md = new TbMsgMetaData();
md.putValue("deviceName", device.getName()); md.putValue("deviceName", device.getName());
md.putValue("deviceLabel", device.getLabel());
md.putValue("deviceType", device.getType()); md.putValue("deviceType", device.getType());
DeviceStateData deviceStateData = DeviceStateData.builder() DeviceStateData deviceStateData = DeviceStateData.builder()
.customerId(device.getCustomerId()) .customerId(device.getCustomerId())
@ -660,6 +663,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev
.build(); .build();
TbMsgMetaData md = new TbMsgMetaData(); TbMsgMetaData md = new TbMsgMetaData();
md.putValue("deviceName", getEntryValue(ed, EntityKeyType.ENTITY_FIELD, "name", "")); 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", "")); md.putValue("deviceType", getEntryValue(ed, EntityKeyType.ENTITY_FIELD, "type", ""));
return DeviceStateData.builder() return DeviceStateData.builder()
.customerId(deviceIdInfo.getCustomerId()) .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.cluster.TbClusterService;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType; 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.alarm.AlarmInfo;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId; 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.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; 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.attributes.AttributesService;
import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.gen.transport.TransportProtos.LocalSubscriptionServiceMsgProto; 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.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
import org.thingsboard.server.queue.util.TbCoreComponent; 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.DefaultDeviceStateService;
import org.thingsboard.server.service.state.DeviceStateService; import org.thingsboard.server.service.state.DeviceStateService;
import org.thingsboard.server.service.ws.notification.sub.NotificationRequestUpdate; import org.thingsboard.server.service.ws.notification.sub.NotificationRequestUpdate;
@ -100,7 +97,6 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
private final TbLocalSubscriptionService localSubscriptionService; private final TbLocalSubscriptionService localSubscriptionService;
private final DeviceStateService deviceStateService; private final DeviceStateService deviceStateService;
private final TbClusterService clusterService; private final TbClusterService clusterService;
private final NotificationRuleProcessingService notificationRuleProcessingService;
private final Map<EntityId, Set<TbSubscription>> subscriptionsByEntityId = new ConcurrentHashMap<>(); private final Map<EntityId, Set<TbSubscription>> subscriptionsByEntityId = new ConcurrentHashMap<>();
private final Map<String, Map<Integer, TbSubscription>> subscriptionsByWsSessionId = 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(), s -> alarm.getCreatedTime() >= s.getTs() || alarm.getAssignTs() >= s.getTs(),
alarm, false alarm, false
); );
notificationRuleProcessingService.process(tenantId, alarm, false);
callback.onSuccess(); callback.onSuccess();
} }
@ -313,7 +308,6 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
s -> alarm.getCreatedTime() >= s.getTs(), s -> alarm.getCreatedTime() >= s.getTs(),
alarm, true alarm, true
); );
notificationRuleProcessingService.process(tenantId, alarm, true);
callback.onSuccess(); 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.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; 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.common.util.JacksonUtil;
import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.EntityType; 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.queue.util.TbCoreComponent;
import org.thingsboard.server.service.sync.vc.data.ClearRepositoryGitRequest; 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.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.EntitiesContentGitRequest;
import org.thingsboard.server.service.sync.vc.data.EntityContentGitRequest; import org.thingsboard.server.service.sync.vc.data.EntityContentGitRequest;
import org.thingsboard.server.service.sync.vc.data.ListBranchesGitRequest; import org.thingsboard.server.service.sync.vc.data.ListBranchesGitRequest;
@ -78,11 +77,9 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.TreeMap;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit; 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.dao.alarm.AlarmService;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
import org.thingsboard.server.service.entitiy.alarm.TbAlarmCommentService; import org.thingsboard.server.service.entitiy.alarm.TbAlarmCommentService;
import org.thingsboard.server.service.notification.rule.NotificationRuleProcessingService;
import org.thingsboard.server.service.subscription.TbSubscriptionUtils; import org.thingsboard.server.service.subscription.TbSubscriptionUtils;
import java.util.Collection; import java.util.Collection;
@ -68,6 +69,7 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
private final TbAlarmCommentService alarmCommentService; private final TbAlarmCommentService alarmCommentService;
private final TbApiUsageReportClient apiUsageClient; private final TbApiUsageReportClient apiUsageClient;
private final TbApiUsageStateService apiUsageStateService; private final TbApiUsageStateService apiUsageStateService;
private final NotificationRuleProcessingService notificationRuleProcessingService;
@Override @Override
protected String getExecutorPrefix() { protected String getExecutorPrefix() {
@ -232,6 +234,7 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
return TbSubscriptionUtils.toAlarmUpdateProto(tenantId, entityId, alarm); 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); 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.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.UpdateMessage; import org.thingsboard.server.common.data.UpdateMessage;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.notification.rule.NotificationRuleProcessingService;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
@ -53,6 +55,9 @@ public class DefaultUpdateService implements UpdateService {
@Value("${updates.enabled}") @Value("${updates.enabled}")
private boolean updatesEnabled; private boolean updatesEnabled;
@Autowired
private NotificationRuleProcessingService notificationRuleProcessingService;
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1, ThingsBoardThreadFactory.forName("tb-update-service")); private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1, ThingsBoardThreadFactory.forName("tb-update-service"));
private ScheduledFuture checkUpdatesFuture = null; private ScheduledFuture checkUpdatesFuture = null;
@ -121,11 +126,15 @@ public class DefaultUpdateService implements UpdateService {
request.put(PLATFORM_PARAM, platform); request.put(PLATFORM_PARAM, platform);
request.put(VERSION_PARAM, version); request.put(VERSION_PARAM, version);
request.put(INSTANCE_ID_PARAM, instanceId.toString()); 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( updateMessage = new UpdateMessage(
response.get("message").asText(), response.get("message").asText(),
response.get("updateAvailable").asBoolean() response.get("updateAvailable").asBoolean()
); );
if (updateMessage.isUpdateAvailable() && !updateMessage.equals(prevUpdateMessage)) {
notificationRuleProcessingService.process(updateMessage);
}
} catch (Exception e) { } catch (Exception e) {
log.trace(e.getMessage()); 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.NotificationRule;
import org.thingsboard.server.common.data.notification.rule.NotificationRuleInfo; 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;
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.EntityActionNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.data.notification.rule.trigger.RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig;
@ -180,6 +181,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
AlarmNotificationRuleTriggerConfig triggerConfig = new AlarmNotificationRuleTriggerConfig(); AlarmNotificationRuleTriggerConfig triggerConfig = new AlarmNotificationRuleTriggerConfig();
triggerConfig.setAlarmTypes(null); triggerConfig.setAlarmTypes(null);
triggerConfig.setAlarmSeverities(null); triggerConfig.setAlarmSeverities(null);
triggerConfig.setNotifyOn(Set.of(AlarmAction.CREATED, AlarmAction.SEVERITY_CHANGED, AlarmAction.ACKNOWLEDGED, AlarmAction.CLEARED));
notificationRule.setTriggerConfig(triggerConfig); notificationRule.setTriggerConfig(triggerConfig);
EscalatedNotificationRuleRecipientsConfig recipientsConfig = new EscalatedNotificationRuleRecipientsConfig(); EscalatedNotificationRuleRecipientsConfig recipientsConfig = new EscalatedNotificationRuleRecipientsConfig();
@ -251,8 +253,6 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
wsClient.close(); wsClient.close();
}); });
// TODO: test severity changes
} }
@Test @Test
@ -273,6 +273,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
AlarmNotificationRuleTriggerConfig triggerConfig = new AlarmNotificationRuleTriggerConfig(); AlarmNotificationRuleTriggerConfig triggerConfig = new AlarmNotificationRuleTriggerConfig();
triggerConfig.setAlarmTypes(Set.of(alarmType)); triggerConfig.setAlarmTypes(Set.of(alarmType));
triggerConfig.setAlarmSeverities(null); triggerConfig.setAlarmSeverities(null);
triggerConfig.setNotifyOn(Set.of(AlarmAction.CREATED, AlarmAction.SEVERITY_CHANGED, AlarmAction.ACKNOWLEDGED));
AlarmNotificationRuleTriggerConfig.ClearRule clearRule = new AlarmNotificationRuleTriggerConfig.ClearRule(); AlarmNotificationRuleTriggerConfig.ClearRule clearRule = new AlarmNotificationRuleTriggerConfig.ClearRule();
clearRule.setAlarmStatuses(Set.of(AlarmSearchStatus.CLEARED, AlarmSearchStatus.UNACK)); 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 created;
private final boolean modified; private final boolean modified;
private final boolean cleared; private final boolean cleared;
private final boolean deleted;
private final AlarmInfo alarm; private final AlarmInfo alarm;
private final Alarm old; private final Alarm old;
private final List<EntityId> propagatedEntitiesList; private final List<EntityId> propagatedEntitiesList;
@Builder @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.successful = successful;
this.created = created; this.created = created;
this.modified = modified; this.modified = modified;
this.cleared = cleared; this.cleared = cleared;
this.deleted = deleted;
this.alarm = alarm; this.alarm = alarm;
this.old = old; this.old = old;
this.propagatedEntitiesList = propagatedEntitiesList; this.propagatedEntitiesList = propagatedEntitiesList;
@ -52,6 +54,7 @@ public class AlarmApiCallResult {
this.created = other.created; this.created = other.created;
this.modified = other.modified; this.modified = other.modified;
this.cleared = other.cleared; this.cleared = other.cleared;
this.deleted = other.deleted;
this.alarm = other.alarm; this.alarm = other.alarm;
this.old = other.old; this.old = other.old;
this.propagatedEntitiesList = propagatedEntitiesList; 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() { public AlarmSeverity getOldSeverity() {
return isSeverityChanged() ? old.getSeverity() : null; 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; String result = template;
for (Map<String, String> context : contexts) { for (Map<String, String> context : contexts) {
for (Map.Entry<String, String> kv : context.entrySet()) { 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; 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, DEVICE_INACTIVITY,
ENTITY_ACTION, ENTITY_ACTION,
ALARM_COMMENT, 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 lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus; 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.EntityId;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import static org.thingsboard.server.common.data.util.CollectionsUtil.mapOf;
@Data @Data
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
@Builder @Builder
public class AlarmCommentNotificationInfo implements NotificationInfo { public class AlarmCommentNotificationInfo implements RuleOriginatedNotificationInfo {
private String comment; private String comment;
private String userName;
private String alarmType; private String alarmType;
private UUID alarmId; private UUID alarmId;
private EntityId alarmOriginator; private EntityId alarmOriginator;
private String alarmOriginatorName;
private AlarmSeverity alarmSeverity; private AlarmSeverity alarmSeverity;
private AlarmStatus alarmStatus; private AlarmStatus alarmStatus;
private CustomerId alarmCustomerId;
@Override @Override
public Map<String, String> getTemplateData() { public Map<String, String> getTemplateData() {
return Map.of( return mapOf(
"comment", comment, "comment", comment,
"userName", userName,
"alarmType", alarmType, "alarmType", alarmType,
"alarmId", alarmId.toString(), "alarmId", alarmId.toString(),
"alarmSeverity", alarmSeverity.toString(), "alarmSeverity", alarmSeverity.toString(),
"alarmStatus", alarmStatus.toString(), "alarmStatus", alarmStatus.toString(),
"alarmOriginatorEntityType", alarmOriginator.getEntityType().toString(), "alarmOriginatorEntityType", alarmOriginator.getEntityType().toString(),
"alarmOriginatorId", alarmOriginator.getId().toString() "alarmOriginatorId", alarmOriginator.getId().toString(),
"alarmOriginatorName", alarmOriginatorName
); );
} }
@Override
public CustomerId getOriginatorEntityCustomerId() {
return alarmCustomerId;
}
@Override @Override
public EntityId getStateEntityId() { public EntityId getStateEntityId() {
return alarmOriginator; 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.Map;
import java.util.UUID; import java.util.UUID;
import static org.thingsboard.server.common.data.util.CollectionsUtil.mapOf;
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@ -36,27 +38,30 @@ public class AlarmNotificationInfo implements RuleOriginatedNotificationInfo {
private String alarmType; private String alarmType;
private UUID alarmId; private UUID alarmId;
private EntityId alarmOriginator; private EntityId alarmOriginator;
private String alarmOriginatorName;
private AlarmSeverity alarmSeverity; private AlarmSeverity alarmSeverity;
private AlarmStatus alarmStatus; private AlarmStatus alarmStatus;
private CustomerId alarmCustomerId; private CustomerId alarmCustomerId;
@Override
public CustomerId getOriginatorEntityCustomerId() {
return alarmCustomerId;
}
@Override @Override
public Map<String, String> getTemplateData() { public Map<String, String> getTemplateData() {
return Map.of( // TODO: readable status change
return mapOf(
"alarmType", alarmType, "alarmType", alarmType,
"alarmId", alarmId.toString(), "alarmId", alarmId.toString(),
"alarmSeverity", alarmSeverity.toString(), "alarmSeverity", alarmSeverity.toString(),
"alarmStatus", alarmStatus.toString(), "alarmStatus", alarmStatus.toString(),
"alarmOriginatorEntityType", alarmOriginator.getEntityType().toString(), "alarmOriginatorEntityType", alarmOriginator.getEntityType().toString(),
"alarmOriginatorName", alarmOriginatorName,
"alarmOriginatorId", alarmOriginator.getId().toString() "alarmOriginatorId", alarmOriginator.getId().toString()
); );
} }
@Override
public CustomerId getOriginatorEntityCustomerId() {
return alarmCustomerId;
}
@Override @Override
public EntityId getStateEntityId() { public EntityId getStateEntityId() {
return alarmOriginator; 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; package org.thingsboard.server.common.data.notification.info;
import com.google.common.base.Strings;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
@ -26,6 +27,8 @@ import org.thingsboard.server.common.data.id.EntityId;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import static org.thingsboard.server.common.data.util.CollectionsUtil.mapOf;
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@ -34,6 +37,7 @@ public class DeviceInactivityNotificationInfo implements RuleOriginatedNotificat
private UUID deviceId; private UUID deviceId;
private String deviceName; private String deviceName;
private String deviceLabel;
private String deviceType; private String deviceType;
private CustomerId deviceCustomerId; private CustomerId deviceCustomerId;
@ -44,9 +48,10 @@ public class DeviceInactivityNotificationInfo implements RuleOriginatedNotificat
@Override @Override
public Map<String, String> getTemplateData() { public Map<String, String> getTemplateData() {
return Map.of( return mapOf(
"deviceId", deviceId.toString(), "deviceId", deviceId.toString(),
"deviceName", deviceName, "deviceName", deviceName,
"deviceLabel", deviceLabel,
"deviceType", deviceType "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.Map;
import java.util.UUID; import java.util.UUID;
import static org.thingsboard.server.common.data.util.CollectionsUtil.mapOf;
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@ -46,7 +48,7 @@ public class EntityActionNotificationInfo implements RuleOriginatedNotificationI
@Override @Override
public Map<String, String> getTemplateData() { public Map<String, String> getTemplateData() {
return Map.of( return mapOf(
"entityType", entityId.getEntityType().name(), "entityType", entityId.getEntityType().name(),
"entityId", entityId.toString(), "entityId", entityId.toString(),
"entityName", entityName, "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; package org.thingsboard.server.common.data.notification.info;
import com.google.common.base.Strings;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import java.util.Map; import java.util.Map;
import static org.thingsboard.server.common.data.util.CollectionsUtil.mapOf;
@Data @Data
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
@ -42,7 +42,7 @@ public class RuleEngineComponentLifecycleEventNotificationInfo implements Notifi
@Override @Override
public Map<String, String> getTemplateData() { public Map<String, String> getTemplateData() {
return Map.of( return mapOf(
"ruleChainId", ruleChainId.toString(), "ruleChainId", ruleChainId.toString(),
"ruleChainName", ruleChainName, "ruleChainName", ruleChainName,
"componentId", componentId.toString(), "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 @NotNull
private NotificationRuleTriggerType triggerType; private NotificationRuleTriggerType triggerType;
@NotNull @NotNull
@Valid
private NotificationRuleTriggerConfig triggerConfig; private NotificationRuleTriggerConfig triggerConfig;
@NotNull @NotNull
@Valid @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<String> alarmTypes;
private Set<AlarmSeverity> alarmSeverities; private Set<AlarmSeverity> alarmSeverities;
private Set<AlarmSearchStatus> alarmStatuses; private Set<AlarmSearchStatus> alarmStatuses;
private boolean onlyUserComments;
@Override @Override
public NotificationRuleTriggerType getTriggerType() { 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.AlarmSearchStatus;
import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import javax.validation.constraints.NotEmpty;
import java.util.Set; import java.util.Set;
@Data @Data
@ -26,6 +27,9 @@ public class AlarmNotificationRuleTriggerConfig implements NotificationRuleTrigg
private Set<String> alarmTypes; private Set<String> alarmTypes;
private Set<AlarmSeverity> alarmSeverities; private Set<AlarmSeverity> alarmSeverities;
@NotEmpty
private Set<AlarmAction> notifyOn;
private ClearRule clearRule; private ClearRule clearRule;
@Override @Override
@ -38,4 +42,8 @@ public class AlarmNotificationRuleTriggerConfig implements NotificationRuleTrigg
private Set<AlarmSearchStatus> alarmStatuses; 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 = DeviceInactivityNotificationRuleTriggerConfig.class, name = "DEVICE_INACTIVITY"),
@Type(value = EntityActionNotificationRuleTriggerConfig.class, name = "ENTITY_ACTION"), @Type(value = EntityActionNotificationRuleTriggerConfig.class, name = "ENTITY_ACTION"),
@Type(value = AlarmCommentNotificationRuleTriggerConfig.class, name = "ALARM_COMMENT"), @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 { 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, ALARM_COMMENT,
DEVICE_INACTIVITY, DEVICE_INACTIVITY,
ENTITY_ACTION, 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.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.TenantProfileType; import org.thingsboard.server.common.data.TenantProfileType;
@AllArgsConstructor @AllArgsConstructor
@ -107,6 +108,25 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura
return (long) (getProfileThreshold(key) * (warnThreshold > 0.0 ? warnThreshold : 0.8)); 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 @Override
public TenantProfileType getType() { public TenantProfileType getType() {
return TenantProfileType.DEFAULT; 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 * See the License for the specific language governing permissions and
* limitations under the License. * 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.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -47,4 +52,19 @@ public class CollectionsUtil {
return count; 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.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import static org.thingsboard.common.util.CollectionsUtil.diffSets; import static org.thingsboard.server.common.data.util.CollectionsUtil.diffSets;
@Data @Data
@NoArgsConstructor @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.concurrent.TimeUnit;
import java.util.stream.Collectors; 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.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_3_VER_ID;
import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_DELIVERY_METHOD; 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.context.event.EventListener;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service; 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.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils; 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 { } else {
deleteEntityRelations(tenantId, alarm.getId()); deleteEntityRelations(tenantId, alarm.getId());
alarmDao.removeById(tenantId, alarm.getUuidId()); 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_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_TARGET_CONFIGURATION_PROPERTY = "configuration";
public static final String NOTIFICATION_TABLE_NAME = "notification"; 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_RECIPIENT_ID_PROPERTY = "recipient_id";
public static final String NOTIFICATION_TYPE_PROPERTY = "type"; public static final String NOTIFICATION_TYPE_PROPERTY = "type";
public static final String NOTIFICATION_SUBJECT_PROPERTY = "subject"; 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_ADDITIONAL_CONFIG_PROPERTY = "additional_config";
public static final String NOTIFICATION_STATUS_PROPERTY = "status"; 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 com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j; 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.BaseData;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.id.TenantId; 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.TenantEntityWithDataDao;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.usagerecord.ApiLimitService;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
@ -32,6 +34,8 @@ import java.util.function.Function;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import static org.apache.commons.lang3.StringUtils.capitalize;
@Slf4j @Slf4j
public abstract class DataValidator<D extends BaseData<?>> { public abstract class DataValidator<D extends BaseData<?>> {
private static final Pattern EMAIL_PATTERN = 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 NAME = "name";
private static final String TOPIC = "topic"; private static final String TOPIC = "topic";
@Autowired @Lazy
private ApiLimitService apiLimitService;
// Returns old instance of the same object that is fetched during validation. // Returns old instance of the same object that is fetched during validation.
public D validate(D data, Function<D, TenantId> tenantIdFunction) { public D validate(D data, Function<D, TenantId> tenantIdFunction) {
try { try {
@ -97,15 +104,9 @@ public abstract class DataValidator<D extends BaseData<?>> {
} }
protected void validateNumberOfEntitiesPerTenant(TenantId tenantId, protected void validateNumberOfEntitiesPerTenant(TenantId tenantId,
TenantEntityDao tenantEntityDao,
long maxEntities,
EntityType entityType) { EntityType entityType) {
if (maxEntities > 0) { if (!apiLimitService.checkEntitiesLimit(tenantId, entityType)) {
long currentEntitiesCount = tenantEntityDao.countByTenantId(tenantId); throw new DataValidationException(String.format("%ss limit reached", capitalize(entityType.name().toLowerCase().replaceAll("_", " "))));
if (currentEntitiesCount >= maxEntities) {
throw new DataValidationException(String.format("Can't create more then %d %ss!",
maxEntities, 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.asset.Asset;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId; 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.AssetDao;
import org.thingsboard.server.dao.asset.BaseAssetService; import org.thingsboard.server.dao.asset.BaseAssetService;
import org.thingsboard.server.dao.customer.CustomerDao; import org.thingsboard.server.dao.customer.CustomerDao;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator; 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.tenant.TenantService;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
@ -48,17 +46,10 @@ public class AssetDataValidator extends DataValidator<Asset> {
@Autowired @Autowired
private CustomerDao customerDao; private CustomerDao customerDao;
@Autowired
@Lazy
private TbTenantProfileCache tenantProfileCache;
@Override @Override
protected void validateCreate(TenantId tenantId, Asset asset) { protected void validateCreate(TenantId tenantId, Asset asset) {
DefaultTenantProfileConfiguration profileConfiguration =
(DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
if (!BaseAssetService.TB_SERVICE_QUEUE.equals(asset.getType())) { if (!BaseAssetService.TB_SERVICE_QUEUE.equals(asset.getType())) {
long maxAssets = profileConfiguration.getMaxAssets(); validateNumberOfEntitiesPerTenant(tenantId, EntityType.ASSET);
validateNumberOfEntitiesPerTenant(tenantId, assetDao, maxAssets, 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; package org.thingsboard.server.dao.service.validator;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.id.TenantId; 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.CustomerDao;
import org.thingsboard.server.dao.customer.CustomerServiceImpl; import org.thingsboard.server.dao.customer.CustomerServiceImpl;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator; 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.tenant.TenantService;
import java.util.Optional; import java.util.Optional;
@ -41,17 +38,9 @@ public class CustomerDataValidator extends DataValidator<Customer> {
@Autowired @Autowired
private TenantService tenantService; private TenantService tenantService;
@Autowired
@Lazy
private TbTenantProfileCache tenantProfileCache;
@Override @Override
protected void validateCreate(TenantId tenantId, Customer customer) { protected void validateCreate(TenantId tenantId, Customer customer) {
DefaultTenantProfileConfiguration profileConfiguration = validateNumberOfEntitiesPerTenant(tenantId, EntityType.CUSTOMER);
(DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
long maxCustomers = profileConfiguration.getMaxCustomers();
validateNumberOfEntitiesPerTenant(tenantId, customerDao, maxCustomers, EntityType.CUSTOMER);
customerDao.findCustomersByTenantIdAndTitle(customer.getTenantId().getId(), customer.getTitle()).ifPresent( customerDao.findCustomersByTenantIdAndTitle(customer.getTenantId().getId(), customer.getTitle()).ifPresent(
c -> { c -> {
throw new DataValidationException("Customer with such title already exists!"); 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; package org.thingsboard.server.dao.service.validator;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.id.TenantId; 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.dashboard.DashboardDao;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator; 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.tenant.TenantService;
@Component @Component
@ -38,16 +35,9 @@ public class DashboardDataValidator extends DataValidator<Dashboard> {
@Autowired @Autowired
private TenantService tenantService; private TenantService tenantService;
@Autowired
@Lazy
private TbTenantProfileCache tenantProfileCache;
@Override @Override
protected void validateCreate(TenantId tenantId, Dashboard data) { protected void validateCreate(TenantId tenantId, Dashboard data) {
DefaultTenantProfileConfiguration profileConfiguration = validateNumberOfEntitiesPerTenant(tenantId, EntityType.DASHBOARD);
(DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
long maxDashboards = profileConfiguration.getMaxDashboards();
validateNumberOfEntitiesPerTenant(tenantId, dashboardDao, maxDashboards, EntityType.DASHBOARD);
} }
@Override @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; package org.thingsboard.server.dao.service.validator;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device; 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.device.data.DeviceTransportConfiguration;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId; 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.CustomerDao;
import org.thingsboard.server.dao.device.DeviceDao; import org.thingsboard.server.dao.device.DeviceDao;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.tenant.TenantService;
import java.util.Optional; import java.util.Optional;
@ -48,16 +45,9 @@ public class DeviceDataValidator extends AbstractHasOtaPackageValidator<Device>
@Autowired @Autowired
private CustomerDao customerDao; private CustomerDao customerDao;
@Autowired
@Lazy
private TbTenantProfileCache tenantProfileCache;
@Override @Override
protected void validateCreate(TenantId tenantId, Device device) { protected void validateCreate(TenantId tenantId, Device device) {
DefaultTenantProfileConfiguration profileConfiguration = validateNumberOfEntitiesPerTenant(tenantId, EntityType.DEVICE);
(DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
long maxDevices = profileConfiguration.getMaxDevices();
validateNumberOfEntitiesPerTenant(tenantId, deviceDao, maxDevices, EntityType.DEVICE);
} }
@Override @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 @Autowired
private TenantService tenantService; private TenantService tenantService;
@Autowired
@Lazy
private TbTenantProfileCache tenantProfileCache;
@Override @Override
protected void validateCreate(TenantId tenantId, RuleChain data) { protected void validateCreate(TenantId tenantId, RuleChain data) {
DefaultTenantProfileConfiguration profileConfiguration = validateNumberOfEntitiesPerTenant(tenantId, EntityType.RULE_CHAIN);
(DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
long maxRuleChains = profileConfiguration.getMaxRuleChains();
validateNumberOfEntitiesPerTenant(tenantId, ruleChainDao, maxRuleChains, EntityType.RULE_CHAIN);
} }
@Override @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.context.annotation.Lazy;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Customer; 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.EntityType;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.security.Authority; 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.customer.CustomerDao;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.service.DataValidator; 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.tenant.TenantService;
import org.thingsboard.server.dao.user.UserDao; import org.thingsboard.server.dao.user.UserDao;
import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.user.UserService;
@ -49,10 +46,6 @@ public class UserDataValidator extends DataValidator<User> {
@Autowired @Autowired
private CustomerDao customerDao; private CustomerDao customerDao;
@Autowired
@Lazy
private TbTenantProfileCache tenantProfileCache;
@Autowired @Autowired
@Lazy @Lazy
private TenantService tenantService; private TenantService tenantService;
@ -60,10 +53,7 @@ public class UserDataValidator extends DataValidator<User> {
@Override @Override
protected void validateCreate(TenantId tenantId, User user) { protected void validateCreate(TenantId tenantId, User user) {
if (!user.getTenantId().getId().equals(ModelConstants.NULL_UUID)) { if (!user.getTenantId().getId().equals(ModelConstants.NULL_UUID)) {
DefaultTenantProfileConfiguration profileConfiguration = validateNumberOfEntitiesPerTenant(tenantId, EntityType.USER);
(DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
long maxUsers = profileConfiguration.getMaxUsers();
validateNumberOfEntitiesPerTenant(tenantId, userDao, maxUsers, 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_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, recipient_id UUID NOT NULL CONSTRAINT fk_notification_recipient_id REFERENCES tb_user(id) ON DELETE CASCADE,
type VARCHAR(50) NOT NULL, type VARCHAR(50) NOT NULL,
subject VARCHAR(255), subject VARCHAR(255),
text VARCHAR(1000) NOT NULL, body VARCHAR(1000) NOT NULL,
additional_config VARCHAR(1000), additional_config VARCHAR(1000),
status VARCHAR(32) status VARCHAR(32)
) PARTITION BY RANGE (created_time); ) 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; UPDATE alarm a SET acknowledged = true, ack_ts = a_ts WHERE a.id = a_id AND a.tenant_id = t_id;
END IF; END IF;
SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id; 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 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 com.google.gson.JsonPrimitive;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull; 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.common.util.DonAsynchron;
import org.thingsboard.rule.engine.api.EmptyNodeConfiguration; import org.thingsboard.rule.engine.api.EmptyNodeConfiguration;
import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.RuleNode;

Loading…
Cancel
Save