Browse Source

Merge remote-tracking branch 'upstream/develop/3.4' into bug/fix-active-flag-on-cloud

pull/6684/head
Volodymyr Babak 4 years ago
parent
commit
5a51028577
  1. 2
      application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java
  2. 4
      application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java
  3. 20
      application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java
  4. 3
      application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java
  5. 9
      application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java
  6. 259
      application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java
  7. 2
      application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
  8. 133
      application/src/test/java/org/thingsboard/server/controller/BaseAssetControllerTest.java
  9. 55
      application/src/test/java/org/thingsboard/server/controller/BaseCustomerControllerTest.java
  10. 20
      application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java
  11. 5
      common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileInfo.java
  12. 4
      msa/js-executor/queue/kafkaTemplate.js
  13. 6
      msa/tb/docker-cassandra/Dockerfile
  14. 6
      msa/tb/docker-postgres/Dockerfile
  15. 15
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java
  16. 11
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java
  17. 176
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/DeviceStateTest.java
  18. 1
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java
  19. 1
      transport/coap/src/main/resources/tb-coap-transport.yml
  20. 1
      transport/http/src/main/resources/tb-http-transport.yml
  21. 1
      transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml
  22. 1
      transport/mqtt/src/main/resources/tb-mqtt-transport.yml
  23. 1
      transport/snmp/src/main/resources/tb-snmp-transport.yml
  24. 15
      ui-ngx/src/app/core/api/widget-subscription.ts
  25. 3
      ui-ngx/src/app/modules/home/components/widget/legend-config.component.html
  26. 6
      ui-ngx/src/app/modules/home/components/widget/legend-config.component.ts
  27. 8
      ui-ngx/src/app/modules/home/components/widget/legend.component.html
  28. 3
      ui-ngx/src/app/modules/home/components/widget/legend.component.ts
  29. 5
      ui-ngx/src/app/shared/models/widget.models.ts
  30. 2
      ui-ngx/src/assets/locale/locale.constant-en_US.json

2
application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java

@ -108,7 +108,7 @@ public class DeviceProfileController extends BaseController {
checkParameter(DEVICE_PROFILE_ID, strDeviceProfileId); checkParameter(DEVICE_PROFILE_ID, strDeviceProfileId);
try { try {
DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId)); DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId));
return checkNotNull(deviceProfileService.findDeviceProfileInfoById(getTenantId(), deviceProfileId)); return new DeviceProfileInfo(checkDeviceProfileId(deviceProfileId, Operation.READ));
} catch (Exception e) { } catch (Exception e) {
throw handleException(e); throw handleException(e);
} }

4
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; return EdgeEventActionType.RELATION_ADD_OR_UPDATE;
case RELATION_DELETED: case RELATION_DELETED:
return EdgeEventActionType.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: case ASSIGNED_TO_EDGE:
return EdgeEventActionType.ASSIGNED_TO_EDGE; return EdgeEventActionType.ASSIGNED_TO_EDGE;
case UNASSIGNED_FROM_EDGE: case UNASSIGNED_FROM_EDGE:

20
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.alarm.AlarmStatus;
import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardException; 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.EdgeId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.queue.util.TbCoreComponent; 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 { public void ack(Alarm alarm, SecurityUser user) throws ThingsboardException {
try { try {
long ackTs = System.currentTimeMillis(); long ackTs = System.currentTimeMillis();
alarmService.ackAlarm(user.getTenantId(), alarm.getId(), ackTs).get(); alarmService.ackAlarm(alarm.getTenantId(), alarm.getId(), ackTs).get();
alarm.setAckTs(ackTs); alarm.setAckTs(ackTs);
alarm.setStatus(alarm.getStatus().isCleared() ? AlarmStatus.CLEARED_ACK : AlarmStatus.ACTIVE_ACK); alarm.setStatus(alarm.getStatus().isCleared() ? AlarmStatus.CLEARED_ACK : AlarmStatus.ACTIVE_ACK);
notificationEntityService.notifyCreateOrUpdateAlarm(alarm, ActionType.ALARM_ACK, user); 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 { public void clear(Alarm alarm, SecurityUser user) throws ThingsboardException {
try { try {
long clearTs = System.currentTimeMillis(); 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.setClearTs(clearTs);
alarm.setStatus(alarm.getStatus().isAck() ? AlarmStatus.CLEARED_ACK : AlarmStatus.CLEARED_UNACK); alarm.setStatus(alarm.getStatus().isAck() ? AlarmStatus.CLEARED_ACK : AlarmStatus.CLEARED_UNACK);
notificationEntityService.notifyCreateOrUpdateAlarm(alarm, ActionType.ALARM_CLEAR, user); notificationEntityService.notifyCreateOrUpdateAlarm(alarm, ActionType.ALARM_CLEAR, user);
@ -78,11 +79,20 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb
@Override @Override
public Boolean delete(Alarm alarm, SecurityUser user) throws ThingsboardException { 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 { try {
List<EdgeId> relatedEdgeIds = findRelatedEdgeIds(user.getTenantId(), alarm.getOriginator()); List<EdgeId> relatedEdgeIds = findRelatedEdgeIds(alarm.getTenantId(), alarm.getOriginator());
notificationEntityService.notifyDeleteAlarm(user.getTenantId(), alarm, alarm.getOriginator(), user.getCustomerId(), notificationEntityService.notifyDeleteAlarm(alarm.getTenantId(), alarm, alarm.getOriginator(), customerId,
relatedEdgeIds, user, JacksonUtil.OBJECT_MAPPER.writeValueAsString(alarm)); 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) { } catch (Exception e) {
throw handleException(e); throw handleException(e);
} }

3
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.alarm.Alarm;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.SecurityUser;
public interface TbAlarmService { public interface TbAlarmService {
@ -28,4 +29,6 @@ public interface TbAlarmService {
void clear(Alarm alarm, SecurityUser user) throws ThingsboardException; void clear(Alarm alarm, SecurityUser user) throws ThingsboardException;
Boolean delete(Alarm alarm, SecurityUser user) throws ThingsboardException; Boolean delete(Alarm alarm, SecurityUser user) throws ThingsboardException;
Boolean delete(Alarm alarm, CustomerId customerId) throws ThingsboardException;
} }

9
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.asset.Asset;
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.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId; 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 java.util.List;
import static org.thingsboard.server.service.entitiy.DefaultTbNotificationEntityService.edgeTypeByActionType;
@Service @Service
@TbCoreComponent @TbCoreComponent
@AllArgsConstructor @AllArgsConstructor
@ -47,7 +48,7 @@ public class DefaultTbAssetService extends AbstractTbEntityService implements Tb
try { try {
Asset savedAsset = checkNotNull(assetService.saveAsset(asset)); Asset savedAsset = checkNotNull(assetService.saveAsset(asset));
vcService.autoCommit(user, savedAsset.getId()); 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; return savedAsset;
} catch (Exception e) { } catch (Exception e) {
notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.ASSET), asset, null, actionType, user, e); notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.ASSET), asset, null, actionType, user, e);
@ -80,7 +81,7 @@ public class DefaultTbAssetService extends AbstractTbEntityService implements Tb
try { try {
Asset savedAsset = checkNotNull(assetService.assignAssetToCustomer(tenantId, assetId, customerId)); Asset savedAsset = checkNotNull(assetService.assignAssetToCustomer(tenantId, assetId, customerId));
notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, assetId, customerId, savedAsset, 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; return savedAsset;
} catch (Exception e) { } catch (Exception e) {
@ -99,7 +100,7 @@ public class DefaultTbAssetService extends AbstractTbEntityService implements Tb
CustomerId customerId = customer.getId(); CustomerId customerId = customer.getId();
notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, assetId, customerId, savedAsset, notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, assetId, customerId, savedAsset,
actionType, EdgeEventActionType.UNASSIGNED_FROM_CUSTOMER, user, actionType, edgeTypeByActionType(actionType), user,
true, customerId.toString(), customer.getName()); true, customerId.toString(), customer.getName());
return savedAsset; return savedAsset;

259
application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java

@ -16,6 +16,7 @@
package org.thingsboard.server.controller; package org.thingsboard.server.controller;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.mockito.ArgumentMatcher;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.boot.test.mock.mockito.SpyBean;
import org.thingsboard.server.cluster.TbClusterService; 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 org.thingsboard.server.dao.model.ModelConstants;
import java.util.Locale; import java.util.Locale;
import java.util.Objects;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times; 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, protected void testNotifyEntityAllOneTime(HasName entity, EntityId entityId, EntityId originatorId,
TenantId tenantId, CustomerId customerId, UserId userId, String userName, TenantId tenantId, CustomerId customerId, UserId userId, String userName,
ActionType actionType, Object... additionalInfo) { ActionType actionType, Object... additionalInfo) {
testSendNotificationMsgToEdgeServiceOneTime(entityId, tenantId, actionType); int cntTime = 1;
testLogEntityActionOneTime(entity, originatorId, tenantId, customerId, userId, userName, actionType, additionalInfo); testSendNotificationMsgToEdgeServiceTime(entityId, tenantId, actionType, cntTime);
testPushMsgToRuleEngineOneTime(originatorId, tenantId); testLogEntityAction(entity, originatorId, tenantId, customerId, userId, userName, actionType, cntTime, additionalInfo);
testPushMsgToRuleEngineTime(originatorId, tenantId, cntTime);
Mockito.reset(tbClusterService, auditLogService); Mockito.reset(tbClusterService, auditLogService);
} }
protected void testNotifyEntityDeleteOneTimeMsgToEdgeServiceNever(HasName entity, EntityId entityId, EntityId originatorId, protected void testNotifyEntityNeverMsgToEdgeServiceOneTime(HasName entity, EntityId entityId, TenantId tenantId,
TenantId tenantId, CustomerId customerId, UserId userId, String userName, ActionType actionType) {
ActionType actionType, Object... additionalInfo) { testSendNotificationMsgToEdgeServiceTime(entityId, tenantId, actionType, 1);
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);
testLogEntityActionNever(entityId, entity); testLogEntityActionNever(entityId, entity);
testPushMsgToRuleEngineNever(entityId); testPushMsgToRuleEngineNever(entityId);
Mockito.reset(tbClusterService, auditLogService); Mockito.reset(tbClusterService, auditLogService);
} }
protected void testNotifyEntityOneTimeMsgToEdgeServiceNever(HasName entity, EntityId entityId, EntityId originatorId, protected void testNotifyEntityOneTimeMsgToEdgeServiceNever(HasName entity, EntityId entityId, EntityId originatorId,
TenantId tenantId, CustomerId customerId, UserId userId, String userName, TenantId tenantId, CustomerId customerId, UserId userId,
ActionType actionType, Object... additionalInfo) { 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); testNotificationMsgToEdgeServiceNever(entityId);
testLogEntityActionOneTime(entity, originatorId, tenantId, customerId, userId, userName, actionType, additionalInfo); ArgumentMatcher<HasName> matcherEntityClassEquals = argument -> argument.getClass().equals(entity.getClass());
testPushMsgToRuleEngineOneTime(originatorId, tenantId); ArgumentMatcher<EntityId> 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); Mockito.reset(tbClusterService, auditLogService);
} }
protected void testNotifyEntityBroadcastEntityStateChangeEventOneTimeMsgToEdgeServiceNever(HasName entity, EntityId entityId, EntityId originatorId, protected void testNotifyEntityBroadcastEntityStateChangeEventOneTimeMsgToEdgeServiceNever(HasName entity, EntityId entityId, EntityId originatorId,
TenantId tenantId, CustomerId customerId, UserId userId, String userName, TenantId tenantId, CustomerId customerId, UserId userId, String userName,
ActionType actionType, Object... additionalInfo) { ActionType actionType, Object... additionalInfo) {
int cntTime = 1;
testNotificationMsgToEdgeServiceNever(entityId); testNotificationMsgToEdgeServiceNever(entityId);
testLogEntityActionOneTime(entity, originatorId, tenantId, customerId, userId, userName, actionType, additionalInfo); testLogEntityAction(entity, originatorId, tenantId, customerId, userId, userName, actionType, cntTime, additionalInfo);
testPushMsgToRuleEngineOneTime(originatorId, tenantId); testPushMsgToRuleEngineTime(originatorId, tenantId, cntTime);
testBroadcastEntityStateChangeEventOneTime(entityId, tenantId); testBroadcastEntityStateChangeEventTime(entityId, tenantId, cntTime);
Mockito.reset(tbClusterService, auditLogService); Mockito.reset(tbClusterService, auditLogService);
} }
protected void testNotifyEntityError(HasName entity, TenantId tenantId, protected void testNotifyEntityEqualsOneTimeError(HasName entity, TenantId tenantId,
UserId userId, String userName, ActionType actionType, Exception exp, UserId userId, String userName, ActionType actionType, Exception exp,
Object... additionalInfo) { Object... additionalInfo) {
CustomerId customer_NULL_UUID = (CustomerId) EntityIdFactory.getByTypeAndUuid(EntityType.CUSTOMER, ModelConstants.NULL_UUID); CustomerId customer_NULL_UUID = (CustomerId) EntityIdFactory.getByTypeAndUuid(EntityType.CUSTOMER, ModelConstants.NULL_UUID);
EntityId entity_NULL_UUID = EntityIdFactory.getByTypeAndUuid(EntityType.valueOf(entity.getClass().toString() EntityId entity_originator_NULL_UUID = createEntityId_NULL_UUID(entity);
.substring(entity.getClass().toString().lastIndexOf(".") + 1).toUpperCase(Locale.ENGLISH)), testNotificationMsgToEdgeServiceNever(entity_originator_NULL_UUID);
ModelConstants.NULL_UUID); ArgumentMatcher<HasName> matcherEntityEquals = argument -> argument.getClass().equals(entity.getClass());
testNotificationMsgToEdgeServiceNever(entity_NULL_UUID); ArgumentMatcher<Exception> matcherError = argument -> argument.getMessage().contains(exp.getMessage())
if (additionalInfo.length > 0) { & argument.getClass().equals(exp.getClass());
Mockito.verify(auditLogService, times(1)).logEntityAction(Mockito.eq(tenantId), testLogEntityActionErrorAdditionalInfo(matcherEntityEquals, entity_originator_NULL_UUID, tenantId, customer_NULL_UUID, userId,
Mockito.eq(customer_NULL_UUID), Mockito.eq(userId), Mockito.eq(userName), userName, actionType, 1, matcherError, additionalInfo);
Mockito.eq(entity_NULL_UUID), Mockito.any(entity.getClass()), Mockito.eq(actionType), testPushMsgToRuleEngineNever(entity_originator_NULL_UUID);
Mockito.argThat(argument -> }
argument.getMessage().equals(exp.getMessage())), Mockito.eq(additionalInfo));
} else { protected void testNotifyEntityIsNullOneTimeError(HasName entity, TenantId tenantId,
Mockito.verify(auditLogService, times(1)).logEntityAction(Mockito.eq(tenantId), UserId userId, String userName, ActionType actionType, Exception exp,
Mockito.eq(customer_NULL_UUID), Mockito.eq(userId), Mockito.eq(userName), Object... additionalInfo) {
Mockito.eq(entity_NULL_UUID), Mockito.any(entity.getClass()), Mockito.eq(actionType), CustomerId customer_NULL_UUID = (CustomerId) EntityIdFactory.getByTypeAndUuid(EntityType.CUSTOMER, ModelConstants.NULL_UUID);
Mockito.argThat(argument -> EntityId entity_originator_NULL_UUID = createEntityId_NULL_UUID(entity);
argument.getMessage().equals(exp.getMessage()))); testNotificationMsgToEdgeServiceNever(entity_originator_NULL_UUID);
} ArgumentMatcher<HasName> matcherEntityIsNull = Objects::isNull;
testPushMsgToRuleEngineNever(entity_NULL_UUID); ArgumentMatcher<Exception> matcherError = argument -> argument.getMessage().contains(exp.getMessage()) &
Mockito.reset(tbClusterService, auditLogService); 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) { protected void testNotifyEntityNever(EntityId entityId, HasName entity) {
entityId = entityId == null ? createEntityId_NULL_UUID(entity) : entityId;
testNotificationMsgToEdgeServiceNever(entityId); testNotificationMsgToEdgeServiceNever(entityId);
testLogEntityActionNever(entityId, entity); testLogEntityActionNever(entityId, entity);
testPushMsgToRuleEngineNever(entityId); testPushMsgToRuleEngineNever(entityId);
@ -139,36 +154,144 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest {
Mockito.any(entityId.getClass()), Mockito.any(), Mockito.any()); Mockito.any(entityId.getClass()), Mockito.any(), Mockito.any());
} }
private void testLogEntityActionOneTime(HasName entity, EntityId originatorId, TenantId tenantId, CustomerId customerId, private void testPushMsgToRuleEngineTime(EntityId originatorId, TenantId tenantId, int cntTime) {
UserId userId, String userName, ActionType actionType, Object... additionalInfo) { ArgumentMatcher<EntityId> matcherOriginatorId = cntTime == 1 ? argument -> argument.equals(originatorId) :
if (additionalInfo.length == 0) { argument -> argument.getClass().equals(originatorId.getClass());
Mockito.verify(auditLogService, times(1)).logEntityAction(Mockito.eq(tenantId), Mockito.verify(tbClusterService, times(cntTime)).pushMsgToRuleEngine(Mockito.eq(tenantId),
Mockito.eq(customerId), Mockito.eq(userId), Mockito.eq(userName), Mockito.eq(originatorId), Mockito.argThat(matcherOriginatorId), Mockito.any(TbMsg.class), Mockito.isNull());
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 testPushMsgToRuleEngineOneTime(EntityId originatorId, TenantId tenantId) { private void testSendNotificationMsgToEdgeServiceTime(EntityId entityId, TenantId tenantId, ActionType actionType, int cntTime) {
Mockito.verify(tbClusterService, times(1)).pushMsgToRuleEngine(Mockito.eq(tenantId), Mockito.verify(tbClusterService, times(cntTime)).sendNotificationMsgToEdge(Mockito.eq(tenantId),
Mockito.eq(originatorId), Mockito.any(TbMsg.class), Mockito.isNull()); Mockito.any(), Mockito.eq(entityId), Mockito.any(), 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(),
Mockito.eq(edgeTypeByActionType(actionType))); Mockito.eq(edgeTypeByActionType(actionType)));
} }
private void testBroadcastEntityStateChangeEventOneTime(EntityId entityId, TenantId tenantId) { private void testBroadcastEntityStateChangeEventTime(EntityId entityId, TenantId tenantId, int cntTime) {
Mockito.verify(tbClusterService, times(1)).broadcastEntityStateChangeEvent(Mockito.eq(tenantId), Mockito.verify(tbClusterService, times(cntTime)).broadcastEntityStateChangeEvent(Mockito.eq(tenantId),
Mockito.any(entityId.getClass()), Mockito.any(ComponentLifecycleEvent.class)); 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<HasName> matcherEntityEquals = argument -> argument.equals(entity);
ArgumentMatcher<EntityId> matcherOriginatorId = argument -> argument.equals(originatorId);
testLogEntityActionAdditionalInfo(matcherEntityEquals, matcherOriginatorId, tenantId, customerId, userId, userName,
actionType, cntTime, additionalInfo);
}
private void testLogEntityActionAdditionalInfo(ArgumentMatcher<HasName> matcherEntity, ArgumentMatcher<EntityId> 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<HasName> matcherEntity, EntityId originatorId, TenantId tenantId,
CustomerId customerId, UserId userId, String userName, ActionType actionType,
int cntTime, ArgumentMatcher<Exception> 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> T extractParameter(Class<T> clazz, int index, Object... additionalInfo) { private <T> T extractParameter(Class<T> clazz, int index, Object... additionalInfo) {
T result = null; T result = null;
if (additionalInfo != null && additionalInfo.length > index) { if (additionalInfo != null && additionalInfo.length > index) {
@ -179,4 +302,10 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest {
} }
return result; 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);
}
} }

2
application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java

@ -237,6 +237,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
loginSysAdmin(); loginSysAdmin();
doDelete("/api/tenant/" + tenantId.getId().toString()) doDelete("/api/tenant/" + tenantId.getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
deleteDifferentTenant();
verifyNoTenantsLeft(); verifyNoTenantsLeft();
@ -334,6 +335,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
loginSysAdmin(); loginSysAdmin();
doDelete("/api/tenant/" + savedDifferentTenant.getId().getId().toString()) doDelete("/api/tenant/" + savedDifferentTenant.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
savedDifferentTenant = null;
} }
} }

133
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.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.mockito.Mockito;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.Tenant; 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.asset.Asset; 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.Edge;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority; 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.dao.model.ModelConstants;
import org.thingsboard.server.service.stats.DefaultRuleEngineStatisticsService; import org.thingsboard.server.service.stats.DefaultRuleEngineStatisticsService;
@ -83,8 +86,14 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
Asset asset = new Asset(); Asset asset = new Asset();
asset.setName("My asset"); asset.setName("My asset");
asset.setType("default"); asset.setType("default");
Mockito.reset(tbClusterService, auditLogService);
Asset savedAsset = doPost("/api/asset", asset, Asset.class); 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);
Assert.assertNotNull(savedAsset.getId()); Assert.assertNotNull(savedAsset.getId());
Assert.assertTrue(savedAsset.getCreatedTime() > 0); Assert.assertTrue(savedAsset.getCreatedTime() > 0);
@ -93,9 +102,14 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
Assert.assertEquals(NULL_UUID, savedAsset.getCustomerId().getId()); Assert.assertEquals(NULL_UUID, savedAsset.getCustomerId().getId());
Assert.assertEquals(asset.getName(), savedAsset.getName()); Assert.assertEquals(asset.getName(), savedAsset.getName());
Mockito.reset(tbClusterService, auditLogService);
savedAsset.setName("My new asset"); savedAsset.setName("My new asset");
doPost("/api/asset", savedAsset, Asset.class); 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); Asset foundAsset = doGet("/api/asset/" + savedAsset.getId().getId().toString(), Asset.class);
Assert.assertEquals(foundAsset.getName(), savedAsset.getName()); Assert.assertEquals(foundAsset.getName(), savedAsset.getName());
} }
@ -105,13 +119,33 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
Asset asset = new Asset(); Asset asset = new Asset();
asset.setName(RandomStringUtils.randomAlphabetic(300)); asset.setName(RandomStringUtils.randomAlphabetic(300));
asset.setType("default"); 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.setName("Normal name");
asset.setType(RandomStringUtils.randomAlphabetic(300)); 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.setType("default");
asset.setLabel(RandomStringUtils.randomAlphabetic(300)); 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 @Test
@ -122,7 +156,13 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
Asset savedAsset = doPost("/api/asset", asset, Asset.class); Asset savedAsset = doPost("/api/asset", asset, Asset.class);
loginDifferentTenant(); loginDifferentTenant();
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/asset", savedAsset, Asset.class, status().isForbidden()); doPost("/api/asset", savedAsset, Asset.class, status().isForbidden());
testNotifyEntityNever(savedAsset.getId(), savedAsset);
deleteDifferentTenant(); deleteDifferentTenant();
} }
@ -140,12 +180,21 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
@Test @Test
public void testFindAssetTypesByTenantId() throws Exception { public void testFindAssetTypesByTenantId() throws Exception {
List<Asset> assets = new ArrayList<>(); List<Asset> 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 asset = new Asset();
asset.setName("My asset B" + i); asset.setName("My asset B" + i);
asset.setType("typeB"); asset.setType("typeB");
assets.add(doPost("/api/asset", asset, Asset.class)); 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++) { for (int i = 0; i < 7; i++) {
Asset asset = new Asset(); Asset asset = new Asset();
asset.setName("My asset C" + i); asset.setName("My asset C" + i);
@ -176,9 +225,15 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
asset.setType("default"); asset.setType("default");
Asset savedAsset = doPost("/api/asset", asset, Asset.class); Asset savedAsset = doPost("/api/asset", asset, Asset.class);
Mockito.reset(tbClusterService, auditLogService);
doDelete("/api/asset/" + savedAsset.getId().getId().toString()) doDelete("/api/asset/" + savedAsset.getId().getId().toString())
.andExpect(status().isOk()); .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()) doGet("/api/asset/" + savedAsset.getId().getId().toString())
.andExpect(status().isNotFound()); .andExpect(status().isNotFound());
} }
@ -202,9 +257,16 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
view.setType("default"); view.setType("default");
EntityView savedView = doPost("/api/entityView", view, EntityView.class); EntityView savedView = doPost("/api/entityView", view, EntityView.class);
Mockito.reset(tbClusterService, auditLogService);
doDelete("/api/asset/" + savedAsset1.getId().getId().toString()) doDelete("/api/asset/" + savedAsset1.getId().getId().toString())
.andExpect(status().isBadRequest()); .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()); savedView.setEntityId(savedAsset2.getId());
doPost("/api/entityView", savedView, EntityView.class); doPost("/api/entityView", savedView, EntityView.class);
@ -220,18 +282,32 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
public void testSaveAssetWithEmptyType() throws Exception { public void testSaveAssetWithEmptyType() throws Exception {
Asset asset = new Asset(); Asset asset = new Asset();
asset.setName("My asset"); asset.setName("My asset");
Mockito.reset(tbClusterService, auditLogService);
String msgError = "Asset type should be specified";
doPost("/api/asset", asset) doPost("/api/asset", asset)
.andExpect(status().isBadRequest()) .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 @Test
public void testSaveAssetWithEmptyName() throws Exception { public void testSaveAssetWithEmptyName() throws Exception {
Asset asset = new Asset(); Asset asset = new Asset();
asset.setType("default"); asset.setType("default");
Mockito.reset(tbClusterService, auditLogService);
String msgError = "Asset name should be specified";
doPost("/api/asset", asset) doPost("/api/asset", asset)
.andExpect(status().isBadRequest()) .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 @Test
@ -245,17 +321,29 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
customer.setTitle("My customer"); customer.setTitle("My customer");
Customer savedCustomer = doPost("/api/customer", customer, Customer.class); Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
Mockito.reset(tbClusterService, auditLogService);
Asset assignedAsset = doPost("/api/customer/" + savedCustomer.getId().getId().toString() Asset assignedAsset = doPost("/api/customer/" + savedCustomer.getId().getId().toString()
+ "/asset/" + savedAsset.getId().getId().toString(), Asset.class); + "/asset/" + savedAsset.getId().getId().toString(), Asset.class);
Assert.assertEquals(savedCustomer.getId(), assignedAsset.getCustomerId()); 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); Asset foundAsset = doGet("/api/asset/" + savedAsset.getId().getId().toString(), Asset.class);
Assert.assertEquals(savedCustomer.getId(), foundAsset.getCustomerId()); Assert.assertEquals(savedCustomer.getId(), foundAsset.getCustomerId());
Mockito.reset(tbClusterService, auditLogService);
Asset unassignedAsset = Asset unassignedAsset =
doDelete("/api/customer/asset/" + savedAsset.getId().getId().toString(), Asset.class); doDelete("/api/customer/asset/" + savedAsset.getId().getId().toString(), Asset.class);
Assert.assertEquals(ModelConstants.NULL_UUID, unassignedAsset.getCustomerId().getId()); 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); foundAsset = doGet("/api/asset/" + savedAsset.getId().getId().toString(), Asset.class);
Assert.assertEquals(ModelConstants.NULL_UUID, foundAsset.getCustomerId().getId()); Assert.assertEquals(ModelConstants.NULL_UUID, foundAsset.getCustomerId().getId());
} }
@ -267,9 +355,14 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
asset.setType("default"); asset.setType("default");
Asset savedAsset = doPost("/api/asset", asset, Asset.class); 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() doPost("/api/customer/" + Uuids.timeBased().toString()
+ "/asset/" + savedAsset.getId().getId().toString()) + "/asset/" + savedAsset.getId().getId().toString())
.andExpect(status().isNotFound()); .andExpect(status().isNotFound());
testNotifyEntityNever(asset.getId(), asset);
} }
@Test @Test
@ -301,10 +394,14 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
asset.setType("default"); asset.setType("default");
Asset savedAsset = doPost("/api/asset", asset, Asset.class); Asset savedAsset = doPost("/api/asset", asset, Asset.class);
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/customer/" + savedCustomer.getId().getId().toString() doPost("/api/customer/" + savedCustomer.getId().getId().toString()
+ "/asset/" + savedAsset.getId().getId().toString()) + "/asset/" + savedAsset.getId().getId().toString())
.andExpect(status().isForbidden()); .andExpect(status().isForbidden());
testNotifyEntityNever(savedAsset.getId(), savedAsset);
loginSysAdmin(); loginSysAdmin();
doDelete("/api/tenant/" + savedTenant2.getId().getId().toString()) doDelete("/api/tenant/" + savedTenant2.getId().getId().toString())
@ -314,7 +411,11 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
@Test @Test
public void testFindTenantAssets() throws Exception { public void testFindTenantAssets() throws Exception {
List<Asset> assets = new ArrayList<>(); List<Asset> 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 asset = new Asset();
asset.setName("Asset" + i); asset.setName("Asset" + i);
asset.setType("default"); asset.setType("default");
@ -332,6 +433,10 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
} }
} while (pageData.hasNext()); } 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)); loadedAssets.removeIf(asset -> asset.getType().equals(DefaultRuleEngineStatisticsService.TB_SERVICE_QUEUE));
Collections.sort(assets, idComparator); Collections.sort(assets, idComparator);
@ -731,17 +836,31 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
asset.setType("default"); asset.setType("default");
Asset savedAsset = doPost("/api/asset", asset, Asset.class); Asset savedAsset = doPost("/api/asset", asset, Asset.class);
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/edge/" + savedEdge.getId().getId().toString() doPost("/api/edge/" + savedEdge.getId().getId().toString()
+ "/asset/" + savedAsset.getId().getId().toString(), Asset.class); + "/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<Asset> pageData = doGetTypedWithPageLink("/api/edge/" + savedEdge.getId().getId().toString() + "/assets?", PageData<Asset> pageData = doGetTypedWithPageLink("/api/edge/" + savedEdge.getId().getId().toString() + "/assets?",
new TypeReference<PageData<Asset>>() {}, new PageLink(100)); new TypeReference<PageData<Asset>>() {}, new PageLink(100));
Assert.assertEquals(1, pageData.getData().size()); Assert.assertEquals(1, pageData.getData().size());
Mockito.reset(tbClusterService, auditLogService);
doDelete("/api/edge/" + savedEdge.getId().getId().toString() doDelete("/api/edge/" + savedEdge.getId().getId().toString()
+ "/asset/" + savedAsset.getId().getId().toString(), Asset.class); + "/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?", pageData = doGetTypedWithPageLink("/api/edge/" + savedEdge.getId().getId().toString() + "/assets?",
new TypeReference<PageData<Asset>>() {}, new PageLink(100)); new TypeReference<PageData<Asset>>() {}, new PageLink(100));

55
application/src/test/java/org/thingsboard/server/controller/BaseCustomerControllerTest.java

@ -122,7 +122,6 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
@Test @Test
public void testSaveCustomerWithViolationOfValidation() throws Exception { public void testSaveCustomerWithViolationOfValidation() throws Exception {
Customer customer = new Customer(); Customer customer = new Customer();
String validationError = "Validation error: ";
customer.setTitle(RandomStringUtils.randomAlphabetic(300)); customer.setTitle(RandomStringUtils.randomAlphabetic(300));
Mockito.reset(tbClusterService, auditLogService); 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"; String msgError = "length of title must be equal or less than 255";
doPost("/api/customer", customer).andExpect(statusReason(containsString(msgError))); doPost("/api/customer", customer).andExpect(statusReason(containsString(msgError)));
testNotifyEntityError(customer, savedTenant.getId(), customer.setTenantId(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.setTitle("Normal title"); customer.setTitle("Normal title");
customer.setCity(RandomStringUtils.randomAlphabetic(300)); customer.setCity(RandomStringUtils.randomAlphabetic(300));
msgError = "length of city must be equal or less than 255"; msgError = "length of city must be equal or less than 255";
doPost("/api/customer", customer).andExpect(statusReason(containsString(msgError))); doPost("/api/customer", customer).andExpect(statusReason(containsString(msgError)));
testNotifyEntityError(customer, savedTenant.getId(), testNotifyEntityEqualsOneTimeError(customer,savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(validationError + msgError)); tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError));
Mockito.reset(tbClusterService, auditLogService);
customer.setCity("Normal city"); customer.setCity("Normal city");
customer.setCountry(RandomStringUtils.randomAlphabetic(300)); customer.setCountry(RandomStringUtils.randomAlphabetic(300));
msgError = "length of country must be equal or less than 255"; msgError = "length of country must be equal or less than 255";
doPost("/api/customer", customer).andExpect(statusReason(containsString(msgError))); doPost("/api/customer", customer).andExpect(statusReason(containsString(msgError)));
testNotifyEntityError(customer, savedTenant.getId(), testNotifyEntityEqualsOneTimeError(customer,savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(validationError + msgError)); tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError));
Mockito.reset(tbClusterService, auditLogService);
customer.setCountry("Ukraine"); customer.setCountry("Ukraine");
customer.setPhone(RandomStringUtils.randomAlphabetic(300)); customer.setPhone(RandomStringUtils.randomAlphabetic(300));
msgError = "length of phone must be equal or less than 255"; msgError = "length of phone must be equal or less than 255";
doPost("/api/customer", customer).andExpect(statusReason(containsString(msgError))); doPost("/api/customer", customer).andExpect(statusReason(containsString(msgError)));
testNotifyEntityError(customer, savedTenant.getId(), testNotifyEntityEqualsOneTimeError(customer,savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(validationError + msgError)); tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError));
Mockito.reset(tbClusterService, auditLogService);
customer.setPhone("+3892555554512"); customer.setPhone("+3892555554512");
customer.setState(RandomStringUtils.randomAlphabetic(300)); customer.setState(RandomStringUtils.randomAlphabetic(300));
msgError = "length of state must be equal or less than 255"; msgError = "length of state must be equal or less than 255";
doPost("/api/customer", customer).andExpect(statusReason(containsString(msgError))); doPost("/api/customer", customer).andExpect(statusReason(containsString(msgError)));
testNotifyEntityError(customer, savedTenant.getId(), testNotifyEntityEqualsOneTimeError(customer,savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(validationError + msgError)); tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError));
Mockito.reset(tbClusterService, auditLogService);
customer.setState("Normal state"); customer.setState("Normal state");
customer.setZip(RandomStringUtils.randomAlphabetic(300)); customer.setZip(RandomStringUtils.randomAlphabetic(300));
msgError = "length of zip or postal code must be equal or less than 255"; msgError = "length of zip or postal code must be equal or less than 255";
doPost("/api/customer", customer).andExpect(statusReason(containsString(msgError))); doPost("/api/customer", customer).andExpect(statusReason(containsString(msgError)));
testNotifyEntityError(customer, savedTenant.getId(), testNotifyEntityEqualsOneTimeError(customer,savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(validationError + msgError)); tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError));
} }
@Test @Test
@ -211,8 +215,6 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
Assert.assertNotNull(foundCustomer); Assert.assertNotNull(foundCustomer);
Assert.assertEquals(savedCustomer, foundCustomer); Assert.assertEquals(savedCustomer, foundCustomer);
Mockito.reset(tbClusterService, auditLogService);
doDelete("/api/customer/" + savedCustomer.getId().getId().toString()) doDelete("/api/customer/" + savedCustomer.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
@ -247,8 +249,8 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
.andExpect(status().isBadRequest()) .andExpect(status().isBadRequest())
.andExpect(statusReason(containsString(msgError))); .andExpect(statusReason(containsString(msgError)));
testNotifyEntityError(customer, savedTenant.getId(), testNotifyEntityEqualsOneTimeError(customer,savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError + "!")); tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError));
} }
@Test @Test
@ -264,16 +266,20 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
.andExpect(status().isBadRequest()) .andExpect(status().isBadRequest())
.andExpect(statusReason(containsString(msgError))); .andExpect(statusReason(containsString(msgError)));
testNotifyEntityError(customer, savedTenant.getId(), testNotifyEntityEqualsOneTimeError(customer, savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError + "!")); tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError));
} }
@Test @Test
public void testFindCustomers() throws Exception { public void testFindCustomers() throws Exception {
TenantId tenantId = savedTenant.getId(); TenantId tenantId = savedTenant.getId();
List<ListenableFuture<Customer>> futures = new ArrayList<>(135); int cntEntity = 135;
for (int i = 0; i < 135; i++) {
Mockito.reset(tbClusterService, auditLogService);
List<ListenableFuture<Customer>> futures = new ArrayList<>(cntEntity);
for (int i = 0; i < cntEntity; i++) {
Customer customer = new Customer(); Customer customer = new Customer();
customer.setTenantId(tenantId); customer.setTenantId(tenantId);
customer.setTitle("Customer" + i); customer.setTitle("Customer" + i);
@ -282,6 +288,10 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
} }
List<Customer> customers = Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS); List<Customer> customers = Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS);
testNotifyManyEntityManyTimeMsgToEdgeServiceNever(new Customer(), new Customer(),
tenantId, tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
ActionType.ADDED, cntEntity);
List<Customer> loadedCustomers = new ArrayList<>(135); List<Customer> loadedCustomers = new ArrayList<>(135);
PageLink pageLink = new PageLink(23); PageLink pageLink = new PageLink(23);
PageData<Customer> pageData = null; PageData<Customer> pageData = null;
@ -370,5 +380,4 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size()); Assert.assertEquals(0, pageData.getData().size());
} }
} }

20
application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java

@ -126,6 +126,16 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController
Assert.assertEquals(savedDeviceProfile, foundDeviceProfile); 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 @Test
public void testFindDeviceProfileInfoById() throws Exception { public void testFindDeviceProfileInfoById() throws Exception {
DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile");
@ -137,6 +147,16 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController
Assert.assertEquals(savedDeviceProfile.getType(), foundDeviceProfileInfo.getType()); 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 @Test
public void testFindDefaultDeviceProfileInfo() throws Exception { public void testFindDefaultDeviceProfileInfo() throws Exception {
DeviceProfileInfo foundDefaultDeviceProfileInfo = doGet("/api/deviceProfileInfo/default", DeviceProfileInfo.class); DeviceProfileInfo foundDefaultDeviceProfileInfo = doGet("/api/deviceProfileInfo/default", DeviceProfileInfo.class);

5
common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileInfo.java

@ -63,4 +63,9 @@ public class DeviceProfileInfo extends EntityInfo {
this.transportType = transportType; this.transportType = transportType;
} }
public DeviceProfileInfo(DeviceProfile profile) {
this(profile.getId(), profile.getName(), profile.getImage(), profile.getDefaultDashboardId(),
profile.getType(), profile.getTransportType());
}
} }

4
msa/js-executor/queue/kafkaTemplate.js

@ -13,7 +13,7 @@
* 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.
*/ */
const {logLevel, Kafka, CompressionTypes} = require('kafkajs'); const {logLevel, Kafka, CompressionTypes, Partitioners} = require('kafkajs');
const config = require('config'), const config = require('config'),
JsInvokeMessageProcessor = require('../api/jsInvokeMessageProcessor'), JsInvokeMessageProcessor = require('../api/jsInvokeMessageProcessor'),
@ -159,7 +159,7 @@ async function sendMessagesAsBatch(isImmediately) {
} }
consumer = kafkaClient.consumer({groupId: 'js-executor-group'}); consumer = kafkaClient.consumer({groupId: 'js-executor-group'});
producer = kafkaClient.producer(); producer = kafkaClient.producer({ createPartitioner: Partitioners.DefaultPartitioner });
/* /*
//producer event instrumentation to debug //producer event instrumentation to debug

6
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/ 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 \ 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 \ && 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 - \ && 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 \ && 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 - \ && curl -L https://downloads.apache.org/cassandra/KEYS | apt-key add - \
&& apt-get update \ && 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 cassandra disable \
&& update-rc.d postgresql disable \ && update-rc.d postgresql disable \
&& apt-get purge -y --auto-remove \
&& sed -i.old '/ulimit/d' /etc/init.d/cassandra \ && sed -i.old '/ulimit/d' /etc/init.d/cassandra \
&& chmod a+x /tmp/*.sh \ && chmod a+x /tmp/*.sh \
&& mv /tmp/start-tb.sh /usr/bin \ && mv /tmp/start-tb.sh /usr/bin \

6
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/ 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 \ 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 \ && 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 - \ && curl -L https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - \
&& apt-get update \ && 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 \ && update-rc.d postgresql disable \
&& apt-get purge -y --auto-remove \
&& chmod a+x /tmp/*.sh \ && chmod a+x /tmp/*.sh \
&& mv /tmp/start-tb.sh /usr/bin \ && mv /tmp/start-tb.sh /usr/bin \
&& mv /tmp/upgrade-tb.sh /usr/bin \ && mv /tmp/upgrade-tb.sh /usr/bin \

15
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.JsonParser;
import com.google.gson.JsonPrimitive; import com.google.gson.JsonPrimitive;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.common.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;
@ -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.DataConstants;
import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.kv.AttributeKvEntry; 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.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.session.SessionMsgType; 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) { private boolean attributeContainsInEntityView(String scope, String attrKey, EntityView entityView) {
AttributesEntityView attributesEntityView = entityView.getKeys().getAttributes();
List<String> keys = null;
switch (scope) { switch (scope) {
case DataConstants.CLIENT_SCOPE: case DataConstants.CLIENT_SCOPE:
return entityView.getKeys().getAttributes().getCs().contains(attrKey); keys = attributesEntityView.getCs();
break;
case DataConstants.SERVER_SCOPE: case DataConstants.SERVER_SCOPE:
return entityView.getKeys().getAttributes().getSs().contains(attrKey); keys = attributesEntityView.getSs();
break;
case DataConstants.SHARED_SCOPE: case DataConstants.SHARED_SCOPE:
return entityView.getKeys().getAttributes().getSh().contains(attrKey); keys = attributesEntityView.getSh();
break;
} }
return false; return CollectionsUtil.contains(keys, attrKey);
} }
@Override @Override

11
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) { private void processAlarmDeleteNotification(TbContext ctx, TbMsg msg) {
Alarm alarm = JacksonUtil.fromString(msg.getData(), Alarm.class); 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); ctx.tellSuccess(msg);
} }
@ -219,11 +220,15 @@ class DeviceState {
} }
if (!keys.isEmpty()) { if (!keys.isEmpty()) {
EntityKeyType keyType = getKeyTypeFromScope(scope); EntityKeyType keyType = getKeyTypeFromScope(scope);
keys.forEach(key -> latestValues.removeValue(new EntityKey(keyType, key))); Set<AlarmConditionFilterKey> 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()) { for (DeviceProfileAlarm alarm : deviceProfile.getAlarmSettings()) {
AlarmState alarmState = alarmStates.computeIfAbsent(alarm.getId(), AlarmState alarmState = alarmStates.computeIfAbsent(alarm.getId(),
a -> new AlarmState(this.deviceProfile, deviceId, alarm, getOrInitPersistedAlarmState(alarm), dynamicPredicateValueCtx)); 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); ctx.tellSuccess(msg);

176
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<TbMsg> 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<TbMsg> 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;
}
}

1
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.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId; 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.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.query.BooleanFilterPredicate; import org.thingsboard.server.common.data.query.BooleanFilterPredicate;

1
transport/coap/src/main/resources/tb-coap-transport.yml

@ -151,6 +151,7 @@ queue:
bootstrap.servers: "${TB_KAFKA_SERVERS:localhost:9092}" bootstrap.servers: "${TB_KAFKA_SERVERS:localhost:9092}"
acks: "${TB_KAFKA_ACKS:all}" acks: "${TB_KAFKA_ACKS:all}"
retries: "${TB_KAFKA_RETRIES:1}" retries: "${TB_KAFKA_RETRIES:1}"
compression.type: "${TB_KAFKA_COMPRESSION_TYPE:none}" # none or gzip
batch.size: "${TB_KAFKA_BATCH_SIZE:16384}" batch.size: "${TB_KAFKA_BATCH_SIZE:16384}"
linger.ms: "${TB_KAFKA_LINGER_MS:1}" linger.ms: "${TB_KAFKA_LINGER_MS:1}"
max.request.size: "${TB_KAFKA_MAX_REQUEST_SIZE:1048576}" max.request.size: "${TB_KAFKA_MAX_REQUEST_SIZE:1048576}"

1
transport/http/src/main/resources/tb-http-transport.yml

@ -138,6 +138,7 @@ queue:
bootstrap.servers: "${TB_KAFKA_SERVERS:localhost:9092}" bootstrap.servers: "${TB_KAFKA_SERVERS:localhost:9092}"
acks: "${TB_KAFKA_ACKS:all}" acks: "${TB_KAFKA_ACKS:all}"
retries: "${TB_KAFKA_RETRIES:1}" retries: "${TB_KAFKA_RETRIES:1}"
compression.type: "${TB_KAFKA_COMPRESSION_TYPE:none}" # none or gzip
batch.size: "${TB_KAFKA_BATCH_SIZE:16384}" batch.size: "${TB_KAFKA_BATCH_SIZE:16384}"
linger.ms: "${TB_KAFKA_LINGER_MS:1}" linger.ms: "${TB_KAFKA_LINGER_MS:1}"
max.request.size: "${TB_KAFKA_MAX_REQUEST_SIZE:1048576}" max.request.size: "${TB_KAFKA_MAX_REQUEST_SIZE:1048576}"

1
transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml

@ -216,6 +216,7 @@ queue:
bootstrap.servers: "${TB_KAFKA_SERVERS:localhost:9092}" bootstrap.servers: "${TB_KAFKA_SERVERS:localhost:9092}"
acks: "${TB_KAFKA_ACKS:all}" acks: "${TB_KAFKA_ACKS:all}"
retries: "${TB_KAFKA_RETRIES:1}" retries: "${TB_KAFKA_RETRIES:1}"
compression.type: "${TB_KAFKA_COMPRESSION_TYPE:none}" # none or gzip
batch.size: "${TB_KAFKA_BATCH_SIZE:16384}" batch.size: "${TB_KAFKA_BATCH_SIZE:16384}"
linger.ms: "${TB_KAFKA_LINGER_MS:1}" linger.ms: "${TB_KAFKA_LINGER_MS:1}"
max.request.size: "${TB_KAFKA_MAX_REQUEST_SIZE:1048576}" max.request.size: "${TB_KAFKA_MAX_REQUEST_SIZE:1048576}"

1
transport/mqtt/src/main/resources/tb-mqtt-transport.yml

@ -168,6 +168,7 @@ queue:
bootstrap.servers: "${TB_KAFKA_SERVERS:localhost:9092}" bootstrap.servers: "${TB_KAFKA_SERVERS:localhost:9092}"
acks: "${TB_KAFKA_ACKS:all}" acks: "${TB_KAFKA_ACKS:all}"
retries: "${TB_KAFKA_RETRIES:1}" retries: "${TB_KAFKA_RETRIES:1}"
compression.type: "${TB_KAFKA_COMPRESSION_TYPE:none}" # none or gzip
batch.size: "${TB_KAFKA_BATCH_SIZE:16384}" batch.size: "${TB_KAFKA_BATCH_SIZE:16384}"
linger.ms: "${TB_KAFKA_LINGER_MS:1}" linger.ms: "${TB_KAFKA_LINGER_MS:1}"
max.request.size: "${TB_KAFKA_MAX_REQUEST_SIZE:1048576}" max.request.size: "${TB_KAFKA_MAX_REQUEST_SIZE:1048576}"

1
transport/snmp/src/main/resources/tb-snmp-transport.yml

@ -114,6 +114,7 @@ queue:
bootstrap.servers: "${TB_KAFKA_SERVERS:localhost:9092}" bootstrap.servers: "${TB_KAFKA_SERVERS:localhost:9092}"
acks: "${TB_KAFKA_ACKS:all}" acks: "${TB_KAFKA_ACKS:all}"
retries: "${TB_KAFKA_RETRIES:1}" retries: "${TB_KAFKA_RETRIES:1}"
compression.type: "${TB_KAFKA_COMPRESSION_TYPE:none}" # none or gzip
batch.size: "${TB_KAFKA_BATCH_SIZE:16384}" batch.size: "${TB_KAFKA_BATCH_SIZE:16384}"
linger.ms: "${TB_KAFKA_LINGER_MS:1}" linger.ms: "${TB_KAFKA_LINGER_MS:1}"
max.request.size: "${TB_KAFKA_MAX_REQUEST_SIZE:1048576}" max.request.size: "${TB_KAFKA_MAX_REQUEST_SIZE:1048576}"

15
ui-ngx/src/app/core/api/widget-subscription.ts

@ -285,7 +285,8 @@ export class WidgetSubscription implements IWidgetSubscription {
(this.legendConfig.showMin === true || (this.legendConfig.showMin === true ||
this.legendConfig.showMax === true || this.legendConfig.showMax === true ||
this.legendConfig.showAvg === true || this.legendConfig.showAvg === true ||
this.legendConfig.showTotal === true); this.legendConfig.showTotal === true ||
this.legendConfig.showLatest === true);
this.initDataSubscription().subscribe(() => { this.initDataSubscription().subscribe(() => {
subscriptionSubject.next(this); subscriptionSubject.next(this);
subscriptionSubject.complete(); subscriptionSubject.complete();
@ -1295,6 +1296,7 @@ export class WidgetSubscription implements IWidgetSubscription {
max: null, max: null,
avg: null, avg: null,
total: null, total: null,
latest: null,
hidden: false hidden: false
}; };
this.legendData.data.push(legendKeyData); this.legendData.data.push(legendKeyData);
@ -1526,6 +1528,9 @@ export class WidgetSubscription implements IWidgetSubscription {
if (this.legendConfig.showTotal) { if (this.legendConfig.showTotal) {
legendKeyData.total = this.ctx.widgetUtils.formatValue(calculateTotal(data), decimals, units); 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); this.callbacks.legendDataUpdated(this, detectChanges !== false);
} }
@ -1598,3 +1603,11 @@ function calculateTotal(data: DataSet): number {
return null; return null;
} }
} }
function calculateLatest(data: DataSet): number {
if (data.length > 0) {
return Number(data[data.length - 1][1]);
} else {
return null;
}
}

3
ui-ngx/src/app/modules/home/components/widget/legend-config.component.html

@ -50,6 +50,9 @@
<mat-checkbox formControlName="showTotal" fxFlex="48"> <mat-checkbox formControlName="showTotal" fxFlex="48">
{{ 'legend.show-total' | translate }} {{ 'legend.show-total' | translate }}
</mat-checkbox> </mat-checkbox>
<mat-checkbox formControlName="showLatest" fxFlex="48">
{{ 'legend.show-latest' | translate }}
</mat-checkbox>
<mat-checkbox formControlName="sortDataKeys" fxFlex="48"> <mat-checkbox formControlName="sortDataKeys" fxFlex="48">
{{ 'legend.sort-legend' | translate }} {{ 'legend.sort-legend' | translate }}
</mat-checkbox> </mat-checkbox>

6
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, []], showMin: [null, []],
showMax: [null, []], showMax: [null, []],
showAvg: [null, []], showAvg: [null, []],
showTotal: [null, []] showTotal: [null, []],
showLatest: [null, []]
}); });
this.legendSettingsFormDirectionChanges$ = this.legendConfigForm.get('direction').valueChanges this.legendSettingsFormDirectionChanges$ = this.legendConfigForm.get('direction').valueChanges
.subscribe((direction: LegendDirection) => { .subscribe((direction: LegendDirection) => {
@ -124,7 +125,8 @@ export class LegendConfigComponent implements OnInit, OnDestroy, ControlValueAcc
showMin: isDefined(legendConfig.showMin) ? legendConfig.showMin : false, showMin: isDefined(legendConfig.showMin) ? legendConfig.showMin : false,
showMax: isDefined(legendConfig.showMax) ? legendConfig.showMax : false, showMax: isDefined(legendConfig.showMax) ? legendConfig.showMax : false,
showAvg: isDefined(legendConfig.showAvg) ? legendConfig.showAvg : 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}); }, {emitEvent: false});
} }
this.onDirectionChanged(legendConfig.direction); this.onDirectionChanged(legendConfig.direction);

8
ui-ngx/src/app/modules/home/components/widget/legend.component.html

@ -23,6 +23,7 @@
<th *ngIf="legendConfig.showMax === true">{{ 'legend.max' | translate }}</th> <th *ngIf="legendConfig.showMax === true">{{ 'legend.max' | translate }}</th>
<th *ngIf="legendConfig.showAvg === true">{{ 'legend.avg' | translate }}</th> <th *ngIf="legendConfig.showAvg === true">{{ 'legend.avg' | translate }}</th>
<th *ngIf="legendConfig.showTotal === true">{{ 'legend.total' | translate }}</th> <th *ngIf="legendConfig.showTotal === true">{{ 'legend.total' | translate }}</th>
<th *ngIf="legendConfig.showLatest === true">{{ 'legend.latest' | translate }}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -38,6 +39,7 @@
<td class="tb-legend-value" *ngIf="legendConfig.showMax === true">{{ legendData.data[legendKey.dataIndex].max }}</td> <td class="tb-legend-value" *ngIf="legendConfig.showMax === true">{{ legendData.data[legendKey.dataIndex].max }}</td>
<td class="tb-legend-value" *ngIf="legendConfig.showAvg === true">{{ legendData.data[legendKey.dataIndex].avg }}</td> <td class="tb-legend-value" *ngIf="legendConfig.showAvg === true">{{ legendData.data[legendKey.dataIndex].avg }}</td>
<td class="tb-legend-value" *ngIf="legendConfig.showTotal === true">{{ legendData.data[legendKey.dataIndex].total }}</td> <td class="tb-legend-value" *ngIf="legendConfig.showTotal === true">{{ legendData.data[legendKey.dataIndex].total }}</td>
<td class="tb-legend-value" *ngIf="legendConfig.showLatest === true">{{ legendData.data[legendKey.dataIndex].latest }}</td>
</tr> </tr>
</ng-container> </ng-container>
<tr *ngIf="isRowDirection" class="tb-legend-keys" [ngClass]="{ 'tb-row-direction': !displayHeader }"> <tr *ngIf="isRowDirection" class="tb-legend-keys" [ngClass]="{ 'tb-row-direction': !displayHeader }">
@ -75,5 +77,11 @@
{{ legendData.data[legendKey.dataIndex].total }} {{ legendData.data[legendKey.dataIndex].total }}
</td> </td>
</tr> </tr>
<tr class="tb-legend-keys" *ngIf="isRowDirection && legendConfig.showLatest === true">
<td class="tb-legend-type">{{ 'legend.latest' | translate }}</td>
<td class="tb-legend-value" *ngFor="let legendKey of legendKeys()">
{{ legendData.data[legendKey.dataIndex].latest }}
</td>
</tr>
</tbody> </tbody>
</table> </table>

3
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.displayHeader = this.legendConfig.showMin === true ||
this.legendConfig.showMax === true || this.legendConfig.showMax === true ||
this.legendConfig.showAvg === 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.isHorizontal = this.legendConfig.position === LegendPosition.bottom ||
this.legendConfig.position === LegendPosition.top; this.legendConfig.position === LegendPosition.top;

5
ui-ngx/src/app/shared/models/widget.models.ts

@ -243,6 +243,7 @@ export interface LegendConfig {
showMax: boolean; showMax: boolean;
showAvg: boolean; showAvg: boolean;
showTotal: boolean; showTotal: boolean;
showLatest: boolean;
} }
export function defaultLegendConfig(wType: widgetType): LegendConfig { export function defaultLegendConfig(wType: widgetType): LegendConfig {
@ -253,7 +254,8 @@ export function defaultLegendConfig(wType: widgetType): LegendConfig {
showMin: false, showMin: false,
showMax: false, showMax: false,
showAvg: wType === widgetType.timeseries, showAvg: wType === widgetType.timeseries,
showTotal: false showTotal: false,
showLatest: false
}; };
} }
@ -362,6 +364,7 @@ export interface LegendKeyData {
max: string; max: string;
avg: string; avg: string;
total: string; total: string;
latest: string;
hidden: boolean; hidden: boolean;
} }

2
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -2501,11 +2501,13 @@
"show-min": "Show min value", "show-min": "Show min value",
"show-avg": "Show average value", "show-avg": "Show average value",
"show-total": "Show total value", "show-total": "Show total value",
"show-latest": "Show latest value",
"settings": "Legend settings", "settings": "Legend settings",
"min": "min", "min": "min",
"max": "max", "max": "max",
"avg": "avg", "avg": "avg",
"total": "total", "total": "total",
"latest": "latest",
"comparison-time-ago": { "comparison-time-ago": {
"previousInterval": "(previous interval)", "previousInterval": "(previous interval)",
"customInterval": "(custom interval)", "customInterval": "(custom interval)",

Loading…
Cancel
Save