diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java index c9330c0a77..ddd1140f9a 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java @@ -108,7 +108,7 @@ public class DeviceProfileController extends BaseController { checkParameter(DEVICE_PROFILE_ID, strDeviceProfileId); try { DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId)); - return checkNotNull(deviceProfileService.findDeviceProfileInfoById(getTenantId(), deviceProfileId)); + return new DeviceProfileInfo(checkDeviceProfileId(deviceProfileId, Operation.READ)); } catch (Exception e) { throw handleException(e); } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java index 125cf9be90..7ee7696f1a 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java @@ -350,6 +350,10 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS return EdgeEventActionType.RELATION_ADD_OR_UPDATE; case RELATION_DELETED: return EdgeEventActionType.RELATION_DELETED; + case ASSIGNED_TO_CUSTOMER: + return EdgeEventActionType.ASSIGNED_TO_CUSTOMER; + case UNASSIGNED_FROM_CUSTOMER: + return EdgeEventActionType.UNASSIGNED_FROM_CUSTOMER; case ASSIGNED_TO_EDGE: return EdgeEventActionType.ASSIGNED_TO_EDGE; case UNASSIGNED_FROM_EDGE: diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java index 4930f54a5d..c467e0804b 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java @@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.queue.util.TbCoreComponent; @@ -54,7 +55,7 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb public void ack(Alarm alarm, SecurityUser user) throws ThingsboardException { try { long ackTs = System.currentTimeMillis(); - alarmService.ackAlarm(user.getTenantId(), alarm.getId(), ackTs).get(); + alarmService.ackAlarm(alarm.getTenantId(), alarm.getId(), ackTs).get(); alarm.setAckTs(ackTs); alarm.setStatus(alarm.getStatus().isCleared() ? AlarmStatus.CLEARED_ACK : AlarmStatus.ACTIVE_ACK); notificationEntityService.notifyCreateOrUpdateAlarm(alarm, ActionType.ALARM_ACK, user); @@ -67,7 +68,7 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb public void clear(Alarm alarm, SecurityUser user) throws ThingsboardException { try { long clearTs = System.currentTimeMillis(); - alarmService.clearAlarm(user.getTenantId(), alarm.getId(), null, clearTs).get(); + alarmService.clearAlarm(alarm.getTenantId(), alarm.getId(), null, clearTs).get(); alarm.setClearTs(clearTs); alarm.setStatus(alarm.getStatus().isAck() ? AlarmStatus.CLEARED_ACK : AlarmStatus.CLEARED_UNACK); notificationEntityService.notifyCreateOrUpdateAlarm(alarm, ActionType.ALARM_CLEAR, user); @@ -78,11 +79,20 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb @Override public Boolean delete(Alarm alarm, SecurityUser user) throws ThingsboardException { + return delete(alarm, user.getCustomerId(), user); + } + + @Override + public Boolean delete(Alarm alarm, CustomerId customerId) throws ThingsboardException { + return delete(alarm, customerId, null); + } + + private Boolean delete(Alarm alarm, CustomerId customerId, SecurityUser user) throws ThingsboardException { try { - List relatedEdgeIds = findRelatedEdgeIds(user.getTenantId(), alarm.getOriginator()); - notificationEntityService.notifyDeleteAlarm(user.getTenantId(), alarm, alarm.getOriginator(), user.getCustomerId(), + List relatedEdgeIds = findRelatedEdgeIds(alarm.getTenantId(), alarm.getOriginator()); + notificationEntityService.notifyDeleteAlarm(alarm.getTenantId(), alarm, alarm.getOriginator(), customerId, relatedEdgeIds, user, JacksonUtil.OBJECT_MAPPER.writeValueAsString(alarm)); - return alarmService.deleteAlarm(user.getTenantId(), alarm.getId()).isSuccessful(); + return alarmService.deleteAlarm(alarm.getTenantId(), alarm.getId()).isSuccessful(); } catch (Exception e) { throw handleException(e); } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java index 7a6d7f6a81..de617a965f 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java @@ -17,6 +17,7 @@ package org.thingsboard.server.service.entitiy.alarm; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.service.security.model.SecurityUser; public interface TbAlarmService { @@ -28,4 +29,6 @@ public interface TbAlarmService { void clear(Alarm alarm, SecurityUser user) throws ThingsboardException; Boolean delete(Alarm alarm, SecurityUser user) throws ThingsboardException; + + Boolean delete(Alarm alarm, CustomerId customerId) throws ThingsboardException; } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java index 4ee8554c4a..e118346588 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java @@ -23,7 +23,6 @@ import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.Edge; -import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.CustomerId; @@ -35,6 +34,8 @@ import org.thingsboard.server.service.security.model.SecurityUser; import java.util.List; +import static org.thingsboard.server.service.entitiy.DefaultTbNotificationEntityService.edgeTypeByActionType; + @Service @TbCoreComponent @AllArgsConstructor @@ -47,7 +48,7 @@ public class DefaultTbAssetService extends AbstractTbEntityService implements Tb try { Asset savedAsset = checkNotNull(assetService.saveAsset(asset)); vcService.autoCommit(user, savedAsset.getId()); - notificationEntityService.notifyCreateOrUpdateEntity(tenantId, savedAsset.getId(), asset, savedAsset.getCustomerId(), actionType, user); + notificationEntityService.notifyCreateOrUpdateEntity(tenantId, savedAsset.getId(), savedAsset, savedAsset.getCustomerId(), actionType, user); return savedAsset; } catch (Exception e) { notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.ASSET), asset, null, actionType, user, e); @@ -80,7 +81,7 @@ public class DefaultTbAssetService extends AbstractTbEntityService implements Tb try { Asset savedAsset = checkNotNull(assetService.assignAssetToCustomer(tenantId, assetId, customerId)); notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, assetId, customerId, savedAsset, - actionType, EdgeEventActionType.ASSIGNED_TO_CUSTOMER, user, true, customerId.toString(), customer.getName()); + actionType, edgeTypeByActionType(actionType), user, true, customerId.toString(), customer.getName()); return savedAsset; } catch (Exception e) { @@ -99,7 +100,7 @@ public class DefaultTbAssetService extends AbstractTbEntityService implements Tb CustomerId customerId = customer.getId(); notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, assetId, customerId, savedAsset, - actionType, EdgeEventActionType.UNASSIGNED_FROM_CUSTOMER, user, + actionType, edgeTypeByActionType(actionType), user, true, customerId.toString(), customer.getName()); return savedAsset; diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java index 8988425ef8..2c7f9ae51a 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java @@ -16,6 +16,7 @@ package org.thingsboard.server.controller; import lombok.extern.slf4j.Slf4j; +import org.mockito.ArgumentMatcher; import org.mockito.Mockito; import org.springframework.boot.test.mock.mockito.SpyBean; import org.thingsboard.server.cluster.TbClusterService; @@ -33,6 +34,7 @@ import org.thingsboard.server.dao.audit.AuditLogService; import org.thingsboard.server.dao.model.ModelConstants; import java.util.Locale; +import java.util.Objects; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -50,73 +52,86 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest { protected void testNotifyEntityAllOneTime(HasName entity, EntityId entityId, EntityId originatorId, TenantId tenantId, CustomerId customerId, UserId userId, String userName, ActionType actionType, Object... additionalInfo) { - testSendNotificationMsgToEdgeServiceOneTime(entityId, tenantId, actionType); - testLogEntityActionOneTime(entity, originatorId, tenantId, customerId, userId, userName, actionType, additionalInfo); - testPushMsgToRuleEngineOneTime(originatorId, tenantId); + int cntTime = 1; + testSendNotificationMsgToEdgeServiceTime(entityId, tenantId, actionType, cntTime); + testLogEntityAction(entity, originatorId, tenantId, customerId, userId, userName, actionType, cntTime, additionalInfo); + testPushMsgToRuleEngineTime(originatorId, tenantId, cntTime); Mockito.reset(tbClusterService, auditLogService); } - protected void testNotifyEntityDeleteOneTimeMsgToEdgeServiceNever(HasName entity, EntityId entityId, EntityId originatorId, - TenantId tenantId, CustomerId customerId, UserId userId, String userName, - ActionType actionType, Object... additionalInfo) { - testNotificationMsgToEdgeServiceNever(entityId); - testLogEntityActionOneTime(entity, originatorId, tenantId, customerId, userId, userName, actionType, additionalInfo); - testPushMsgToRuleEngineOneTime(entityId, tenantId); - testBroadcastEntityStateChangeEventOneTime(entityId, tenantId); - Mockito.reset(tbClusterService, auditLogService); - } - protected void testNotifyEntityNeverMsgToEdgeServiceOneTime(HasName entity, EntityId entityId, TenantId tenantId, ActionType actionType) { - testSendNotificationMsgToEdgeServiceOneTime(entityId, tenantId, actionType); + protected void testNotifyEntityNeverMsgToEdgeServiceOneTime(HasName entity, EntityId entityId, TenantId tenantId, + ActionType actionType) { + testSendNotificationMsgToEdgeServiceTime(entityId, tenantId, actionType, 1); testLogEntityActionNever(entityId, entity); testPushMsgToRuleEngineNever(entityId); Mockito.reset(tbClusterService, auditLogService); } protected void testNotifyEntityOneTimeMsgToEdgeServiceNever(HasName entity, EntityId entityId, EntityId originatorId, - TenantId tenantId, CustomerId customerId, UserId userId, String userName, - ActionType actionType, Object... additionalInfo) { + TenantId tenantId, CustomerId customerId, UserId userId, + String userName, ActionType actionType, Object... additionalInfo) { + int cntTime = 1; + testNotificationMsgToEdgeServiceNever(entityId); + testLogEntityAction(entity, originatorId, tenantId, customerId, userId, userName, actionType, cntTime, additionalInfo); + testPushMsgToRuleEngineTime(originatorId, tenantId, cntTime); + Mockito.reset(tbClusterService, auditLogService); + } + + protected void testNotifyManyEntityManyTimeMsgToEdgeServiceNever(HasName entity, HasName originator, + TenantId tenantId, CustomerId customerId, UserId userId, String userName, + ActionType actionType, int cntTime, Object... additionalInfo) { + EntityId entityId = createEntityId_NULL_UUID(entity); + EntityId originatorId = createEntityId_NULL_UUID(originator); testNotificationMsgToEdgeServiceNever(entityId); - testLogEntityActionOneTime(entity, originatorId, tenantId, customerId, userId, userName, actionType, additionalInfo); - testPushMsgToRuleEngineOneTime(originatorId, tenantId); + ArgumentMatcher matcherEntityClassEquals = argument -> argument.getClass().equals(entity.getClass()); + ArgumentMatcher matcherOriginatorId = argument -> argument.getClass().equals(originatorId.getClass()); + testLogEntityActionAdditionalInfo(matcherEntityClassEquals, matcherOriginatorId, tenantId, customerId, userId, userName, actionType, cntTime, + additionalInfo); + testPushMsgToRuleEngineTime(originatorId, tenantId, cntTime); Mockito.reset(tbClusterService, auditLogService); } protected void testNotifyEntityBroadcastEntityStateChangeEventOneTimeMsgToEdgeServiceNever(HasName entity, EntityId entityId, EntityId originatorId, TenantId tenantId, CustomerId customerId, UserId userId, String userName, ActionType actionType, Object... additionalInfo) { + int cntTime = 1; testNotificationMsgToEdgeServiceNever(entityId); - testLogEntityActionOneTime(entity, originatorId, tenantId, customerId, userId, userName, actionType, additionalInfo); - testPushMsgToRuleEngineOneTime(originatorId, tenantId); - testBroadcastEntityStateChangeEventOneTime(entityId, tenantId); + testLogEntityAction(entity, originatorId, tenantId, customerId, userId, userName, actionType, cntTime, additionalInfo); + testPushMsgToRuleEngineTime(originatorId, tenantId, cntTime); + testBroadcastEntityStateChangeEventTime(entityId, tenantId, cntTime); Mockito.reset(tbClusterService, auditLogService); } - protected void testNotifyEntityError(HasName entity, TenantId tenantId, - UserId userId, String userName, ActionType actionType, Exception exp, - Object... additionalInfo) { + protected void testNotifyEntityEqualsOneTimeError(HasName entity, TenantId tenantId, + UserId userId, String userName, ActionType actionType, Exception exp, + Object... additionalInfo) { CustomerId customer_NULL_UUID = (CustomerId) EntityIdFactory.getByTypeAndUuid(EntityType.CUSTOMER, ModelConstants.NULL_UUID); - EntityId entity_NULL_UUID = EntityIdFactory.getByTypeAndUuid(EntityType.valueOf(entity.getClass().toString() - .substring(entity.getClass().toString().lastIndexOf(".") + 1).toUpperCase(Locale.ENGLISH)), - ModelConstants.NULL_UUID); - testNotificationMsgToEdgeServiceNever(entity_NULL_UUID); - if (additionalInfo.length > 0) { - Mockito.verify(auditLogService, times(1)).logEntityAction(Mockito.eq(tenantId), - Mockito.eq(customer_NULL_UUID), Mockito.eq(userId), Mockito.eq(userName), - Mockito.eq(entity_NULL_UUID), Mockito.any(entity.getClass()), Mockito.eq(actionType), - Mockito.argThat(argument -> - argument.getMessage().equals(exp.getMessage())), Mockito.eq(additionalInfo)); - } else { - Mockito.verify(auditLogService, times(1)).logEntityAction(Mockito.eq(tenantId), - Mockito.eq(customer_NULL_UUID), Mockito.eq(userId), Mockito.eq(userName), - Mockito.eq(entity_NULL_UUID), Mockito.any(entity.getClass()), Mockito.eq(actionType), - Mockito.argThat(argument -> - argument.getMessage().equals(exp.getMessage()))); - } - testPushMsgToRuleEngineNever(entity_NULL_UUID); - Mockito.reset(tbClusterService, auditLogService); + EntityId entity_originator_NULL_UUID = createEntityId_NULL_UUID(entity); + testNotificationMsgToEdgeServiceNever(entity_originator_NULL_UUID); + ArgumentMatcher matcherEntityEquals = argument -> argument.getClass().equals(entity.getClass()); + ArgumentMatcher matcherError = argument -> argument.getMessage().contains(exp.getMessage()) + & argument.getClass().equals(exp.getClass()); + testLogEntityActionErrorAdditionalInfo(matcherEntityEquals, entity_originator_NULL_UUID, tenantId, customer_NULL_UUID, userId, + userName, actionType, 1, matcherError, additionalInfo); + testPushMsgToRuleEngineNever(entity_originator_NULL_UUID); + } + + protected void testNotifyEntityIsNullOneTimeError(HasName entity, TenantId tenantId, + UserId userId, String userName, ActionType actionType, Exception exp, + Object... additionalInfo) { + CustomerId customer_NULL_UUID = (CustomerId) EntityIdFactory.getByTypeAndUuid(EntityType.CUSTOMER, ModelConstants.NULL_UUID); + EntityId entity_originator_NULL_UUID = createEntityId_NULL_UUID(entity); + testNotificationMsgToEdgeServiceNever(entity_originator_NULL_UUID); + ArgumentMatcher matcherEntityIsNull = Objects::isNull; + ArgumentMatcher matcherError = argument -> argument.getMessage().contains(exp.getMessage()) & + argument.getClass().equals(exp.getClass()); + testLogEntityActionErrorAdditionalInfo(matcherEntityIsNull, entity_originator_NULL_UUID, tenantId, customer_NULL_UUID, + userId, userName, actionType, 1, matcherError, additionalInfo); + testPushMsgToRuleEngineNever(entity_originator_NULL_UUID); } protected void testNotifyEntityNever(EntityId entityId, HasName entity) { + entityId = entityId == null ? createEntityId_NULL_UUID(entity) : entityId; testNotificationMsgToEdgeServiceNever(entityId); testLogEntityActionNever(entityId, entity); testPushMsgToRuleEngineNever(entityId); @@ -139,36 +154,144 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest { Mockito.any(entityId.getClass()), Mockito.any(), Mockito.any()); } - private void testLogEntityActionOneTime(HasName entity, EntityId originatorId, TenantId tenantId, CustomerId customerId, - UserId userId, String userName, ActionType actionType, Object... additionalInfo) { - if (additionalInfo.length == 0) { - Mockito.verify(auditLogService, times(1)).logEntityAction(Mockito.eq(tenantId), - Mockito.eq(customerId), Mockito.eq(userId), Mockito.eq(userName), Mockito.eq(originatorId), - Mockito.eq(entity), Mockito.eq(actionType), Mockito.isNull()); - } else { - String additionalInfoStr = extractParameter(String.class, 0, additionalInfo); - Mockito.verify(auditLogService, times(1)).logEntityAction(Mockito.eq(tenantId), - Mockito.eq(customerId), Mockito.eq(userId), Mockito.eq(userName), Mockito.eq(originatorId), - Mockito.eq(entity), Mockito.eq(actionType), Mockito.isNull(), Mockito.eq(additionalInfoStr)); - } + private void testPushMsgToRuleEngineTime(EntityId originatorId, TenantId tenantId, int cntTime) { + ArgumentMatcher matcherOriginatorId = cntTime == 1 ? argument -> argument.equals(originatorId) : + argument -> argument.getClass().equals(originatorId.getClass()); + Mockito.verify(tbClusterService, times(cntTime)).pushMsgToRuleEngine(Mockito.eq(tenantId), + Mockito.argThat(matcherOriginatorId), Mockito.any(TbMsg.class), Mockito.isNull()); } - private void testPushMsgToRuleEngineOneTime(EntityId originatorId, TenantId tenantId) { - Mockito.verify(tbClusterService, times(1)).pushMsgToRuleEngine(Mockito.eq(tenantId), - Mockito.eq(originatorId), Mockito.any(TbMsg.class), Mockito.isNull()); - } - - private void testSendNotificationMsgToEdgeServiceOneTime(EntityId entityId, TenantId tenantId, ActionType actionType) { - Mockito.verify(tbClusterService, times(1)).sendNotificationMsgToEdge(Mockito.eq(tenantId), - Mockito.isNull(), Mockito.eq(entityId), Mockito.isNull(), Mockito.isNull(), + private void testSendNotificationMsgToEdgeServiceTime(EntityId entityId, TenantId tenantId, ActionType actionType, int cntTime) { + Mockito.verify(tbClusterService, times(cntTime)).sendNotificationMsgToEdge(Mockito.eq(tenantId), + Mockito.any(), Mockito.eq(entityId), Mockito.any(), Mockito.isNull(), Mockito.eq(edgeTypeByActionType(actionType))); } - private void testBroadcastEntityStateChangeEventOneTime(EntityId entityId, TenantId tenantId) { - Mockito.verify(tbClusterService, times(1)).broadcastEntityStateChangeEvent(Mockito.eq(tenantId), + private void testBroadcastEntityStateChangeEventTime(EntityId entityId, TenantId tenantId, int cntTime) { + Mockito.verify(tbClusterService, times(cntTime)).broadcastEntityStateChangeEvent(Mockito.eq(tenantId), Mockito.any(entityId.getClass()), Mockito.any(ComponentLifecycleEvent.class)); } + private void testLogEntityAction(HasName entity, EntityId originatorId, TenantId tenantId, + CustomerId customerId, UserId userId, String userName, + ActionType actionType, int cntTime, Object... additionalInfo) { + ArgumentMatcher matcherEntityEquals = argument -> argument.equals(entity); + ArgumentMatcher matcherOriginatorId = argument -> argument.equals(originatorId); + testLogEntityActionAdditionalInfo(matcherEntityEquals, matcherOriginatorId, tenantId, customerId, userId, userName, + actionType, cntTime, additionalInfo); + } + + private void testLogEntityActionAdditionalInfo(ArgumentMatcher matcherEntity, ArgumentMatcher matcherOriginatorId, + TenantId tenantId, CustomerId customerId, UserId userId, String userName, + ActionType actionType, int cntTime, Object... additionalInfo) { + switch (additionalInfo.length) { + case 1: + Mockito.verify(auditLogService, times(cntTime)) + .logEntityAction(Mockito.eq(tenantId), + Mockito.eq(customerId), + Mockito.eq(userId), + Mockito.eq(userName), + Mockito.argThat(matcherOriginatorId), + Mockito.argThat(matcherEntity), + Mockito.eq(actionType), + Mockito.isNull(), + Mockito.eq(extractParameter(String.class, 0, additionalInfo))); + break; + case 2: + Mockito.verify(auditLogService, times(cntTime)) + .logEntityAction(Mockito.eq(tenantId), + Mockito.eq(customerId), + Mockito.eq(userId), + Mockito.eq(userName), + Mockito.argThat(matcherOriginatorId), + Mockito.argThat(matcherEntity), + Mockito.eq(actionType), + Mockito.isNull(), + Mockito.eq(extractParameter(String.class, 0, additionalInfo)), + Mockito.eq(extractParameter(String.class, 1, additionalInfo))); + break; + case 3: + Mockito.verify(auditLogService, times(cntTime)) + .logEntityAction(Mockito.eq(tenantId), + Mockito.eq(customerId), + Mockito.eq(userId), + Mockito.eq(userName), + Mockito.argThat(matcherOriginatorId), + Mockito.argThat(matcherEntity), + Mockito.eq(actionType), + Mockito.isNull(), + Mockito.eq(extractParameter(String.class, 0, additionalInfo)), + Mockito.eq(extractParameter(String.class, 1, additionalInfo)), + Mockito.eq(extractParameter(String.class, 2, additionalInfo))); + break; + default: + Mockito.verify(auditLogService, times(cntTime)) + .logEntityAction(Mockito.eq(tenantId), + Mockito.eq(customerId), + Mockito.eq(userId), + Mockito.eq(userName), + Mockito.argThat(matcherOriginatorId), + Mockito.argThat(matcherEntity), + Mockito.eq(actionType), + Mockito.isNull()); + } + } + + private void testLogEntityActionErrorAdditionalInfo(ArgumentMatcher matcherEntity, EntityId originatorId, TenantId tenantId, + CustomerId customerId, UserId userId, String userName, ActionType actionType, + int cntTime, ArgumentMatcher matcherError, Object... additionalInfo) { + switch (additionalInfo.length) { + case 1: + Mockito.verify(auditLogService, times(cntTime)) + .logEntityAction(Mockito.eq(tenantId), + Mockito.eq(customerId), + Mockito.eq(userId), + Mockito.eq(userName), + Mockito.eq(originatorId), + Mockito.argThat(matcherEntity), + Mockito.eq(actionType), + Mockito.argThat(matcherError), + Mockito.eq(extractParameter(String.class, 0, additionalInfo))); + break; + case 2: + Mockito.verify(auditLogService, times(cntTime)) + .logEntityAction(Mockito.eq(tenantId), + Mockito.eq(customerId), + Mockito.eq(userId), + Mockito.eq(userName), + Mockito.eq(originatorId), + Mockito.argThat(matcherEntity), + Mockito.eq(actionType), + Mockito.argThat(matcherError), + Mockito.eq(extractParameter(String.class, 0, additionalInfo)), + Mockito.eq(extractParameter(String.class, 1, additionalInfo))); + case 3: + Mockito.verify(auditLogService, times(cntTime)) + .logEntityAction(Mockito.eq(tenantId), + Mockito.eq(customerId), + Mockito.eq(userId), + Mockito.eq(userName), + Mockito.eq(originatorId), + Mockito.argThat(matcherEntity), + Mockito.eq(actionType), + Mockito.argThat(matcherError), + Mockito.eq(extractParameter(String.class, 0, additionalInfo)), + Mockito.eq(extractParameter(String.class, 1, additionalInfo)), + Mockito.eq(extractParameter(String.class, 3, additionalInfo))); + break; + default: + Mockito.verify(auditLogService, times(cntTime)) + .logEntityAction(Mockito.eq(tenantId), + Mockito.eq(customerId), + Mockito.eq(userId), + Mockito.eq(userName), + Mockito.eq(originatorId), + Mockito.argThat(matcherEntity), + Mockito.eq(actionType), + Mockito.argThat(matcherError)); + } + } + private T extractParameter(Class clazz, int index, Object... additionalInfo) { T result = null; if (additionalInfo != null && additionalInfo.length > index) { @@ -179,4 +302,10 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest { } return result; } + + private EntityId createEntityId_NULL_UUID(HasName entity) { + return EntityIdFactory.getByTypeAndUuid(EntityType.valueOf(entity.getClass().toString() + .substring(entity.getClass().toString().lastIndexOf(".") + 1).toUpperCase(Locale.ENGLISH)), + ModelConstants.NULL_UUID); + } } diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java index 80eaed9ff1..8b17c9baf3 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java @@ -237,6 +237,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { loginSysAdmin(); doDelete("/api/tenant/" + tenantId.getId().toString()) .andExpect(status().isOk()); + deleteDifferentTenant(); verifyNoTenantsLeft(); @@ -334,6 +335,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { loginSysAdmin(); doDelete("/api/tenant/" + savedDifferentTenant.getId().getId().toString()) .andExpect(status().isOk()); + savedDifferentTenant = null; } } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseAssetControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseAssetControllerTest.java index 0861ab34c8..0f8f2d11be 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseAssetControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseAssetControllerTest.java @@ -22,17 +22,20 @@ import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.mockito.Mockito; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.service.stats.DefaultRuleEngineStatisticsService; @@ -83,8 +86,14 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { Asset asset = new Asset(); asset.setName("My asset"); asset.setType("default"); + + Mockito.reset(tbClusterService, auditLogService); + Asset savedAsset = doPost("/api/asset", asset, Asset.class); + testNotifyEntityOneTimeMsgToEdgeServiceNever(savedAsset, savedAsset.getId(), savedAsset.getId(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED); + Assert.assertNotNull(savedAsset); Assert.assertNotNull(savedAsset.getId()); Assert.assertTrue(savedAsset.getCreatedTime() > 0); @@ -93,9 +102,14 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { Assert.assertEquals(NULL_UUID, savedAsset.getCustomerId().getId()); Assert.assertEquals(asset.getName(), savedAsset.getName()); + Mockito.reset(tbClusterService, auditLogService); + savedAsset.setName("My new asset"); doPost("/api/asset", savedAsset, Asset.class); + testNotifyEntityAllOneTime(savedAsset, savedAsset.getId(), savedAsset.getId(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.UPDATED); + Asset foundAsset = doGet("/api/asset/" + savedAsset.getId().getId().toString(), Asset.class); Assert.assertEquals(foundAsset.getName(), savedAsset.getName()); } @@ -105,13 +119,33 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { Asset asset = new Asset(); asset.setName(RandomStringUtils.randomAlphabetic(300)); asset.setType("default"); - doPost("/api/asset", asset).andExpect(statusReason(containsString("length of name must be equal or less than 255"))); + + Mockito.reset(tbClusterService, auditLogService); + + String msgError = "length of name must be equal or less than 255"; + doPost("/api/asset", asset).andExpect(statusReason(containsString(msgError))); + + + testNotifyEntityEqualsOneTimeError(asset, savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); + Mockito.reset(tbClusterService, auditLogService); + asset.setName("Normal name"); asset.setType(RandomStringUtils.randomAlphabetic(300)); - doPost("/api/asset", asset).andExpect(statusReason(containsString("length of type must be equal or less than 255"))); + msgError = "length of type must be equal or less than 255"; + doPost("/api/asset", asset).andExpect(statusReason(containsString(msgError))); + + testNotifyEntityEqualsOneTimeError(asset, savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); + Mockito.reset(tbClusterService, auditLogService); + asset.setType("default"); asset.setLabel(RandomStringUtils.randomAlphabetic(300)); - doPost("/api/asset", asset).andExpect(statusReason(containsString("length of label must be equal or less than 255"))); + msgError = "length of label must be equal or less than 255"; + doPost("/api/asset", asset).andExpect(statusReason(containsString(msgError))); + + testNotifyEntityEqualsOneTimeError(asset, savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); } @Test @@ -122,7 +156,13 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { Asset savedAsset = doPost("/api/asset", asset, Asset.class); loginDifferentTenant(); + + Mockito.reset(tbClusterService, auditLogService); + doPost("/api/asset", savedAsset, Asset.class, status().isForbidden()); + + testNotifyEntityNever(savedAsset.getId(), savedAsset); + deleteDifferentTenant(); } @@ -140,12 +180,21 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { @Test public void testFindAssetTypesByTenantId() throws Exception { List assets = new ArrayList<>(); - for (int i = 0; i < 3; i++) { + + Mockito.reset(tbClusterService, auditLogService); + + int cntTime = 3; + for (int i = 0; i < cntTime; i++) { Asset asset = new Asset(); asset.setName("My asset B" + i); asset.setType("typeB"); assets.add(doPost("/api/asset", asset, Asset.class)); } + + testNotifyManyEntityManyTimeMsgToEdgeServiceNever(new Asset(), new Asset(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.ADDED, cntTime); + for (int i = 0; i < 7; i++) { Asset asset = new Asset(); asset.setName("My asset C" + i); @@ -176,9 +225,15 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { asset.setType("default"); Asset savedAsset = doPost("/api/asset", asset, Asset.class); + Mockito.reset(tbClusterService, auditLogService); + doDelete("/api/asset/" + savedAsset.getId().getId().toString()) .andExpect(status().isOk()); + testNotifyEntityOneTimeMsgToEdgeServiceNever(savedAsset, savedAsset.getId(), savedAsset.getId(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.DELETED, savedAsset.getId().getId().toString()); + doGet("/api/asset/" + savedAsset.getId().getId().toString()) .andExpect(status().isNotFound()); } @@ -202,9 +257,16 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { view.setType("default"); EntityView savedView = doPost("/api/entityView", view, EntityView.class); + Mockito.reset(tbClusterService, auditLogService); + doDelete("/api/asset/" + savedAsset1.getId().getId().toString()) .andExpect(status().isBadRequest()); + String msgError = "Can't delete asset that has entity views"; + + testNotifyEntityIsNullOneTimeError(savedAsset1, savedTenant.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.DELETED, new DataValidationException(msgError), savedAsset1.getId().getId().toString()); + savedView.setEntityId(savedAsset2.getId()); doPost("/api/entityView", savedView, EntityView.class); @@ -220,18 +282,32 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { public void testSaveAssetWithEmptyType() throws Exception { Asset asset = new Asset(); asset.setName("My asset"); + + Mockito.reset(tbClusterService, auditLogService); + + String msgError = "Asset type should be specified"; doPost("/api/asset", asset) .andExpect(status().isBadRequest()) - .andExpect(statusReason(containsString("Asset type should be specified"))); + .andExpect(statusReason(containsString(msgError))); + + testNotifyEntityEqualsOneTimeError(asset, savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); } @Test public void testSaveAssetWithEmptyName() throws Exception { Asset asset = new Asset(); asset.setType("default"); + + Mockito.reset(tbClusterService, auditLogService); + + String msgError = "Asset name should be specified"; doPost("/api/asset", asset) .andExpect(status().isBadRequest()) - .andExpect(statusReason(containsString("Asset name should be specified"))); + .andExpect(statusReason(containsString(msgError))); + + testNotifyEntityEqualsOneTimeError(asset, savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); } @Test @@ -245,17 +321,29 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { customer.setTitle("My customer"); Customer savedCustomer = doPost("/api/customer", customer, Customer.class); + Mockito.reset(tbClusterService, auditLogService); + Asset assignedAsset = doPost("/api/customer/" + savedCustomer.getId().getId().toString() + "/asset/" + savedAsset.getId().getId().toString(), Asset.class); Assert.assertEquals(savedCustomer.getId(), assignedAsset.getCustomerId()); + testNotifyEntityAllOneTime(assignedAsset, assignedAsset.getId(), assignedAsset.getId(), + savedTenant.getId(), savedCustomer.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.ASSIGNED_TO_CUSTOMER, savedCustomer.getId().toString(), savedCustomer.getTitle()); + Asset foundAsset = doGet("/api/asset/" + savedAsset.getId().getId().toString(), Asset.class); Assert.assertEquals(savedCustomer.getId(), foundAsset.getCustomerId()); + Mockito.reset(tbClusterService, auditLogService); + Asset unassignedAsset = doDelete("/api/customer/asset/" + savedAsset.getId().getId().toString(), Asset.class); Assert.assertEquals(ModelConstants.NULL_UUID, unassignedAsset.getCustomerId().getId()); + testNotifyEntityAllOneTime(savedAsset, savedAsset.getId(), savedAsset.getId(), + savedTenant.getId(), savedCustomer.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.UNASSIGNED_FROM_CUSTOMER, savedCustomer.getId().toString(), savedCustomer.getTitle()); + foundAsset = doGet("/api/asset/" + savedAsset.getId().getId().toString(), Asset.class); Assert.assertEquals(ModelConstants.NULL_UUID, foundAsset.getCustomerId().getId()); } @@ -267,9 +355,14 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { asset.setType("default"); Asset savedAsset = doPost("/api/asset", asset, Asset.class); + Mockito.reset(tbClusterService, auditLogService); + + String msgError = "Asset name should be specified"; doPost("/api/customer/" + Uuids.timeBased().toString() + "/asset/" + savedAsset.getId().getId().toString()) .andExpect(status().isNotFound()); + + testNotifyEntityNever(asset.getId(), asset); } @Test @@ -301,10 +394,14 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { asset.setType("default"); Asset savedAsset = doPost("/api/asset", asset, Asset.class); + Mockito.reset(tbClusterService, auditLogService); + doPost("/api/customer/" + savedCustomer.getId().getId().toString() + "/asset/" + savedAsset.getId().getId().toString()) .andExpect(status().isForbidden()); + testNotifyEntityNever(savedAsset.getId(), savedAsset); + loginSysAdmin(); doDelete("/api/tenant/" + savedTenant2.getId().getId().toString()) @@ -314,7 +411,11 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { @Test public void testFindTenantAssets() throws Exception { List assets = new ArrayList<>(); - for (int i = 0; i < 178; i++) { + int cntEntity = 178; + + Mockito.reset(tbClusterService, auditLogService); + + for (int i = 0; i < cntEntity; i++) { Asset asset = new Asset(); asset.setName("Asset" + i); asset.setType("default"); @@ -332,6 +433,10 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { } } while (pageData.hasNext()); + testNotifyManyEntityManyTimeMsgToEdgeServiceNever(new Asset(), new Asset(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.ADDED, cntEntity); + loadedAssets.removeIf(asset -> asset.getType().equals(DefaultRuleEngineStatisticsService.TB_SERVICE_QUEUE)); Collections.sort(assets, idComparator); @@ -731,17 +836,31 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { asset.setType("default"); Asset savedAsset = doPost("/api/asset", asset, Asset.class); + Mockito.reset(tbClusterService, auditLogService); + doPost("/api/edge/" + savedEdge.getId().getId().toString() + "/asset/" + savedAsset.getId().getId().toString(), Asset.class); + testNotifyEntityAllOneTime(savedAsset, savedAsset.getId(), savedAsset.getId(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ASSIGNED_TO_EDGE, + savedAsset.getId().getId().toString(), savedEdge.getId().getId().toString(), edge.getName()); + + PageData pageData = doGetTypedWithPageLink("/api/edge/" + savedEdge.getId().getId().toString() + "/assets?", new TypeReference>() {}, new PageLink(100)); Assert.assertEquals(1, pageData.getData().size()); + Mockito.reset(tbClusterService, auditLogService); + doDelete("/api/edge/" + savedEdge.getId().getId().toString() + "/asset/" + savedAsset.getId().getId().toString(), Asset.class); + + testNotifyEntityAllOneTime(savedAsset, savedAsset.getId(), savedAsset.getId(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.UNASSIGNED_FROM_EDGE, savedAsset.getId().getId().toString(), savedEdge.getId().getId().toString(), savedEdge.getName()); + pageData = doGetTypedWithPageLink("/api/edge/" + savedEdge.getId().getId().toString() + "/assets?", new TypeReference>() {}, new PageLink(100)); diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseCustomerControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseCustomerControllerTest.java index 45c8d72529..1e2349985f 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseCustomerControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseCustomerControllerTest.java @@ -122,7 +122,6 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest @Test public void testSaveCustomerWithViolationOfValidation() throws Exception { Customer customer = new Customer(); - String validationError = "Validation error: "; customer.setTitle(RandomStringUtils.randomAlphabetic(300)); Mockito.reset(tbClusterService, auditLogService); @@ -130,49 +129,54 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest String msgError = "length of title must be equal or less than 255"; doPost("/api/customer", customer).andExpect(statusReason(containsString(msgError))); - testNotifyEntityError(customer, savedTenant.getId(), - tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(validationError + msgError)); + customer.setTenantId(savedTenant.getId()); + testNotifyEntityEqualsOneTimeError(customer,savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); + Mockito.reset(tbClusterService, auditLogService); customer.setTitle("Normal title"); customer.setCity(RandomStringUtils.randomAlphabetic(300)); msgError = "length of city must be equal or less than 255"; doPost("/api/customer", customer).andExpect(statusReason(containsString(msgError))); - testNotifyEntityError(customer, savedTenant.getId(), - tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(validationError + msgError)); + testNotifyEntityEqualsOneTimeError(customer,savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); + Mockito.reset(tbClusterService, auditLogService); customer.setCity("Normal city"); customer.setCountry(RandomStringUtils.randomAlphabetic(300)); msgError = "length of country must be equal or less than 255"; doPost("/api/customer", customer).andExpect(statusReason(containsString(msgError))); - testNotifyEntityError(customer, savedTenant.getId(), - tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(validationError + msgError)); + testNotifyEntityEqualsOneTimeError(customer,savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); + Mockito.reset(tbClusterService, auditLogService); customer.setCountry("Ukraine"); customer.setPhone(RandomStringUtils.randomAlphabetic(300)); msgError = "length of phone must be equal or less than 255"; doPost("/api/customer", customer).andExpect(statusReason(containsString(msgError))); - testNotifyEntityError(customer, savedTenant.getId(), - tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(validationError + msgError)); + testNotifyEntityEqualsOneTimeError(customer,savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); + Mockito.reset(tbClusterService, auditLogService); customer.setPhone("+3892555554512"); customer.setState(RandomStringUtils.randomAlphabetic(300)); msgError = "length of state must be equal or less than 255"; doPost("/api/customer", customer).andExpect(statusReason(containsString(msgError))); - testNotifyEntityError(customer, savedTenant.getId(), - tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(validationError + msgError)); + testNotifyEntityEqualsOneTimeError(customer,savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); + Mockito.reset(tbClusterService, auditLogService); customer.setState("Normal state"); customer.setZip(RandomStringUtils.randomAlphabetic(300)); msgError = "length of zip or postal code must be equal or less than 255"; doPost("/api/customer", customer).andExpect(statusReason(containsString(msgError))); - testNotifyEntityError(customer, savedTenant.getId(), - tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(validationError + msgError)); - + testNotifyEntityEqualsOneTimeError(customer,savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); } @Test @@ -211,8 +215,6 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest Assert.assertNotNull(foundCustomer); Assert.assertEquals(savedCustomer, foundCustomer); - Mockito.reset(tbClusterService, auditLogService); - doDelete("/api/customer/" + savedCustomer.getId().getId().toString()) .andExpect(status().isOk()); } @@ -247,8 +249,8 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest .andExpect(status().isBadRequest()) .andExpect(statusReason(containsString(msgError))); - testNotifyEntityError(customer, savedTenant.getId(), - tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError + "!")); + testNotifyEntityEqualsOneTimeError(customer,savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); } @Test @@ -264,16 +266,20 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest .andExpect(status().isBadRequest()) .andExpect(statusReason(containsString(msgError))); - testNotifyEntityError(customer, savedTenant.getId(), - tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError + "!")); + testNotifyEntityEqualsOneTimeError(customer, savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); } @Test public void testFindCustomers() throws Exception { TenantId tenantId = savedTenant.getId(); - List> futures = new ArrayList<>(135); - for (int i = 0; i < 135; i++) { + int cntEntity = 135; + + Mockito.reset(tbClusterService, auditLogService); + + List> futures = new ArrayList<>(cntEntity); + for (int i = 0; i < cntEntity; i++) { Customer customer = new Customer(); customer.setTenantId(tenantId); customer.setTitle("Customer" + i); @@ -282,6 +288,10 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest } List customers = Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS); + testNotifyManyEntityManyTimeMsgToEdgeServiceNever(new Customer(), new Customer(), + tenantId, tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.ADDED, cntEntity); + List loadedCustomers = new ArrayList<>(135); PageLink pageLink = new PageLink(23); PageData pageData = null; @@ -370,5 +380,4 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); } - } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java index 143fdce0f3..f427fc7826 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java @@ -126,6 +126,16 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController Assert.assertEquals(savedDeviceProfile, foundDeviceProfile); } + @Test + public void whenGetDeviceProfileById_thenPermissionsAreChecked() throws Exception { + DeviceProfile deviceProfile = createDeviceProfile("Device profile 1", null); + deviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); + + loginDifferentTenant(); + doGet("/api/deviceProfile/" + deviceProfile.getId()) + .andExpect(status().isForbidden()); + } + @Test public void testFindDeviceProfileInfoById() throws Exception { DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); @@ -137,6 +147,16 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController Assert.assertEquals(savedDeviceProfile.getType(), foundDeviceProfileInfo.getType()); } + @Test + public void whenGetDeviceProfileInfoById_thenPermissionsAreChecked() throws Exception { + DeviceProfile deviceProfile = createDeviceProfile("Device profile 1", null); + deviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); + + loginDifferentTenant(); + doGet("/api/deviceProfileInfo/" + deviceProfile.getId()) + .andExpect(status().isForbidden()); + } + @Test public void testFindDefaultDeviceProfileInfo() throws Exception { DeviceProfileInfo foundDefaultDeviceProfileInfo = doGet("/api/deviceProfileInfo/default", DeviceProfileInfo.class); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileInfo.java index a05950d11d..f99d5181f7 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileInfo.java @@ -63,4 +63,9 @@ public class DeviceProfileInfo extends EntityInfo { this.transportType = transportType; } + public DeviceProfileInfo(DeviceProfile profile) { + this(profile.getId(), profile.getName(), profile.getImage(), profile.getDefaultDashboardId(), + profile.getType(), profile.getTransportType()); + } + } diff --git a/msa/js-executor/queue/kafkaTemplate.js b/msa/js-executor/queue/kafkaTemplate.js index 656779869d..1d1ad75318 100644 --- a/msa/js-executor/queue/kafkaTemplate.js +++ b/msa/js-executor/queue/kafkaTemplate.js @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -const {logLevel, Kafka, CompressionTypes} = require('kafkajs'); +const {logLevel, Kafka, CompressionTypes, Partitioners} = require('kafkajs'); const config = require('config'), JsInvokeMessageProcessor = require('../api/jsInvokeMessageProcessor'), @@ -159,7 +159,7 @@ async function sendMessagesAsBatch(isImmediately) { } consumer = kafkaClient.consumer({groupId: 'js-executor-group'}); - producer = kafkaClient.producer(); + producer = kafkaClient.producer({ createPartitioner: Partitioners.DefaultPartitioner }); /* //producer event instrumentation to debug diff --git a/msa/tb/docker-cassandra/Dockerfile b/msa/tb/docker-cassandra/Dockerfile index 9d16d46ef5..f4d00a8d37 100644 --- a/msa/tb/docker-cassandra/Dockerfile +++ b/msa/tb/docker-cassandra/Dockerfile @@ -44,15 +44,17 @@ ENV CASSANDRA_LOG=/var/log/cassandra COPY logback.xml ${pkg.name}.conf start-db.sh stop-db.sh start-tb.sh upgrade-tb.sh install-tb.sh ${pkg.name}.deb /tmp/ RUN apt-get update \ - && apt-get install -y curl nmap procps gnupg2 \ + && apt-get install -y --no-install-recommends curl nmap procps gnupg2 \ && echo "deb http://apt.postgresql.org/pub/repos/apt/ $(. /etc/os-release && echo -n $VERSION_CODENAME)-pgdg main" | tee --append /etc/apt/sources.list.d/pgdg.list > /dev/null \ && curl -L https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - \ && echo 'deb http://downloads.apache.org/cassandra/debian 40x main' | tee --append /etc/apt/sources.list.d/cassandra.list > /dev/null \ && curl -L https://downloads.apache.org/cassandra/KEYS | apt-key add - \ && apt-get update \ - && apt-get install -y cassandra cassandra-tools postgresql-12 \ + && apt-get install -y --no-install-recommends cassandra cassandra-tools postgresql-${PG_MAJOR} \ + && rm -rf /var/lib/apt/lists/* \ && update-rc.d cassandra disable \ && update-rc.d postgresql disable \ + && apt-get purge -y --auto-remove \ && sed -i.old '/ulimit/d' /etc/init.d/cassandra \ && chmod a+x /tmp/*.sh \ && mv /tmp/start-tb.sh /usr/bin \ diff --git a/msa/tb/docker-postgres/Dockerfile b/msa/tb/docker-postgres/Dockerfile index 5dfdba41bd..90616c2ff0 100644 --- a/msa/tb/docker-postgres/Dockerfile +++ b/msa/tb/docker-postgres/Dockerfile @@ -36,12 +36,14 @@ ENV PGLOG=/var/log/postgres COPY logback.xml ${pkg.name}.conf start-db.sh stop-db.sh start-tb.sh upgrade-tb.sh install-tb.sh ${pkg.name}.deb /tmp/ RUN apt-get update \ - && apt-get install -y curl gnupg2 \ + && apt-get install -y --no-install-recommends curl gnupg2 \ && echo "deb http://apt.postgresql.org/pub/repos/apt/ $(. /etc/os-release && echo -n $VERSION_CODENAME)-pgdg main" | tee --append /etc/apt/sources.list.d/pgdg.list > /dev/null \ && curl -L https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - \ && apt-get update \ - && apt-get install -y postgresql-12 \ + && apt-get install -y --no-install-recommends postgresql-${PG_MAJOR} \ + && rm -rf /var/lib/apt/lists/* \ && update-rc.d postgresql disable \ + && apt-get purge -y --auto-remove \ && chmod a+x /tmp/*.sh \ && mv /tmp/start-tb.sh /usr/bin \ && mv /tmp/upgrade-tb.sh /usr/bin \ diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java index b27caf6838..7b4f1792c4 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java @@ -21,6 +21,7 @@ import com.google.gson.JsonElement; import com.google.gson.JsonParser; import com.google.gson.JsonPrimitive; import lombok.extern.slf4j.Slf4j; +import org.thingsboard.common.util.CollectionsUtil; import org.thingsboard.common.util.DonAsynchron; import org.thingsboard.rule.engine.api.EmptyNodeConfiguration; import org.thingsboard.rule.engine.api.RuleNode; @@ -32,6 +33,7 @@ import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.common.data.objects.AttributesEntityView; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.session.SessionMsgType; @@ -140,15 +142,20 @@ public class TbCopyAttributesToEntityViewNode implements TbNode { } private boolean attributeContainsInEntityView(String scope, String attrKey, EntityView entityView) { + AttributesEntityView attributesEntityView = entityView.getKeys().getAttributes(); + List keys = null; switch (scope) { case DataConstants.CLIENT_SCOPE: - return entityView.getKeys().getAttributes().getCs().contains(attrKey); + keys = attributesEntityView.getCs(); + break; case DataConstants.SERVER_SCOPE: - return entityView.getKeys().getAttributes().getSs().contains(attrKey); + keys = attributesEntityView.getSs(); + break; case DataConstants.SHARED_SCOPE: - return entityView.getKeys().getAttributes().getSh().contains(attrKey); + keys = attributesEntityView.getSh(); + break; } - return false; + return CollectionsUtil.contains(keys, attrKey); } @Override diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java index 1d139c9b8d..f0bbdfb66b 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java @@ -197,7 +197,8 @@ class DeviceState { private void processAlarmDeleteNotification(TbContext ctx, TbMsg msg) { Alarm alarm = JacksonUtil.fromString(msg.getData(), Alarm.class); - alarmStates.values().removeIf(alarmState -> alarmState.getCurrentAlarm().getId().equals(alarm.getId())); + alarmStates.values().removeIf(alarmState -> alarmState.getCurrentAlarm() != null + && alarmState.getCurrentAlarm().getId().equals(alarm.getId())); ctx.tellSuccess(msg); } @@ -219,11 +220,15 @@ class DeviceState { } if (!keys.isEmpty()) { EntityKeyType keyType = getKeyTypeFromScope(scope); - keys.forEach(key -> latestValues.removeValue(new EntityKey(keyType, key))); + Set removedKeys = keys.stream().map(key -> new EntityKey(keyType, key)) + .peek(latestValues::removeValue) + .map(DataSnapshot::toConditionKey).collect(Collectors.toSet()); + SnapshotUpdate update = new SnapshotUpdate(AlarmConditionKeyType.ATTRIBUTE, removedKeys); + for (DeviceProfileAlarm alarm : deviceProfile.getAlarmSettings()) { AlarmState alarmState = alarmStates.computeIfAbsent(alarm.getId(), a -> new AlarmState(this.deviceProfile, deviceId, alarm, getOrInitPersistedAlarmState(alarm), dynamicPredicateValueCtx)); - stateChanged |= alarmState.process(ctx, msg, latestValues, null); + stateChanged |= alarmState.process(ctx, msg, latestValues, update); } } ctx.tellSuccess(msg); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/DeviceStateTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/DeviceStateTest.java new file mode 100644 index 0000000000..0f7795ab9d --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/DeviceStateTest.java @@ -0,0 +1,176 @@ +/** + * Copyright © 2016-2022 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.rule.engine.profile; + +import com.google.common.util.concurrent.Futures; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.rule.engine.api.RuleEngineAlarmService; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmSeverity; +import org.thingsboard.server.common.data.device.profile.AlarmCondition; +import org.thingsboard.server.common.data.device.profile.AlarmConditionFilter; +import org.thingsboard.server.common.data.device.profile.AlarmConditionFilterKey; +import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType; +import org.thingsboard.server.common.data.device.profile.AlarmRule; +import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; +import org.thingsboard.server.common.data.device.profile.DeviceProfileData; +import org.thingsboard.server.common.data.device.profile.SimpleAlarmConditionSpec; +import org.thingsboard.server.common.data.id.AlarmId; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.query.BooleanFilterPredicate; +import org.thingsboard.server.common.data.query.EntityKeyValueType; +import org.thingsboard.server.common.data.query.FilterPredicateValue; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; +import org.thingsboard.server.common.msg.session.SessionMsgType; +import org.thingsboard.server.dao.attributes.AttributesService; +import org.thingsboard.server.dao.device.DeviceService; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyCollection; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class DeviceStateTest { + + private TbContext ctx; + + @Before + public void beforeEach() { + ctx = mock(TbContext.class); + + when(ctx.getDeviceService()).thenReturn(mock(DeviceService.class)); + + AttributesService attributesService = mock(AttributesService.class); + when(attributesService.find(any(), any(), any(), anyCollection())).thenReturn(Futures.immediateFuture(Collections.emptyList())); + when(ctx.getAttributesService()).thenReturn(attributesService); + + RuleEngineAlarmService alarmService = mock(RuleEngineAlarmService.class); + when(alarmService.findLatestByOriginatorAndType(any(), any(), any())).thenReturn(Futures.immediateFuture(null)); + when(alarmService.createOrUpdateAlarm(any())).thenAnswer(invocationOnMock -> { + Alarm alarm = invocationOnMock.getArgument(0); + alarm.setId(new AlarmId(UUID.randomUUID())); + return alarm; + }); + when(ctx.getAlarmService()).thenReturn(alarmService); + + when(ctx.newMsg(any(), any(), any(), any(), any(), any())).thenAnswer(invocationOnMock -> { + String data = invocationOnMock.getArgument(invocationOnMock.getArguments().length - 1); + return TbMsg.newMsg(null, null, new TbMsgMetaData(), data); + }); + + } + + @Test + public void whenAttributeIsDeleted_thenUnneededAlarmRulesAreNotReevaluated() throws Exception { + + DeviceProfileAlarm alarmConfig = createAlarmConfigWithBoolAttrCondition("enabled", false); + DeviceId deviceId = new DeviceId(UUID.randomUUID()); + DeviceState deviceState = createDeviceState(deviceId, alarmConfig); + + TbMsg attributeUpdateMsg = TbMsg.newMsg(SessionMsgType.POST_ATTRIBUTES_REQUEST.name(), + deviceId, new TbMsgMetaData(), "{ \"enabled\": false }"); + + deviceState.process(ctx, attributeUpdateMsg); + + ArgumentCaptor resultMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx).enqueueForTellNext(resultMsgCaptor.capture(), eq("Alarm Created")); + Alarm alarm = JacksonUtil.fromString(resultMsgCaptor.getValue().getData(), Alarm.class); + + deviceState.process(ctx, TbMsg.newMsg(DataConstants.ALARM_CLEAR, deviceId, new TbMsgMetaData(), JacksonUtil.toString(alarm))); + reset(ctx); + + String deletedAttributes = "{ \"attributes\": [ \"other\" ] }"; + deviceState.process(ctx, TbMsg.newMsg(DataConstants.ATTRIBUTES_DELETED, deviceId, new TbMsgMetaData(), deletedAttributes)); + verify(ctx, never()).enqueueForTellNext(any(), anyString()); + } + + @Test + public void whenDeletingClearedAlarm_thenNoError() throws Exception { + DeviceProfileAlarm alarmConfig = createAlarmConfigWithBoolAttrCondition("enabled", false); + DeviceId deviceId = new DeviceId(UUID.randomUUID()); + DeviceState deviceState = createDeviceState(deviceId, alarmConfig); + + TbMsg attributeUpdateMsg = TbMsg.newMsg(SessionMsgType.POST_ATTRIBUTES_REQUEST.name(), + deviceId, new TbMsgMetaData(), "{ \"enabled\": false }"); + + deviceState.process(ctx, attributeUpdateMsg); + ArgumentCaptor resultMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx).enqueueForTellNext(resultMsgCaptor.capture(), eq("Alarm Created")); + Alarm alarm = JacksonUtil.fromString(resultMsgCaptor.getValue().getData(), Alarm.class); + + deviceState.process(ctx, TbMsg.newMsg(DataConstants.ALARM_CLEAR, deviceId, new TbMsgMetaData(), JacksonUtil.toString(alarm))); + + TbMsg alarmDeleteNotification = TbMsg.newMsg(DataConstants.ALARM_DELETE, deviceId, new TbMsgMetaData(), JacksonUtil.toString(alarm)); + assertDoesNotThrow(() -> { + deviceState.process(ctx, alarmDeleteNotification); + }); + } + + + private DeviceState createDeviceState(DeviceId deviceId, DeviceProfileAlarm... alarmConfigs) { + DeviceProfile deviceProfile = new DeviceProfile(); + DeviceProfileData profileData = new DeviceProfileData(); + profileData.setAlarms(List.of(alarmConfigs)); + deviceProfile.setProfileData(profileData); + + ProfileState profileState = new ProfileState(deviceProfile); + return new DeviceState(ctx, new TbDeviceProfileNodeConfiguration(), + deviceId, profileState, null); + } + + private DeviceProfileAlarm createAlarmConfigWithBoolAttrCondition(String key, boolean value) { + + AlarmConditionFilter condition = new AlarmConditionFilter(); + condition.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.ATTRIBUTE, key)); + condition.setValueType(EntityKeyValueType.BOOLEAN); + BooleanFilterPredicate predicate = new BooleanFilterPredicate(); + predicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL); + predicate.setValue(new FilterPredicateValue<>(value)); + condition.setPredicate(predicate); + + DeviceProfileAlarm alarmConfig = new DeviceProfileAlarm(); + alarmConfig.setId("MyAlarmID"); + alarmConfig.setAlarmType("MyAlarm"); + AlarmRule alarmRule = new AlarmRule(); + AlarmCondition alarmCondition = new AlarmCondition(); + alarmCondition.setSpec(new SimpleAlarmConditionSpec()); + alarmCondition.setCondition(List.of(condition)); + alarmRule.setCondition(alarmCondition); + alarmConfig.setCreateRules(new TreeMap<>(Map.of(AlarmSeverity.CRITICAL, alarmRule))); + + return alarmConfig; + } + +} diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java index 8acd638ca4..315550a287 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java @@ -49,7 +49,6 @@ import org.thingsboard.server.common.data.device.profile.RepeatingAlarmCondition import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; -import org.thingsboard.server.common.data.id.QueueId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.query.BooleanFilterPredicate; diff --git a/transport/coap/src/main/resources/tb-coap-transport.yml b/transport/coap/src/main/resources/tb-coap-transport.yml index ef25058751..2d408e0008 100644 --- a/transport/coap/src/main/resources/tb-coap-transport.yml +++ b/transport/coap/src/main/resources/tb-coap-transport.yml @@ -151,6 +151,7 @@ queue: bootstrap.servers: "${TB_KAFKA_SERVERS:localhost:9092}" acks: "${TB_KAFKA_ACKS:all}" retries: "${TB_KAFKA_RETRIES:1}" + compression.type: "${TB_KAFKA_COMPRESSION_TYPE:none}" # none or gzip batch.size: "${TB_KAFKA_BATCH_SIZE:16384}" linger.ms: "${TB_KAFKA_LINGER_MS:1}" max.request.size: "${TB_KAFKA_MAX_REQUEST_SIZE:1048576}" diff --git a/transport/http/src/main/resources/tb-http-transport.yml b/transport/http/src/main/resources/tb-http-transport.yml index 1478f88f4d..8e8cbc0641 100644 --- a/transport/http/src/main/resources/tb-http-transport.yml +++ b/transport/http/src/main/resources/tb-http-transport.yml @@ -138,6 +138,7 @@ queue: bootstrap.servers: "${TB_KAFKA_SERVERS:localhost:9092}" acks: "${TB_KAFKA_ACKS:all}" retries: "${TB_KAFKA_RETRIES:1}" + compression.type: "${TB_KAFKA_COMPRESSION_TYPE:none}" # none or gzip batch.size: "${TB_KAFKA_BATCH_SIZE:16384}" linger.ms: "${TB_KAFKA_LINGER_MS:1}" max.request.size: "${TB_KAFKA_MAX_REQUEST_SIZE:1048576}" diff --git a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml index 387c0f95ed..fb273a6118 100644 --- a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml +++ b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml @@ -216,6 +216,7 @@ queue: bootstrap.servers: "${TB_KAFKA_SERVERS:localhost:9092}" acks: "${TB_KAFKA_ACKS:all}" retries: "${TB_KAFKA_RETRIES:1}" + compression.type: "${TB_KAFKA_COMPRESSION_TYPE:none}" # none or gzip batch.size: "${TB_KAFKA_BATCH_SIZE:16384}" linger.ms: "${TB_KAFKA_LINGER_MS:1}" max.request.size: "${TB_KAFKA_MAX_REQUEST_SIZE:1048576}" diff --git a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml index 14339f5056..3514449861 100644 --- a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml +++ b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml @@ -168,6 +168,7 @@ queue: bootstrap.servers: "${TB_KAFKA_SERVERS:localhost:9092}" acks: "${TB_KAFKA_ACKS:all}" retries: "${TB_KAFKA_RETRIES:1}" + compression.type: "${TB_KAFKA_COMPRESSION_TYPE:none}" # none or gzip batch.size: "${TB_KAFKA_BATCH_SIZE:16384}" linger.ms: "${TB_KAFKA_LINGER_MS:1}" max.request.size: "${TB_KAFKA_MAX_REQUEST_SIZE:1048576}" diff --git a/transport/snmp/src/main/resources/tb-snmp-transport.yml b/transport/snmp/src/main/resources/tb-snmp-transport.yml index 82060defb3..5f95ad382d 100644 --- a/transport/snmp/src/main/resources/tb-snmp-transport.yml +++ b/transport/snmp/src/main/resources/tb-snmp-transport.yml @@ -114,6 +114,7 @@ queue: bootstrap.servers: "${TB_KAFKA_SERVERS:localhost:9092}" acks: "${TB_KAFKA_ACKS:all}" retries: "${TB_KAFKA_RETRIES:1}" + compression.type: "${TB_KAFKA_COMPRESSION_TYPE:none}" # none or gzip batch.size: "${TB_KAFKA_BATCH_SIZE:16384}" linger.ms: "${TB_KAFKA_LINGER_MS:1}" max.request.size: "${TB_KAFKA_MAX_REQUEST_SIZE:1048576}" diff --git a/ui-ngx/src/app/core/api/widget-subscription.ts b/ui-ngx/src/app/core/api/widget-subscription.ts index 37cc0c5584..41aeb5dd14 100644 --- a/ui-ngx/src/app/core/api/widget-subscription.ts +++ b/ui-ngx/src/app/core/api/widget-subscription.ts @@ -285,7 +285,8 @@ export class WidgetSubscription implements IWidgetSubscription { (this.legendConfig.showMin === true || this.legendConfig.showMax === true || this.legendConfig.showAvg === true || - this.legendConfig.showTotal === true); + this.legendConfig.showTotal === true || + this.legendConfig.showLatest === true); this.initDataSubscription().subscribe(() => { subscriptionSubject.next(this); subscriptionSubject.complete(); @@ -1295,6 +1296,7 @@ export class WidgetSubscription implements IWidgetSubscription { max: null, avg: null, total: null, + latest: null, hidden: false }; this.legendData.data.push(legendKeyData); @@ -1526,6 +1528,9 @@ export class WidgetSubscription implements IWidgetSubscription { if (this.legendConfig.showTotal) { legendKeyData.total = this.ctx.widgetUtils.formatValue(calculateTotal(data), decimals, units); } + if (this.legendConfig.showLatest) { + legendKeyData.latest = this.ctx.widgetUtils.formatValue(calculateLatest(data), decimals, units); + } this.callbacks.legendDataUpdated(this, detectChanges !== false); } @@ -1598,3 +1603,11 @@ function calculateTotal(data: DataSet): number { return null; } } + +function calculateLatest(data: DataSet): number { + if (data.length > 0) { + return Number(data[data.length - 1][1]); + } else { + return null; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/legend-config.component.html b/ui-ngx/src/app/modules/home/components/widget/legend-config.component.html index bf7a27d4af..6e08046a22 100644 --- a/ui-ngx/src/app/modules/home/components/widget/legend-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/legend-config.component.html @@ -50,6 +50,9 @@ {{ 'legend.show-total' | translate }} + + {{ 'legend.show-latest' | translate }} + {{ 'legend.sort-legend' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/legend-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/legend-config.component.ts index 588e1237e7..16e32bc866 100644 --- a/ui-ngx/src/app/modules/home/components/widget/legend-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/legend-config.component.ts @@ -66,7 +66,8 @@ export class LegendConfigComponent implements OnInit, OnDestroy, ControlValueAcc showMin: [null, []], showMax: [null, []], showAvg: [null, []], - showTotal: [null, []] + showTotal: [null, []], + showLatest: [null, []] }); this.legendSettingsFormDirectionChanges$ = this.legendConfigForm.get('direction').valueChanges .subscribe((direction: LegendDirection) => { @@ -124,7 +125,8 @@ export class LegendConfigComponent implements OnInit, OnDestroy, ControlValueAcc showMin: isDefined(legendConfig.showMin) ? legendConfig.showMin : false, showMax: isDefined(legendConfig.showMax) ? legendConfig.showMax : false, showAvg: isDefined(legendConfig.showAvg) ? legendConfig.showAvg : false, - showTotal: isDefined(legendConfig.showTotal) ? legendConfig.showTotal : false + showTotal: isDefined(legendConfig.showTotal) ? legendConfig.showTotal : false, + showLatest: isDefined(legendConfig.showLatest) ? legendConfig.showLatest : false }, {emitEvent: false}); } this.onDirectionChanged(legendConfig.direction); diff --git a/ui-ngx/src/app/modules/home/components/widget/legend.component.html b/ui-ngx/src/app/modules/home/components/widget/legend.component.html index 0b02d3389c..e92c5c4baf 100644 --- a/ui-ngx/src/app/modules/home/components/widget/legend.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/legend.component.html @@ -23,6 +23,7 @@ {{ 'legend.max' | translate }} {{ 'legend.avg' | translate }} {{ 'legend.total' | translate }} + {{ 'legend.latest' | translate }} @@ -38,6 +39,7 @@ {{ legendData.data[legendKey.dataIndex].max }} {{ legendData.data[legendKey.dataIndex].avg }} {{ legendData.data[legendKey.dataIndex].total }} + {{ legendData.data[legendKey.dataIndex].latest }} @@ -75,5 +77,11 @@ {{ legendData.data[legendKey.dataIndex].total }} + + {{ 'legend.latest' | translate }} + + {{ legendData.data[legendKey.dataIndex].latest }} + + diff --git a/ui-ngx/src/app/modules/home/components/widget/legend.component.ts b/ui-ngx/src/app/modules/home/components/widget/legend.component.ts index 0074280732..4548621517 100644 --- a/ui-ngx/src/app/modules/home/components/widget/legend.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/legend.component.ts @@ -43,7 +43,8 @@ export class LegendComponent implements OnInit { this.displayHeader = this.legendConfig.showMin === true || this.legendConfig.showMax === true || this.legendConfig.showAvg === true || - this.legendConfig.showTotal === true; + this.legendConfig.showTotal === true || + this.legendConfig.showLatest === true; this.isHorizontal = this.legendConfig.position === LegendPosition.bottom || this.legendConfig.position === LegendPosition.top; diff --git a/ui-ngx/src/app/shared/models/widget.models.ts b/ui-ngx/src/app/shared/models/widget.models.ts index 12167a2f18..35b3b0fc21 100644 --- a/ui-ngx/src/app/shared/models/widget.models.ts +++ b/ui-ngx/src/app/shared/models/widget.models.ts @@ -243,6 +243,7 @@ export interface LegendConfig { showMax: boolean; showAvg: boolean; showTotal: boolean; + showLatest: boolean; } export function defaultLegendConfig(wType: widgetType): LegendConfig { @@ -253,7 +254,8 @@ export function defaultLegendConfig(wType: widgetType): LegendConfig { showMin: false, showMax: false, showAvg: wType === widgetType.timeseries, - showTotal: false + showTotal: false, + showLatest: false }; } @@ -362,6 +364,7 @@ export interface LegendKeyData { max: string; avg: string; total: string; + latest: string; hidden: boolean; } diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 30c4d78059..4567b6ddf9 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2501,11 +2501,13 @@ "show-min": "Show min value", "show-avg": "Show average value", "show-total": "Show total value", + "show-latest": "Show latest value", "settings": "Legend settings", "min": "min", "max": "max", "avg": "avg", "total": "total", + "latest": "latest", "comparison-time-ago": { "previousInterval": "(previous interval)", "customInterval": "(custom interval)",