Browse Source

Merge branch 'master' into develop/2.3

pull/1463/head
Igor Kulikov 8 years ago
parent
commit
cd1d60c9e5
  1. 91
      dao/src/main/java/org/thingsboard/server/dao/model/sql/TsKvEntity.java
  2. 2
      dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java
  3. 34
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java
  4. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNodeConfiguration.java
  5. 97
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateRelationNode.java
  6. 6
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateRelationNodeConfiguration.java
  7. 45
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeleteRelationNode.java
  8. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeleteRelationNodeConfiguration.java
  9. 8
      rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js
  10. 1
      ui/src/app/locale/locale.constant-de_DE.json
  11. 1
      ui/src/app/locale/locale.constant-en_US.json
  12. 3
      ui/src/app/locale/locale.constant-es_ES.json
  13. 1
      ui/src/app/locale/locale.constant-fr_FR.json
  14. 1
      ui/src/app/locale/locale.constant-it_IT.json
  15. 1
      ui/src/app/locale/locale.constant-ja_JA.json
  16. 1
      ui/src/app/locale/locale.constant-ko_KR.json
  17. 1
      ui/src/app/locale/locale.constant-ru_RU.json
  18. 3
      ui/src/app/locale/locale.constant-tr_TR.json
  19. 1
      ui/src/app/locale/locale.constant-zh_CN.json
  20. 28
      ui/src/app/widget/lib/google-map.js
  21. 105
      ui/src/app/widget/lib/map-widget2.js
  22. 12
      ui/src/app/widget/lib/openstreet-map.js
  23. 25
      ui/src/app/widget/lib/tencent-map.js
  24. 34
      ui/src/app/widget/lib/widget-utils.js

91
dao/src/main/java/org/thingsboard/server/dao/model/sql/TsKvEntity.java

@ -62,49 +62,53 @@ public final class TsKvEntity implements ToData<TsKvEntry> {
}
public TsKvEntity(Long longValue, Double doubleValue, Long longCountValue, Long doubleCountValue, String aggType) {
switch (aggType) {
case AVG:
double sum = 0.0;
if (longValue != null) {
sum += longValue;
}
if (doubleValue != null) {
sum += doubleValue;
}
long totalCount = longCountValue + doubleCountValue;
if (totalCount > 0) {
this.doubleValue = sum / (longCountValue + doubleCountValue);
} else {
this.doubleValue = 0.0;
}
break;
case SUM:
if (doubleCountValue > 0) {
this.doubleValue = doubleValue + (longValue != null ? longValue.doubleValue() : 0.0);
} else {
this.longValue = longValue;
}
break;
case MIN:
case MAX:
if (longCountValue > 0 && doubleCountValue > 0) {
this.doubleValue = MAX.equals(aggType) ? Math.max(doubleValue, longValue.doubleValue()) : Math.min(doubleValue, longValue.doubleValue());
} else if (doubleCountValue > 0) {
this.doubleValue = doubleValue;
} else if (longCountValue > 0) {
this.longValue = longValue;
}
break;
if(!isAllNull(longValue, doubleValue, longCountValue, doubleCountValue)) {
switch (aggType) {
case AVG:
double sum = 0.0;
if (longValue != null) {
sum += longValue;
}
if (doubleValue != null) {
sum += doubleValue;
}
long totalCount = longCountValue + doubleCountValue;
if (totalCount > 0) {
this.doubleValue = sum / (longCountValue + doubleCountValue);
} else {
this.doubleValue = 0.0;
}
break;
case SUM:
if (doubleCountValue > 0) {
this.doubleValue = doubleValue + (longValue != null ? longValue.doubleValue() : 0.0);
} else {
this.longValue = longValue;
}
break;
case MIN:
case MAX:
if (longCountValue > 0 && doubleCountValue > 0) {
this.doubleValue = MAX.equals(aggType) ? Math.max(doubleValue, longValue.doubleValue()) : Math.min(doubleValue, longValue.doubleValue());
} else if (doubleCountValue > 0) {
this.doubleValue = doubleValue;
} else if (longCountValue > 0) {
this.longValue = longValue;
}
break;
}
}
}
public TsKvEntity(Long booleanValueCount, Long strValueCount, Long longValueCount, Long doubleValueCount) {
if (booleanValueCount != 0) {
this.longValue = booleanValueCount;
} else if (strValueCount != 0) {
this.longValue = strValueCount;
} else {
this.longValue = longValueCount + doubleValueCount;
if(!isAllNull(booleanValueCount, strValueCount, longValueCount, doubleValueCount)) {
if (booleanValueCount != 0) {
this.longValue = booleanValueCount;
} else if (strValueCount != 0) {
this.longValue = strValueCount;
} else {
this.longValue = longValueCount + doubleValueCount;
}
}
}
@ -155,4 +159,13 @@ public final class TsKvEntity implements ToData<TsKvEntry> {
public boolean isNotEmpty() {
return strValue != null || longValue != null || doubleValue != null || booleanValue != null;
}
private static boolean isAllNull(Object... args) {
for (Object arg : args) {
if(arg != null) {
return false;
}
}
return true;
}
}

2
dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java

@ -148,7 +148,7 @@ public class BaseTimeseriesService implements TimeseriesService {
private void saveAndRegisterFutures(TenantId tenantId, List<ListenableFuture<Void>> futures, EntityId entityId, TsKvEntry tsKvEntry, long ttl) {
if (entityId.getEntityType().equals(EntityType.ENTITY_VIEW)) {
throw new IncorrectParameterException("Telemetry data can't be stored for entity view. Only read only");
throw new IncorrectParameterException("Telemetry data can't be stored for entity view. Read only");
}
futures.add(timeseriesDao.savePartition(tenantId, entityId, tsKvEntry.getTs(), tsKvEntry.getKey(), ttl));
futures.add(timeseriesDao.saveLatest(tenantId, entityId, tsKvEntry));

34
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java

@ -22,6 +22,7 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNode;
@ -78,28 +79,28 @@ public abstract class TbAbstractRelationActionNode<C extends TbAbstractRelationA
@Override
public void onMsg(TbContext ctx, TbMsg msg) {
withCallback(processEntityRelationAction(ctx, msg),
filterResult -> ctx.tellNext(msg, filterResult ? SUCCESS : FAILURE), t -> ctx.tellFailure(msg, t), ctx.getDbCallbackExecutor());
filterResult -> ctx.tellNext(filterResult.getMsg(), filterResult.isResult() ? SUCCESS : FAILURE), t -> ctx.tellFailure(msg, t), ctx.getDbCallbackExecutor());
}
@Override
public void destroy() {
}
protected ListenableFuture<Boolean> processEntityRelationAction(TbContext ctx, TbMsg msg) {
protected ListenableFuture<RelationContainer> processEntityRelationAction(TbContext ctx, TbMsg msg) {
return Futures.transformAsync(getEntity(ctx, msg), entityContainer -> doProcessEntityRelationAction(ctx, msg, entityContainer));
}
protected abstract boolean createEntityIfNotExists();
protected abstract ListenableFuture<Boolean> doProcessEntityRelationAction(TbContext ctx, TbMsg msg, EntityContainer entityContainer);
protected abstract ListenableFuture<RelationContainer> doProcessEntityRelationAction(TbContext ctx, TbMsg msg, EntityContainer entityContainer);
protected abstract C loadEntityNodeActionConfig(TbNodeConfiguration configuration) throws TbNodeException;
protected ListenableFuture<EntityContainer> getEntity(TbContext ctx, TbMsg msg) {
String entityName = TbNodeUtils.processPattern(this.config.getEntityNamePattern(), msg.getMetaData());
String entityName = processPattern(msg, this.config.getEntityNamePattern());
String type;
if (this.config.getEntityTypePattern() != null) {
type = TbNodeUtils.processPattern(this.config.getEntityTypePattern(), msg.getMetaData());
type = processPattern(msg, this.config.getEntityTypePattern());
} else {
type = null;
}
@ -116,24 +117,30 @@ public abstract class TbAbstractRelationActionNode<C extends TbAbstractRelationA
protected SearchDirectionIds processSingleSearchDirection(TbMsg msg, EntityContainer entityContainer) {
SearchDirectionIds searchDirectionIds = new SearchDirectionIds();
if (EntitySearchDirection.FROM.name().equals(config.getDirection())) {
if (EntitySearchDirection.FROM.name().equals(this.config.getDirection())) {
searchDirectionIds.setFromId(EntityIdFactory.getByTypeAndId(entityContainer.getEntityType().name(), entityContainer.getEntityId().toString()));
searchDirectionIds.setToId(msg.getOriginator());
searchDirectionIds.setOrignatorDirectionFrom(false);
} else {
searchDirectionIds.setToId(EntityIdFactory.getByTypeAndId(entityContainer.getEntityType().name(), entityContainer.getEntityId().toString()));
searchDirectionIds.setFromId(msg.getOriginator());
searchDirectionIds.setOrignatorDirectionFrom(true);
}
return searchDirectionIds;
}
protected ListenableFuture<List<EntityRelation>> processListSearchDirection(TbContext ctx, TbMsg msg) {
if (EntitySearchDirection.FROM.name().equals(config.getDirection())) {
return ctx.getRelationService().findByToAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), config.getRelationType(), RelationTypeGroup.COMMON);
if (EntitySearchDirection.FROM.name().equals(this.config.getDirection())) {
return ctx.getRelationService().findByToAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), processPattern(msg, this.config.getRelationTypePattern()), RelationTypeGroup.COMMON);
} else {
return ctx.getRelationService().findByFromAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), config.getRelationType(), RelationTypeGroup.COMMON);
return ctx.getRelationService().findByFromAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), processPattern(msg, this.config.getRelationTypePattern()), RelationTypeGroup.COMMON);
}
}
protected String processPattern(TbMsg msg, String pattern){
return TbNodeUtils.processPattern(pattern, msg.getMetaData());
}
@Data
@AllArgsConstructor
private static class EntityKey {
@ -146,6 +153,7 @@ public abstract class TbAbstractRelationActionNode<C extends TbAbstractRelationA
protected static class SearchDirectionIds {
private EntityId fromId;
private EntityId toId;
private boolean orignatorDirectionFrom;
}
private static class EntityCacheLoader extends CacheLoader<EntityKey, EntityContainer> {
@ -233,7 +241,15 @@ public abstract class TbAbstractRelationActionNode<C extends TbAbstractRelationA
}
return targetEntity;
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
protected static class RelationContainer {
private TbMsg msg;
private boolean result;
}

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

@ -21,7 +21,7 @@ import lombok.Data;
public abstract class TbAbstractRelationActionNodeConfiguration {
private String direction;
private String relationType;
private String relationTypePattern;
private String entityType;
private String entityNamePattern;

97
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateRelationNode.java

@ -35,13 +35,20 @@ import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.msg.TbMsg;
import java.util.ArrayList;
import java.util.List;
@Slf4j
@RuleNode(
type = ComponentType.ACTION,
name = "create relation",
configClazz = TbCreateRelationNodeConfiguration.class,
nodeDescription = "Finds target Entity by entity name pattern and (entity type pattern for Asset, Device) and then create a relation to Originator Entity by type and direction." +
" If Selected entity type: Asset, Device or Customer will create new Entity if it doesn't exist and 'Create new entity if not exists' is set to true.",
" If Selected entity type: Asset, Device or Customer will create new Entity if it doesn't exist and selected checkbox 'Create new entity if not exists'.<br>" +
" In case that relation from the message originator to the selected entity not exist and If selected checkbox 'Remove current relations'," +
" before creating the new relation all existed relations to message originator by type and direction will be removed.<br>" +
" If relation from the message originator to the selected entity created and If selected checkbox 'Change originator to related entity'," +
" outbound message will be processed as a message from this entity.",
nodeDetails = "If the relation already exists or successfully created - Message send via <b>Success</b> chain, otherwise <b>Failure</b> chain will be used.",
uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbActionNodeCreateRelationConfig",
@ -49,6 +56,8 @@ import org.thingsboard.server.common.msg.TbMsg;
)
public class TbCreateRelationNode extends TbAbstractRelationActionNode<TbCreateRelationNodeConfiguration> {
private String relationType;
@Override
protected TbCreateRelationNodeConfiguration loadEntityNodeActionConfig(TbNodeConfiguration configuration) throws TbNodeException {
return TbNodeUtils.convert(configuration, TbCreateRelationNodeConfiguration.class);
@ -60,19 +69,60 @@ public class TbCreateRelationNode extends TbAbstractRelationActionNode<TbCreateR
}
@Override
protected ListenableFuture<Boolean> doProcessEntityRelationAction(TbContext ctx, TbMsg msg, EntityContainer entity) {
return createIfAbsent(ctx, msg, entity);
protected ListenableFuture<RelationContainer> doProcessEntityRelationAction(TbContext ctx, TbMsg msg, EntityContainer entity) {
ListenableFuture<Boolean> future = createIfAbsent(ctx, msg, entity);
return Futures.transform(future, result -> {
RelationContainer container = new RelationContainer();
if (result && config.isChangeOriginatorToRelatedEntity()) {
TbMsg tbMsg = ctx.transformMsg(msg, msg.getType(), entity.getEntityId(), msg.getMetaData(), msg.getData());
container.setMsg(tbMsg);
} else {
container.setMsg(msg);
}
container.setResult(result);
return container;
});
}
private ListenableFuture<Boolean> createIfAbsent(TbContext ctx, TbMsg msg, EntityContainer entityContainer) {
relationType = processPattern(msg, config.getRelationTypePattern());
SearchDirectionIds sdId = processSingleSearchDirection(msg, entityContainer);
return Futures.transformAsync(ctx.getRelationService().checkRelation(ctx.getTenantId(), sdId.getFromId(), sdId.getToId(), config.getRelationType(), RelationTypeGroup.COMMON),
result -> {
if (!result) {
return processCreateRelation(ctx, entityContainer, sdId);
}
return Futures.immediateFuture(true);
});
ListenableFuture<Boolean> checkRelationFuture = Futures.transformAsync(ctx.getRelationService().checkRelation(ctx.getTenantId(), sdId.getFromId(), sdId.getToId(), relationType, RelationTypeGroup.COMMON), result -> {
if (!result) {
if (config.isRemoveCurrentRelations()) {
return processDeleteRelations(ctx, processFindRelations(ctx, msg, sdId));
}
}
return Futures.immediateFuture(true);
}, ctx.getDbCallbackExecutor());
return Futures.transformAsync(checkRelationFuture, result -> {
if (!result) {
return processCreateRelation(ctx, entityContainer, sdId);
}
return Futures.immediateFuture(true);
}, ctx.getDbCallbackExecutor());
}
private ListenableFuture<List<EntityRelation>> processFindRelations(TbContext ctx, TbMsg msg, SearchDirectionIds sdId) {
if (sdId.isOrignatorDirectionFrom()) {
return ctx.getRelationService().findByFromAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), relationType, RelationTypeGroup.COMMON);
} else {
return ctx.getRelationService().findByToAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), relationType, RelationTypeGroup.COMMON);
}
}
private ListenableFuture<Boolean> processDeleteRelations(TbContext ctx, ListenableFuture<List<EntityRelation>> listListenableFuture) {
return Futures.transformAsync(listListenableFuture, entityRelations -> {
if (!entityRelations.isEmpty()) {
List<ListenableFuture<Boolean>> list = new ArrayList<>();
for (EntityRelation relation : entityRelations) {
list.add(ctx.getRelationService().deleteRelationAsync(ctx.getTenantId(), relation));
}
return Futures.transform(Futures.allAsList(list), result -> false);
}
return Futures.immediateFuture(false);
}, ctx.getDbCallbackExecutor());
}
private ListenableFuture<Boolean> processCreateRelation(TbContext ctx, EntityContainer entityContainer, SearchDirectionIds sdId) {
@ -96,17 +146,17 @@ public class TbCreateRelationNode extends TbAbstractRelationActionNode<TbCreateR
private ListenableFuture<Boolean> processView(TbContext ctx, EntityContainer entityContainer, SearchDirectionIds sdId) {
return Futures.transformAsync(ctx.getEntityViewService().findEntityViewByIdAsync(ctx.getTenantId(), new EntityViewId(entityContainer.getEntityId().getId())), entityView -> {
if (entityView != null) {
return ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), new EntityRelation(sdId.getFromId(), sdId.getToId(), config.getRelationType(), RelationTypeGroup.COMMON));
return processSave(ctx, sdId);
} else {
return Futures.immediateFuture(true);
}
});
}, ctx.getDbCallbackExecutor());
}
private ListenableFuture<Boolean> processDevice(TbContext ctx, EntityContainer entityContainer, SearchDirectionIds sdId) {
return Futures.transformAsync(ctx.getDeviceService().findDeviceByIdAsync(ctx.getTenantId(), new DeviceId(entityContainer.getEntityId().getId())), device -> {
if (device != null) {
return ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), new EntityRelation(sdId.getFromId(), sdId.getToId(), config.getRelationType(), RelationTypeGroup.COMMON));
return processSave(ctx, sdId);
} else {
return Futures.immediateFuture(true);
}
@ -116,40 +166,45 @@ public class TbCreateRelationNode extends TbAbstractRelationActionNode<TbCreateR
private ListenableFuture<Boolean> processAsset(TbContext ctx, EntityContainer entityContainer, SearchDirectionIds sdId) {
return Futures.transformAsync(ctx.getAssetService().findAssetByIdAsync(ctx.getTenantId(), new AssetId(entityContainer.getEntityId().getId())), asset -> {
if (asset != null) {
return ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), new EntityRelation(sdId.getFromId(), sdId.getToId(), config.getRelationType(), RelationTypeGroup.COMMON));
return processSave(ctx, sdId);
} else {
return Futures.immediateFuture(true);
}
});
}, ctx.getDbCallbackExecutor());
}
private ListenableFuture<Boolean> processCustomer(TbContext ctx, EntityContainer entityContainer, SearchDirectionIds sdId) {
return Futures.transformAsync(ctx.getCustomerService().findCustomerByIdAsync(ctx.getTenantId(), new CustomerId(entityContainer.getEntityId().getId())), customer -> {
if (customer != null) {
return ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), new EntityRelation(sdId.getFromId(), sdId.getToId(), config.getRelationType(), RelationTypeGroup.COMMON));
return processSave(ctx, sdId);
} else {
return Futures.immediateFuture(true);
}
});
}, ctx.getDbCallbackExecutor());
}
private ListenableFuture<Boolean> processDashboard(TbContext ctx, EntityContainer entityContainer, SearchDirectionIds sdId) {
return Futures.transformAsync(ctx.getDashboardService().findDashboardByIdAsync(ctx.getTenantId(), new DashboardId(entityContainer.getEntityId().getId())), dashboard -> {
if (dashboard != null) {
return ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), new EntityRelation(sdId.getFromId(), sdId.getToId(), config.getRelationType(), RelationTypeGroup.COMMON));
return processSave(ctx, sdId);
} else {
return Futures.immediateFuture(true);
}
});
}, ctx.getDbCallbackExecutor());
}
private ListenableFuture<Boolean> processTenant(TbContext ctx, EntityContainer entityContainer, SearchDirectionIds sdId) {
return Futures.transformAsync(ctx.getTenantService().findTenantByIdAsync(ctx.getTenantId(), new TenantId(entityContainer.getEntityId().getId())), tenant -> {
if (tenant != null) {
return ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), new EntityRelation(sdId.getFromId(), sdId.getToId(), config.getRelationType(), RelationTypeGroup.COMMON));
return processSave(ctx, sdId);
} else {
return Futures.immediateFuture(true);
}
});
}, ctx.getDbCallbackExecutor());
}
private ListenableFuture<Boolean> processSave(TbContext ctx, SearchDirectionIds sdId) {
return ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), new EntityRelation(sdId.getFromId(), sdId.getToId(), relationType, RelationTypeGroup.COMMON));
}
}

6
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateRelationNodeConfiguration.java

@ -23,15 +23,19 @@ import org.thingsboard.server.common.data.relation.EntitySearchDirection;
public class TbCreateRelationNodeConfiguration extends TbAbstractRelationActionNodeConfiguration implements NodeConfiguration<TbCreateRelationNodeConfiguration> {
private boolean createEntityIfNotExists;
private boolean changeOriginatorToRelatedEntity;
private boolean removeCurrentRelations;
@Override
public TbCreateRelationNodeConfiguration defaultConfiguration() {
TbCreateRelationNodeConfiguration configuration = new TbCreateRelationNodeConfiguration();
configuration.setDirection(EntitySearchDirection.FROM.name());
configuration.setRelationType("Contains");
configuration.setRelationTypePattern("Contains");
configuration.setEntityNamePattern("");
configuration.setEntityCacheExpiration(300);
configuration.setCreateEntityIfNotExists(false);
configuration.setRemoveCurrentRelations(false);
configuration.setChangeOriginatorToRelatedEntity(false);
return configuration;
}
}

45
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeleteRelationNode.java

@ -42,11 +42,13 @@ import java.util.List;
" if 'Delete single entity' is set to true, otherwise rule node will delete all relations to the originator of the message by type and direction.",
nodeDetails = "If the relation(s) successfully deleted - Message send via <b>Success</b> chain, otherwise <b>Failure</b> chain will be used.",
uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective ="tbActionNodeDeleteRelationConfig",
configDirective = "tbActionNodeDeleteRelationConfig",
icon = "remove_circle"
)
public class TbDeleteRelationNode extends TbAbstractRelationActionNode<TbDeleteRelationNodeConfiguration> {
private String relationType;
@Override
protected TbDeleteRelationNodeConfiguration loadEntityNodeActionConfig(TbNodeConfiguration configuration) throws TbNodeException {
return TbNodeUtils.convert(configuration, TbDeleteRelationNodeConfiguration.class);
@ -58,35 +60,39 @@ public class TbDeleteRelationNode extends TbAbstractRelationActionNode<TbDeleteR
}
@Override
protected ListenableFuture<Boolean> processEntityRelationAction(TbContext ctx, TbMsg msg) {
if(config.isDeleteForSingleEntity()){
return Futures.transformAsync(getEntity(ctx, msg), entityContainer -> doProcessEntityRelationAction(ctx, msg, entityContainer));
} else {
return processList(ctx, msg);
}
protected ListenableFuture<RelationContainer> processEntityRelationAction(TbContext ctx, TbMsg msg) {
return getRelationContainerListenableFuture(ctx, msg);
}
@Override
protected ListenableFuture<Boolean> doProcessEntityRelationAction(TbContext ctx, TbMsg msg, EntityContainer entityContainer) {
return processSingle(ctx, msg, entityContainer);
protected ListenableFuture<RelationContainer> doProcessEntityRelationAction(TbContext ctx, TbMsg msg, EntityContainer entityContainer) {
return Futures.transform(processSingle(ctx, msg, entityContainer), result -> new RelationContainer(msg, result));
}
private ListenableFuture<RelationContainer> getRelationContainerListenableFuture(TbContext ctx, TbMsg msg) {
relationType = processPattern(msg, config.getRelationTypePattern());
if (config.isDeleteForSingleEntity()) {
return Futures.transformAsync(getEntity(ctx, msg), entityContainer -> doProcessEntityRelationAction(ctx, msg, entityContainer));
} else {
return Futures.transform(processList(ctx, msg), result -> new RelationContainer(msg, result));
}
}
private ListenableFuture<Boolean> processList(TbContext ctx, TbMsg msg) {
return Futures.transformAsync(processListSearchDirection(ctx, msg), entityRelations -> {
if(entityRelations.isEmpty()){
if (entityRelations.isEmpty()) {
return Futures.immediateFuture(true);
} else {
List<ListenableFuture<Boolean>> listenableFutureList = new ArrayList<>();
for (EntityRelation entityRelation: entityRelations) {
for (EntityRelation entityRelation : entityRelations) {
listenableFutureList.add(ctx.getRelationService().deleteRelationAsync(ctx.getTenantId(), entityRelation));
}
return Futures.transformAsync(Futures.allAsList(listenableFutureList), booleans -> {
for (Boolean bool : booleans) {
if (!bool) {
return Futures.immediateFuture(false);
}
}
return Futures.immediateFuture(true);
for (Boolean bool : booleans) {
if (!bool) {
return Futures.immediateFuture(false);
}
}
return Futures.immediateFuture(true);
});
}
});
@ -94,8 +100,7 @@ public class TbDeleteRelationNode extends TbAbstractRelationActionNode<TbDeleteR
private ListenableFuture<Boolean> processSingle(TbContext ctx, TbMsg msg, EntityContainer entityContainer) {
SearchDirectionIds sdId = processSingleSearchDirection(msg, entityContainer);
return Futures.transformAsync(ctx.getRelationService().checkRelation(ctx.getTenantId(), sdId.getFromId(), sdId.getToId(), config.getRelationType(), RelationTypeGroup.COMMON),
return Futures.transformAsync(ctx.getRelationService().checkRelation(ctx.getTenantId(), sdId.getFromId(), sdId.getToId(), relationType, RelationTypeGroup.COMMON),
result -> {
if (result) {
return processSingleDeleteRelation(ctx, sdId);
@ -105,7 +110,7 @@ public class TbDeleteRelationNode extends TbAbstractRelationActionNode<TbDeleteR
}
private ListenableFuture<Boolean> processSingleDeleteRelation(TbContext ctx, SearchDirectionIds sdId) {
return ctx.getRelationService().deleteRelationAsync(ctx.getTenantId(), sdId.getFromId(), sdId.getToId(), config.getRelationType(), RelationTypeGroup.COMMON);
return ctx.getRelationService().deleteRelationAsync(ctx.getTenantId(), sdId.getFromId(), sdId.getToId(), relationType, RelationTypeGroup.COMMON);
}
}

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

@ -29,7 +29,7 @@ public class TbDeleteRelationNodeConfiguration extends TbAbstractRelationActionN
TbDeleteRelationNodeConfiguration configuration = new TbDeleteRelationNodeConfiguration();
configuration.setDeleteForSingleEntity(true);
configuration.setDirection(EntitySearchDirection.FROM.name());
configuration.setRelationType("Contains");
configuration.setRelationTypePattern("Contains");
configuration.setEntityNamePattern("");
configuration.setEntityCacheExpiration(300);
return configuration;

8
rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js

File diff suppressed because one or more lines are too long

1
ui/src/app/locale/locale.constant-de_DE.json

@ -1559,6 +1559,7 @@
"widget-action": {
"action-cell-button": "Aktionszellenschaltfläche",
"row-click": "Klick auf Zeile",
"polygon-click": "Klick auf Polygon",
"marker-click": "Klick auf Marker",
"tooltip-tag-action": "Tooltip-Tag-Aktion"
}

1
ui/src/app/locale/locale.constant-en_US.json

@ -1564,6 +1564,7 @@
"widget-action": {
"action-cell-button": "Action cell button",
"row-click": "On row click",
"polygon-click": "On polygon click",
"marker-click": "On marker click",
"tooltip-tag-action": "Tooltip tag action"
}

3
ui/src/app/locale/locale.constant-es_ES.json

@ -1559,7 +1559,8 @@
"widget-action": {
"action-cell-button": "Botón de acción de celda",
"row-click": "Clic en la fila",
"marker-click": "Clic en el marcador",
"polygon-click": "Clic en la fila",
"marker-click": "Clic en el polígono",
"tooltip-tag-action": "Acción de etiqueta para globo de ayuda"
}
},

1
ui/src/app/locale/locale.constant-fr_FR.json

@ -336,6 +336,7 @@
"action-cell-button": "Action cell button",
"marker-click": "On marker click",
"row-click": "On row click",
"polygon-click": "On polygon click",
"tooltip-tag-action": "Tooltip tag action"
}
},

1
ui/src/app/locale/locale.constant-it_IT.json

@ -1425,6 +1425,7 @@
"widget-action": {
"action-cell-button": "Action cell button",
"row-click": "On row click",
"polygon-click": "On polygon click",
"marker-click": "On marker click",
"tooltip-tag-action": "Tooltip tag action"
}

1
ui/src/app/locale/locale.constant-ja_JA.json

@ -1442,6 +1442,7 @@
"widget-action": {
"action-cell-button": "アクションセルボタン",
"row-click": "行のクリック",
"polygon-click": "ポリゴンクリック",
"marker-click": "マーカークリック",
"tooltip-tag-action": "ツールチップのタグアクション"
}

1
ui/src/app/locale/locale.constant-ko_KR.json

@ -1318,6 +1318,7 @@
"widget-action": {
"action-cell-button": "Action cell button",
"row-click": "On row click",
"polygon-click": "On polygon click",
"marker-click": "On marker click",
"tooltip-tag-action": "Tooltip tag action"
}

1
ui/src/app/locale/locale.constant-ru_RU.json

@ -1558,6 +1558,7 @@
"action-cell-button": "Кнопка действия ячейки",
"row-click": "Действий при щелчке на строку",
"marker-click": "Действия при щелчке на указателе",
"polygon-click": "Действия при щелчке на полигон",
"tooltip-tag-action": "Действие при подсказке"
}
},

3
ui/src/app/locale/locale.constant-tr_TR.json

@ -1524,7 +1524,8 @@
"widget-action": {
"action-cell-button": "Eylem hücre butonu",
"row-click": "Satır tıklama eylemi",
"marker-click": "İşaretçi tıklama eylemi",
"polygon-click": "Satır tıklama eylemi",
"marker-click": "Çokgen tıklama eylemi",
"tooltip-tag-action": "İpucu etiket eylemi"
}
},

1
ui/src/app/locale/locale.constant-zh_CN.json

@ -1429,6 +1429,7 @@
"action-cell-button": "动作单元格按钮",
"row-click": "点击行",
"marker-click": "点击标记",
"polygon-click": "单击多边形",
"tooltip-tag-action": "提示标签动作"
}
},

28
ui/src/app/widget/lib/google-map.js

@ -315,7 +315,7 @@ export default class TbGoogleMap {
}
createPolygon(latLangs, settings) {
createPolygon(latLangs, settings, location, onClickListener, markerArgs) {
let polygon = new google.maps.Polygon({
map: this.map,
paths: latLangs,
@ -325,6 +325,32 @@ export default class TbGoogleMap {
fillOpacity: settings.polygonOpacity,
strokeWeight: settings.polygonStrokeWeight
});
//initialize-tooltip
let popup = new google.maps.InfoWindow({
content: ''
});
if (!this.tooltips) this.tooltips = [];
this.tooltips.push({
markerArgs: markerArgs,
popup: popup,
locationSettings: settings,
dsIndex: location.dsIndex
});
if (onClickListener) {
google.maps.event.addListener(polygon, 'click', function (event) {
if (settings.displayTooltip) {
if (!polygon.anchor) {
polygon.anchor = new google.maps.MVCObject();
}
polygon.anchor.set("position", event.latLng);
popup.open(this.map, polygon.anchor);
}
onClickListener();
});
}
return polygon;
}
/* eslint-disable no-undef */

105
ui/src/app/widget/lib/map-widget2.js

@ -233,7 +233,6 @@ export default class TbMapWidgetV2 {
}
update() {
var tbMap = this;
@ -381,6 +380,17 @@ export default class TbMapWidgetV2 {
tbMap.ctx.actionsApi.handleWidgetAction($event, descriptors[0], entityId, entityName);
}
}
function locationPolygonClick($event, location) {
var descriptors = tbMap.ctx.actionsApi.getActionDescriptors('polygonClick');
if (descriptors.length) {
var datasource = tbMap.subscription.datasources[location.dsIndex];
var entityId = {};
entityId.id = datasource.entityId;
entityId.entityType = datasource.entityType;
var entityName = datasource.entityName;
tbMap.ctx.actionsApi.handleWidgetAction($event, descriptors[0], entityId, entityName);
}
}
function updateLocation(location, data, dataMap) {
var locationChanged = false;
@ -427,21 +437,6 @@ export default class TbMapWidgetV2 {
locationChanged = true;
}
}
if (location.settings.showPolygon && dataMap.dsDataMap[location.dsIndex][location.settings.polygonKeyName] !== null) {
let polygonLatLngsRaw = angular.fromJson(dataMap.dsDataMap[location.dsIndex][location.settings.polygonKeyName]);
let polygonLatLngs = !polygonLatLngsRaw || mapPolygonArray(polygonLatLngsRaw);
if (!location.polygon && polygonLatLngs.length > 0) {
location.polygon = tbMap.map.createPolygon(polygonLatLngs, location.settings);
tbMap.polygons.push(location.polygon);
} else if (polygonLatLngs.length > 0) {
let prevPolygonArr = tbMap.map.getPolygonLatLngs(location.polygon);
if (!prevPolygonArr || !arraysEqual(prevPolygonArr, polygonLatLngs)) {
tbMap.map.setPolygonLatLngs(location.polygon, polygonLatLngs);
}
}
}
}
if (location.marker) {
updateLocationStyle(location, dataMap);
@ -451,6 +446,25 @@ export default class TbMapWidgetV2 {
return locationChanged;
}
function createUpdatePolygon(location, dataMap) {
if (location.settings.showPolygon && dataMap.dsDataMap[location.dsIndex][location.settings.polygonKeyName] !== null) {
let polygonLatLngsRaw = angular.fromJson(dataMap.dsDataMap[location.dsIndex][location.settings.polygonKeyName]);
let polygonLatLngs = !polygonLatLngsRaw || mapPolygonArray(polygonLatLngsRaw);
if (!location.polygon && polygonLatLngs.length > 0) {
location.polygon = tbMap.map.createPolygon(polygonLatLngs, location.settings, location, function (event) {
tbMap.callbacks.onLocationClick(location);
locationPolygonClick(event, location);
}, [location.dsIndex]);
tbMap.polygons.push(location.polygon);
} else if (polygonLatLngs.length > 0) {
let prevPolygonArr = tbMap.map.getPolygonLatLngs(location.polygon);
if (!prevPolygonArr || !arraysEqual(prevPolygonArr, polygonLatLngs)) {
tbMap.map.setPolygonLatLngs(location.polygon, polygonLatLngs);
}
}
}
}
function loadLocations(data, datasources) {
var bounds = tbMap.map.createBounds();
tbMap.locations = [];
@ -459,7 +473,6 @@ export default class TbMapWidgetV2 {
var currentDatasourceIndex = -1;
var latIndex = -1;
var lngIndex = -1;
for (var i = 0; i < data.length; i++) {
var dataKeyData = data[i];
var dataKey = dataKeyData.dataKey;
@ -480,7 +493,6 @@ export default class TbMapWidgetV2 {
} else if (nameToCheck === tbMap.locationSettings.lngKeyName) {
lngIndex = i;
}
if (latIndex > -1 && lngIndex > -1) {
var location = {
latIndex: latIndex,
@ -499,6 +511,7 @@ export default class TbMapWidgetV2 {
}
tbMap.locations.push(location);
updateLocation(location, data, dataMap);
if (location.polyline) {
tbMap.map.extendBounds(bounds, location.polyline);
} else if (location.marker) {
@ -507,8 +520,28 @@ export default class TbMapWidgetV2 {
latIndex = -1;
lngIndex = -1;
}
}
data.forEach(function (dataEl, index) {
let nameToCheck;
if (dataEl.dataKey.locationAttrName) {
nameToCheck = dataEl.dataKey.locationAttrName;
} else {
nameToCheck = dataEl.dataKey.label;
}
if (nameToCheck === tbMap.locationSettings.polygonKeyName) {
let location = {
polIndex: index,
settings: angular.copy(tbMap.locationSettings)
};
location.dsIndex = datasources.findIndex(function (ds) {
return dataEl.datasource.entityId === ds.entityId;
});
tbMap.locations.push(location);
createUpdatePolygon(location, dataMap);
tbMap.map.extendBounds(bounds, location.polygon);
}
});
tbMap.map.fitBounds(bounds);
}
@ -532,10 +565,13 @@ export default class TbMapWidgetV2 {
for (var p = 0; p < tbMap.locations.length; p++) {
var location = tbMap.locations[p];
locationsChanged |= updateLocation(location, data, dataMap);
createUpdatePolygon(location, dataMap);
if (location.polyline) {
tbMap.map.extendBounds(bounds, location.polyline);
} else if (location.marker) {
tbMap.map.extendBoundsWithMarker(bounds, location.marker);
} else if (location.polygon) {
tbMap.map.extendBounds(bounds, location.polygon);
}
}
if (locationsChanged && tbMap.initBounds) {
@ -585,23 +621,30 @@ export default class TbMapWidgetV2 {
}
}
}
}
resize() {
if (this.map && this.map.inited()) {
this.map.invalidateSize();
let map = this.map;
if (this.locations && this.locations.length > 0) {
var bounds = this.map.createBounds();
for (var m = 0; m < this.markers.length; m++) {
this.map.extendBoundsWithMarker(bounds, this.markers[m]);
map.invalidateSize();
var bounds = map.createBounds();
if (this.markers && this.markers.length>0) {
this.markers.forEach(function (marker) {
map.extendBoundsWithMarker(bounds, marker);
});
}
if (this.polylines) {
for (var p = 0; p < this.polylines.length; p++) {
this.map.extendBounds(bounds, this.polylines[p]);
}
if (this.polylines && this.polylines.length>0) {
this.polylines.forEach(function (polyline) {
map.extendBounds(bounds, polyline);
})
}
if (this.polygons && this.polygons.length > 0) {
this.polygons.forEach(function (polygon) {
map.extendBounds(bounds, polygon);
})
}
this.map.fitBounds(bounds);
map.fitBounds(bounds);
}
}
}
@ -638,6 +681,10 @@ export default class TbMapWidgetV2 {
name: 'widget-action.marker-click',
multiple: false
},
'polygonClick': {
name: 'widget-action.polygon-click',
multiple: false
},
'tooltipAction': {
name: 'widget-action.tooltip-tag-action',
multiple: true

12
ui/src/app/widget/lib/openstreet-map.js

@ -190,7 +190,7 @@ export default class TbOpenStreetMap {
this.map.removeLayer(polyline);
}
createPolygon(latLangs, settings) {
createPolygon(latLangs, settings, location, onClickListener, markerArgs) {
let polygon = L.polygon(latLangs, {
fill: true,
fillColor: settings.polygonColor,
@ -199,6 +199,13 @@ export default class TbOpenStreetMap {
fillOpacity: settings.polygonOpacity,
opacity: settings.polygonStrokeOpacity
}).addTo(this.map);
if (settings.displayTooltip) {
this.createTooltip(polygon, location.dsIndex, settings, markerArgs);
}
if (onClickListener) {
polygon.on('click', onClickListener);
}
return polygon;
}
@ -224,6 +231,7 @@ export default class TbOpenStreetMap {
setPolygonLatLngs(polygon, latLngs) {
polygon.setLatLngs(latLngs);
polygon.redraw();
}
fitBounds(bounds) {
@ -272,7 +280,7 @@ export default class TbOpenStreetMap {
}
extendBounds(bounds, polyline) {
if (polyline && polyline.getLatLngs()) {
if (polyline && polyline.getLatLngs() && polyline.getBounds()) {
bounds.extend(polyline.getBounds());
}
}

25
ui/src/app/widget/lib/tencent-map.js

@ -331,7 +331,7 @@ export default class TbTencentMap {
}
/* eslint-disable no-undef */
createPolygon(latLangs, settings) {
createPolygon(latLangs, settings, location, onClickListener, markerArgs) {
let polygon = new qq.maps.Polygon({
map: this.map,
path: latLangs,
@ -339,8 +339,31 @@ export default class TbTencentMap {
fillColor: settings.polygonColor,
strokeWeight: settings.polygonStrokeWeight
});
//initialize-tooltip
let popup = new qq.maps.InfoWindow({
content: ''
});
if (!this.tooltips) this.tooltips = [];
this.tooltips.push({
markerArgs: markerArgs,
popup: popup,
locationSettings: settings,
dsIndex: location.dsIndex
});
if (onClickListener) {
qq.maps.event.addListener(polygon, 'click', function (event) {
if (settings.displayTooltip) {
popup.setMap(this.map);
popup.setPosition(event.latLng);
popup.open();
}
onClickListener();
});
}
return polygon;
}
/* eslint-disable no-undef */
removePolygon (polygon) {

34
ui/src/app/widget/lib/widget-utils.js

@ -73,22 +73,24 @@ export function processPattern(pattern, datasources, dsIndex) {
export function fillPattern(pattern, replaceInfo, data) {
var text = angular.copy(pattern);
for (var v = 0; v < replaceInfo.variables.length; v++) {
var variableInfo = replaceInfo.variables[v];
var txtVal = '';
if (variableInfo.dataKeyIndex > -1 && data[variableInfo.dataKeyIndex]) {
var varData = data[variableInfo.dataKeyIndex].data;
if (varData.length > 0) {
var val = varData[varData.length - 1][1];
if (isNumber(val)) {
txtVal = padValue(val, variableInfo.valDec, 0);
} else {
txtVal = val;
}
}
}
text = text.split(variableInfo.variable).join(txtVal);
}
if (replaceInfo) {
for (var v = 0; v < replaceInfo.variables.length; v++) {
var variableInfo = replaceInfo.variables[v];
var txtVal = '';
if (variableInfo.dataKeyIndex > -1 && data[variableInfo.dataKeyIndex]) {
var varData = data[variableInfo.dataKeyIndex].data;
if (varData.length > 0) {
var val = varData[varData.length - 1][1];
if (isNumber(val)) {
txtVal = padValue(val, variableInfo.valDec, 0);
} else {
txtVal = val;
}
}
}
text = text.split(variableInfo.variable).join(txtVal);
}
}
return text;
}

Loading…
Cancel
Save