Browse Source

Merge remote-tracking branch 'origin/master' into develop/3.5

pull/7862/head
Andrii Shvaika 4 years ago
parent
commit
34d7a6ced8
  1. 10
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  2. 12
      application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
  3. 8
      application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java
  4. 2
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java
  5. 4
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/RuleChainMsgConstructor.java
  6. 13
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/RuleChainEdgeProcessor.java
  7. 7
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/TelemetryEdgeProcessor.java
  8. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java
  9. 2
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java
  10. 2
      application/src/main/resources/thingsboard.yml
  11. 2
      application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java
  12. 31
      application/src/test/java/org/thingsboard/server/controller/BaseRuleChainControllerTest.java
  13. 13
      application/src/test/java/org/thingsboard/server/edge/BaseDeviceEdgeTest.java
  14. 45
      application/src/test/java/org/thingsboard/server/edge/BaseRuleChainEdgeTest.java
  15. 59
      application/src/test/java/org/thingsboard/server/edge/BaseTelemetryEdgeTest.java
  16. 2
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java
  17. 1
      common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainMetaData.java
  18. 31
      common/data/src/main/java/org/thingsboard/server/common/data/util/ReflectionUtils.java
  19. 2
      common/data/src/main/java/org/thingsboard/server/common/data/validation/Length.java
  20. 2
      common/data/src/main/java/org/thingsboard/server/common/data/validation/NoXss.java
  21. 2
      dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java
  22. 37
      dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java
  23. 33
      dao/src/main/java/org/thingsboard/server/dao/service/ConstraintValidator.java
  24. 65
      dao/src/main/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidator.java
  25. 4
      dao/src/test/java/org/thingsboard/server/dao/service/BaseOtaPackageServiceTest.java
  26. 4
      dao/src/test/java/org/thingsboard/server/dao/service/NoXssValidatorTest.java
  27. 2
      msa/tb/pom.xml
  28. 2
      pom.xml
  29. 6
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java
  30. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNodeConfiguration.java
  31. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNodeConfiguration.java
  32. 1
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/AbstractTbMsgPushNode.java
  33. 33
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNodeTest.java
  34. 3
      ui-ngx/src/app/modules/home/components/import-export/import-export.models.ts
  35. 2
      ui-ngx/src/app/modules/home/components/import-export/table-columns-assignment.component.html
  36. 27
      ui-ngx/src/app/modules/home/components/import-export/table-columns-assignment.component.ts
  37. 28
      ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html
  38. 11
      ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.scss
  39. 0
      ui-ngx/src/assets/locale/locale.constant-ca_ES.json
  40. 4002
      ui-ngx/src/assets/locale/locale.constant-da_DK.json
  41. 5
      ui-ngx/src/assets/locale/locale.constant-en_US.json

10
application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java

@ -50,6 +50,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.common.msg.tools.TbRateLimits; import org.thingsboard.server.common.msg.tools.TbRateLimits;
import org.thingsboard.server.common.stats.TbApiUsageReportClient; import org.thingsboard.server.common.stats.TbApiUsageReportClient;
import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.audit.AuditLogService; import org.thingsboard.server.dao.audit.AuditLogService;
@ -58,6 +59,7 @@ import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.ClaimDevicesService; import org.thingsboard.server.dao.device.ClaimDevicesService;
import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceCredentialsService;
import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.edge.EdgeEventService; import org.thingsboard.server.dao.edge.EdgeEventService;
import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.edge.EdgeService;
@ -176,6 +178,14 @@ public class ActorSystemContext {
@Getter @Getter
private DeviceService deviceService; private DeviceService deviceService;
@Autowired
@Getter
private DeviceProfileService deviceProfileService;
@Autowired
@Getter
private AssetProfileService assetProfileService;
@Autowired @Autowired
@Getter @Getter
private DeviceCredentialsService deviceCredentialsService; private DeviceCredentialsService deviceCredentialsService;

12
application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java

@ -71,12 +71,14 @@ import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.TbMsgProcessingStackItem; import org.thingsboard.server.common.msg.TbMsgProcessingStackItem;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.cassandra.CassandraCluster; import org.thingsboard.server.dao.cassandra.CassandraCluster;
import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceCredentialsService;
import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.edge.EdgeEventService; import org.thingsboard.server.dao.edge.EdgeEventService;
import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.edge.EdgeService;
@ -559,6 +561,16 @@ class DefaultTbContext implements TbContext {
return mainCtx.getDeviceService(); return mainCtx.getDeviceService();
} }
@Override
public DeviceProfileService getDeviceProfileService() {
return mainCtx.getDeviceProfileService();
}
@Override
public AssetProfileService getAssetProfileService() {
return mainCtx.getAssetProfileService();
}
@Override @Override
public DeviceCredentialsService getDeviceCredentialsService() { public DeviceCredentialsService getDeviceCredentialsService() {
return mainCtx.getDeviceCredentialsService(); return mainCtx.getDeviceCredentialsService();

8
application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java

@ -16,6 +16,7 @@
package org.thingsboard.server.service.edge; package org.thingsboard.server.service.edge;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
@ -23,6 +24,7 @@ import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.EdgeUtils;
@ -67,6 +69,8 @@ import java.util.concurrent.Executors;
@Slf4j @Slf4j
public class DefaultEdgeNotificationService implements EdgeNotificationService { public class DefaultEdgeNotificationService implements EdgeNotificationService {
public static final String EDGE_IS_ROOT_BODY_KEY = "isRoot";
@Autowired @Autowired
private EdgeService edgeService; private EdgeService edgeService;
@ -142,7 +146,9 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService {
public Edge setEdgeRootRuleChain(TenantId tenantId, Edge edge, RuleChainId ruleChainId) throws Exception { public Edge setEdgeRootRuleChain(TenantId tenantId, Edge edge, RuleChainId ruleChainId) throws Exception {
edge.setRootRuleChainId(ruleChainId); edge.setRootRuleChainId(ruleChainId);
Edge savedEdge = edgeService.saveEdge(edge); Edge savedEdge = edgeService.saveEdge(edge);
saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.RULE_CHAIN, EdgeEventActionType.UPDATED, ruleChainId, null).get(); ObjectNode isRootBody = JacksonUtil.OBJECT_MAPPER.createObjectNode();
isRootBody.put(EDGE_IS_ROOT_BODY_KEY, Boolean.TRUE);
saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.RULE_CHAIN, EdgeEventActionType.UPDATED, ruleChainId, isRootBody).get();
return savedEdge; return savedEdge;
} }

2
application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java

@ -524,7 +524,7 @@ public final class EdgeGrpcSession implements Closeable {
case CUSTOMER: case CUSTOMER:
return ctx.getCustomerProcessor().convertCustomerEventToDownlink(edgeEvent); return ctx.getCustomerProcessor().convertCustomerEventToDownlink(edgeEvent);
case RULE_CHAIN: case RULE_CHAIN:
return ctx.getRuleChainProcessor().convertRuleChainEventToDownlink(edge, edgeEvent); return ctx.getRuleChainProcessor().convertRuleChainEventToDownlink(edgeEvent);
case RULE_CHAIN_METADATA: case RULE_CHAIN_METADATA:
return ctx.getRuleChainProcessor().convertRuleChainMetadataEventToDownlink(edgeEvent, this.edgeVersion); return ctx.getRuleChainProcessor().convertRuleChainMetadataEventToDownlink(edgeEvent, this.edgeVersion);
case ALARM: case ALARM:

4
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/RuleChainMsgConstructor.java

@ -35,13 +35,13 @@ import org.thingsboard.server.service.edge.rpc.constructor.rule.RuleChainMetadat
@TbCoreComponent @TbCoreComponent
public class RuleChainMsgConstructor { public class RuleChainMsgConstructor {
public RuleChainUpdateMsg constructRuleChainUpdatedMsg(RuleChainId edgeRootRuleChainId, UpdateMsgType msgType, RuleChain ruleChain) { public RuleChainUpdateMsg constructRuleChainUpdatedMsg(UpdateMsgType msgType, RuleChain ruleChain, boolean isRoot) {
RuleChainUpdateMsg.Builder builder = RuleChainUpdateMsg.newBuilder() RuleChainUpdateMsg.Builder builder = RuleChainUpdateMsg.newBuilder()
.setMsgType(msgType) .setMsgType(msgType)
.setIdMSB(ruleChain.getId().getId().getMostSignificantBits()) .setIdMSB(ruleChain.getId().getId().getMostSignificantBits())
.setIdLSB(ruleChain.getId().getId().getLeastSignificantBits()) .setIdLSB(ruleChain.getId().getId().getLeastSignificantBits())
.setName(ruleChain.getName()) .setName(ruleChain.getName())
.setRoot(ruleChain.getId().equals(edgeRootRuleChainId)) .setRoot(isRoot)
.setDebugMode(ruleChain.isDebugMode()) .setDebugMode(ruleChain.isDebugMode())
.setConfiguration(JacksonUtil.toString(ruleChain.getConfiguration())); .setConfiguration(JacksonUtil.toString(ruleChain.getConfiguration()));
if (ruleChain.getFirstRuleNodeId() != null) { if (ruleChain.getFirstRuleNodeId() != null) {

13
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/RuleChainEdgeProcessor.java

@ -19,7 +19,6 @@ import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
@ -33,12 +32,14 @@ import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import static org.thingsboard.server.service.edge.DefaultEdgeNotificationService.EDGE_IS_ROOT_BODY_KEY;
@Component @Component
@Slf4j @Slf4j
@TbCoreComponent @TbCoreComponent
public class RuleChainEdgeProcessor extends BaseEdgeProcessor { public class RuleChainEdgeProcessor extends BaseEdgeProcessor {
public DownlinkMsg convertRuleChainEventToDownlink(Edge edge, EdgeEvent edgeEvent) { public DownlinkMsg convertRuleChainEventToDownlink(EdgeEvent edgeEvent) {
RuleChainId ruleChainId = new RuleChainId(edgeEvent.getEntityId()); RuleChainId ruleChainId = new RuleChainId(edgeEvent.getEntityId());
DownlinkMsg downlinkMsg = null; DownlinkMsg downlinkMsg = null;
switch (edgeEvent.getAction()) { switch (edgeEvent.getAction()) {
@ -47,9 +48,15 @@ public class RuleChainEdgeProcessor extends BaseEdgeProcessor {
case ASSIGNED_TO_EDGE: case ASSIGNED_TO_EDGE:
RuleChain ruleChain = ruleChainService.findRuleChainById(edgeEvent.getTenantId(), ruleChainId); RuleChain ruleChain = ruleChainService.findRuleChainById(edgeEvent.getTenantId(), ruleChainId);
if (ruleChain != null) { if (ruleChain != null) {
boolean isRoot = false;
if (edgeEvent.getBody() != null && edgeEvent.getBody().get(EDGE_IS_ROOT_BODY_KEY) != null) {
try {
isRoot = Boolean.parseBoolean(edgeEvent.getBody().get(EDGE_IS_ROOT_BODY_KEY).asText());
} catch (Exception ignored) {}
}
UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction());
RuleChainUpdateMsg ruleChainUpdateMsg = RuleChainUpdateMsg ruleChainUpdateMsg =
ruleChainMsgConstructor.constructRuleChainUpdatedMsg(edge.getRootRuleChainId(), msgType, ruleChain); ruleChainMsgConstructor.constructRuleChainUpdatedMsg(msgType, ruleChain, isRoot);
downlinkMsg = DownlinkMsg.newBuilder() downlinkMsg = DownlinkMsg.newBuilder()
.setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .setDownlinkMsgId(EdgeUtils.nextPositiveInt())
.addRuleChainUpdateMsg(ruleChainUpdateMsg) .addRuleChainUpdateMsg(ruleChainUpdateMsg)

7
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/TelemetryEdgeProcessor.java

@ -48,7 +48,7 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKey; import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.TbMsgMetaData;
@ -71,9 +71,7 @@ import org.thingsboard.server.queue.util.TbCoreComponent;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
@Component @Component
@Slf4j @Slf4j
@ -325,6 +323,9 @@ public class TelemetryEdgeProcessor extends BaseEdgeProcessor {
case CUSTOMER: case CUSTOMER:
entityId = new CustomerId(edgeEvent.getEntityId()); entityId = new CustomerId(edgeEvent.getEntityId());
break; break;
case USER:
entityId = new UserId(edgeEvent.getEntityId());
break;
case EDGE: case EDGE:
entityId = new EdgeId(edgeEvent.getEntityId()); entityId = new EdgeId(edgeEvent.getEntityId());
break; break;

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java

@ -165,7 +165,8 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService {
Map<String, Object> entityData = new HashMap<>(); Map<String, Object> entityData = new HashMap<>();
ObjectNode attributes = JacksonUtil.OBJECT_MAPPER.createObjectNode(); ObjectNode attributes = JacksonUtil.OBJECT_MAPPER.createObjectNode();
for (AttributeKvEntry attr : ssAttributes) { for (AttributeKvEntry attr : ssAttributes) {
if (DefaultDeviceStateService.PERSISTENT_ATTRIBUTES.contains(attr.getKey())) { if (DefaultDeviceStateService.PERSISTENT_ATTRIBUTES.contains(attr.getKey())
&& !DefaultDeviceStateService.INACTIVITY_TIMEOUT.equals(attr.getKey())) {
continue; continue;
} }
if (attr.getDataType() == DataType.BOOLEAN && attr.getBooleanValue().isPresent()) { if (attr.getDataType() == DataType.BOOLEAN && attr.getBooleanValue().isPresent()) {

2
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java

@ -87,7 +87,7 @@ public abstract class AbstractBulkImportService<E extends HasId<? extends Entity
@Autowired @Autowired
private EntityActionService entityActionService; private EntityActionService entityActionService;
private static ThreadPoolExecutor executor; private ThreadPoolExecutor executor;
@PostConstruct @PostConstruct
private void initExecutor() { private void initExecutor() {

2
application/src/main/resources/thingsboard.yml

@ -148,7 +148,7 @@ ui:
# Help parameters # Help parameters
help: help:
# Base url for UI help assets # Base url for UI help assets
base-url: "${UI_HELP_BASE_URL:https://raw.githubusercontent.com/thingsboard/thingsboard-ui-help/release-3.4.2}" base-url: "${UI_HELP_BASE_URL:https://raw.githubusercontent.com/thingsboard/thingsboard-ui-help/release-3.4.3}"
database: database:
ts_max_intervals: "${DATABASE_TS_MAX_INTERVALS:700}" # Max number of DB queries generated by single API call to fetch telemetry records ts_max_intervals: "${DATABASE_TS_MAX_INTERVALS:700}" # Max number of DB queries generated by single API call to fetch telemetry records

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

@ -597,7 +597,7 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest {
} }
protected String msgErrorFieldLength(String fieldName) { protected String msgErrorFieldLength(String fieldName) {
return "length of " + fieldName + " must be equal or less than 255"; return fieldName + " length must be equal or less than 255";
} }
protected String msgErrorNoFound(String entityClassName, String assetIdStr) { protected String msgErrorNoFound(String entityClassName, String assetIdStr) {

31
application/src/test/java/org/thingsboard/server/controller/BaseRuleChainControllerTest.java

@ -26,6 +26,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Primary;
import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfiguration;
import org.thingsboard.rule.engine.action.TbCreateAlarmNode;
import org.thingsboard.rule.engine.action.TbCreateAlarmNodeConfiguration;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;
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;
@ -35,7 +37,9 @@ import org.thingsboard.server.common.data.id.RuleChainId;
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.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleNode;
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.exception.DataValidationException;
import org.thingsboard.server.dao.rule.RuleChainDao; import org.thingsboard.server.dao.rule.RuleChainDao;
@ -44,6 +48,7 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -254,9 +259,35 @@ public abstract class BaseRuleChainControllerTest extends AbstractControllerTest
testEntityDaoWithRelationsTransactionalException(ruleChainDao, savedTenant.getId(), ruleChainId, "/api/ruleChain/" + ruleChainId); testEntityDaoWithRelationsTransactionalException(ruleChainDao, savedTenant.getId(), ruleChainId, "/api/ruleChain/" + ruleChainId);
} }
@Test
public void givenRuleNodeWithInvalidConfiguration_thenReturnError() throws Exception {
RuleChain ruleChain = createRuleChain("Rule chain with invalid nodes");
RuleChainMetaData ruleChainMetaData = new RuleChainMetaData();
ruleChainMetaData.setRuleChainId(ruleChain.getId());
RuleNode createAlarmNode = new RuleNode();
createAlarmNode.setName("Create alarm");
createAlarmNode.setType(TbCreateAlarmNode.class.getName());
TbCreateAlarmNodeConfiguration invalidCreateAlarmNodeConfiguration = new TbCreateAlarmNodeConfiguration();
invalidCreateAlarmNodeConfiguration.setSeverity("<script/>");
invalidCreateAlarmNodeConfiguration.setAlarmType("<script/>");
createAlarmNode.setConfiguration(mapper.valueToTree(invalidCreateAlarmNodeConfiguration));
List<RuleNode> ruleNodes = new ArrayList<>();
ruleNodes.add(createAlarmNode);
ruleChainMetaData.setFirstNodeIndex(0);
ruleChainMetaData.setNodes(ruleNodes);
String error = getErrorMessage(doPost("/api/ruleChain/metadata", ruleChainMetaData)
.andExpect(status().isBadRequest()));
assertThat(error).contains("severity is malformed");
assertThat(error).contains("alarmType is malformed");
}
private RuleChain createRuleChain(String name) { private RuleChain createRuleChain(String name) {
RuleChain ruleChain = new RuleChain(); RuleChain ruleChain = new RuleChain();
ruleChain.setName(name); ruleChain.setName(name);
return doPost("/api/ruleChain", ruleChain, RuleChain.class); return doPost("/api/ruleChain", ruleChain, RuleChain.class);
} }
} }

13
application/src/test/java/org/thingsboard/server/edge/BaseDeviceEdgeTest.java

@ -319,6 +319,8 @@ abstract public class BaseDeviceEdgeTest extends AbstractEdgeTest {
Device device = findDeviceByName("Edge Device 1"); Device device = findDeviceByName("Edge Device 1");
sendAttributesRequestAndVerify(device, DataConstants.SERVER_SCOPE, "{\"key1\":\"value1\"}", sendAttributesRequestAndVerify(device, DataConstants.SERVER_SCOPE, "{\"key1\":\"value1\"}",
"key1", "value1"); "key1", "value1");
sendAttributesRequestAndVerify(device, DataConstants.SERVER_SCOPE, "{\"inactivityTimeout\":3600000}",
"inactivityTimeout", "3600000");
sendAttributesRequestAndVerify(device, DataConstants.SHARED_SCOPE, "{\"key2\":\"value2\"}", sendAttributesRequestAndVerify(device, DataConstants.SHARED_SCOPE, "{\"key2\":\"value2\"}",
"key2", "value2"); "key2", "value2");
} }
@ -576,7 +578,16 @@ abstract public class BaseDeviceEdgeTest extends AbstractEdgeTest {
for (TransportProtos.KeyValueProto keyValueProto : attributesUpdatedMsg.getKvList()) { for (TransportProtos.KeyValueProto keyValueProto : attributesUpdatedMsg.getKvList()) {
if (keyValueProto.getKey().equals(expectedKey)) { if (keyValueProto.getKey().equals(expectedKey)) {
Assert.assertEquals(expectedKey, keyValueProto.getKey()); Assert.assertEquals(expectedKey, keyValueProto.getKey());
Assert.assertEquals(expectedValue, keyValueProto.getStringV()); switch (keyValueProto.getType()) {
case STRING_V:
Assert.assertEquals(expectedValue, keyValueProto.getStringV());
break;
case LONG_V:
Assert.assertEquals(Long.parseLong(expectedValue), keyValueProto.getLongV());
break;
default:
Assert.fail("Unexpected data type: " + keyValueProto.getType());
}
found = true; found = true;
} }
} }

45
application/src/test/java/org/thingsboard/server/edge/BaseRuleChainEdgeTest.java

@ -18,6 +18,7 @@ package org.thingsboard.server.edge;
import com.google.protobuf.AbstractMessage; import com.google.protobuf.AbstractMessage;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleChainMetaData;
@ -165,4 +166,48 @@ abstract public class BaseRuleChainEdgeTest extends AbstractEdgeTest {
doPost("/api/ruleChain/metadata", ruleChainMetaData, RuleChainMetaData.class); doPost("/api/ruleChain/metadata", ruleChainMetaData, RuleChainMetaData.class);
} }
@Test
public void testSetRootRuleChain() throws Exception {
// create rule chain
edgeImitator.expectMessageAmount(1);
RuleChain ruleChain = new RuleChain();
ruleChain.setName("Edge New Root Rule Chain");
ruleChain.setType(RuleChainType.EDGE);
RuleChain savedRuleChain = doPost("/api/ruleChain", ruleChain, RuleChain.class);
doPost("/api/edge/" + edge.getUuidId()
+ "/ruleChain/" + savedRuleChain.getUuidId(), RuleChain.class);
Assert.assertTrue(edgeImitator.waitForMessages());
// set new rule chain as root
RuleChainId currentRootRuleChainId = edge.getRootRuleChainId();
edgeImitator.expectMessageAmount(1);
doPost("/api/edge/" + edge.getUuidId()
+ "/" + savedRuleChain.getUuidId() + "/root", Edge.class);
Assert.assertTrue(edgeImitator.waitForMessages());
Optional<RuleChainUpdateMsg> ruleChainUpdateMsgOpt = edgeImitator.findMessageByType(RuleChainUpdateMsg.class);
Assert.assertTrue(ruleChainUpdateMsgOpt.isPresent());
RuleChainUpdateMsg ruleChainUpdateMsg = ruleChainUpdateMsgOpt.get();
Assert.assertEquals(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE, ruleChainUpdateMsg.getMsgType());
Assert.assertEquals(ruleChainUpdateMsg.getIdMSB(), savedRuleChain.getUuidId().getMostSignificantBits());
Assert.assertEquals(ruleChainUpdateMsg.getIdLSB(), savedRuleChain.getUuidId().getLeastSignificantBits());
Assert.assertTrue(ruleChainUpdateMsg.getRoot());
// revert root rule chain
edgeImitator.expectMessageAmount(1);
doPost("/api/edge/" + edge.getUuidId()
+ "/" + currentRootRuleChainId.getId() + "/root", Edge.class);
Assert.assertTrue(edgeImitator.waitForMessages());
// unassign rule chain from edge
edgeImitator.expectMessageAmount(1);
doDelete("/api/edge/" + edge.getUuidId()
+ "/ruleChain/" + savedRuleChain.getUuidId(), RuleChain.class);
Assert.assertTrue(edgeImitator.waitForMessages());
// delete rule chain
edgeImitator.expectMessageAmount(1);
doDelete("/api/ruleChain/" + savedRuleChain.getUuidId())
.andExpect(status().isOk());
Assert.assertFalse(edgeImitator.waitForMessages(1));
}
} }

59
application/src/test/java/org/thingsboard/server/edge/BaseTelemetryEdgeTest.java

@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.gen.edge.v1.AttributeDeleteMsg; import org.thingsboard.server.gen.edge.v1.AttributeDeleteMsg;
import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg; import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg;
import org.thingsboard.server.gen.edge.v1.EntityDataProto; import org.thingsboard.server.gen.edge.v1.EntityDataProto;
@ -67,36 +68,11 @@ abstract public class BaseTelemetryEdgeTest extends AbstractEdgeTest {
public void testAttributes() throws Exception { public void testAttributes() throws Exception {
Device device = findDeviceByName("Edge Device 1"); Device device = findDeviceByName("Edge Device 1");
testAttributesUpdatedMsg(device); testAttributesUpdatedMsg(device.getId());
testPostAttributesMsg(device); testPostAttributesMsg(device);
testAttributesDeleteMsg(device); testAttributesDeleteMsg(device);
} }
private void testAttributesUpdatedMsg(Device device) throws Exception {
String attributesData = "{\"scope\":\"SERVER_SCOPE\",\"kv\":{\"key1\":\"value1\"}}";
JsonNode attributesEntityData = mapper.readTree(attributesData);
EdgeEvent edgeEvent1 = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.ATTRIBUTES_UPDATED, device.getId().getId(), EdgeEventType.DEVICE, attributesEntityData);
edgeImitator.expectMessageAmount(1);
edgeEventService.saveAsync(edgeEvent1).get();
clusterService.onEdgeEventUpdate(tenantId, edge.getId());
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof EntityDataProto);
EntityDataProto latestEntityDataMsg = (EntityDataProto) latestMessage;
Assert.assertEquals(device.getUuidId().getMostSignificantBits(), latestEntityDataMsg.getEntityIdMSB());
Assert.assertEquals(device.getUuidId().getLeastSignificantBits(), latestEntityDataMsg.getEntityIdLSB());
Assert.assertEquals(device.getId().getEntityType().name(), latestEntityDataMsg.getEntityType());
Assert.assertEquals("SERVER_SCOPE", latestEntityDataMsg.getPostAttributeScope());
Assert.assertTrue(latestEntityDataMsg.hasAttributesUpdatedMsg());
TransportProtos.PostAttributeMsg attributesUpdatedMsg = latestEntityDataMsg.getAttributesUpdatedMsg();
Assert.assertEquals(1, attributesUpdatedMsg.getKvCount());
TransportProtos.KeyValueProto keyValueProto = attributesUpdatedMsg.getKv(0);
Assert.assertEquals("key1", keyValueProto.getKey());
Assert.assertEquals("value1", keyValueProto.getStringV());
}
private void testPostAttributesMsg(Device device) throws Exception { private void testPostAttributesMsg(Device device) throws Exception {
String postAttributesData = "{\"scope\":\"SERVER_SCOPE\",\"kv\":{\"key2\":\"value2\"}}"; String postAttributesData = "{\"scope\":\"SERVER_SCOPE\",\"kv\":{\"key2\":\"value2\"}}";
JsonNode postAttributesEntityData = mapper.readTree(postAttributesData); JsonNode postAttributesEntityData = mapper.readTree(postAttributesData);
@ -226,4 +202,35 @@ abstract public class BaseTelemetryEdgeTest extends AbstractEdgeTest {
edgeImitator.setRandomFailuresOnTimeseriesDownlink(false); edgeImitator.setRandomFailuresOnTimeseriesDownlink(false);
} }
@Test
public void testAttributesUpdatedMsg_userEntity() throws Exception {
testAttributesUpdatedMsg(tenantAdmin.getId());
}
private void testAttributesUpdatedMsg(EntityId entityId) throws Exception {
String attributesData = "{\"scope\":\"SERVER_SCOPE\",\"kv\":{\"key1\":\"value1\"}}";
JsonNode attributesEntityData = mapper.readTree(attributesData);
EdgeEvent edgeEvent1 = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.ATTRIBUTES_UPDATED, entityId.getId(), EdgeEventType.valueOf(entityId.getEntityType().name()), attributesEntityData);
edgeImitator.expectMessageAmount(1);
edgeEventService.saveAsync(edgeEvent1).get();
clusterService.onEdgeEventUpdate(tenantId, edge.getId());
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof EntityDataProto);
EntityDataProto latestEntityDataMsg = (EntityDataProto) latestMessage;
Assert.assertEquals(entityId.getId().getMostSignificantBits(), latestEntityDataMsg.getEntityIdMSB());
Assert.assertEquals(entityId.getId().getLeastSignificantBits(), latestEntityDataMsg.getEntityIdLSB());
Assert.assertEquals(entityId.getEntityType().name(), latestEntityDataMsg.getEntityType());
Assert.assertEquals("SERVER_SCOPE", latestEntityDataMsg.getPostAttributeScope());
Assert.assertTrue(latestEntityDataMsg.hasAttributesUpdatedMsg());
TransportProtos.PostAttributeMsg attributesUpdatedMsg = latestEntityDataMsg.getAttributesUpdatedMsg();
Assert.assertEquals(1, attributesUpdatedMsg.getKvCount());
TransportProtos.KeyValueProto keyValueProto = attributesUpdatedMsg.getKv(0);
Assert.assertEquals("key1", keyValueProto.getKey());
Assert.assertEquals("value1", keyValueProto.getStringV());
}
} }

2
common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java

@ -31,6 +31,7 @@ import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoXss;
import java.util.List; import java.util.List;
@ -49,6 +50,7 @@ public class Alarm extends BaseData<AlarmId> implements HasName, HasTenantId, Ha
@ApiModelProperty(position = 4, value = "JSON object with Customer Id", accessMode = ApiModelProperty.AccessMode.READ_ONLY) @ApiModelProperty(position = 4, value = "JSON object with Customer Id", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private CustomerId customerId; private CustomerId customerId;
@NoXss
@ApiModelProperty(position = 6, required = true, value = "representing type of the Alarm", example = "High Temperature Alarm") @ApiModelProperty(position = 6, required = true, value = "representing type of the Alarm", example = "High Temperature Alarm")
@Length(fieldName = "type") @Length(fieldName = "type")
private String type; private String type;

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

@ -39,7 +39,6 @@ public class RuleChainMetaData {
@ApiModelProperty(position = 2, required = true, value = "Index of the first rule node in the 'nodes' list") @ApiModelProperty(position = 2, required = true, value = "Index of the first rule node in the 'nodes' list")
private Integer firstNodeIndex; private Integer firstNodeIndex;
@Valid
@ApiModelProperty(position = 3, required = true, value = "List of rule node JSON objects") @ApiModelProperty(position = 3, required = true, value = "List of rule node JSON objects")
private List<RuleNode> nodes; private List<RuleNode> nodes;

31
common/data/src/main/java/org/thingsboard/server/common/data/util/ReflectionUtils.java

@ -0,0 +1,31 @@
/**
* 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.server.common.data.util;
import java.lang.annotation.Annotation;
@SuppressWarnings("unchecked")
public class ReflectionUtils {
private ReflectionUtils() {}
public static <T> T getAnnotationProperty(String targetType, String annotationType, String property) throws Exception {
Class<Annotation> annotationClass = (Class<Annotation>) Class.forName(annotationType);
Annotation annotation = Class.forName(targetType).getAnnotation(annotationClass);
return (T) annotationClass.getDeclaredMethod(property).invoke(annotation);
}
}

2
common/data/src/main/java/org/thingsboard/server/common/data/validation/Length.java

@ -26,7 +26,7 @@ import java.lang.annotation.Target;
@Target(ElementType.FIELD) @Target(ElementType.FIELD)
@Constraint(validatedBy = {}) @Constraint(validatedBy = {})
public @interface Length { public @interface Length {
String message() default "length of {fieldName} must be equal or less than {max}"; String message() default "length must be equal or less than {max}";
String fieldName(); String fieldName();

2
common/data/src/main/java/org/thingsboard/server/common/data/validation/NoXss.java

@ -26,7 +26,7 @@ import java.lang.annotation.Target;
@Target(ElementType.FIELD) @Target(ElementType.FIELD)
@Constraint(validatedBy = {}) @Constraint(validatedBy = {})
public @interface NoXss { public @interface NoXss {
String message() default "field value is malformed"; String message() default "is malformed";
Class<?>[] groups() default {}; Class<?>[] groups() default {};

2
dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java

@ -389,7 +389,7 @@ public class AuditLogServiceImpl implements AuditLogService {
try { try {
auditLogValidator.validate(auditLogEntry, AuditLog::getTenantId); auditLogValidator.validate(auditLogEntry, AuditLog::getTenantId);
} catch (Exception e) { } catch (Exception e) {
if (StringUtils.contains(e.getMessage(), "value is malformed")) { if (StringUtils.contains(e.getMessage(), "is malformed")) {
auditLogEntry.setEntityName("MALFORMED"); auditLogEntry.setEntityName("MALFORMED");
} else { } else {
return Futures.immediateFailedFuture(e); return Futures.immediateFailedFuture(e);

37
dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java

@ -52,22 +52,20 @@ import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.rule.RuleNodeUpdateResult; import org.thingsboard.server.common.data.rule.RuleNodeUpdateResult;
import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.ConstraintValidator;
import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator; import org.thingsboard.server.dao.service.Validator;
import org.thingsboard.server.dao.service.validator.RuleChainDataValidator;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.DataConstants.TENANT; import static org.thingsboard.server.common.data.DataConstants.TENANT;
@ -138,12 +136,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
if (ruleChain == null) { if (ruleChain == null) {
return RuleChainUpdateResult.failed(); return RuleChainUpdateResult.failed();
} }
ConstraintValidator.validateFields(ruleChainMetaData); RuleChainDataValidator.validateMetaData(ruleChainMetaData);
List<RuleNodeUpdateResult> updatedRuleNodes = new ArrayList<>();
if (CollectionUtils.isNotEmpty(ruleChainMetaData.getConnections())) {
validateCircles(ruleChainMetaData.getConnections());
}
List<RuleNode> nodes = ruleChainMetaData.getNodes(); List<RuleNode> nodes = ruleChainMetaData.getNodes();
List<RuleNode> toAddOrUpdate = new ArrayList<>(); List<RuleNode> toAddOrUpdate = new ArrayList<>();
@ -161,6 +154,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
} }
} }
List<RuleNodeUpdateResult> updatedRuleNodes = new ArrayList<>();
List<RuleNode> existingRuleNodes = getRuleChainNodes(tenantId, ruleChainMetaData.getRuleChainId()); List<RuleNode> existingRuleNodes = getRuleChainNodes(tenantId, ruleChainMetaData.getRuleChainId());
for (RuleNode existingNode : existingRuleNodes) { for (RuleNode existingNode : existingRuleNodes) {
deleteEntityRelations(tenantId, existingNode.getId()); deleteEntityRelations(tenantId, existingNode.getId());
@ -250,31 +244,6 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
return RuleChainUpdateResult.successful(updatedRuleNodes); return RuleChainUpdateResult.successful(updatedRuleNodes);
} }
private void validateCircles(List<NodeConnectionInfo> connectionInfos) {
Map<Integer, Set<Integer>> connectionsMap = new HashMap<>();
for (NodeConnectionInfo nodeConnection : connectionInfos) {
if (nodeConnection.getFromIndex() == nodeConnection.getToIndex()) {
throw new DataValidationException("Can't create the relation to yourself.");
}
connectionsMap
.computeIfAbsent(nodeConnection.getFromIndex(), from -> new HashSet<>())
.add(nodeConnection.getToIndex());
}
connectionsMap.keySet().forEach(key -> validateCircles(key, connectionsMap.get(key), connectionsMap));
}
private void validateCircles(int from, Set<Integer> toList, Map<Integer, Set<Integer>> connectionsMap) {
if (toList == null) {
return;
}
for (Integer to : toList) {
if (from == to) {
throw new DataValidationException("Can't create circling relations in rule chain.");
}
validateCircles(from, connectionsMap.get(to), connectionsMap);
}
}
@Override @Override
public RuleChainMetaData loadRuleChainMetaData(TenantId tenantId, RuleChainId ruleChainId) { public RuleChainMetaData loadRuleChainMetaData(TenantId tenantId, RuleChainId ruleChainId) {
Validator.validateId(ruleChainId, "Incorrect rule chain id."); Validator.validateId(ruleChainId, "Incorrect rule chain id.");

33
dao/src/main/java/org/thingsboard/server/dao/service/ConstraintValidator.java

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.dao.service; package org.thingsboard.server.dao.service;
import com.google.common.collect.Iterators;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.hibernate.validator.HibernateValidator; import org.hibernate.validator.HibernateValidator;
import org.hibernate.validator.HibernateValidatorConfiguration; import org.hibernate.validator.HibernateValidatorConfiguration;
@ -23,11 +24,10 @@ import org.thingsboard.server.common.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoXss; import org.thingsboard.server.common.data.validation.NoXss;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import javax.validation.ConstraintViolation; import javax.validation.Path;
import javax.validation.Validation; import javax.validation.Validation;
import javax.validation.Validator; import javax.validation.Validator;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Slf4j @Slf4j
@ -40,14 +40,30 @@ public class ConstraintValidator {
} }
public static void validateFields(Object data) { public static void validateFields(Object data) {
Set<ConstraintViolation<Object>> constraintsViolations = fieldsValidator.validate(data); validateFields(data, "Validation error: ");
List<String> validationErrors = constraintsViolations.stream() }
.map(ConstraintViolation::getMessage)
public static void validateFields(Object data, String errorPrefix) {
List<String> constraintsViolations = getConstraintsViolations(data);
if (!constraintsViolations.isEmpty()) {
throw new DataValidationException(errorPrefix + String.join(", ", constraintsViolations));
}
}
public static List<String> getConstraintsViolations(Object data) {
return fieldsValidator.validate(data).stream()
.map(constraintViolation -> {
String property;
if (constraintViolation.getConstraintDescriptor().getAttributes().containsKey("fieldName")) {
property = constraintViolation.getConstraintDescriptor().getAttributes().get("fieldName").toString();
} else {
Path propertyPath = constraintViolation.getPropertyPath();
property = Iterators.getLast(propertyPath.iterator()).toString();
}
return property + " " + constraintViolation.getMessage();
})
.distinct() .distinct()
.collect(Collectors.toList()); .collect(Collectors.toList());
if (!validationErrors.isEmpty()) {
throw new DataValidationException("Validation error: " + String.join(", ", validationErrors));
}
} }
private static void initializeValidators() { private static void initializeValidators() {
@ -60,4 +76,5 @@ public class ConstraintValidator {
fieldsValidator = validatorConfiguration.buildValidatorFactory().getValidator(); fieldsValidator = validatorConfiguration.buildValidatorFactory().getValidator();
} }
} }

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

@ -15,23 +15,39 @@
*/ */
package org.thingsboard.server.dao.service.validator; package org.thingsboard.server.dao.service.validator;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.rule.NodeConnectionInfo;
import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.common.data.util.ReflectionUtils;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.rule.RuleChainDao; import org.thingsboard.server.dao.rule.RuleChainDao;
import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.service.ConstraintValidator;
import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.tenant.TenantService;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@Component @Component
@Slf4j
public class RuleChainDataValidator extends DataValidator<RuleChain> { public class RuleChainDataValidator extends DataValidator<RuleChain> {
@Autowired @Autowired
@ -83,4 +99,53 @@ public class RuleChainDataValidator extends DataValidator<RuleChain> {
} }
} }
} }
public static void validateMetaData(RuleChainMetaData ruleChainMetaData) {
ConstraintValidator.validateFields(ruleChainMetaData);
ruleChainMetaData.getNodes().forEach(RuleChainDataValidator::validateRuleNode);
if (CollectionUtils.isNotEmpty(ruleChainMetaData.getConnections())) {
validateCircles(ruleChainMetaData.getConnections());
}
}
public static void validateRuleNode(RuleNode ruleNode) {
String errorPrefix = "'" + ruleNode.getName() + "' node configuration is invalid: ";
ConstraintValidator.validateFields(ruleNode, errorPrefix);
Object nodeConfig;
try {
Class<Object> nodeConfigType = ReflectionUtils.getAnnotationProperty(ruleNode.getType(),
"org.thingsboard.rule.engine.api.RuleNode", "configClazz");
nodeConfig = JacksonUtil.treeToValue(ruleNode.getConfiguration(), nodeConfigType);
} catch (Exception e) {
log.warn("Failed to validate node configuration: {}", ExceptionUtils.getRootCauseMessage(e));
return;
}
ConstraintValidator.validateFields(nodeConfig, errorPrefix);
}
private static void validateCircles(List<NodeConnectionInfo> connectionInfos) {
Map<Integer, Set<Integer>> connectionsMap = new HashMap<>();
for (NodeConnectionInfo nodeConnection : connectionInfos) {
if (nodeConnection.getFromIndex() == nodeConnection.getToIndex()) {
throw new DataValidationException("Can't create the relation to yourself.");
}
connectionsMap
.computeIfAbsent(nodeConnection.getFromIndex(), from -> new HashSet<>())
.add(nodeConnection.getToIndex());
}
connectionsMap.keySet().forEach(key -> validateCircles(key, connectionsMap.get(key), connectionsMap));
}
private static void validateCircles(int from, Set<Integer> toList, Map<Integer, Set<Integer>> connectionsMap) {
if (toList == null) {
return;
}
for (Integer to : toList) {
if (from == to) {
throw new DataValidationException("Can't create circling relations in rule chain.");
}
validateCircles(from, connectionsMap.get(to), connectionsMap);
}
}
} }

4
dao/src/test/java/org/thingsboard/server/dao/service/BaseOtaPackageServiceTest.java

@ -674,7 +674,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
firmwareInfo.setTenantId(tenantId); firmwareInfo.setTenantId(tenantId);
thrown.expect(DataValidationException.class); thrown.expect(DataValidationException.class);
thrown.expectMessage("length of title must be equal or less than 255"); thrown.expectMessage("title length must be equal or less than 255");
otaPackageService.saveOtaPackageInfo(firmwareInfo, true); otaPackageService.saveOtaPackageInfo(firmwareInfo, true);
} }
@ -689,7 +689,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
firmwareInfo.setTitle(TITLE); firmwareInfo.setTitle(TITLE);
firmwareInfo.setVersion(StringUtils.random(257)); firmwareInfo.setVersion(StringUtils.random(257));
thrown.expectMessage("length of version must be equal or less than 255"); thrown.expectMessage("version length must be equal or less than 255");
otaPackageService.saveOtaPackageInfo(firmwareInfo, true); otaPackageService.saveOtaPackageInfo(firmwareInfo, true);
} }

4
dao/src/test/java/org/thingsboard/server/dao/service/NoXssValidatorTest.java

@ -43,7 +43,7 @@ public class NoXssValidatorTest {
assertThatThrownBy(() -> { assertThatThrownBy(() -> {
ConstraintValidator.validateFields(invalidAsset); ConstraintValidator.validateFields(invalidAsset);
}).hasMessageContaining("field value is malformed"); }).hasMessageContaining("is malformed");
} }
@Test @Test
@ -55,7 +55,7 @@ public class NoXssValidatorTest {
assertThatThrownBy(() -> { assertThatThrownBy(() -> {
ConstraintValidator.validateFields(invalidAsset); ConstraintValidator.validateFields(invalidAsset);
}).hasMessageContaining("field value is malformed"); }).hasMessageContaining("is malformed");
} }
} }

2
msa/tb/pom.xml

@ -38,7 +38,7 @@
<tb-postgres.docker.name>tb-postgres</tb-postgres.docker.name> <tb-postgres.docker.name>tb-postgres</tb-postgres.docker.name>
<tb-cassandra.docker.name>tb-cassandra</tb-cassandra.docker.name> <tb-cassandra.docker.name>tb-cassandra</tb-cassandra.docker.name>
<pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder> <pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder>
<pkg.upgradeVersion>3.4.2</pkg.upgradeVersion> <pkg.upgradeVersion>3.4.3</pkg.upgradeVersion>
</properties> </properties>
<dependencies> <dependencies>

2
pom.xml

@ -77,7 +77,7 @@
<zookeeper.version>3.5.5</zookeeper.version> <zookeeper.version>3.5.5</zookeeper.version>
<protobuf.version>3.21.9</protobuf.version> <protobuf.version>3.21.9</protobuf.version>
<grpc.version>1.42.1</grpc.version> <grpc.version>1.42.1</grpc.version>
<tbel.version>1.0.4</tbel.version> <tbel.version>1.0.5</tbel.version>
<lombok.version>1.18.18</lombok.version> <lombok.version>1.18.18</lombok.version>
<paho.client.version>1.2.4</paho.client.version> <paho.client.version>1.2.4</paho.client.version>
<paho.mqttv5.client.version>1.2.5</paho.mqttv5.client.version> <paho.mqttv5.client.version>1.2.5</paho.mqttv5.client.version>

6
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java

@ -42,12 +42,14 @@ import org.thingsboard.server.common.data.rule.RuleNodeState;
import org.thingsboard.server.common.data.script.ScriptLanguage; import org.thingsboard.server.common.data.script.ScriptLanguage;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.cassandra.CassandraCluster; import org.thingsboard.server.dao.cassandra.CassandraCluster;
import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceCredentialsService;
import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.edge.EdgeEventService; import org.thingsboard.server.dao.edge.EdgeEventService;
import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.edge.EdgeService;
@ -223,6 +225,10 @@ public interface TbContext {
DeviceService getDeviceService(); DeviceService getDeviceService();
DeviceProfileService getDeviceProfileService();
AssetProfileService getAssetProfileService();
DeviceCredentialsService getDeviceCredentialsService(); DeviceCredentialsService getDeviceCredentialsService();
TbClusterService getClusterService(); TbClusterService getClusterService();

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

@ -17,6 +17,7 @@ package org.thingsboard.rule.engine.action;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.script.ScriptLanguage; import org.thingsboard.server.common.data.script.ScriptLanguage;
import org.thingsboard.server.common.data.validation.NoXss;
@Data @Data
public abstract class TbAbstractAlarmNodeConfiguration { public abstract class TbAbstractAlarmNodeConfiguration {
@ -46,6 +47,7 @@ public abstract class TbAbstractAlarmNodeConfiguration {
"return details;"; "return details;";
@NoXss
private String alarmType; private String alarmType;
private ScriptLanguage scriptLang; private ScriptLanguage scriptLang;
private String alarmDetailsBuildJs; private String alarmDetailsBuildJs;

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

@ -19,6 +19,7 @@ import lombok.Data;
import org.thingsboard.rule.engine.api.NodeConfiguration; import org.thingsboard.rule.engine.api.NodeConfiguration;
import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.script.ScriptLanguage; import org.thingsboard.server.common.data.script.ScriptLanguage;
import org.thingsboard.server.common.data.validation.NoXss;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -26,6 +27,7 @@ import java.util.List;
@Data @Data
public class TbCreateAlarmNodeConfiguration extends TbAbstractAlarmNodeConfiguration implements NodeConfiguration<TbCreateAlarmNodeConfiguration> { public class TbCreateAlarmNodeConfiguration extends TbAbstractAlarmNodeConfiguration implements NodeConfiguration<TbCreateAlarmNodeConfiguration> {
@NoXss
private String severity; private String severity;
private boolean propagate; private boolean propagate;
private boolean propagateToOwner; private boolean propagateToOwner;

1
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/AbstractTbMsgPushNode.java

@ -168,6 +168,7 @@ public abstract class AbstractTbMsgPushNode<T extends BaseTbMsgPushNodeConfigura
case DASHBOARD: case DASHBOARD:
case TENANT: case TENANT:
case CUSTOMER: case CUSTOMER:
case USER:
case EDGE: case EDGE:
return true; return true;
default: default:

33
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNodeTest.java

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.rule.engine.edge; package org.thingsboard.rule.engine.edge;
import com.google.common.util.concurrent.SettableFuture;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -22,21 +23,28 @@ import org.mockito.Mock;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner; import org.mockito.junit.MockitoJUnitRunner;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ListeningExecutor;
import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
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.common.data.id.UserId;
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.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.common.msg.session.SessionMsgType;
import org.thingsboard.server.dao.edge.EdgeEventService;
import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.edge.EdgeService;
import java.util.List;
import java.util.UUID; import java.util.UUID;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
@ -52,6 +60,10 @@ public class TbMsgPushToEdgeNodeTest {
@Mock @Mock
private EdgeService edgeService; private EdgeService edgeService;
@Mock
private EdgeEventService edgeEventService;
@Mock
private ListeningExecutor dbCallbackExecutor;
@Before @Before
public void setUp() throws TbNodeException { public void setUp() throws TbNodeException {
@ -73,4 +85,25 @@ public class TbMsgPushToEdgeNodeTest {
verify(ctx).ack(msg); verify(ctx).ack(msg);
} }
@Test
public void testAttributeUpdateMsg_userEntity() {
Mockito.when(ctx.getTenantId()).thenReturn(tenantId);
Mockito.when(ctx.getEdgeService()).thenReturn(edgeService);
Mockito.when(ctx.getEdgeEventService()).thenReturn(edgeEventService);
Mockito.when(ctx.getDbCallbackExecutor()).thenReturn(dbCallbackExecutor);
Mockito.when(edgeEventService.saveAsync(any())).thenReturn(SettableFuture.create());
UserId userId = new UserId(UUID.randomUUID());
EdgeId edgeId = new EdgeId(UUID.randomUUID());
PageData<EdgeId> edgePageData = new PageData<>(List.of(edgeId), 1, 1, false);
Mockito.when(edgeService.findRelatedEdgeIdsByEntityId(tenantId, userId, new PageLink(TbMsgPushToEdgeNode.DEFAULT_PAGE_SIZE))).thenReturn(edgePageData);
TbMsg msg = TbMsg.newMsg(DataConstants.ATTRIBUTES_UPDATED, userId, new TbMsgMetaData(),
TbMsgDataType.JSON, "{}", null, null);
node.onMsg(ctx, msg);
verify(edgeEventService).saveAsync(any());
}
} }

3
ui-ngx/src/app/modules/home/components/import-export/import-export.models.ts

@ -70,9 +70,6 @@ export enum ImportEntityColumnType {
secret = 'SECRET' secret = 'SECRET'
} }
export const importEntityObjectColumns =
[ImportEntityColumnType.name, ImportEntityColumnType.type, ImportEntityColumnType.accessToken];
export const importEntityColumnTypeTranslations = new Map<ImportEntityColumnType, string>( export const importEntityColumnTypeTranslations = new Map<ImportEntityColumnType, string>(
[ [
[ImportEntityColumnType.name, 'import.column-type.name'], [ImportEntityColumnType.name, 'import.column-type.name'],

2
ui-ngx/src/app/modules/home/components/import-export/table-columns-assignment.component.html

@ -49,7 +49,7 @@
<mat-form-field floatLabel="always" hideRequiredMarker <mat-form-field floatLabel="always" hideRequiredMarker
*ngIf="isColumnTypeDiffers(column.type)"> *ngIf="isColumnTypeDiffers(column.type)">
<mat-label></mat-label> <mat-label></mat-label>
<input matInput required <input matInput [required]="isColumnTypeDiffers(column.type)"
[(ngModel)]="column.key" (ngModelChange)="columnsUpdated()" [(ngModel)]="column.key" (ngModelChange)="columnsUpdated()"
placeholder="{{ 'import.column-value' | translate }}"/> placeholder="{{ 'import.column-value' | translate }}"/>
</mat-form-field> </mat-form-field>

27
ui-ngx/src/app/modules/home/components/import-export/table-columns-assignment.component.ts

@ -22,8 +22,7 @@ import { EntityType } from '@shared/models/entity-type.models';
import { import {
CsvColumnParam, CsvColumnParam,
ImportEntityColumnType, ImportEntityColumnType,
importEntityColumnTypeTranslations, importEntityColumnTypeTranslations
importEntityObjectColumns
} from '@home/components/import-export/import-export.models'; } from '@home/components/import-export/import-export.models';
import { BehaviorSubject, Observable } from 'rxjs'; import { BehaviorSubject, Observable } from 'rxjs';
import { CollectionViewer, DataSource } from '@angular/cdk/collections'; import { CollectionViewer, DataSource } from '@angular/cdk/collections';
@ -117,7 +116,9 @@ export class TableColumnsAssignmentComponent implements OnInit, ControlValueAcce
case EntityType.EDGE: case EntityType.EDGE:
this.columnTypes.push( this.columnTypes.push(
{ value: ImportEntityColumnType.routingKey }, { value: ImportEntityColumnType.routingKey },
{ value: ImportEntityColumnType.secret } { value: ImportEntityColumnType.secret },
{ value: ImportEntityColumnType.serverAttribute },
{ value: ImportEntityColumnType.timeseries }
); );
break; break;
} }
@ -143,8 +144,6 @@ export class TableColumnsAssignmentComponent implements OnInit, ControlValueAcce
const isSelectType = this.columns.findIndex((column) => column.type === ImportEntityColumnType.type) > -1; const isSelectType = this.columns.findIndex((column) => column.type === ImportEntityColumnType.type) > -1;
const isSelectLabel = this.columns.findIndex((column) => column.type === ImportEntityColumnType.label) > -1; const isSelectLabel = this.columns.findIndex((column) => column.type === ImportEntityColumnType.label) > -1;
const isSelectDescription = this.columns.findIndex((column) => column.type === ImportEntityColumnType.description) > -1; const isSelectDescription = this.columns.findIndex((column) => column.type === ImportEntityColumnType.description) > -1;
const isSelectRoutingKey = this.columns.findIndex((column) => column.type === ImportEntityColumnType.routingKey) > -1;
const isSelectSecret = this.columns.findIndex((column) => column.type === ImportEntityColumnType.secret) > -1;
const hasInvalidColumn = this.columns.findIndex((column) => !this.columnValid(column)) > -1; const hasInvalidColumn = this.columns.findIndex((column) => !this.columnValid(column)) > -1;
this.valid = isSelectName && isSelectType && !hasInvalidColumn; this.valid = isSelectName && isSelectType && !hasInvalidColumn;
@ -167,14 +166,16 @@ export class TableColumnsAssignmentComponent implements OnInit, ControlValueAcce
}); });
} }
const routingKeyColumnType = this.columnTypes.find((columnType) => columnType.value === ImportEntityColumnType.routingKey); if (this.entityType === EntityType.EDGE) {
if (routingKeyColumnType) { const isSelectRoutingKey = this.columns.findIndex((column) => column.type === ImportEntityColumnType.routingKey) > -1;
routingKeyColumnType.disabled = isSelectRoutingKey; const isSelectSecret = this.columns.findIndex((column) => column.type === ImportEntityColumnType.secret) > -1;
}
const secretColumnType = this.columnTypes.find((columnType) => columnType.value === ImportEntityColumnType.secret); this.valid = this.valid && isSelectSecret && isSelectRoutingKey;
if (secretColumnType) {
secretColumnType.disabled = isSelectSecret; this.columnTypes.find((columnType) => columnType.value === ImportEntityColumnType.routingKey).disabled = isSelectRoutingKey;
this.columnTypes.find((columnType) => columnType.value === ImportEntityColumnType.secret).disabled = isSelectSecret;
} }
if (this.propagateChange) { if (this.propagateChange) {
this.propagateChange(this.columns); this.propagateChange(this.columns);
} else { } else {
@ -190,7 +191,7 @@ export class TableColumnsAssignmentComponent implements OnInit, ControlValueAcce
} }
private columnValid(column: CsvColumnParam): boolean { private columnValid(column: CsvColumnParam): boolean {
if (!importEntityObjectColumns.includes(column.type)) { if (this.isColumnTypeDiffers(column.type)) {
return column.key && column.key.trim().length > 0; return column.key && column.key.trim().length > 0;
} else { } else {
return true; return true;

28
ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html

@ -19,7 +19,7 @@
[formGroup]="multipleInputFormGroup" [formGroup]="multipleInputFormGroup"
tb-toast toastTarget="{{ toastTargetId }}" tb-toast toastTarget="{{ toastTargetId }}"
(ngSubmit)="saveForm()" novalidate autocomplete="off"> (ngSubmit)="saveForm()" novalidate autocomplete="off">
<div style="padding: 0 8px;" *ngIf="entityDetected && isAllParametersValid"> <div class="tb-multiple-input-container" *ngIf="entityDetected && isAllParametersValid">
<fieldset *ngFor="let source of sources" [ngClass]="{'fields-group': settings.showGroupTitle}"> <fieldset *ngFor="let source of sources" [ngClass]="{'fields-group': settings.showGroupTitle}">
<legend class="group-title" *ngIf="settings.showGroupTitle">{{ getGroupTitle(source.datasource) }} <legend class="group-title" *ngIf="settings.showGroupTitle">{{ getGroupTitle(source.datasource) }}
</legend> </legend>
@ -142,19 +142,19 @@
</div> </div>
</div> </div>
</fieldset> </fieldset>
<div class="mat-padding" fxLayout="row" fxLayoutAlign="end center" </div>
*ngIf="entityDetected && settings.showActionButtons"> <div class="mat-padding" fxLayout="row" fxLayoutAlign="end center"
<button mat-button color="primary" type="button" *ngIf="entityDetected && isAllParametersValid && settings.showActionButtons">
(click)="discardAll()" style="max-height: 50px; margin-right:20px;" <button mat-button color="primary" type="button"
[disabled]="!multipleInputFormGroup.dirty"> (click)="discardAll()" style="max-height: 50px; margin-right:20px;"
{{ resetButtonLabel }} [disabled]="!multipleInputFormGroup.dirty">
</button> {{ resetButtonLabel }}
<button mat-button mat-raised-button color="primary" type="submit" </button>
style="max-height: 50px; margin-right:20px;" <button mat-button mat-raised-button color="primary" type="submit"
[disabled]="!multipleInputFormGroup.dirty || multipleInputFormGroup.invalid"> style="max-height: 50px; margin-right:20px;"
{{ saveButtonLabel }} [disabled]="!multipleInputFormGroup.dirty || multipleInputFormGroup.invalid">
</button> {{ saveButtonLabel }}
</div> </button>
</div> </div>
<div class="tb-multiple-input__errors" fxLayout="column" fxLayoutAlign="center center" style="height: 100%;" <div class="tb-multiple-input__errors" fxLayout="column" fxLayoutAlign="center center" style="height: 100%;"
*ngIf="(!entityDetected || !isAllParametersValid) && datasourceDetected"> *ngIf="(!entityDetected || !isAllParametersValid) && datasourceDetected">

11
ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.scss

@ -17,8 +17,15 @@
.tb-multiple-input { .tb-multiple-input {
height: 100%; height: 100%;
overflow-x: hidden; display: flex;
overflow-y: auto; flex-direction: column;
.tb-multiple-input-container {
padding: 0 8px;
flex: 1 1 100%;
overflow-x: hidden;
overflow-y: auto;
}
.fields-group { .fields-group {
padding: 0 8px; padding: 0 8px;

0
ui-ngx/src/assets/locale/locale.constant-es_CA.json → ui-ngx/src/assets/locale/locale.constant-ca_ES.json

4002
ui-ngx/src/assets/locale/locale.constant-da_DK.json

File diff suppressed because it is too large

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

@ -4793,7 +4793,7 @@
"ko_KR": "한국어", "ko_KR": "한국어",
"ru_RU": "Русский", "ru_RU": "Русский",
"es_ES": "Español", "es_ES": "Español",
"es_CA": "Catalan", "ca_ES": "Catalan",
"ja_JP": "日本語", "ja_JP": "日本語",
"tr_TR": "Türkçe", "tr_TR": "Türkçe",
"fa_IR": "فارسي", "fa_IR": "فارسي",
@ -4804,7 +4804,8 @@
"lv_LV": "Latviešu", "lv_LV": "Latviešu",
"ka_GE": "ქართული", "ka_GE": "ქართული",
"pt_BR": "Português do Brasil", "pt_BR": "Português do Brasil",
"sl_SI": "Slovenščina" "sl_SI": "Slovenščina",
"da_DK": "Dansk"
} }
} }
} }

Loading…
Cancel
Save