Browse Source

Merge branch 'master' of https://github.com/thingsboard/thingsboard into editable-polygons

 Conflicts:
	ui-ngx/package-lock.json
	ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts
	ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts
	ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts
	ui-ngx/src/app/modules/home/components/widget/lib/maps/polygon.ts
	ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts
pull/3280/head
Mrkartoshka 6 years ago
parent
commit
5596bd9f61
  1. 20
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java
  2. 7
      application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java
  3. 18
      application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubCtx.java
  4. 7
      application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityDataUpdate.java
  5. 6
      application/src/main/resources/thingsboard.yml
  6. 30
      dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java
  7. 65
      dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java
  8. 40
      dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultQueryLogComponent.java
  9. 22
      dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java
  10. 21
      dao/src/main/java/org/thingsboard/server/dao/sql/query/QueryLogComponent.java
  11. 14921
      ui-ngx/package-lock.json
  12. 2
      ui-ngx/package.json
  13. 6
      ui-ngx/src/app/core/api/alarm-data-subscription.ts
  14. 8
      ui-ngx/src/app/core/api/alarm-data.service.ts
  15. 6
      ui-ngx/src/app/core/api/entity-data-subscription.ts
  16. 1
      ui-ngx/src/app/core/api/widget-api.models.ts
  17. 17
      ui-ngx/src/app/core/api/widget-subscription.ts
  18. 36
      ui-ngx/src/app/core/http/entity.service.ts
  19. 22
      ui-ngx/src/app/core/utils.ts
  20. 384
      ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts
  21. 53
      ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts
  22. 11
      ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts
  23. 162
      ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts
  24. 90
      ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts
  25. 11
      ui-ngx/src/app/modules/home/components/widget/lib/maps/polygon.ts
  26. 17
      ui-ngx/src/app/modules/home/components/widget/lib/maps/polyline.ts
  27. 2
      ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/google-map.ts
  28. 2
      ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/here-map.ts
  29. 6
      ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts
  30. 52
      ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/index.ts
  31. 2
      ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/openstreet-map.ts
  32. 2
      ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/tencent-map.ts
  33. 3
      ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts
  34. 2
      ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.ts
  35. 20
      ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.ts
  36. 21
      ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts
  37. 3
      ui-ngx/src/app/shared/components/entity/entity-select.component.html
  38. 6
      ui-ngx/src/app/shared/components/entity/entity-select.component.ts
  39. 10
      ui-ngx/src/app/shared/models/entity-type.models.ts
  40. 17
      ui-ngx/src/app/shared/models/query/query.models.ts
  41. 1
      ui-ngx/src/assets/locale/locale.constant-en_US.json

20
application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java

@ -128,6 +128,8 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
private long dynamicPageLinkRefreshInterval;
@Value("${server.ws.dynamic_page_link.refresh_pool_size:1}")
private int dynamicPageLinkRefreshPoolSize;
@Value("${server.ws.max_entities_per_data_subscription:1000}")
private int maxEntitiesPerDataSubscription;
@Value("${server.ws.max_entities_per_alarm_subscription:1000}")
private int maxEntitiesPerAlarmSubscription;
@ -220,7 +222,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
} else if (cmd.getTsCmd() != null) {
handleTimeSeriesCmd(theCtx, cmd.getTsCmd());
} else if (!theCtx.isInitialDataSent()) {
EntityDataUpdate update = new EntityDataUpdate(theCtx.getCmdId(), theCtx.getData(), null);
EntityDataUpdate update = new EntityDataUpdate(theCtx.getCmdId(), theCtx.getData(), null, theCtx.getMaxEntitiesPerDataSubscription());
wsService.sendWsMsg(theCtx.getSessionId(), update);
theCtx.setInitialDataSent(true);
}
@ -298,7 +300,8 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
private TbEntityDataSubCtx createSubCtx(TelemetryWebSocketSessionRef sessionRef, EntityDataCmd cmd) {
Map<Integer, TbAbstractDataSubCtx> sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>());
TbEntityDataSubCtx ctx = new TbEntityDataSubCtx(serviceId, wsService, entityService, localSubscriptionService, attributesService, stats, sessionRef, cmd.getCmdId());
TbEntityDataSubCtx ctx = new TbEntityDataSubCtx(serviceId, wsService, entityService, localSubscriptionService,
attributesService, stats, sessionRef, cmd.getCmdId(), maxEntitiesPerDataSubscription);
ctx.setAndResolveQuery(cmd.getQuery());
sessionSubs.put(cmd.getCmdId(), ctx);
return ctx;
@ -306,7 +309,8 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
private TbAlarmDataSubCtx createSubCtx(TelemetryWebSocketSessionRef sessionRef, AlarmDataCmd cmd) {
Map<Integer, TbAbstractDataSubCtx> sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>());
TbAlarmDataSubCtx ctx = new TbAlarmDataSubCtx(serviceId, wsService, entityService, localSubscriptionService, attributesService, stats, alarmService, sessionRef, cmd.getCmdId(), maxEntitiesPerAlarmSubscription);
TbAlarmDataSubCtx ctx = new TbAlarmDataSubCtx(serviceId, wsService, entityService, localSubscriptionService,
attributesService, stats, alarmService, sessionRef, cmd.getCmdId(), maxEntitiesPerAlarmSubscription);
ctx.setAndResolveQuery(cmd.getQuery());
sessionSubs.put(cmd.getCmdId(), ctx);
return ctx;
@ -372,10 +376,10 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
});
EntityDataUpdate update;
if (!ctx.isInitialDataSent()) {
update = new EntityDataUpdate(ctx.getCmdId(), ctx.getData(), null);
update = new EntityDataUpdate(ctx.getCmdId(), ctx.getData(), null, ctx.getMaxEntitiesPerDataSubscription());
ctx.setInitialDataSent(true);
} else {
update = new EntityDataUpdate(ctx.getCmdId(), null, ctx.getData().getData());
update = new EntityDataUpdate(ctx.getCmdId(), null, ctx.getData().getData(), ctx.getMaxEntitiesPerDataSubscription());
}
wsService.sendWsMsg(ctx.getSessionId(), update);
if (subscribe) {
@ -422,10 +426,10 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
});
EntityDataUpdate update;
if (!ctx.isInitialDataSent()) {
update = new EntityDataUpdate(ctx.getCmdId(), ctx.getData(), null);
update = new EntityDataUpdate(ctx.getCmdId(), ctx.getData(), null, ctx.getMaxEntitiesPerDataSubscription());
ctx.setInitialDataSent(true);
} else {
update = new EntityDataUpdate(ctx.getCmdId(), null, ctx.getData().getData());
update = new EntityDataUpdate(ctx.getCmdId(), null, ctx.getData().getData(), ctx.getMaxEntitiesPerDataSubscription());
}
wsService.sendWsMsg(ctx.getSessionId(), update);
ctx.createSubscriptions(latestCmd.getKeys(), true);
@ -440,7 +444,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
}, wsCallBackExecutor);
} else {
if (!ctx.isInitialDataSent()) {
EntityDataUpdate update = new EntityDataUpdate(ctx.getCmdId(), ctx.getData(), null);
EntityDataUpdate update = new EntityDataUpdate(ctx.getCmdId(), ctx.getData(), null, ctx.getMaxEntitiesPerDataSubscription());
wsService.sendWsMsg(ctx.getSessionId(), update);
ctx.setInitialDataSent(true);
}

7
application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java

@ -57,6 +57,7 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledFuture;
import java.util.function.Function;
@ -98,9 +99,9 @@ public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends
this.stats = stats;
this.sessionRef = sessionRef;
this.cmdId = cmdId;
this.subToEntityIdMap = new HashMap<>();
this.subToDynamicValueKeySet = new HashSet<>();
this.dynamicValues = new HashMap<>();
this.subToEntityIdMap = new ConcurrentHashMap<>();
this.subToDynamicValueKeySet = ConcurrentHashMap.newKeySet();
this.dynamicValues = new ConcurrentHashMap<>();
}
public void setAndResolveQuery(T query) {

18
application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubCtx.java

@ -15,13 +15,10 @@
*/
package org.thingsboard.server.service.subscription;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.EntityData;
import org.thingsboard.server.common.data.query.EntityDataQuery;
import org.thingsboard.server.common.data.query.EntityKey;
@ -38,8 +35,6 @@ import org.thingsboard.server.service.telemetry.cmd.v2.TimeSeriesCmd;
import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
@ -48,8 +43,6 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ScheduledFuture;
import java.util.function.Function;
import java.util.stream.Collectors;
@Slf4j
@ -63,11 +56,14 @@ public class TbEntityDataSubCtx extends TbAbstractDataSubCtx<EntityDataQuery> {
private boolean initialDataSent;
private TimeSeriesCmd curTsCmd;
private LatestValueCmd latestValueCmd;
@Getter
private final int maxEntitiesPerDataSubscription;
public TbEntityDataSubCtx(String serviceId, TelemetryWebSocketService wsService, EntityService entityService,
TbLocalSubscriptionService localSubscriptionService, AttributesService attributesService,
SubscriptionServiceStatistics stats, TelemetryWebSocketSessionRef sessionRef, int cmdId) {
SubscriptionServiceStatistics stats, TelemetryWebSocketSessionRef sessionRef, int cmdId, int maxEntitiesPerDataSubscription) {
super(serviceId, wsService, entityService, localSubscriptionService, attributesService, stats, sessionRef, cmdId);
this.maxEntitiesPerDataSubscription = maxEntitiesPerDataSubscription;
}
@Override
@ -120,7 +116,7 @@ public class TbEntityDataSubCtx extends TbAbstractDataSubCtx<EntityDataQuery> {
if (!latestUpdate.isEmpty()) {
Map<EntityKeyType, Map<String, TsValue>> latestMap = Collections.singletonMap(keyType, latestUpdate);
entityData = new EntityData(entityId, latestMap, null);
wsService.sendWsMsg(sessionId, new EntityDataUpdate(cmdId, null, Collections.singletonList(entityData)));
wsService.sendWsMsg(sessionId, new EntityDataUpdate(cmdId, null, Collections.singletonList(entityData), maxEntitiesPerDataSubscription));
}
}
@ -163,7 +159,7 @@ public class TbEntityDataSubCtx extends TbAbstractDataSubCtx<EntityDataQuery> {
Map<String, TsValue[]> tsMap = new HashMap<>();
tsUpdate.forEach((key, tsValue) -> tsMap.put(key, tsValue.toArray(new TsValue[tsValue.size()])));
entityData = new EntityData(entityId, null, tsMap);
wsService.sendWsMsg(sessionId, new EntityDataUpdate(cmdId, null, Collections.singletonList(entityData)));
wsService.sendWsMsg(sessionId, new EntityDataUpdate(cmdId, null, Collections.singletonList(entityData), maxEntitiesPerDataSubscription));
}
}
@ -207,7 +203,7 @@ public class TbEntityDataSubCtx extends TbAbstractDataSubCtx<EntityDataQuery> {
);
}
}
wsService.sendWsMsg(sessionRef.getSessionId(), new EntityDataUpdate(cmdId, data, null));
wsService.sendWsMsg(sessionRef.getSessionId(), new EntityDataUpdate(cmdId, data, null, maxEntitiesPerDataSubscription));
subIdsToCancel.forEach(subId -> localSubscriptionService.cancelSubscription(getSessionId(), subId));
subsToAdd.forEach(localSubscriptionService::addSubscription);
}

7
application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityDataUpdate.java

@ -18,6 +18,7 @@ package org.thingsboard.server.service.telemetry.cmd.v2;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.EntityData;
import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode;
@ -27,8 +28,12 @@ import java.util.List;
public class EntityDataUpdate extends DataUpdate<EntityData> {
public EntityDataUpdate(int cmdId, PageData<EntityData> data, List<EntityData> update) {
@Getter
private long allowedEntities;
public EntityDataUpdate(int cmdId, PageData<EntityData> data, List<EntityData> update, long allowedEntities) {
super(cmdId, data, update, SubscriptionErrorCode.NO_ERROR.getCode(), null);
this.allowedEntities = allowedEntities;
}
public EntityDataUpdate(int cmdId, int errorCode, String errorMsg) {

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

@ -50,7 +50,8 @@ server:
refresh_interval: "${TB_SERVER_WS_DYNAMIC_PAGE_LINK_REFRESH_INTERVAL_SEC:60}"
refresh_pool_size: "${TB_SERVER_WS_DYNAMIC_PAGE_LINK_REFRESH_POOL_SIZE:1}"
max_per_user: "${TB_SERVER_WS_DYNAMIC_PAGE_LINK_MAX_PER_USER:10}"
max_entities_per_alarm_subscription: "${TB_SERVER_WS_MAX_ENTITIES_PER_ALARM_SUBSCRIPTION:1000}"
max_entities_per_data_subscription: "${TB_SERVER_WS_MAX_ENTITIES_PER_DATA_SUBSCRIPTION:10000}"
max_entities_per_alarm_subscription: "${TB_SERVER_WS_MAX_ENTITIES_PER_ALARM_SUBSCRIPTION:10000}"
rest:
limits:
tenant:
@ -273,7 +274,8 @@ sql:
# Specify whether to remove null characters from strValue of attributes and timeseries before insert
remove_null_chars: "${SQL_REMOVE_NULL_CHARS:true}"
# Specify whether to log database queries and their parameters generated by entity query repository
log_entity_queries: "${SQL_LOG_ENTITY_QUERIES:false}"
log_queries: "${SQL_LOG_QUERIES:false}"
log_queries_threshold: "${SQL_LOG_QUERIES_THRESHOLD:5000}"
postgres:
# Specify partitioning size for timestamp key-value storage. Example: DAYS, MONTHS, YEARS, INDEFINITE.
ts_key_value_partitioning: "${SQL_POSTGRES_TS_KV_PARTITIONING:MONTHS}"

30
dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java

@ -43,6 +43,7 @@ import org.thingsboard.server.dao.model.ModelConstants;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@ -114,12 +115,12 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository {
protected final NamedParameterJdbcTemplate jdbcTemplate;
private final TransactionTemplate transactionTemplate;
@Value("${sql.log_entity_queries:false}")
private boolean logSqlQueries;
private final DefaultQueryLogComponent queryLog;
public DefaultAlarmQueryRepository(NamedParameterJdbcTemplate jdbcTemplate, TransactionTemplate transactionTemplate) {
public DefaultAlarmQueryRepository(NamedParameterJdbcTemplate jdbcTemplate, TransactionTemplate transactionTemplate, DefaultQueryLogComponent queryLog) {
this.jdbcTemplate = jdbcTemplate;
this.transactionTemplate = transactionTemplate;
this.queryLog = queryLog;
}
@Override
@ -230,8 +231,17 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository {
if (!textSearchQuery.isEmpty()) {
mainQuery = String.format("select * from (%s) a WHERE %s", mainQuery, textSearchQuery);
}
String countQuery = mainQuery;
int totalElements = jdbcTemplate.queryForObject(String.format("select count(*) from (%s) result", countQuery), ctx, Integer.class);
String countQuery = String.format("select count(*) from (%s) result", mainQuery);
long queryTs = System.currentTimeMillis();
int totalElements;
try {
totalElements = jdbcTemplate.queryForObject(countQuery, ctx, Integer.class);
} finally {
queryLog.logQuery(ctx, countQuery, System.currentTimeMillis() - queryTs);
}
if (totalElements == 0) {
return AlarmDataAdapter.createAlarmData(pageLink, Collections.emptyList(), totalElements, orderedEntityIds);
}
String dataQuery = mainQuery + sortPart;
@ -239,10 +249,12 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository {
if (pageLink.getPageSize() > 0) {
dataQuery = String.format("%s limit %s offset %s", dataQuery, pageLink.getPageSize(), startIndex);
}
List<Map<String, Object>> rows = jdbcTemplate.queryForList(dataQuery, ctx);
if (logSqlQueries) {
log.info("QUERY: {}", dataQuery);
Arrays.asList(ctx.getParameterNames()).forEach(param -> log.info("QUERY PARAM: {}->{}", param, ctx.getValue(param)));
queryTs = System.currentTimeMillis();
List<Map<String, Object>> rows;
try {
rows = jdbcTemplate.queryForList(dataQuery, ctx);
} finally {
queryLog.logQuery(ctx, dataQuery, System.currentTimeMillis() - queryTs);
}
return AlarmDataAdapter.createAlarmData(pageLink, rows, totalElements, orderedEntityIds);
});

65
dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java

@ -17,12 +17,8 @@ package org.thingsboard.server.dao.sql.query;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.CustomerId;
@ -189,6 +185,22 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
" WHEN entity.entity_type = 'ENTITY_VIEW'" +
" THEN (select name from entity_view where id = entity_id)" +
" END as label";
private static final String SELECT_ADDITIONAL_INFO = " CASE" +
" WHEN entity.entity_type = 'TENANT'" +
" THEN (select additional_info from tenant where id = entity_id)" +
" WHEN entity.entity_type = 'CUSTOMER' " +
" THEN (select additional_info from customer where id = entity_id)" +
" WHEN entity.entity_type = 'USER'" +
" THEN (select additional_info from tb_user where id = entity_id)" +
" WHEN entity.entity_type = 'DASHBOARD'" +
" THEN (select '' from dashboard where id = entity_id)" +
" WHEN entity.entity_type = 'ASSET'" +
" THEN (select additional_info from asset where id = entity_id)" +
" WHEN entity.entity_type = 'DEVICE'" +
" THEN (select additional_info from device where id = entity_id)" +
" WHEN entity.entity_type = 'ENTITY_VIEW'" +
" THEN (select additional_info from entity_view where id = entity_id)" +
" END as additional_info";
static {
entityTableMap.put(EntityType.ASSET, "asset");
@ -221,13 +233,12 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
private final NamedParameterJdbcTemplate jdbcTemplate;
private final TransactionTemplate transactionTemplate;
private final DefaultQueryLogComponent queryLog;
@Value("${sql.log_entity_queries:false}")
private boolean logSqlQueries;
public DefaultEntityQueryRepository(NamedParameterJdbcTemplate jdbcTemplate, TransactionTemplate transactionTemplate) {
public DefaultEntityQueryRepository(NamedParameterJdbcTemplate jdbcTemplate, TransactionTemplate transactionTemplate, DefaultQueryLogComponent queryLog) {
this.jdbcTemplate = jdbcTemplate;
this.transactionTemplate = transactionTemplate;
this.queryLog = queryLog;
}
@Override
@ -238,11 +249,14 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
ctx.append(addEntityTableQuery(ctx, query.getEntityFilter()));
ctx.append(" e where ");
ctx.append(buildEntityWhere(ctx, query.getEntityFilter(), Collections.emptyList()));
if (logSqlQueries) {
log.info("QUERY: {}", ctx.getQuery());
Arrays.asList(ctx.getParameterNames()).forEach(param -> log.info("QUERY PARAM: {}->{}", param, ctx.getValue(param)));
}
return transactionTemplate.execute(status -> jdbcTemplate.queryForObject(ctx.getQuery(), ctx, Long.class));
return transactionTemplate.execute(status -> {
long startTs = System.currentTimeMillis();
try {
return jdbcTemplate.queryForObject(ctx.getQuery(), ctx, Long.class);
} finally {
queryLog.logQuery(ctx, ctx.getQuery(), System.currentTimeMillis() - startTs);
}
});
}
@Override
@ -313,7 +327,14 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
fromClauseCount = fromClauseData;
}
String countQuery = String.format("select count(id) %s", fromClauseCount);
int totalElements = jdbcTemplate.queryForObject(countQuery, ctx, Integer.class);
long startTs = System.currentTimeMillis();
int totalElements;
try {
totalElements = jdbcTemplate.queryForObject(countQuery, ctx, Integer.class);
} finally {
queryLog.logQuery(ctx, countQuery, System.currentTimeMillis() - startTs);
}
if (totalElements == 0) {
return new PageData<>();
@ -338,11 +359,13 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
if (pageLink.getPageSize() > 0) {
dataQuery = String.format("%s limit %s offset %s", dataQuery, pageLink.getPageSize(), startIndex);
}
if (logSqlQueries) {
log.info("QUERY: {}", dataQuery);
Arrays.asList(ctx.getParameterNames()).forEach(param -> log.info("QUERY PARAM: {}->{}", param, ctx.getValue(param)));
startTs = System.currentTimeMillis();
List<Map<String, Object>> rows;
try {
rows = jdbcTemplate.queryForList(dataQuery, ctx);
} finally {
queryLog.logQuery(ctx, countQuery, System.currentTimeMillis() - startTs);
}
List<Map<String, Object>> rows = jdbcTemplate.queryForList(dataQuery, ctx);
return EntityDataAdapter.createEntityData(pageLink, selectionMapping, rows, totalElements);
});
}
@ -437,7 +460,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
//TODO: fetch last level only.
//TODO: fetch distinct records.
String lvlFilter = getLvlFilter(entityFilter.getMaxLevel());
String selectFields = "SELECT tenant_id, customer_id, id, created_time, type, name "
String selectFields = "SELECT tenant_id, customer_id, id, created_time, type, name, additional_info "
+ (entityType.equals(EntityType.ENTITY_VIEW) ? "" : ", label ")
+ "FROM " + entityType.name() + " WHERE id in ( SELECT entity_id";
String from = getQueryTemplate(entityFilter.getDirection());
@ -470,7 +493,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
+ SELECT_TYPE + ", " + SELECT_NAME + ", " + SELECT_LABEL + ", " +
SELECT_FIRST_NAME + ", " + SELECT_LAST_NAME + ", " + SELECT_EMAIL + ", " + SELECT_REGION + ", " +
SELECT_TITLE + ", " + SELECT_COUNTRY + ", " + SELECT_STATE + ", " + SELECT_CITY + ", " +
SELECT_ADDRESS + ", " + SELECT_ADDRESS_2 + ", " + SELECT_ZIP + ", " + SELECT_PHONE +
SELECT_ADDRESS + ", " + SELECT_ADDRESS_2 + ", " + SELECT_ZIP + ", " + SELECT_PHONE + ", " + SELECT_ADDITIONAL_INFO +
", entity.entity_type as entity_type";
String from = getQueryTemplate(entityFilter.getDirection());
ctx.addUuidParameter("relation_root_id", rootId.getId());
@ -557,7 +580,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
if (!StringUtils.isEmpty(searchText) && !selectionMapping.isEmpty()) {
String lowerSearchText = "%" + searchText.toLowerCase() + "%";
ctx.addStringParameter("lowerSearchTextParam", lowerSearchText);
List<String> searchAliases = selectionMapping.stream().map(EntityKeyMapping::getValueAlias).collect(Collectors.toList());
List<String> searchAliases = selectionMapping.stream().filter(EntityKeyMapping::isSearchable).map(EntityKeyMapping::getValueAlias).collect(Collectors.toList());
String searchAliasesExpression;
if (searchAliases.size() > 1) {
searchAliasesExpression = "CONCAT(" + String.join(" , ", searchAliases) + ")";

40
dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultQueryLogComponent.java

@ -0,0 +1,40 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.sql.query;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Component
@Slf4j
public class DefaultQueryLogComponent implements QueryLogComponent {
@Value("${sql.log_queries:false}")
private boolean logSqlQueries;
@Value("${sql.log_queries_threshold:5000}")
private long logQueriesThreshold;
@Override
public void logQuery(QueryContext ctx, String query, long duration) {
if (logSqlQueries && duration > logQueriesThreshold) {
log.info("QUERY: {} took {}ms", query, duration);
Arrays.asList(ctx.getParameterNames()).forEach(param -> log.info("QUERY PARAM: {} -> {}", param, ctx.getValue(param)));
}
}
}

22
dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java

@ -71,15 +71,17 @@ public class EntityKeyMapping {
private static final String ADDRESS_2 = "address2";
private static final String ZIP = "zip";
private static final String PHONE = "phone";
private static final String ADDITIONAL_INFO = "additionalInfo";
public static final List<String> typedEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE);
public static final List<String> commonEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME);
public static final List<String> typedEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, ADDITIONAL_INFO);
public static final List<String> widgetEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME);
public static final List<String> commonEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, ADDITIONAL_INFO);
public static final List<String> dashboardEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, TITLE);
public static final List<String> labeledEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, LABEL);
public static final List<String> contactBasedEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, EMAIL, TITLE, COUNTRY, STATE, CITY, ADDRESS, ADDRESS_2, ZIP, PHONE);
public static final List<String> labeledEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, LABEL, ADDITIONAL_INFO);
public static final List<String> contactBasedEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, EMAIL, TITLE, COUNTRY, STATE, CITY, ADDRESS, ADDRESS_2, ZIP, PHONE, ADDITIONAL_INFO);
public static final Set<String> commonEntityFieldsSet = new HashSet<>(commonEntityFields);
public static final Set<String> relationQueryEntityFieldsSet = new HashSet<>(Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, LABEL, FIRST_NAME, LAST_NAME, EMAIL, REGION, TITLE, COUNTRY, STATE, CITY, ADDRESS, ADDRESS_2, ZIP, PHONE));
public static final Set<String> relationQueryEntityFieldsSet = new HashSet<>(Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, LABEL, FIRST_NAME, LAST_NAME, EMAIL, REGION, TITLE, COUNTRY, STATE, CITY, ADDRESS, ADDRESS_2, ZIP, PHONE, ADDITIONAL_INFO));
static {
allowedEntityFieldMap.put(EntityType.DEVICE, new HashSet<>(labeledEntityFields));
@ -90,13 +92,13 @@ public class EntityKeyMapping {
allowedEntityFieldMap.get(EntityType.TENANT).add(REGION);
allowedEntityFieldMap.put(EntityType.CUSTOMER, new HashSet<>(contactBasedEntityFields));
allowedEntityFieldMap.put(EntityType.USER, new HashSet<>(Arrays.asList(CREATED_TIME, FIRST_NAME, LAST_NAME, EMAIL)));
allowedEntityFieldMap.put(EntityType.USER, new HashSet<>(Arrays.asList(CREATED_TIME, FIRST_NAME, LAST_NAME, EMAIL, ADDITIONAL_INFO)));
allowedEntityFieldMap.put(EntityType.DASHBOARD, new HashSet<>(dashboardEntityFields));
allowedEntityFieldMap.put(EntityType.RULE_CHAIN, new HashSet<>(commonEntityFields));
allowedEntityFieldMap.put(EntityType.RULE_NODE, new HashSet<>(commonEntityFields));
allowedEntityFieldMap.put(EntityType.WIDGET_TYPE, new HashSet<>(commonEntityFields));
allowedEntityFieldMap.put(EntityType.WIDGETS_BUNDLE, new HashSet<>(commonEntityFields));
allowedEntityFieldMap.put(EntityType.WIDGET_TYPE, new HashSet<>(widgetEntityFields));
allowedEntityFieldMap.put(EntityType.WIDGETS_BUNDLE, new HashSet<>(widgetEntityFields));
entityFieldColumnMap.put(CREATED_TIME, ModelConstants.CREATED_TIME_PROPERTY);
entityFieldColumnMap.put(ENTITY_TYPE, ModelConstants.ENTITY_TYPE_PROPERTY);
@ -115,6 +117,7 @@ public class EntityKeyMapping {
entityFieldColumnMap.put(ADDRESS_2, ModelConstants.ADDRESS2_PROPERTY);
entityFieldColumnMap.put(ZIP, ModelConstants.ZIP_PROPERTY);
entityFieldColumnMap.put(PHONE, ModelConstants.PHONE_PROPERTY);
entityFieldColumnMap.put(ADDITIONAL_INFO, ModelConstants.ADDITIONAL_INFO_PROPERTY);
Map<String, String> contactBasedAliases = new HashMap<>();
contactBasedAliases.put(NAME, TITLE);
@ -140,6 +143,7 @@ public class EntityKeyMapping {
private String alias;
private boolean isLatest;
private boolean isSelection;
private boolean isSearchable;
private boolean isSortOrder;
private boolean ignore = false;
private List<KeyFilter> keyFilters;
@ -304,6 +308,7 @@ public class EntityKeyMapping {
EntityKeyMapping mapping = new EntityKeyMapping();
mapping.setLatest(false);
mapping.setSelection(true);
mapping.setSearchable(!key.getKey().equals(ADDITIONAL_INFO));
mapping.setEntityKey(key);
return mapping;
}
@ -312,6 +317,7 @@ public class EntityKeyMapping {
key -> {
EntityKeyMapping mapping = new EntityKeyMapping();
mapping.setLatest(true);
mapping.setSearchable(true);
mapping.setSelection(true);
mapping.setEntityKey(key);
return mapping;

21
dao/src/main/java/org/thingsboard/server/dao/sql/query/QueryLogComponent.java

@ -0,0 +1,21 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.sql.query;
public interface QueryLogComponent {
void logQuery(QueryContext ctx, String query, long duration);
}

14921
ui-ngx/package-lock.json

File diff suppressed because it is too large

2
ui-ngx/package.json

@ -107,7 +107,7 @@
"@types/js-beautify": "^1.8.2",
"@types/jstree": "^3.3.40",
"@types/jszip": "^3.4.1",
"@types/leaflet": "^1.5.12",
"@types/leaflet": "^1.5.17",
"@types/leaflet-markercluster": "^1.0.3",
"@types/leaflet-polylinedecorator": "^1.6.0",
"@types/lodash": "^4.14.151",

6
ui-ngx/src/app/core/api/alarm-data-subscription.ts

@ -31,7 +31,6 @@ import {
} from '@shared/models/query/query.models';
import { SubscriptionTimewindow } from '@shared/models/time/time.models';
import { AlarmDataListener } from '@core/api/alarm-data.service';
import { UtilsService } from '@core/services/utils.service';
import { PageData } from '@shared/models/page/page-data';
import { deepClone, isDefined, isDefinedAndNotNull, isObject } from '@core/utils';
import { simulatedAlarm } from '@shared/models/alarm.models';
@ -68,8 +67,7 @@ export class AlarmDataSubscription {
constructor(public alarmDataSubscriptionOptions: AlarmDataSubscriptionOptions,
private listener: AlarmDataListener,
private telemetryService: TelemetryService,
private utils: UtilsService) {
private telemetryService: TelemetryService) {
}
public unsubscribe() {
@ -166,7 +164,7 @@ export class AlarmDataSubscription {
private onPageData(pageData: PageData<AlarmData>, allowedEntities: number, totalEntities: number) {
this.pageData = pageData;
this.resetData();
this.listener.alarmsLoaded(pageData, this.alarmDataSubscriptionOptions.pageLink, allowedEntities, totalEntities);
this.listener.alarmsLoaded(pageData, allowedEntities, totalEntities);
}
private onDataUpdate(update: Array<AlarmData>) {

8
ui-ngx/src/app/core/api/alarm-data.service.ts

@ -20,7 +20,6 @@ import { PageData } from '@shared/models/page/page-data';
import { AlarmData, AlarmDataPageLink, KeyFilter } from '@shared/models/query/query.models';
import { Injectable } from '@angular/core';
import { TelemetryWebsocketService } from '@core/ws/telemetry-websocket.service';
import { UtilsService } from '@core/services/utils.service';
import {
AlarmDataSubscription,
AlarmDataSubscriptionOptions,
@ -31,7 +30,7 @@ import { deepClone } from '@core/utils';
export interface AlarmDataListener {
subscriptionTimewindow?: SubscriptionTimewindow;
alarmSource: Datasource;
alarmsLoaded: (pageData: PageData<AlarmData>, pageLink: AlarmDataPageLink, allowedEntities: number, totalEntities: number) => void;
alarmsLoaded: (pageData: PageData<AlarmData>, allowedEntities: number, totalEntities: number) => void;
alarmsUpdated: (update: Array<AlarmData>, pageData: PageData<AlarmData>) => void;
subscription?: AlarmDataSubscription;
}
@ -41,8 +40,7 @@ export interface AlarmDataListener {
})
export class AlarmDataService {
constructor(private telemetryService: TelemetryWebsocketService,
private utils: UtilsService) {}
constructor(private telemetryService: TelemetryWebsocketService) {}
public subscribeForAlarms(listener: AlarmDataListener,
@ -88,7 +86,7 @@ export class AlarmDataService {
alarmDataSubscriptionOptions.additionalKeyFilters = additionalKeyFilters;
}
return new AlarmDataSubscription(alarmDataSubscriptionOptions,
listener, this.telemetryService, this.utils);
listener, this.telemetryService);
}
}

6
ui-ngx/src/app/core/api/entity-data-subscription.ts

@ -193,6 +193,12 @@ export class EntityDataSubscription {
key: 'label'
});
}
if (!entityFields.find(key => key.key === 'additionalInfo')) {
entityFields.push({
type: EntityKeyType.ENTITY_FIELD,
key: 'additionalInfo'
});
}
this.attrFields = this.entityDataSubscriptionOptions.dataKeys.filter(dataKey => dataKey.type === DataKeyType.attribute).map(
dataKey => ({ type: EntityKeyType.ATTRIBUTE, key: dataKey.name })

1
ui-ngx/src/app/core/api/widget-api.models.ts

@ -242,6 +242,7 @@ export interface SubscriptionEntityInfo {
entityId: EntityId;
entityName: string;
entityLabel: string;
entityDescription: string;
}
export interface IWidgetSubscription {

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

@ -455,6 +455,7 @@ export class WidgetSubscription implements IWidgetSubscription {
let entityId: EntityId;
let entityName: string;
let entityLabel: string;
let entityDescription: string;
if (this.type === widgetType.rpc) {
if (this.targetDeviceId) {
entityId = {
@ -474,6 +475,18 @@ export class WidgetSubscription implements IWidgetSubscription {
if (labelValue) {
entityLabel = labelValue.value;
}
const additionalInfoValue = entityFields.additionalInfo;
if (additionalInfoValue) {
const additionalInfo = additionalInfoValue.value;
if (additionalInfo && additionalInfo.length) {
try {
const additionalInfoJson = JSON.parse(additionalInfo);
if (additionalInfoJson && additionalInfoJson.description) {
entityDescription = additionalInfoJson.description;
}
} catch (e) {}
}
}
}
}
} else {
@ -485,6 +498,7 @@ export class WidgetSubscription implements IWidgetSubscription {
};
entityName = datasource.entityName;
entityLabel = datasource.entityLabel;
entityDescription = datasource.entityDescription;
break;
}
}
@ -493,7 +507,8 @@ export class WidgetSubscription implements IWidgetSubscription {
return {
entityId,
entityName,
entityLabel
entityLabel,
entityDescription
};
} else {
return null;

36
ui-ngx/src/app/core/http/entity.service.ts

@ -579,6 +579,9 @@ export class EntityService {
}
if (useAliasEntityTypes) {
entityTypes.push(AliasEntityType.CURRENT_USER);
if (authUser.authority !== Authority.SYS_ADMIN) {
entityTypes.push(AliasEntityType.CURRENT_USER_OWNER);
}
}
if (allowedEntityTypes && allowedEntityTypes.length) {
for (let index = entityTypes.length - 1; index >= 0; index--) {
@ -937,16 +940,20 @@ export class EntityService {
);
observables.push(observable);
}
return forkJoin(observables).pipe(
map((response) => {
const hasError = response.filter((status) => status === 'error').length > 0;
if (hasError) {
throw Error();
} else {
return response;
}
})
);
if (observables.length) {
return forkJoin(observables).pipe(
map((response) => {
const hasError = response.filter((status) => status === 'error').length > 0;
if (hasError) {
throw Error();
} else {
return response;
}
})
);
} else {
return of(null);
}
}
private getStateEntityInfo(filter: EntityAliasFilter, stateParams: StateParams): {entityId: EntityId} {
@ -988,6 +995,15 @@ export class EntityService {
const authUser = getCurrentAuthUser(this.store);
entityId.entityType = EntityType.USER;
entityId.id = authUser.userId;
} else if (entityType === AliasEntityType.CURRENT_USER_OWNER){
const authUser = getCurrentAuthUser(this.store);
if (authUser.authority === Authority.TENANT_ADMIN) {
entityId.entityType = EntityType.TENANT;
entityId.id = authUser.tenantId;
} else if (authUser.authority === Authority.CUSTOMER_USER) {
entityId.entityType = EntityType.CUSTOMER;
entityId.id = authUser.customerId;
}
}
return entityId;
}

22
ui-ngx/src/app/core/utils.ts

@ -77,6 +77,10 @@ export function isUndefined(value: any): boolean {
return typeof value === 'undefined';
}
export function isUndefinedOrNull(value: any): boolean {
return typeof value === 'undefined' || value === null;
}
export function isDefined(value: any): boolean {
return typeof value !== 'undefined';
}
@ -85,6 +89,10 @@ export function isDefinedAndNotNull(value: any): boolean {
return typeof value !== 'undefined' && value !== null;
}
export function isDefinedAndNotEmptyStr(value: any): boolean {
return typeof value !== 'undefined' && value !== '';
}
export function isFunction(value: any): boolean {
return typeof value === 'function';
}
@ -452,7 +460,7 @@ export function insertVariable(pattern: string, name: string, value: any): strin
const variable = match[0];
const variableName = match[1];
if (variableName === name) {
result = result.split(variable).join(value);
result = result.replace(variable, value);
}
match = varsRegex.exec(pattern);
}
@ -469,17 +477,17 @@ export function createLabelFromDatasource(datasource: Datasource, pattern: strin
const variable = match[0];
const variableName = match[1];
if (variableName === 'dsName') {
label = label.split(variable).join(datasource.name);
label = label.replace(variable, datasource.name);
} else if (variableName === 'entityName') {
label = label.split(variable).join(datasource.entityName);
label = label.replace(variable, datasource.entityName);
} else if (variableName === 'deviceName') {
label = label.split(variable).join(datasource.entityName);
label = label.replace(variable, datasource.entityName);
} else if (variableName === 'entityLabel') {
label = label.split(variable).join(datasource.entityLabel || datasource.entityName);
label = label.replace(variable, datasource.entityLabel || datasource.entityName);
} else if (variableName === 'aliasName') {
label = label.split(variable).join(datasource.aliasName);
label = label.replace(variable, datasource.aliasName);
} else if (variableName === 'entityDescription') {
label = label.split(variable).join(datasource.entityDescription);
label = label.replace(variable, datasource.entityDescription);
}
match = varsRegex.exec(pattern);
}

384
ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts

@ -16,33 +16,40 @@
import L, {
FeatureGroup,
Icon,
LatLngBounds,
LatLngTuple,
markerClusterGroup,
MarkerClusterGroup,
MarkerClusterGroupOptions
} from 'leaflet';
import tinycolor from 'tinycolor2';
import 'leaflet-providers';
import 'leaflet.markercluster/dist/leaflet.markercluster';
import {
defaultSettings,
FormattedData,
MapSettings,
MarkerSettings,
PolygonSettings,
PolylineSettings,
ReplaceInfo,
UnitedMapSettings
} from './map-models';
import { Marker } from './markers';
import { BehaviorSubject, Observable, of } from 'rxjs';
import { filter } from 'rxjs/operators';
import { Observable, of } from 'rxjs';
import { Polyline } from './polyline';
import { Polygon } from './polygon';
import { createTooltip, parseArray, safeExecute } from '@home/components/widget/lib/maps/maps-utils';
import {
createLoadingDiv,
createTooltip,
parseArray,
parseData,
safeExecute
} from '@home/components/widget/lib/maps/maps-utils';
import { WidgetContext } from '@home/models/widget-component.models';
import { DatasourceData } from '@shared/models/widget.models';
import { deepClone, isDefinedAndNotNull } from '@core/utils';
import { deepClone, isDefinedAndNotEmptyStr } from '@core/utils';
import {newArray} from "@angular/compiler/src/util";
import {isArray} from "rxjs/internal-compatibility";
@ -52,8 +59,6 @@ export default abstract class LeafletMap {
polylines: Map<string, Polyline> = new Map();
polygons: Map<string, Polygon> = new Map();
map: L.Map;
map$: BehaviorSubject<L.Map> = new BehaviorSubject(null);
ready$: Observable<L.Map> = this.map$.pipe(filter(map => !!map));
options: UnitedMapSettings;
bounds: L.LatLngBounds;
datasources: FormattedData[];
@ -61,6 +66,16 @@ export default abstract class LeafletMap {
points: FeatureGroup;
markersData: FormattedData[] = [];
polygonsData: FormattedData[] = [];
defaultMarkerIconInfo: { size: number[], icon: Icon };
loadingDiv: JQuery<HTMLElement>;
loading = false;
replaceInfoLabelMarker: Array<ReplaceInfo> = [];
markerLabelText: string;
replaceInfoTooltipMarker: Array<ReplaceInfo> = [];
markerTooltipText: string;
drawRoutes: boolean;
showPolygon: boolean;
updatePending = false;
protected constructor(public ctx: WidgetContext,
public $container: HTMLElement,
@ -69,8 +84,8 @@ export default abstract class LeafletMap {
}
public initSettings(options: MapSettings) {
const { initCallback,
disableScrollZooming,
this.options.tinyColor = tinycolor(this.options.color || defaultSettings.color);
const { disableScrollZooming,
useClusterMarkers,
zoomOnClick,
showCoverageOnHover,
@ -82,9 +97,6 @@ export default abstract class LeafletMap {
if (disableScrollZooming) {
this.map.scrollWheelZoom.disable();
}
if (initCallback) {
setTimeout(options.initCallback, 0);
}
if (useClusterMarkers) {
const clusteringSettings: MarkerClusterGroupOptions = {
zoomToBoundsOnClick: zoomOnClick,
@ -100,7 +112,6 @@ export default abstract class LeafletMap {
clusteringSettings.disableClusteringAtZoom = Math.floor(maxZoom);
}
this.markersCluster = markerClusterGroup(clusteringSettings);
this.ready$.subscribe(map => map.addLayer(this.markersCluster));
}
}
@ -239,6 +250,22 @@ export default abstract class LeafletMap {
}
}
public setLoading(loading: boolean) {
if (this.loading !== loading) {
this.loading = loading;
if (this.loading) {
if (!this.loadingDiv) {
this.loadingDiv = createLoadingDiv(this.ctx.translate.instant('common.loading'));
}
this.$container.append(this.loadingDiv[0]);
} else {
if (this.loadingDiv) {
this.loadingDiv.remove();
}
}
}
}
public setMap(map: L.Map) {
this.map = map;
if (this.options.useDefaultCenterPosition) {
@ -252,11 +279,13 @@ export default abstract class LeafletMap {
if (this.options.editablePolygon) {
this.addPolygonControl();
}
this.map$.next(this.map);
}
public setDataSources(dataSources: FormattedData[]) {
this.datasources = dataSources;
if (this.options.useClusterMarkers) {
this.map.addLayer(this.markersCluster);
}
if (this.updatePending) {
this.updatePending = false;
this.updateData(this.drawRoutes, this.showPolygon);
}
}
public saveMarkerLocation(_e: FormattedData, lat?: number, lng?: number): Observable<any> {
@ -308,7 +337,7 @@ export default abstract class LeafletMap {
this.map.once('zoomend', () => {
let minZoom = this.options.minZoomLevel;
if (this.options.defaultZoomLevel) {
minZoom = Math.min(minZoom, this.options.defaultZoomLevel);
minZoom = Math.max(minZoom, this.options.defaultZoomLevel);
}
if (this.map.getZoom() > minZoom) {
this.map.setZoom(minZoom, { animate: false });
@ -327,7 +356,7 @@ export default abstract class LeafletMap {
if (!expression) return null;
const lat = expression[this.options.latKeyName];
const lng = expression[this.options.lngKeyName];
if (!isDefinedAndNotNull(lat) || isNaN(lat) || !isDefinedAndNotNull(lng) || isNaN(lng)) {
if (!isDefinedAndNotEmptyStr(lat) || isNaN(lat) || !isDefinedAndNotEmptyStr(lng) || isNaN(lng)) {
return null;
}
return L.latLng(lat, lng) as L.LatLng;
@ -352,28 +381,32 @@ export default abstract class LeafletMap {
}
}
convertToPolygonFormat(points: Array<any>): Array<any> {
if (points.length) {
return points.map(point=> {
if (point.length) {
return this.convertToPolygonFormat(point);
} else {
return [point.lat, point.lng];
}
})
convertToPolygonFormat(points: Array<any>): Array<any> {
if (points.length) {
return points.map(point=> {
if (point.length) {
return this.convertToPolygonFormat(point);
} else {
return []
return [point.lat, point.lng];
}
})
} else {
return []
}
}
convertPolygonToCustomFormat(expression: Array<Array<any>>): object {
return {
[this.options.polygonKeyName] : this.convertToPolygonFormat(expression)
}
convertPolygonToCustomFormat(expression: Array<Array<any>>): object {
return {
[this.options.polygonKeyName] : this.convertToPolygonFormat(expression)
}
}
updateData(data: DatasourceData[], formattedData: FormattedData[], drawRoutes: boolean, showPolygon: boolean) {
this.ready$.subscribe(() => {
updateData(drawRoutes: boolean, showPolygon: boolean) {
this.drawRoutes = drawRoutes;
this.showPolygon = showPolygon;
if (this.map) {
const data = this.ctx.data;
const formattedData = parseData(this.ctx.data);
if (drawRoutes) {
this.updatePolylines(parseArray(data), false);
}
@ -381,28 +414,37 @@ export default abstract class LeafletMap {
this.updatePolygons(formattedData, false);
}
this.updateMarkers(formattedData, false);
this.updateBoundsInternal(drawRoutes, showPolygon);
});
this.updateBoundsInternal();
if (this.options.draggableMarker || this.options.editablePolygon) {
this.datasources = formattedData;
}
} else {
this.updatePending = true;
}
}
private updateBoundsInternal(drawRoutes: boolean, showPolygon: boolean) {
private updateBoundsInternal() {
const bounds = new L.LatLngBounds(null, null);
if (drawRoutes) {
if (this.drawRoutes) {
this.polylines.forEach((polyline) => {
bounds.extend(polyline.leafletPoly.getBounds());
});
}
if (showPolygon) {
if (this.showPolygon) {
this.polygons.forEach((polygon) => {
bounds.extend(polygon.leafletPoly.getBounds());
});
}
this.markers.forEach((marker) => {
bounds.extend(marker.leafletMarker.getLatLng());
});
if ((this.options as MarkerSettings).useClusterMarkers && this.markersCluster.getBounds().isValid()) {
bounds.extend(this.markersCluster.getBounds());
} else {
this.markers.forEach((marker) => {
bounds.extend(marker.leafletMarker.getLatLng());
});
}
const mapBounds = this.map.getBounds();
if (bounds.isValid() && (!this.bounds || !mapBounds.contains(bounds))) {
if (bounds.isValid() && (!this.bounds || !this.bounds.isValid() || !this.bounds.equals(bounds) && !mapBounds.contains(bounds))) {
this.bounds = bounds;
this.fitBounds(bounds);
}
@ -411,41 +453,57 @@ export default abstract class LeafletMap {
// Markers
updateMarkers(markersData: FormattedData[], updateBounds = true, callback?) {
const rawMarkers = markersData.filter(mdata => !!this.convertPosition(mdata));
this.ready$.subscribe(() => {
const keys: string[] = [];
rawMarkers.forEach(data => {
if (data.rotationAngle || data.rotationAngle === 0) {
const currentImage = this.options.useMarkerImageFunction ?
safeExecute(this.options.markerImageFunction,
[data, this.options.markerImages, markersData, data.dsIndex]) : this.options.currentImage;
const style = currentImage ? 'background-image: url(' + currentImage.url + ');' : '';
this.options.icon = L.divIcon({
html: `<div class="arrow"
style="transform: translate(-10px, -10px)
rotate(${data.rotationAngle}deg);
${style}"><div>`
});
} else {
this.options.icon = null;
}
if (this.markers.get(data.entityName)) {
this.updateMarker(data.entityName, data, markersData, this.options)
} else {
this.createMarker(data.entityName, data, markersData, this.options as MarkerSettings, updateBounds, callback);
}
keys.push(data.entityName);
});
const toDelete: string[] = [];
this.markers.forEach((v, mKey) => {
if (!keys.includes(mKey)) {
toDelete.push(mKey);
}
});
toDelete.forEach((key) => {
this.deleteMarker(key);
const toDelete = new Set(Array.from(this.markers.keys()));
const createdMarkers: Marker[] = [];
const updatedMarkers: Marker[] = [];
const deletedMarkers: Marker[] = [];
let m: Marker;
rawMarkers.forEach(data => {
if (data.rotationAngle || data.rotationAngle === 0) {
const currentImage = this.options.useMarkerImageFunction ?
safeExecute(this.options.markerImageFunction,
[data, this.options.markerImages, markersData, data.dsIndex]) : this.options.currentImage;
const style = currentImage ? 'background-image: url(' + currentImage.url + ');' : '';
this.options.icon = L.divIcon({
html: `<div class="arrow"
style="transform: translate(-10px, -10px)
rotate(${data.rotationAngle}deg);
${style}"><div>`
});
this.markersData = markersData;
});
} else {
this.options.icon = null;
}
if (this.markers.get(data.entityName)) {
m = this.updateMarker(data.entityName, data, markersData, this.options);
if (m) {
updatedMarkers.push(m);
}
} else {
m = this.createMarker(data.entityName, data, markersData, this.options as MarkerSettings, updateBounds, callback);
if (m) {
createdMarkers.push(m);
}
}
toDelete.delete(data.entityName);
});
toDelete.forEach((key) => {
m = this.deleteMarker(key);
if (m) {
deletedMarkers.push(m);
}
});
this.markersData = markersData;
if ((this.options as MarkerSettings).useClusterMarkers) {
if (createdMarkers.length) {
this.markersCluster.addLayers(createdMarkers.map(marker => marker.leafletMarker));
}
if (updatedMarkers.length) {
this.markersCluster.refreshClusters(updatedMarkers.map(marker => marker.leafletMarker))
}
if (deletedMarkers.length) {
this.markersCluster.removeLayers(deletedMarkers.map(marker => marker.leafletMarker));
}
}
}
dragMarker = (e, data = {} as FormattedData) => {
@ -454,48 +512,45 @@ export default abstract class LeafletMap {
}
private createMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: MarkerSettings,
updateBounds = true, callback?) {
const newMarker = new Marker(this.convertPosition(data), settings, data, dataSources, this.dragMarker);
if (callback)
newMarker.leafletMarker.on('click', () => { callback(data, true) });
if (this.bounds && updateBounds)
this.fitBounds(this.bounds.extend(newMarker.leafletMarker.getLatLng()));
this.markers.set(key, newMarker);
if (this.options.useClusterMarkers) {
this.markersCluster.addLayer(newMarker.leafletMarker);
}
else {
this.map.addLayer(newMarker.leafletMarker);
}
updateBounds = true, callback?): Marker {
const newMarker = new Marker(this, this.convertPosition(data), settings, data, dataSources, this.dragMarker);
if (callback) {
newMarker.leafletMarker.on('click', () => {
callback(data, true)
});
}
if (this.bounds && updateBounds && !(this.options as MarkerSettings).useClusterMarkers) {
this.fitBounds(this.bounds.extend(newMarker.leafletMarker.getLatLng()));
}
this.markers.set(key, newMarker);
if (!this.options.useClusterMarkers) {
this.map.addLayer(newMarker.leafletMarker);
}
return newMarker;
}
private updateMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: MarkerSettings) {
private updateMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: MarkerSettings): Marker {
const marker: Marker = this.markers.get(key);
const location = this.convertPosition(data)
if (!location.equals(marker.location)) {
marker.updateMarkerPosition(location);
}
const location = this.convertPosition(data);
marker.updateMarkerPosition(location);
if (settings.showTooltip) {
marker.updateMarkerTooltip(data);
}
if (settings.useClusterMarkers) {
this.markersCluster.refreshClusters()
}
marker.setDataSources(data, dataSources);
marker.updateMarkerIcon(settings);
return marker;
}
deleteMarker(key: string) {
let marker = this.markers.get(key)?.leafletMarker;
if (marker) {
if (this.options.useClusterMarkers) {
this.markersCluster.removeLayer(marker);
} else {
this.map.removeLayer(marker);
}
this.markers.delete(key);
marker = null;
}
deleteMarker(key: string): Marker {
const marker = this.markers.get(key);
const leafletMarker = marker?.leafletMarker;
if (leafletMarker) {
if (!this.options.useClusterMarkers) {
this.map.removeLayer(leafletMarker);
}
this.markers.delete(key);
}
return marker;
}
deletePolygon(key: string) {
@ -505,29 +560,28 @@ export default abstract class LeafletMap {
this.polygons.delete(key);
polygon = null;
}
return polygon;
}
updatePoints(pointsData: FormattedData[], getTooltip: (point: FormattedData, setTooltip?: boolean) => string) {
this.map$.subscribe(map => {
if (this.points) {
map.removeLayer(this.points);
}
this.points = new FeatureGroup();
pointsData.filter(pdata => !!this.convertPosition(pdata)).forEach(data => {
const point = L.circleMarker(this.convertPosition(data), {
color: this.options.pointColor,
radius: this.options.pointSize
});
if (!this.options.pointTooltipOnRightPanel) {
point.on('click', () => getTooltip(data));
}
else {
createTooltip(point, this.options, data.$datasource, getTooltip(data, false));
}
this.points.addLayer(point);
});
map.addLayer(this.points);
});
if (this.points) {
this.map.removeLayer(this.points);
}
this.points = new FeatureGroup();
pointsData.filter(pdata => !!this.convertPosition(pdata)).forEach(data => {
const point = L.circleMarker(this.convertPosition(data), {
color: this.options.pointColor,
radius: this.options.pointSize
});
if (!this.options.pointTooltipOnRightPanel) {
point.on('click', () => getTooltip(data));
}
else {
createTooltip(point, this.options, data.$datasource, getTooltip(data, false));
}
this.points.addLayer(point);
});
this.map.addLayer(this.points);
}
// Polyline
@ -557,27 +611,23 @@ export default abstract class LeafletMap {
}
createPolyline(data: FormattedData, dataSources: FormattedData[], settings: PolylineSettings, updateBounds = true) {
this.ready$.subscribe(() => {
const poly = new Polyline(this.map,
dataSources.map(el => this.convertPosition(el)).filter(el => !!el), data, dataSources, settings);
if (updateBounds) {
const bounds = poly.leafletPoly.getBounds();
this.fitBounds(bounds);
}
this.polylines.set(data.entityName, poly);
});
const poly = new Polyline(this.map,
dataSources.map(el => this.convertPosition(el)).filter(el => !!el), data, dataSources, settings);
if (updateBounds) {
const bounds = poly.leafletPoly.getBounds();
this.fitBounds(bounds);
}
this.polylines.set(data.entityName, poly);
}
updatePolyline(data: FormattedData, dataSources: FormattedData[], settings: PolylineSettings, updateBounds = true) {
this.ready$.subscribe(() => {
const poly = this.polylines.get(data.entityName);
const oldBounds = poly.leafletPoly.getBounds();
poly.updatePolyline(dataSources.map(el => this.convertPosition(el)).filter(el => !!el), data, dataSources, settings);
const newBounds = poly.leafletPoly.getBounds();
if (updateBounds && oldBounds.toBBoxString() !== newBounds.toBBoxString()) {
this.fitBounds(newBounds);
}
});
const poly = this.polylines.get(data.entityName);
const oldBounds = poly.leafletPoly.getBounds();
poly.updatePolyline(dataSources.map(el => this.convertPosition(el)).filter(el => !!el), data, dataSources, settings);
const newBounds = poly.leafletPoly.getBounds();
if (updateBounds && oldBounds.toBBoxString() !== newBounds.toBBoxString()) {
this.fitBounds(newBounds);
}
}
removePolyline(name: string) {
@ -597,7 +647,7 @@ export default abstract class LeafletMap {
const keys: string[] = [];
this.polygonsData = deepClone(polyData);
polyData.forEach((data: FormattedData) => {
if (data && data.hasOwnProperty(this.options.polygonKeyName) && data[this.options.polygonKeyName] !== null) {
if (data && isDefinedAndNotEmptyStr(data[this.options.polygonKeyName])) {
if (typeof (data[this.options.polygonKeyName]) === 'string') {
data[this.options.polygonKeyName] = JSON.parse(data[this.options.polygonKeyName]);
}
@ -627,27 +677,23 @@ export default abstract class LeafletMap {
this.savePolygonLocation({ ...data, ...this.convertPolygonToCustomFormat(e.layer._latlngs) }).subscribe();
}
createPolygon(polyData: FormattedData, dataSources: FormattedData[], settings: PolygonSettings, updateBounds = true) {
this.ready$.subscribe(() => {
const polygon = new Polygon(this.map, polyData, dataSources, settings, this.dragPolygonVertex);
if (updateBounds) {
const bounds = polygon.leafletPoly.getBounds();
this.fitBounds(bounds);
}
this.polygons.set(polyData.entityName, polygon);
});
createPolygon(polyData: FormattedData, dataSources: FormattedData[], settings: PolygonSettings, updateBounds = true) {
const polygon = new Polygon(this.map, polyData, dataSources, settings, this.dragPolygonVertex);
if (updateBounds) {
const bounds = polygon.leafletPoly.getBounds();
this.fitBounds(bounds);
}
this.polygons.set(polyData.entityName, polygon);
}
updatePolygon(polyData: FormattedData, dataSources: FormattedData[], settings: PolygonSettings, updateBounds = true) {
this.ready$.subscribe(() => {
const poly = this.polygons.get(polyData.entityName);
const oldBounds = poly.leafletPoly.getBounds();
poly.updatePolygon(polyData, dataSources, settings);
const newBounds = poly.leafletPoly.getBounds();
if (updateBounds && oldBounds.toBBoxString() !== newBounds.toBBoxString()) {
this.fitBounds(newBounds);
}
});
const poly = this.polygons.get(polyData.entityName);
const oldBounds = poly.leafletPoly.getBounds();
poly.updatePolygon(polyData, dataSources, settings);
const newBounds = poly.leafletPoly.getBounds();
if (updateBounds && oldBounds.toBBoxString() !== newBounds.toBBoxString()) {
this.fitBounds(newBounds);
}
}
removePolygon(name: string) {

53
ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts

@ -15,27 +15,19 @@
///
import { LatLngTuple } from 'leaflet';
import { Datasource, JsonSettingsSchema } from '@app/shared/models/widget.models';
import { Type } from '@angular/core';
import LeafletMap from './leaflet-map';
import { OpenStreetMap, TencentMap, GoogleMap, HEREMap, ImageMap } from './providers';
import {
openstreetMapSettingsSchema, tencentMapSettingsSchema,
googleMapSettingsSchema, hereMapSettingsSchema, imageMapSettingsSchema
} from './schemes';
import { Datasource } from '@app/shared/models/widget.models';
import { EntityType } from '@shared/models/entity-type.models';
import tinycolor from 'tinycolor2';
export const DEFAULT_MAP_PAGE_SIZE = 16384;
export type GenericFunction = (data: FormattedData, dsData: FormattedData[], dsIndex: number) => string;
export type MarkerImageFunction = (data: FormattedData, dsData: FormattedData[], dsIndex: number) => string;
export type GetTooltip = (point: FormattedData, setTooltip?: boolean) => string;
export type PosFuncton = (origXPos, origYPos) => { x, y };
export type MapSettings = {
draggableMarker: boolean;
editablePolygon: boolean;
initCallback?: () => any;
posFunction: PosFuncton;
defaultZoomLevel?: number;
disableScrollZooming?: boolean;
@ -94,6 +86,7 @@ export type MarkerSettings = {
useTooltipFunction: boolean;
useColorFunction: boolean;
color?: string;
tinyColor?: tinycolor.Instance;
autocloseTooltip: boolean;
showTooltipAction: string;
useClusterMarkers: boolean;
@ -122,6 +115,12 @@ export interface FormattedData {
[key: string]: any
}
export interface ReplaceInfo {
variable: string;
valDec?: number;
dataKeyName: string
}
export type PolygonSettings = {
showPolygon: boolean;
polygonKeyName: string;
@ -203,40 +202,6 @@ export type actionsHandler = ($event: Event, datasource: Datasource) => void;
export type UnitedMapSettings = MapSettings & PolygonSettings & MarkerSettings & PolylineSettings & TripAnimationSettings;
interface IProvider {
MapClass: Type<LeafletMap>,
schema: JsonSettingsSchema,
name: string
}
export const providerSets: { [key: string]: IProvider } = {
'openstreet-map': {
MapClass: OpenStreetMap,
schema: openstreetMapSettingsSchema,
name: 'openstreet-map',
},
'tencent-map': {
MapClass: TencentMap,
schema: tencentMapSettingsSchema,
name: 'tencent-map'
},
'google-map': {
MapClass: GoogleMap,
schema: googleMapSettingsSchema,
name: 'google-map'
},
here: {
MapClass: HEREMap,
schema: hereMapSettingsSchema,
name: 'here'
},
'image-map': {
MapClass: ImageMap,
schema: imageMapSettingsSchema,
name: 'image-map'
}
};
export const defaultSettings: any = {
xPosKeyName: 'xPos',
yPosKeyName: 'yPos',

11
ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts

@ -15,12 +15,10 @@
///
import {
DEFAULT_MAP_PAGE_SIZE,
defaultSettings,
FormattedData,
hereProviders,
MapProviders,
providerSets,
UnitedMapSettings
} from './map-models';
import LeafletMap from './leaflet-map';
@ -46,6 +44,7 @@ import _ from 'lodash';
import { EntityDataPageLink } from '@shared/models/query/query.models';
import { isDefined } from '@core/utils';
import { forkJoin, Observable, of } from 'rxjs';
import { providerSets } from '@home/components/widget/lib/maps/providers';
// @dynamic
export class MapWidgetController implements MapWidgetInterface {
@ -86,6 +85,7 @@ export class MapWidgetController implements MapWidgetInterface {
textSearch: null,
dynamic: true
};
this.map.setLoading(true);
this.ctx.defaultSubscription.subscribeAllForPaginatedData(this.pageLink, null);
}
@ -328,11 +328,8 @@ export class MapWidgetController implements MapWidgetInterface {
}
update() {
const formattedData = parseData(this.data);
this.map.updateData(this.data, formattedData, this.drawRoutes, this.settings.showPolygon);
if (this.settings.draggableMarker || this.settings.editablePolygon) {
this.map.setDataSources(formattedData);
}
this.map.updateData(this.drawRoutes, this.settings.showPolygon);
this.map.setLoading(false);
}
resize() {

162
ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts

@ -15,13 +15,20 @@
///
import L from 'leaflet';
import { FormattedData, MarkerSettings, PolygonSettings, PolylineSettings } from './map-models';
import { FormattedData, MarkerSettings, PolygonSettings, PolylineSettings, ReplaceInfo } from './map-models';
import { Datasource, DatasourceData } from '@app/shared/models/widget.models';
import _ from 'lodash';
import { Observable, Observer, of } from 'rxjs';
import { map } from 'rxjs/operators';
import { createLabelFromDatasource, hashCode, isNumber, isUndefined, padValue } from '@core/utils';
import { Form } from '@angular/forms';
import {
createLabelFromDatasource,
hashCode,
isDefined,
isDefinedAndNotNull, isFunction,
isNumber,
isUndefined,
padValue
} from '@core/utils';
export function createTooltip(target: L.Layer,
settings: MarkerSettings | PolylineSettings | PolygonSettings,
@ -185,7 +192,7 @@ function parseTemplate(template: string, data: { $datasource?: Datasource, [key:
} else {
textValue = value;
}
template = template.split(variable).join(textValue);
template = template.replace(variable, textValue);
match = /\${([^}]*)}/g.exec(template);
}
@ -198,7 +205,7 @@ function parseTemplate(template: string, data: { $datasource?: Datasource, [key:
while (match !== null) {
[actionTags, actionName, actionText] = match;
action = createLinkElement(actionName, actionText);
template = template.split(actionTags).join(action);
template = template.replace(actionTags, action);
match = linkActionRegex.exec(template);
}
@ -206,18 +213,107 @@ function parseTemplate(template: string, data: { $datasource?: Datasource, [key:
while (match !== null) {
[actionTags, actionName, actionText] = match;
action = createButtonElement(actionName, actionText);
template = template.split(actionTags).join(action);
template = template.replace(actionTags, action);
match = buttonActionRegex.exec(template);
}
const compiled = _.template(template);
res = compiled(data);
// const compiled = _.template(template);
// res = compiled(data);
res = template;
} catch (ex) {
console.log(ex, template)
}
return res;
}
export function processPattern(template: string, data: { $datasource?: Datasource, [key: string]: any }): Array<ReplaceInfo> {
const replaceInfo = [];
try {
const reg = /\${([^}]*)}/g;
let match = reg.exec(template);
while (match !== null) {
const variableInfo: ReplaceInfo = {
dataKeyName: '',
valDec: 2,
variable: ''
};
const variable = match[0];
let label = match[1];
let valDec = 2;
const splitValues = label.split(':');
if (splitValues.length > 1) {
label = splitValues[0];
valDec = parseFloat(splitValues[1]);
}
variableInfo.variable = variable;
variableInfo.valDec = valDec;
if (label.startsWith('#')) {
const keyIndexStr = label.substring(1);
const n = Math.floor(Number(keyIndexStr));
if (String(n) === keyIndexStr && n >= 0) {
variableInfo.dataKeyName = data.$datasource.dataKeys[n].label;
}
} else {
variableInfo.dataKeyName = label;
}
replaceInfo.push(variableInfo);
match = reg.exec(template);
}
} catch (ex) {
console.log(ex, template)
}
return replaceInfo;
}
export function fillPattern(markerLabelText: string, replaceInfoLabelMarker: Array<ReplaceInfo>, data: FormattedData) {
let text = createLabelFromDatasource(data.$datasource, markerLabelText);
if (replaceInfoLabelMarker) {
for(const variableInfo of replaceInfoLabelMarker) {
let txtVal = '';
if (variableInfo.dataKeyName && isDefinedAndNotNull(data[variableInfo.dataKeyName])) {
const varData = data[variableInfo.dataKeyName];
if (isNumber(varData)) {
txtVal = padValue(varData, variableInfo.valDec);
} else {
txtVal = varData;
}
}
text = text.replace(variableInfo.variable, txtVal);
}
}
return text;
}
function prepareProcessPattern(template: string, translateFn?: TranslateFunc): string {
if (translateFn) {
template = translateFn(template);
}
let actionTags: string;
let actionText: string;
let actionName: string;
let action: string;
let match = linkActionRegex.exec(template);
while (match !== null) {
[actionTags, actionName, actionText] = match;
action = createLinkElement(actionName, actionText);
template = template.replace(actionTags, action);
match = linkActionRegex.exec(template);
}
match = buttonActionRegex.exec(template);
while (match !== null) {
[actionTags, actionName, actionText] = match;
action = createButtonElement(actionName, actionText);
template = template.replace(actionTags, action);
match = buttonActionRegex.exec(template);
}
return template;
}
export const parseWithTranslation = {
translateFn: null,
@ -232,6 +328,9 @@ export const parseWithTranslation = {
parseTemplate(template: string, data: object, forceTranslate = false): string {
return parseTemplate(forceTranslate ? this.translate(template) : template, data, this.translate.bind(this));
},
prepareProcessPattern(template: string, forceTranslate = false): string {
return prepareProcessPattern(forceTranslate ? this.translate(template) : template, this.translate.bind(this));
},
setTranslate(translateFn: TranslateFunc) {
this.translateFn = translateFn;
}
@ -250,8 +349,8 @@ export function parseData(input: DatasourceData[]): FormattedData[] {
};
entityArray.filter(el => el.data.length).forEach(el => {
const indexDate = el?.data?.length ? el.data.length - 1 : 0;
obj[el?.dataKey?.label] = el?.data[indexDate][0] ? el?.data[indexDate][1] : null;
obj[el?.dataKey?.label + '|ts'] = el?.data[indexDate][0] || null;
obj[el?.dataKey?.label] = el?.data[indexDate][1];
obj[el?.dataKey?.label + '|ts'] = el?.data[indexDate][0];
if (el?.dataKey?.label === 'type') {
obj.deviceType = el?.data[indexDate][1];
}
@ -312,6 +411,24 @@ export function safeExecute(func: (...args: any[]) => any, params = []) {
return res;
}
export function functionValueCalculator(useFunction: boolean, func: (...args: any[]) => any, params = [], defaultValue: any) {
let res;
if (useFunction && isDefined(func) && isFunction(func)) {
try {
res = func(...params);
if (!isDefinedAndNotNull(res) || res === '') {
res = defaultValue;
}
} catch (err) {
res = defaultValue;
console.log('error in external function:', err);
}
} else {
res = defaultValue;
}
return res;
}
export function calculateNewPointCoordinate(coordinate: number, imageSize: number): number {
let pointCoordinate = coordinate / imageSize;
if (pointCoordinate < 0) {
@ -321,3 +438,28 @@ export function calculateNewPointCoordinate(coordinate: number, imageSize: numbe
}
return pointCoordinate;
}
export function createLoadingDiv(loadingText: string): JQuery<HTMLElement> {
return $(`
<div style="
z-index: 12;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
flex-direction: column;
align-content: center;
align-items: center;
justify-content: center;
display: flex;
background: rgba(255,255,255,0.7);
font-size: 16px;
font-family: Roboto;
font-weight: 400;
text-transform: uppercase;
">
<span>${loadingText}</span>
</div>
`);
}

90
ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts

@ -14,22 +14,30 @@
/// limitations under the License.
///
import L, { LeafletMouseEvent } from 'leaflet';
import L, { Icon, LeafletMouseEvent } from 'leaflet';
import { FormattedData, MarkerSettings } from './map-models';
import { aspectCache, bindPopupActions, createTooltip, parseWithTranslation, safeExecute } from './maps-utils';
import {
aspectCache,
bindPopupActions,
createTooltip,
fillPattern,
parseWithTranslation,
processPattern,
safeExecute
} from './maps-utils';
import tinycolor from 'tinycolor2';
import { isDefined } from '@core/utils';
import { isDefined, isDefinedAndNotNull } from '@core/utils';
import LeafletMap from './leaflet-map';
export class Marker {
leafletMarker: L.Marker;
tooltipOffset: L.LatLngTuple;
markerOffset: L.LatLngTuple;
tooltip: L.Popup;
location: L.LatLngExpression;
data: FormattedData;
dataSources: FormattedData[];
constructor(location: L.LatLngExpression, public settings: MarkerSettings,
constructor(private map: LeafletMap, private location: L.LatLng, public settings: MarkerSettings,
data?: FormattedData, dataSources?, onDragendListener?) {
this.setDataSources(data, dataSources);
this.leafletMarker = L.marker(location, {
@ -73,24 +81,35 @@ export class Marker {
}
updateMarkerTooltip(data: FormattedData) {
if(!this.map.markerTooltipText || this.settings.useTooltipFunction) {
const pattern = this.settings.useTooltipFunction ?
safeExecute(this.settings.tooltipFunction, [this.data, this.dataSources, this.data.dsIndex]) : this.settings.tooltipPattern;
this.tooltip.setContent(parseWithTranslation.parseTemplate(pattern, data, true));
safeExecute(this.settings.tooltipFunction, [this.data, this.dataSources, this.data.dsIndex]) : this.settings.tooltipPattern;
this.map.markerTooltipText = parseWithTranslation.prepareProcessPattern(pattern, true);
this.map.replaceInfoTooltipMarker = processPattern(this.map.markerTooltipText, data);
}
this.tooltip.setContent(fillPattern(this.map.markerTooltipText, this.map.replaceInfoTooltipMarker, data));
if (this.tooltip.isOpen() && this.tooltip.getElement()) {
bindPopupActions(this.tooltip, this.settings, data.$datasource);
}
}
updateMarkerPosition(position: L.LatLngExpression) {
updateMarkerPosition(position: L.LatLng) {
if (!this.location.equals(position)) {
this.location = position;
this.leafletMarker.setLatLng(position);
}
}
updateMarkerLabel(settings: MarkerSettings) {
this.leafletMarker.unbindTooltip();
if (settings.showLabel) {
const pattern = settings.useLabelFunction ?
if(!this.map.markerLabelText || settings.useLabelFunction) {
const pattern = settings.useLabelFunction ?
safeExecute(settings.labelFunction, [this.data, this.dataSources, this.data.dsIndex]) : settings.label;
settings.labelText = parseWithTranslation.parseTemplate(pattern, this.data, true);
this.map.markerLabelText = parseWithTranslation.prepareProcessPattern(pattern, true);
this.map.replaceInfoLabelMarker = processPattern(this.map.markerLabelText, this.data);
}
settings.labelText = fillPattern(this.map.markerLabelText, this.map.replaceInfoLabelMarker, this.data);
this.leafletMarker.bindTooltip(`<div style="color: ${settings.labelColor};"><b>${settings.labelText}</b></div>`,
{ className: 'tb-marker-label', permanent: true, direction: 'top', offset: this.tooltipOffset });
}
@ -121,8 +140,14 @@ export class Marker {
const currentImage = this.settings.useMarkerImageFunction ?
safeExecute(this.settings.markerImageFunction,
[this.data, this.settings.markerImages, this.dataSources, this.data.dsIndex]) : this.settings.currentImage;
const currentColor = tinycolor(this.settings.useColorFunction ? safeExecute(this.settings.colorFunction,
[this.data, this.dataSources, this.data.dsIndex]) : this.settings.color).toHex();
let currentColor = this.settings.tinyColor;
if (this.settings.useColorFunction) {
const functionColor = safeExecute(this.settings.colorFunction,
[this.data, this.dataSources, this.data.dsIndex]);
if (isDefinedAndNotNull(functionColor)) {
currentColor = tinycolor(functionColor);
}
}
if (currentImage && currentImage.url) {
aspectCache(currentImage.url).subscribe(
(aspect) => {
@ -157,24 +182,33 @@ export class Marker {
}
}
createDefaultMarkerIcon(color, onMarkerIconReady) {
const icon = L.icon({
iconUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=%E2%80%A2|' + color,
iconSize: [21, 34],
iconAnchor: [21 * this.markerOffset[0], 34 * this.markerOffset[1]],
popupAnchor: [0, -34],
shadowUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_shadow',
shadowSize: [40, 37],
shadowAnchor: [12, 35]
});
const iconInfo = {
size: [21, 34],
icon
};
onMarkerIconReady(iconInfo);
createDefaultMarkerIcon(color: tinycolor.Instance, onMarkerIconReady) {
let icon: { size: number[], icon: Icon };
if (!tinycolor.equals(color, this.settings.tinyColor)) {
icon = this.createColoredMarkerIcon(color);
} else {
if (!this.map.defaultMarkerIconInfo) {
this.map.defaultMarkerIconInfo = this.createColoredMarkerIcon(color);
}
icon = this.map.defaultMarkerIconInfo;
}
onMarkerIconReady(icon);
}
createColoredMarkerIcon(color: tinycolor.Instance): { size: number[], icon: Icon } {
return {
size: [21, 34],
icon: L.icon({
iconUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=%E2%80%A2|' + color.toHex(),
iconSize: [21, 34],
iconAnchor: [21 * this.markerOffset[0], 34 * this.markerOffset[1]],
popupAnchor: [0, -34],
shadowUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_shadow',
shadowSize: [40, 37],
shadowAnchor: [12, 35]
})
};
}
removeMarker() {
/* this.map$.subscribe(map =>

11
ui-ngx/src/app/modules/home/components/widget/lib/maps/polygon.ts

@ -14,9 +14,9 @@
/// limitations under the License.
///
import L, { LatLngExpression, LeafletMouseEvent} from 'leaflet';
import L, { LatLngExpression, LeafletMouseEvent } from 'leaflet';
import { createTooltip, functionValueCalculator, parseWithTranslation, safeExecute } from './maps-utils';
import "leaflet-editable/src/Leaflet.Editable";
import { createTooltip, parseWithTranslation, safeExecute } from './maps-utils';
import { FormattedData, PolygonSettings } from './map-models';
export class Polygon {
@ -111,10 +111,7 @@ export class Polygon {
}
private getPolygonColor(settings: PolygonSettings): string | null {
if (settings.usePolygonColorFunction) {
return safeExecute(settings.polygonColorFunction, [this.data, this.dataSources, this.data.dsIndex]);
} else {
return settings.polygonColor;
}
return functionValueCalculator(settings.usePolygonColorFunction, settings.polygonColorFunction,
[this.data, this.dataSources, this.data.dsIndex], settings.polygonColor);
}
}

17
ui-ngx/src/app/modules/home/components/widget/lib/maps/polyline.ts

@ -18,7 +18,7 @@ import L, { PolylineDecoratorOptions } from 'leaflet';
import 'leaflet-polylinedecorator';
import { FormattedData, PolylineSettings } from './map-models';
import { safeExecute } from '@home/components/widget/lib/maps/maps-utils';
import { functionValueCalculator, safeExecute } from '@home/components/widget/lib/maps/maps-utils';
export class Polyline {
@ -72,15 +72,12 @@ export class Polyline {
getPolyStyle(settings: PolylineSettings): L.PolylineOptions {
return {
interactive: false,
color: settings.useColorFunction ?
safeExecute(settings.colorFunction,
[this.data, this.dataSources, this.data.dsIndex]) : settings.color,
opacity: settings.useStrokeOpacityFunction ?
safeExecute(settings.strokeOpacityFunction,
[this.data, this.dataSources, this.data.dsIndex]) : settings.strokeOpacity,
weight: settings.useStrokeWeightFunction ?
safeExecute(settings.strokeWeightFunction,
[this.data, this.dataSources, this.data.dsIndex]) : settings.strokeWeight,
color: functionValueCalculator(settings.useColorFunction, settings.colorFunction,
[this.data, this.dataSources, this.data.dsIndex], settings.color),
opacity: functionValueCalculator(settings.useStrokeOpacityFunction, settings.strokeOpacityFunction,
[this.data, this.dataSources, this.data.dsIndex], settings.strokeOpacity),
weight: functionValueCalculator(settings.useStrokeWeightFunction, settings.strokeWeightFunction,
[this.data, this.dataSources, this.data.dsIndex], settings.strokeWeight)
}
}

2
ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/google-map.ts

@ -35,6 +35,7 @@ export class GoogleMap extends LeafletMap {
constructor(ctx: WidgetContext, $container, options: UnitedMapSettings) {
super(ctx, $container, options);
this.resource = ctx.$injector.get(ResourcesService);
super.initSettings(options);
this.loadGoogle(() => {
const map = L.map($container, {attributionControl: false, editable: !!options.editablePolygon}).setView(options?.defaultCenterPosition, options?.defaultZoomLevel);
(L.gridLayer as any).googleMutant({
@ -42,7 +43,6 @@ export class GoogleMap extends LeafletMap {
}).addTo(map);
super.setMap(map);
}, options.gmApiKey);
super.initSettings(options);
}
private loadGoogle(callback, apiKey = 'AIzaSyDoEx2kaGz3PxwbI9T7ccTSg5xjdw8Nw8Q') {

2
ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/here-map.ts

@ -25,7 +25,7 @@ export class HEREMap extends LeafletMap {
const map = L.map($container, {editable: !!options.editablePolygon}).setView(options?.defaultCenterPosition, options?.defaultZoomLevel);
const tileLayer = (L.tileLayer as any).provider(options.mapProviderHere || 'HERE.normalDay', options.credentials);
tileLayer.addTo(map);
super.setMap(map);
super.initSettings(options);
super.setMap(map);
}
}

6
ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts

@ -24,7 +24,7 @@ import { WidgetContext } from '@home/models/widget-component.models';
import { DataSet, DatasourceType, widgetType } from '@shared/models/widget.models';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { WidgetSubscriptionOptions } from '@core/api/widget-api.models';
import {isDefinedAndNotNull, isNumber} from '@core/utils';
import { isDefinedAndNotNull, isDefinedAndNotEmptyStr, isNumber } from '@core/utils';
import "leaflet-editable/src/Leaflet.Editable";
import {isArray} from "rxjs/internal-compatibility";
@ -49,8 +49,8 @@ export class ImageMap extends LeafletMap {
this.onResize(true);
} else {
this.onResize();
super.setMap(this.map);
super.initSettings(options);
super.setMap(this.map);
}
});
}
@ -215,7 +215,7 @@ export class ImageMap extends LeafletMap {
convertPosition(expression): L.LatLng {
const xPos = expression[this.options.xPosKeyName];
const yPos = expression[this.options.yPosKeyName];
if (!isDefinedAndNotNull(xPos) || isNaN(xPos) || !isDefinedAndNotNull(yPos) || isNaN(yPos)) {
if (!isDefinedAndNotEmptyStr(xPos) || isNaN(xPos) || !isDefinedAndNotEmptyStr(yPos) || isNaN(yPos)) {
return null;
}
Object.assign(expression, this.posFunction(xPos, yPos));

52
ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/index.ts

@ -14,8 +14,50 @@
/// limitations under the License.
///
export * from './tencent-map';
export * from './google-map';
export * from './here-map';
export * from './image-map';
export * from './openstreet-map';
import {
googleMapSettingsSchema, hereMapSettingsSchema, imageMapSettingsSchema,
openstreetMapSettingsSchema,
tencentMapSettingsSchema
} from '@home/components/widget/lib/maps/schemes';
import { OpenStreetMap } from './openstreet-map';
import { TencentMap } from './tencent-map';
import { GoogleMap } from './google-map';
import { HEREMap } from './here-map';
import { ImageMap } from './image-map';
import { Type } from '@angular/core';
import LeafletMap from '@home/components/widget/lib/maps/leaflet-map';
import { JsonSettingsSchema } from '@shared/models/widget.models';
interface IProvider {
MapClass: Type<LeafletMap>,
schema: JsonSettingsSchema,
name: string
}
export const providerSets: { [key: string]: IProvider } = {
'openstreet-map': {
MapClass: OpenStreetMap,
schema: openstreetMapSettingsSchema,
name: 'openstreet-map',
},
'tencent-map': {
MapClass: TencentMap,
schema: tencentMapSettingsSchema,
name: 'tencent-map'
},
'google-map': {
MapClass: GoogleMap,
schema: googleMapSettingsSchema,
name: 'google-map'
},
here: {
MapClass: HEREMap,
schema: hereMapSettingsSchema,
name: 'here'
},
'image-map': {
MapClass: ImageMap,
schema: imageMapSettingsSchema,
name: 'image-map'
}
};

2
ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/openstreet-map.ts

@ -29,7 +29,7 @@ export class OpenStreetMap extends LeafletMap {
else
tileLayer = (L.tileLayer as any).provider(options.mapProvider || 'OpenStreetMap.Mapnik');
tileLayer.addTo(map);
super.setMap(map);
super.initSettings(options);
super.setMap(map);
}
}

2
ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/tencent-map.ts

@ -31,7 +31,7 @@ export class TencentMap extends LeafletMap {
attribution: '&copy;2020 Tencent - GS(2018)2236号- Data&copy; NavInfo'
}).addTo(map);
txLayer.addTo(map);
super.setMap(map);
super.initSettings(options);
super.setMap(map);
}
}

3
ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts

@ -300,7 +300,8 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI
id: datasource.entityId
},
entityName: datasource.entityName,
entityLabel: datasource.entityLabel
entityLabel: datasource.entityLabel,
entityDescription: datasource.entityDescription
};
}
}

2
ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.ts

@ -159,7 +159,7 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy
}
this.calcLabel();
this.calcTooltip(currentPosition.find(position => position.entityName === this.activeTrip.entityName));
if (this.mapWidget) {
if (this.mapWidget && this.mapWidget.map && this.mapWidget.map.map) {
this.mapWidget.map.updatePolylines(this.interpolatedTimeData.map(ds => _.values(ds)), true, this.activeTrip);
if (this.settings.showPolygon) {
this.mapWidget.map.updatePolygons(this.interpolatedTimeData);

20
ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.ts

@ -35,7 +35,7 @@ import { fromEvent, merge } from 'rxjs';
import { debounceTime, distinctUntilChanged, tap } from 'rxjs/operators';
import { TranslateService } from '@ngx-translate/core';
import { DialogService } from '@core/services/dialog.service';
import { deepClone } from '@core/utils';
import { deepClone, isUndefined } from '@core/utils';
import {
DashboardStateDialogComponent,
DashboardStateDialogData
@ -198,8 +198,9 @@ export class ManageDashboardStatesDialogComponent extends
root: state.root,
layouts: state.layouts
};
if (prevStateId) {
this.states[prevStateId] = newState;
if (prevStateId && prevStateId !== state.id) {
delete this.states[prevStateId];
this.states[state.id] = newState;
} else {
this.states[state.id] = newState;
}
@ -210,6 +211,19 @@ export class ManageDashboardStatesDialogComponent extends
otherState.root = false;
}
}
} else {
let rootFound = false;
for (const id of Object.keys(this.states)) {
const otherState = this.states[id];
if (otherState.root) {
rootFound = true;
break;
}
}
if (!rootFound) {
const firstStateId = Object.keys(this.states)[0];
this.states[firstStateId].root = true;
}
}
this.onStatesUpdated();
}

21
ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts

@ -26,6 +26,8 @@ import { BaseData } from '@shared/models/base-data';
import { EntityId } from '@shared/models/id/entity-id';
import { EntityService } from '@core/http/entity.service';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { getCurrentAuthUser } from '@core/auth/auth.selectors';
import { Authority } from '@shared/models/authority.enum';
@Component({
selector: 'tb-entity-autocomplete',
@ -196,6 +198,18 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit
this.noEntitiesMatchingText = 'customer.no-customers-matching';
this.entityRequiredText = 'customer.default-customer-required';
break;
case AliasEntityType.CURRENT_USER_OWNER:
const authUser = getCurrentAuthUser(this.store);
if (authUser.authority === Authority.TENANT_ADMIN) {
this.entityText = 'tenant.tenant';
this.noEntitiesMatchingText = 'tenant.no-tenants-matching';
this.entityRequiredText = 'tenant.tenant-required';
} else {
this.entityText = 'customer.customer';
this.noEntitiesMatchingText = 'customer.no-customers-matching';
this.entityRequiredText = 'customer.customer-required';
}
break;
}
}
const currentEntity = this.getCurrentEntity();
@ -328,6 +342,13 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit
return EntityType.TENANT;
} else if (entityType === AliasEntityType.CURRENT_USER) {
return EntityType.USER;
} else if (entityType === AliasEntityType.CURRENT_USER_OWNER) {
const authUser = getCurrentAuthUser(this.store);
if (authUser.authority === Authority.TENANT_ADMIN) {
return EntityType.TENANT;
} else {
return EntityType.CUSTOMER;
}
}
return entityType;
}

3
ui-ngx/src/app/shared/components/entity/entity-select.component.html

@ -28,7 +28,8 @@
<tb-entity-autocomplete
fxFlex
*ngIf="modelValue.entityType && modelValue.entityType !== AliasEntityType.CURRENT_TENANT
&& modelValue.entityType !== AliasEntityType.CURRENT_USER"
&& modelValue.entityType !== AliasEntityType.CURRENT_USER
&& modelValue.entityType !== AliasEntityType.CURRENT_USER_OWNER"
[required]="required"
[entityType]="modelValue.entityType"
formControlName="entityId">

6
ui-ngx/src/app/shared/components/entity/entity-select.component.ts

@ -97,7 +97,8 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte
ngOnInit() {
this.entitySelectFormGroup.get('entityType').valueChanges.subscribe(
(value) => {
if(value === AliasEntityType.CURRENT_TENANT || value === AliasEntityType.CURRENT_USER) {
if(value === AliasEntityType.CURRENT_TENANT || value === AliasEntityType.CURRENT_USER ||
value === AliasEntityType.CURRENT_USER_OWNER) {
this.modelValue.id = NULL_UUID;
}
this.updateView(value, this.modelValue.id);
@ -147,7 +148,8 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte
};
if (this.modelValue.entityType && (this.modelValue.id ||
this.modelValue.entityType === AliasEntityType.CURRENT_TENANT ||
this.modelValue.entityType === AliasEntityType.CURRENT_USER)) {
this.modelValue.entityType === AliasEntityType.CURRENT_USER ||
this.modelValue.entityType === AliasEntityType.CURRENT_USER_OWNER)) {
this.propagateChange(this.modelValue);
} else {
this.propagateChange(null);

10
ui-ngx/src/app/shared/models/entity-type.models.ts

@ -51,7 +51,8 @@ export enum EntityType {
export enum AliasEntityType {
CURRENT_CUSTOMER = 'CURRENT_CUSTOMER',
CURRENT_TENANT = 'CURRENT_TENANT',
CURRENT_USER = 'CURRENT_USER'
CURRENT_USER = 'CURRENT_USER',
CURRENT_USER_OWNER = 'CURRENT_USER_OWNER'
}
export interface EntityTypeTranslation {
@ -237,6 +238,13 @@ export const entityTypeTranslations = new Map<EntityType | AliasEntityType, Enti
type: 'entity.type-current-user',
list: 'entity.type-current-user'
}
],
[
AliasEntityType.CURRENT_USER_OWNER,
{
type: 'entity.type-current-user-owner',
list: 'entity.type-current-user-owner'
}
]
]
);

17
ui-ngx/src/app/shared/models/query/query.models.ts

@ -589,6 +589,10 @@ export const entityInfoFields: EntityKey[] = [
{
type: EntityKeyType.ENTITY_FIELD,
key: 'label'
},
{
type: EntityKeyType.ENTITY_FIELD,
key: 'additionalInfo'
}
];
@ -609,7 +613,18 @@ export function entityDataToEntityInfo(entityData: EntityData): EntityInfo {
} else {
entityInfo.label = '';
}
entityInfo.entityDescription = 'TODO: Not implemented';
entityInfo.entityDescription = '';
if (fields.additionalInfo) {
const additionalInfo = fields.additionalInfo.value;
if (additionalInfo && additionalInfo.length) {
try {
const additionalInfoJson = JSON.parse(additionalInfo);
if (additionalInfoJson && additionalInfoJson.description) {
entityInfo.entityDescription = additionalInfoJson.description;
}
} catch (e) {}
}
}
}
return entityInfo;
}

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

@ -853,6 +853,7 @@
"type-current-customer": "Current Customer",
"type-current-tenant": "Current Tenant",
"type-current-user": "Current User",
"type-current-user-owner": "Current User Owner",
"search": "Search entities",
"selected-entities": "{ count, plural, 1 {1 entity} other {# entities} } selected",
"entity-name": "Entity name",

Loading…
Cancel
Save