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; private long dynamicPageLinkRefreshInterval;
@Value("${server.ws.dynamic_page_link.refresh_pool_size:1}") @Value("${server.ws.dynamic_page_link.refresh_pool_size:1}")
private int dynamicPageLinkRefreshPoolSize; private int dynamicPageLinkRefreshPoolSize;
@Value("${server.ws.max_entities_per_data_subscription:1000}")
private int maxEntitiesPerDataSubscription;
@Value("${server.ws.max_entities_per_alarm_subscription:1000}") @Value("${server.ws.max_entities_per_alarm_subscription:1000}")
private int maxEntitiesPerAlarmSubscription; private int maxEntitiesPerAlarmSubscription;
@ -220,7 +222,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
} else if (cmd.getTsCmd() != null) { } else if (cmd.getTsCmd() != null) {
handleTimeSeriesCmd(theCtx, cmd.getTsCmd()); handleTimeSeriesCmd(theCtx, cmd.getTsCmd());
} else if (!theCtx.isInitialDataSent()) { } 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); wsService.sendWsMsg(theCtx.getSessionId(), update);
theCtx.setInitialDataSent(true); theCtx.setInitialDataSent(true);
} }
@ -298,7 +300,8 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
private TbEntityDataSubCtx createSubCtx(TelemetryWebSocketSessionRef sessionRef, EntityDataCmd cmd) { private TbEntityDataSubCtx createSubCtx(TelemetryWebSocketSessionRef sessionRef, EntityDataCmd cmd) {
Map<Integer, TbAbstractDataSubCtx> sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>()); 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()); ctx.setAndResolveQuery(cmd.getQuery());
sessionSubs.put(cmd.getCmdId(), ctx); sessionSubs.put(cmd.getCmdId(), ctx);
return ctx; return ctx;
@ -306,7 +309,8 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
private TbAlarmDataSubCtx createSubCtx(TelemetryWebSocketSessionRef sessionRef, AlarmDataCmd cmd) { private TbAlarmDataSubCtx createSubCtx(TelemetryWebSocketSessionRef sessionRef, AlarmDataCmd cmd) {
Map<Integer, TbAbstractDataSubCtx> sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>()); 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()); ctx.setAndResolveQuery(cmd.getQuery());
sessionSubs.put(cmd.getCmdId(), ctx); sessionSubs.put(cmd.getCmdId(), ctx);
return ctx; return ctx;
@ -372,10 +376,10 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
}); });
EntityDataUpdate update; EntityDataUpdate update;
if (!ctx.isInitialDataSent()) { if (!ctx.isInitialDataSent()) {
update = new EntityDataUpdate(ctx.getCmdId(), ctx.getData(), null); update = new EntityDataUpdate(ctx.getCmdId(), ctx.getData(), null, ctx.getMaxEntitiesPerDataSubscription());
ctx.setInitialDataSent(true); ctx.setInitialDataSent(true);
} else { } 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); wsService.sendWsMsg(ctx.getSessionId(), update);
if (subscribe) { if (subscribe) {
@ -422,10 +426,10 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
}); });
EntityDataUpdate update; EntityDataUpdate update;
if (!ctx.isInitialDataSent()) { if (!ctx.isInitialDataSent()) {
update = new EntityDataUpdate(ctx.getCmdId(), ctx.getData(), null); update = new EntityDataUpdate(ctx.getCmdId(), ctx.getData(), null, ctx.getMaxEntitiesPerDataSubscription());
ctx.setInitialDataSent(true); ctx.setInitialDataSent(true);
} else { } 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); wsService.sendWsMsg(ctx.getSessionId(), update);
ctx.createSubscriptions(latestCmd.getKeys(), true); ctx.createSubscriptions(latestCmd.getKeys(), true);
@ -440,7 +444,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
}, wsCallBackExecutor); }, wsCallBackExecutor);
} else { } else {
if (!ctx.isInitialDataSent()) { 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); wsService.sendWsMsg(ctx.getSessionId(), update);
ctx.setInitialDataSent(true); 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.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.function.Function; import java.util.function.Function;
@ -98,9 +99,9 @@ public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends
this.stats = stats; this.stats = stats;
this.sessionRef = sessionRef; this.sessionRef = sessionRef;
this.cmdId = cmdId; this.cmdId = cmdId;
this.subToEntityIdMap = new HashMap<>(); this.subToEntityIdMap = new ConcurrentHashMap<>();
this.subToDynamicValueKeySet = new HashSet<>(); this.subToDynamicValueKeySet = ConcurrentHashMap.newKeySet();
this.dynamicValues = new HashMap<>(); this.dynamicValues = new ConcurrentHashMap<>();
} }
public void setAndResolveQuery(T query) { 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; package org.thingsboard.server.service.subscription;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.id.EntityId; 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.EntityData;
import org.thingsboard.server.common.data.query.EntityDataQuery; import org.thingsboard.server.common.data.query.EntityDataQuery;
import org.thingsboard.server.common.data.query.EntityKey; 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 org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
@ -48,8 +43,6 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ScheduledFuture;
import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Slf4j @Slf4j
@ -63,11 +56,14 @@ public class TbEntityDataSubCtx extends TbAbstractDataSubCtx<EntityDataQuery> {
private boolean initialDataSent; private boolean initialDataSent;
private TimeSeriesCmd curTsCmd; private TimeSeriesCmd curTsCmd;
private LatestValueCmd latestValueCmd; private LatestValueCmd latestValueCmd;
@Getter
private final int maxEntitiesPerDataSubscription;
public TbEntityDataSubCtx(String serviceId, TelemetryWebSocketService wsService, EntityService entityService, public TbEntityDataSubCtx(String serviceId, TelemetryWebSocketService wsService, EntityService entityService,
TbLocalSubscriptionService localSubscriptionService, AttributesService attributesService, 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); super(serviceId, wsService, entityService, localSubscriptionService, attributesService, stats, sessionRef, cmdId);
this.maxEntitiesPerDataSubscription = maxEntitiesPerDataSubscription;
} }
@Override @Override
@ -120,7 +116,7 @@ public class TbEntityDataSubCtx extends TbAbstractDataSubCtx<EntityDataQuery> {
if (!latestUpdate.isEmpty()) { if (!latestUpdate.isEmpty()) {
Map<EntityKeyType, Map<String, TsValue>> latestMap = Collections.singletonMap(keyType, latestUpdate); Map<EntityKeyType, Map<String, TsValue>> latestMap = Collections.singletonMap(keyType, latestUpdate);
entityData = new EntityData(entityId, latestMap, null); 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<>(); Map<String, TsValue[]> tsMap = new HashMap<>();
tsUpdate.forEach((key, tsValue) -> tsMap.put(key, tsValue.toArray(new TsValue[tsValue.size()]))); tsUpdate.forEach((key, tsValue) -> tsMap.put(key, tsValue.toArray(new TsValue[tsValue.size()])));
entityData = new EntityData(entityId, null, tsMap); 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)); subIdsToCancel.forEach(subId -> localSubscriptionService.cancelSubscription(getSessionId(), subId));
subsToAdd.forEach(localSubscriptionService::addSubscription); 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.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.EntityData; import org.thingsboard.server.common.data.query.EntityData;
import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode; import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode;
@ -27,8 +28,12 @@ import java.util.List;
public class EntityDataUpdate extends DataUpdate<EntityData> { 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); super(cmdId, data, update, SubscriptionErrorCode.NO_ERROR.getCode(), null);
this.allowedEntities = allowedEntities;
} }
public EntityDataUpdate(int cmdId, int errorCode, String errorMsg) { 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_interval: "${TB_SERVER_WS_DYNAMIC_PAGE_LINK_REFRESH_INTERVAL_SEC:60}"
refresh_pool_size: "${TB_SERVER_WS_DYNAMIC_PAGE_LINK_REFRESH_POOL_SIZE:1}" 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_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: rest:
limits: limits:
tenant: tenant:
@ -273,7 +274,8 @@ sql:
# Specify whether to remove null characters from strValue of attributes and timeseries before insert # Specify whether to remove null characters from strValue of attributes and timeseries before insert
remove_null_chars: "${SQL_REMOVE_NULL_CHARS:true}" remove_null_chars: "${SQL_REMOVE_NULL_CHARS:true}"
# Specify whether to log database queries and their parameters generated by entity query repository # 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: postgres:
# Specify partitioning size for timestamp key-value storage. Example: DAYS, MONTHS, YEARS, INDEFINITE. # Specify partitioning size for timestamp key-value storage. Example: DAYS, MONTHS, YEARS, INDEFINITE.
ts_key_value_partitioning: "${SQL_POSTGRES_TS_KV_PARTITIONING:MONTHS}" 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.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@ -114,12 +115,12 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository {
protected final NamedParameterJdbcTemplate jdbcTemplate; protected final NamedParameterJdbcTemplate jdbcTemplate;
private final TransactionTemplate transactionTemplate; private final TransactionTemplate transactionTemplate;
@Value("${sql.log_entity_queries:false}") private final DefaultQueryLogComponent queryLog;
private boolean logSqlQueries;
public DefaultAlarmQueryRepository(NamedParameterJdbcTemplate jdbcTemplate, TransactionTemplate transactionTemplate) { public DefaultAlarmQueryRepository(NamedParameterJdbcTemplate jdbcTemplate, TransactionTemplate transactionTemplate, DefaultQueryLogComponent queryLog) {
this.jdbcTemplate = jdbcTemplate; this.jdbcTemplate = jdbcTemplate;
this.transactionTemplate = transactionTemplate; this.transactionTemplate = transactionTemplate;
this.queryLog = queryLog;
} }
@Override @Override
@ -230,8 +231,17 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository {
if (!textSearchQuery.isEmpty()) { if (!textSearchQuery.isEmpty()) {
mainQuery = String.format("select * from (%s) a WHERE %s", mainQuery, textSearchQuery); mainQuery = String.format("select * from (%s) a WHERE %s", mainQuery, textSearchQuery);
} }
String countQuery = mainQuery; String countQuery = String.format("select count(*) from (%s) result", mainQuery);
int totalElements = jdbcTemplate.queryForObject(String.format("select count(*) from (%s) result", countQuery), ctx, Integer.class); 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; String dataQuery = mainQuery + sortPart;
@ -239,10 +249,12 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository {
if (pageLink.getPageSize() > 0) { if (pageLink.getPageSize() > 0) {
dataQuery = String.format("%s limit %s offset %s", dataQuery, pageLink.getPageSize(), startIndex); dataQuery = String.format("%s limit %s offset %s", dataQuery, pageLink.getPageSize(), startIndex);
} }
List<Map<String, Object>> rows = jdbcTemplate.queryForList(dataQuery, ctx); queryTs = System.currentTimeMillis();
if (logSqlQueries) { List<Map<String, Object>> rows;
log.info("QUERY: {}", dataQuery); try {
Arrays.asList(ctx.getParameterNames()).forEach(param -> log.info("QUERY PARAM: {}->{}", param, ctx.getValue(param))); rows = jdbcTemplate.queryForList(dataQuery, ctx);
} finally {
queryLog.logQuery(ctx, dataQuery, System.currentTimeMillis() - queryTs);
} }
return AlarmDataAdapter.createAlarmData(pageLink, rows, totalElements, orderedEntityIds); 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 lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; 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.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate; import org.springframework.transaction.support.TransactionTemplate;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
@ -189,6 +185,22 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
" WHEN entity.entity_type = 'ENTITY_VIEW'" + " WHEN entity.entity_type = 'ENTITY_VIEW'" +
" THEN (select name from entity_view where id = entity_id)" + " THEN (select name from entity_view where id = entity_id)" +
" END as label"; " 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 { static {
entityTableMap.put(EntityType.ASSET, "asset"); entityTableMap.put(EntityType.ASSET, "asset");
@ -221,13 +233,12 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
private final NamedParameterJdbcTemplate jdbcTemplate; private final NamedParameterJdbcTemplate jdbcTemplate;
private final TransactionTemplate transactionTemplate; private final TransactionTemplate transactionTemplate;
private final DefaultQueryLogComponent queryLog;
@Value("${sql.log_entity_queries:false}") public DefaultEntityQueryRepository(NamedParameterJdbcTemplate jdbcTemplate, TransactionTemplate transactionTemplate, DefaultQueryLogComponent queryLog) {
private boolean logSqlQueries;
public DefaultEntityQueryRepository(NamedParameterJdbcTemplate jdbcTemplate, TransactionTemplate transactionTemplate) {
this.jdbcTemplate = jdbcTemplate; this.jdbcTemplate = jdbcTemplate;
this.transactionTemplate = transactionTemplate; this.transactionTemplate = transactionTemplate;
this.queryLog = queryLog;
} }
@Override @Override
@ -238,11 +249,14 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
ctx.append(addEntityTableQuery(ctx, query.getEntityFilter())); ctx.append(addEntityTableQuery(ctx, query.getEntityFilter()));
ctx.append(" e where "); ctx.append(" e where ");
ctx.append(buildEntityWhere(ctx, query.getEntityFilter(), Collections.emptyList())); ctx.append(buildEntityWhere(ctx, query.getEntityFilter(), Collections.emptyList()));
if (logSqlQueries) { return transactionTemplate.execute(status -> {
log.info("QUERY: {}", ctx.getQuery()); long startTs = System.currentTimeMillis();
Arrays.asList(ctx.getParameterNames()).forEach(param -> log.info("QUERY PARAM: {}->{}", param, ctx.getValue(param))); try {
} return jdbcTemplate.queryForObject(ctx.getQuery(), ctx, Long.class);
return transactionTemplate.execute(status -> jdbcTemplate.queryForObject(ctx.getQuery(), ctx, Long.class)); } finally {
queryLog.logQuery(ctx, ctx.getQuery(), System.currentTimeMillis() - startTs);
}
});
} }
@Override @Override
@ -313,7 +327,14 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
fromClauseCount = fromClauseData; fromClauseCount = fromClauseData;
} }
String countQuery = String.format("select count(id) %s", fromClauseCount); 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) { if (totalElements == 0) {
return new PageData<>(); return new PageData<>();
@ -338,11 +359,13 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
if (pageLink.getPageSize() > 0) { if (pageLink.getPageSize() > 0) {
dataQuery = String.format("%s limit %s offset %s", dataQuery, pageLink.getPageSize(), startIndex); dataQuery = String.format("%s limit %s offset %s", dataQuery, pageLink.getPageSize(), startIndex);
} }
if (logSqlQueries) { startTs = System.currentTimeMillis();
log.info("QUERY: {}", dataQuery); List<Map<String, Object>> rows;
Arrays.asList(ctx.getParameterNames()).forEach(param -> log.info("QUERY PARAM: {}->{}", param, ctx.getValue(param))); 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); return EntityDataAdapter.createEntityData(pageLink, selectionMapping, rows, totalElements);
}); });
} }
@ -437,7 +460,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
//TODO: fetch last level only. //TODO: fetch last level only.
//TODO: fetch distinct records. //TODO: fetch distinct records.
String lvlFilter = getLvlFilter(entityFilter.getMaxLevel()); 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 ") + (entityType.equals(EntityType.ENTITY_VIEW) ? "" : ", label ")
+ "FROM " + entityType.name() + " WHERE id in ( SELECT entity_id"; + "FROM " + entityType.name() + " WHERE id in ( SELECT entity_id";
String from = getQueryTemplate(entityFilter.getDirection()); String from = getQueryTemplate(entityFilter.getDirection());
@ -470,7 +493,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
+ SELECT_TYPE + ", " + SELECT_NAME + ", " + SELECT_LABEL + ", " + + SELECT_TYPE + ", " + SELECT_NAME + ", " + SELECT_LABEL + ", " +
SELECT_FIRST_NAME + ", " + SELECT_LAST_NAME + ", " + SELECT_EMAIL + ", " + SELECT_REGION + ", " + SELECT_FIRST_NAME + ", " + SELECT_LAST_NAME + ", " + SELECT_EMAIL + ", " + SELECT_REGION + ", " +
SELECT_TITLE + ", " + SELECT_COUNTRY + ", " + SELECT_STATE + ", " + SELECT_CITY + ", " + 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"; ", entity.entity_type as entity_type";
String from = getQueryTemplate(entityFilter.getDirection()); String from = getQueryTemplate(entityFilter.getDirection());
ctx.addUuidParameter("relation_root_id", rootId.getId()); ctx.addUuidParameter("relation_root_id", rootId.getId());
@ -557,7 +580,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
if (!StringUtils.isEmpty(searchText) && !selectionMapping.isEmpty()) { if (!StringUtils.isEmpty(searchText) && !selectionMapping.isEmpty()) {
String lowerSearchText = "%" + searchText.toLowerCase() + "%"; String lowerSearchText = "%" + searchText.toLowerCase() + "%";
ctx.addStringParameter("lowerSearchTextParam", lowerSearchText); 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; String searchAliasesExpression;
if (searchAliases.size() > 1) { if (searchAliases.size() > 1) {
searchAliasesExpression = "CONCAT(" + String.join(" , ", searchAliases) + ")"; 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 ADDRESS_2 = "address2";
private static final String ZIP = "zip"; private static final String ZIP = "zip";
private static final String PHONE = "phone"; 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> typedEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, ADDITIONAL_INFO);
public static final List<String> commonEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME); 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> 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> 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); 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> 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 { static {
allowedEntityFieldMap.put(EntityType.DEVICE, new HashSet<>(labeledEntityFields)); allowedEntityFieldMap.put(EntityType.DEVICE, new HashSet<>(labeledEntityFields));
@ -90,13 +92,13 @@ public class EntityKeyMapping {
allowedEntityFieldMap.get(EntityType.TENANT).add(REGION); allowedEntityFieldMap.get(EntityType.TENANT).add(REGION);
allowedEntityFieldMap.put(EntityType.CUSTOMER, new HashSet<>(contactBasedEntityFields)); 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.DASHBOARD, new HashSet<>(dashboardEntityFields));
allowedEntityFieldMap.put(EntityType.RULE_CHAIN, new HashSet<>(commonEntityFields)); allowedEntityFieldMap.put(EntityType.RULE_CHAIN, new HashSet<>(commonEntityFields));
allowedEntityFieldMap.put(EntityType.RULE_NODE, new HashSet<>(commonEntityFields)); allowedEntityFieldMap.put(EntityType.RULE_NODE, new HashSet<>(commonEntityFields));
allowedEntityFieldMap.put(EntityType.WIDGET_TYPE, new HashSet<>(commonEntityFields)); allowedEntityFieldMap.put(EntityType.WIDGET_TYPE, new HashSet<>(widgetEntityFields));
allowedEntityFieldMap.put(EntityType.WIDGETS_BUNDLE, new HashSet<>(commonEntityFields)); allowedEntityFieldMap.put(EntityType.WIDGETS_BUNDLE, new HashSet<>(widgetEntityFields));
entityFieldColumnMap.put(CREATED_TIME, ModelConstants.CREATED_TIME_PROPERTY); entityFieldColumnMap.put(CREATED_TIME, ModelConstants.CREATED_TIME_PROPERTY);
entityFieldColumnMap.put(ENTITY_TYPE, ModelConstants.ENTITY_TYPE_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(ADDRESS_2, ModelConstants.ADDRESS2_PROPERTY);
entityFieldColumnMap.put(ZIP, ModelConstants.ZIP_PROPERTY); entityFieldColumnMap.put(ZIP, ModelConstants.ZIP_PROPERTY);
entityFieldColumnMap.put(PHONE, ModelConstants.PHONE_PROPERTY); entityFieldColumnMap.put(PHONE, ModelConstants.PHONE_PROPERTY);
entityFieldColumnMap.put(ADDITIONAL_INFO, ModelConstants.ADDITIONAL_INFO_PROPERTY);
Map<String, String> contactBasedAliases = new HashMap<>(); Map<String, String> contactBasedAliases = new HashMap<>();
contactBasedAliases.put(NAME, TITLE); contactBasedAliases.put(NAME, TITLE);
@ -140,6 +143,7 @@ public class EntityKeyMapping {
private String alias; private String alias;
private boolean isLatest; private boolean isLatest;
private boolean isSelection; private boolean isSelection;
private boolean isSearchable;
private boolean isSortOrder; private boolean isSortOrder;
private boolean ignore = false; private boolean ignore = false;
private List<KeyFilter> keyFilters; private List<KeyFilter> keyFilters;
@ -304,6 +308,7 @@ public class EntityKeyMapping {
EntityKeyMapping mapping = new EntityKeyMapping(); EntityKeyMapping mapping = new EntityKeyMapping();
mapping.setLatest(false); mapping.setLatest(false);
mapping.setSelection(true); mapping.setSelection(true);
mapping.setSearchable(!key.getKey().equals(ADDITIONAL_INFO));
mapping.setEntityKey(key); mapping.setEntityKey(key);
return mapping; return mapping;
} }
@ -312,6 +317,7 @@ public class EntityKeyMapping {
key -> { key -> {
EntityKeyMapping mapping = new EntityKeyMapping(); EntityKeyMapping mapping = new EntityKeyMapping();
mapping.setLatest(true); mapping.setLatest(true);
mapping.setSearchable(true);
mapping.setSelection(true); mapping.setSelection(true);
mapping.setEntityKey(key); mapping.setEntityKey(key);
return mapping; 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/js-beautify": "^1.8.2",
"@types/jstree": "^3.3.40", "@types/jstree": "^3.3.40",
"@types/jszip": "^3.4.1", "@types/jszip": "^3.4.1",
"@types/leaflet": "^1.5.12", "@types/leaflet": "^1.5.17",
"@types/leaflet-markercluster": "^1.0.3", "@types/leaflet-markercluster": "^1.0.3",
"@types/leaflet-polylinedecorator": "^1.6.0", "@types/leaflet-polylinedecorator": "^1.6.0",
"@types/lodash": "^4.14.151", "@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'; } from '@shared/models/query/query.models';
import { SubscriptionTimewindow } from '@shared/models/time/time.models'; import { SubscriptionTimewindow } from '@shared/models/time/time.models';
import { AlarmDataListener } from '@core/api/alarm-data.service'; import { AlarmDataListener } from '@core/api/alarm-data.service';
import { UtilsService } from '@core/services/utils.service';
import { PageData } from '@shared/models/page/page-data'; import { PageData } from '@shared/models/page/page-data';
import { deepClone, isDefined, isDefinedAndNotNull, isObject } from '@core/utils'; import { deepClone, isDefined, isDefinedAndNotNull, isObject } from '@core/utils';
import { simulatedAlarm } from '@shared/models/alarm.models'; import { simulatedAlarm } from '@shared/models/alarm.models';
@ -68,8 +67,7 @@ export class AlarmDataSubscription {
constructor(public alarmDataSubscriptionOptions: AlarmDataSubscriptionOptions, constructor(public alarmDataSubscriptionOptions: AlarmDataSubscriptionOptions,
private listener: AlarmDataListener, private listener: AlarmDataListener,
private telemetryService: TelemetryService, private telemetryService: TelemetryService) {
private utils: UtilsService) {
} }
public unsubscribe() { public unsubscribe() {
@ -166,7 +164,7 @@ export class AlarmDataSubscription {
private onPageData(pageData: PageData<AlarmData>, allowedEntities: number, totalEntities: number) { private onPageData(pageData: PageData<AlarmData>, allowedEntities: number, totalEntities: number) {
this.pageData = pageData; this.pageData = pageData;
this.resetData(); this.resetData();
this.listener.alarmsLoaded(pageData, this.alarmDataSubscriptionOptions.pageLink, allowedEntities, totalEntities); this.listener.alarmsLoaded(pageData, allowedEntities, totalEntities);
} }
private onDataUpdate(update: Array<AlarmData>) { 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 { AlarmData, AlarmDataPageLink, KeyFilter } from '@shared/models/query/query.models';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { TelemetryWebsocketService } from '@core/ws/telemetry-websocket.service'; import { TelemetryWebsocketService } from '@core/ws/telemetry-websocket.service';
import { UtilsService } from '@core/services/utils.service';
import { import {
AlarmDataSubscription, AlarmDataSubscription,
AlarmDataSubscriptionOptions, AlarmDataSubscriptionOptions,
@ -31,7 +30,7 @@ import { deepClone } from '@core/utils';
export interface AlarmDataListener { export interface AlarmDataListener {
subscriptionTimewindow?: SubscriptionTimewindow; subscriptionTimewindow?: SubscriptionTimewindow;
alarmSource: Datasource; 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; alarmsUpdated: (update: Array<AlarmData>, pageData: PageData<AlarmData>) => void;
subscription?: AlarmDataSubscription; subscription?: AlarmDataSubscription;
} }
@ -41,8 +40,7 @@ export interface AlarmDataListener {
}) })
export class AlarmDataService { export class AlarmDataService {
constructor(private telemetryService: TelemetryWebsocketService, constructor(private telemetryService: TelemetryWebsocketService) {}
private utils: UtilsService) {}
public subscribeForAlarms(listener: AlarmDataListener, public subscribeForAlarms(listener: AlarmDataListener,
@ -88,7 +86,7 @@ export class AlarmDataService {
alarmDataSubscriptionOptions.additionalKeyFilters = additionalKeyFilters; alarmDataSubscriptionOptions.additionalKeyFilters = additionalKeyFilters;
} }
return new AlarmDataSubscription(alarmDataSubscriptionOptions, 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' 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( this.attrFields = this.entityDataSubscriptionOptions.dataKeys.filter(dataKey => dataKey.type === DataKeyType.attribute).map(
dataKey => ({ type: EntityKeyType.ATTRIBUTE, key: dataKey.name }) 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; entityId: EntityId;
entityName: string; entityName: string;
entityLabel: string; entityLabel: string;
entityDescription: string;
} }
export interface IWidgetSubscription { 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 entityId: EntityId;
let entityName: string; let entityName: string;
let entityLabel: string; let entityLabel: string;
let entityDescription: string;
if (this.type === widgetType.rpc) { if (this.type === widgetType.rpc) {
if (this.targetDeviceId) { if (this.targetDeviceId) {
entityId = { entityId = {
@ -474,6 +475,18 @@ export class WidgetSubscription implements IWidgetSubscription {
if (labelValue) { if (labelValue) {
entityLabel = labelValue.value; 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 { } else {
@ -485,6 +498,7 @@ export class WidgetSubscription implements IWidgetSubscription {
}; };
entityName = datasource.entityName; entityName = datasource.entityName;
entityLabel = datasource.entityLabel; entityLabel = datasource.entityLabel;
entityDescription = datasource.entityDescription;
break; break;
} }
} }
@ -493,7 +507,8 @@ export class WidgetSubscription implements IWidgetSubscription {
return { return {
entityId, entityId,
entityName, entityName,
entityLabel entityLabel,
entityDescription
}; };
} else { } else {
return null; return null;

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

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

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

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

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

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

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

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

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

@ -15,13 +15,20 @@
/// ///
import L from 'leaflet'; 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 { Datasource, DatasourceData } from '@app/shared/models/widget.models';
import _ from 'lodash'; import _ from 'lodash';
import { Observable, Observer, of } from 'rxjs'; import { Observable, Observer, of } from 'rxjs';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { createLabelFromDatasource, hashCode, isNumber, isUndefined, padValue } from '@core/utils'; import {
import { Form } from '@angular/forms'; createLabelFromDatasource,
hashCode,
isDefined,
isDefinedAndNotNull, isFunction,
isNumber,
isUndefined,
padValue
} from '@core/utils';
export function createTooltip(target: L.Layer, export function createTooltip(target: L.Layer,
settings: MarkerSettings | PolylineSettings | PolygonSettings, settings: MarkerSettings | PolylineSettings | PolygonSettings,
@ -185,7 +192,7 @@ function parseTemplate(template: string, data: { $datasource?: Datasource, [key:
} else { } else {
textValue = value; textValue = value;
} }
template = template.split(variable).join(textValue); template = template.replace(variable, textValue);
match = /\${([^}]*)}/g.exec(template); match = /\${([^}]*)}/g.exec(template);
} }
@ -198,7 +205,7 @@ function parseTemplate(template: string, data: { $datasource?: Datasource, [key:
while (match !== null) { while (match !== null) {
[actionTags, actionName, actionText] = match; [actionTags, actionName, actionText] = match;
action = createLinkElement(actionName, actionText); action = createLinkElement(actionName, actionText);
template = template.split(actionTags).join(action); template = template.replace(actionTags, action);
match = linkActionRegex.exec(template); match = linkActionRegex.exec(template);
} }
@ -206,18 +213,107 @@ function parseTemplate(template: string, data: { $datasource?: Datasource, [key:
while (match !== null) { while (match !== null) {
[actionTags, actionName, actionText] = match; [actionTags, actionName, actionText] = match;
action = createButtonElement(actionName, actionText); action = createButtonElement(actionName, actionText);
template = template.split(actionTags).join(action); template = template.replace(actionTags, action);
match = buttonActionRegex.exec(template); match = buttonActionRegex.exec(template);
} }
const compiled = _.template(template); // const compiled = _.template(template);
res = compiled(data); // res = compiled(data);
res = template;
} catch (ex) { } catch (ex) {
console.log(ex, template) console.log(ex, template)
} }
return res; 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 = { export const parseWithTranslation = {
translateFn: null, translateFn: null,
@ -232,6 +328,9 @@ export const parseWithTranslation = {
parseTemplate(template: string, data: object, forceTranslate = false): string { parseTemplate(template: string, data: object, forceTranslate = false): string {
return parseTemplate(forceTranslate ? this.translate(template) : template, data, this.translate.bind(this)); 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) { setTranslate(translateFn: TranslateFunc) {
this.translateFn = translateFn; this.translateFn = translateFn;
} }
@ -250,8 +349,8 @@ export function parseData(input: DatasourceData[]): FormattedData[] {
}; };
entityArray.filter(el => el.data.length).forEach(el => { entityArray.filter(el => el.data.length).forEach(el => {
const indexDate = el?.data?.length ? el.data.length - 1 : 0; 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] = el?.data[indexDate][1];
obj[el?.dataKey?.label + '|ts'] = el?.data[indexDate][0] || null; obj[el?.dataKey?.label + '|ts'] = el?.data[indexDate][0];
if (el?.dataKey?.label === 'type') { if (el?.dataKey?.label === 'type') {
obj.deviceType = el?.data[indexDate][1]; obj.deviceType = el?.data[indexDate][1];
} }
@ -312,6 +411,24 @@ export function safeExecute(func: (...args: any[]) => any, params = []) {
return res; 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 { export function calculateNewPointCoordinate(coordinate: number, imageSize: number): number {
let pointCoordinate = coordinate / imageSize; let pointCoordinate = coordinate / imageSize;
if (pointCoordinate < 0) { if (pointCoordinate < 0) {
@ -321,3 +438,28 @@ export function calculateNewPointCoordinate(coordinate: number, imageSize: numbe
} }
return pointCoordinate; 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. /// limitations under the License.
/// ///
import L, { LeafletMouseEvent } from 'leaflet'; import L, { Icon, LeafletMouseEvent } from 'leaflet';
import { FormattedData, MarkerSettings } from './map-models'; 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 tinycolor from 'tinycolor2';
import { isDefined } from '@core/utils'; import { isDefined, isDefinedAndNotNull } from '@core/utils';
import LeafletMap from './leaflet-map';
export class Marker { export class Marker {
leafletMarker: L.Marker; leafletMarker: L.Marker;
tooltipOffset: L.LatLngTuple; tooltipOffset: L.LatLngTuple;
markerOffset: L.LatLngTuple; markerOffset: L.LatLngTuple;
tooltip: L.Popup; tooltip: L.Popup;
location: L.LatLngExpression;
data: FormattedData; data: FormattedData;
dataSources: 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?) { data?: FormattedData, dataSources?, onDragendListener?) {
this.setDataSources(data, dataSources); this.setDataSources(data, dataSources);
this.leafletMarker = L.marker(location, { this.leafletMarker = L.marker(location, {
@ -73,24 +81,35 @@ export class Marker {
} }
updateMarkerTooltip(data: FormattedData) { updateMarkerTooltip(data: FormattedData) {
if(!this.map.markerTooltipText || this.settings.useTooltipFunction) {
const pattern = this.settings.useTooltipFunction ? const pattern = this.settings.useTooltipFunction ?
safeExecute(this.settings.tooltipFunction, [this.data, this.dataSources, this.data.dsIndex]) : this.settings.tooltipPattern; safeExecute(this.settings.tooltipFunction, [this.data, this.dataSources, this.data.dsIndex]) : this.settings.tooltipPattern;
this.tooltip.setContent(parseWithTranslation.parseTemplate(pattern, data, true)); 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()) { if (this.tooltip.isOpen() && this.tooltip.getElement()) {
bindPopupActions(this.tooltip, this.settings, data.$datasource); 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); this.leafletMarker.setLatLng(position);
}
} }
updateMarkerLabel(settings: MarkerSettings) { updateMarkerLabel(settings: MarkerSettings) {
this.leafletMarker.unbindTooltip(); this.leafletMarker.unbindTooltip();
if (settings.showLabel) { 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; 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>`, this.leafletMarker.bindTooltip(`<div style="color: ${settings.labelColor};"><b>${settings.labelText}</b></div>`,
{ className: 'tb-marker-label', permanent: true, direction: 'top', offset: this.tooltipOffset }); { className: 'tb-marker-label', permanent: true, direction: 'top', offset: this.tooltipOffset });
} }
@ -121,8 +140,14 @@ export class Marker {
const currentImage = this.settings.useMarkerImageFunction ? const currentImage = this.settings.useMarkerImageFunction ?
safeExecute(this.settings.markerImageFunction, safeExecute(this.settings.markerImageFunction,
[this.data, this.settings.markerImages, this.dataSources, this.data.dsIndex]) : this.settings.currentImage; [this.data, this.settings.markerImages, this.dataSources, this.data.dsIndex]) : this.settings.currentImage;
const currentColor = tinycolor(this.settings.useColorFunction ? safeExecute(this.settings.colorFunction, let currentColor = this.settings.tinyColor;
[this.data, this.dataSources, this.data.dsIndex]) : this.settings.color).toHex(); 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) { if (currentImage && currentImage.url) {
aspectCache(currentImage.url).subscribe( aspectCache(currentImage.url).subscribe(
(aspect) => { (aspect) => {
@ -157,24 +182,33 @@ export class Marker {
} }
} }
createDefaultMarkerIcon(color, onMarkerIconReady) { createDefaultMarkerIcon(color: tinycolor.Instance, onMarkerIconReady) {
const icon = L.icon({ let icon: { size: number[], icon: Icon };
iconUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=%E2%80%A2|' + color, if (!tinycolor.equals(color, this.settings.tinyColor)) {
iconSize: [21, 34], icon = this.createColoredMarkerIcon(color);
iconAnchor: [21 * this.markerOffset[0], 34 * this.markerOffset[1]], } else {
popupAnchor: [0, -34], if (!this.map.defaultMarkerIconInfo) {
shadowUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_shadow', this.map.defaultMarkerIconInfo = this.createColoredMarkerIcon(color);
shadowSize: [40, 37], }
shadowAnchor: [12, 35] icon = this.map.defaultMarkerIconInfo;
}); }
const iconInfo = { onMarkerIconReady(icon);
size: [21, 34],
icon
};
onMarkerIconReady(iconInfo);
} }
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() { removeMarker() {
/* this.map$.subscribe(map => /* 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. /// 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 "leaflet-editable/src/Leaflet.Editable";
import { createTooltip, parseWithTranslation, safeExecute } from './maps-utils';
import { FormattedData, PolygonSettings } from './map-models'; import { FormattedData, PolygonSettings } from './map-models';
export class Polygon { export class Polygon {
@ -111,10 +111,7 @@ export class Polygon {
} }
private getPolygonColor(settings: PolygonSettings): string | null { private getPolygonColor(settings: PolygonSettings): string | null {
if (settings.usePolygonColorFunction) { return functionValueCalculator(settings.usePolygonColorFunction, settings.polygonColorFunction,
return safeExecute(settings.polygonColorFunction, [this.data, this.dataSources, this.data.dsIndex]); [this.data, this.dataSources, this.data.dsIndex], settings.polygonColor);
} else {
return 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 'leaflet-polylinedecorator';
import { FormattedData, PolylineSettings } from './map-models'; 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 { export class Polyline {
@ -72,15 +72,12 @@ export class Polyline {
getPolyStyle(settings: PolylineSettings): L.PolylineOptions { getPolyStyle(settings: PolylineSettings): L.PolylineOptions {
return { return {
interactive: false, interactive: false,
color: settings.useColorFunction ? color: functionValueCalculator(settings.useColorFunction, settings.colorFunction,
safeExecute(settings.colorFunction, [this.data, this.dataSources, this.data.dsIndex], settings.color),
[this.data, this.dataSources, this.data.dsIndex]) : settings.color, opacity: functionValueCalculator(settings.useStrokeOpacityFunction, settings.strokeOpacityFunction,
opacity: settings.useStrokeOpacityFunction ? [this.data, this.dataSources, this.data.dsIndex], settings.strokeOpacity),
safeExecute(settings.strokeOpacityFunction, weight: functionValueCalculator(settings.useStrokeWeightFunction, settings.strokeWeightFunction,
[this.data, this.dataSources, this.data.dsIndex]) : settings.strokeOpacity, [this.data, this.dataSources, this.data.dsIndex], settings.strokeWeight)
weight: settings.useStrokeWeightFunction ?
safeExecute(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) { constructor(ctx: WidgetContext, $container, options: UnitedMapSettings) {
super(ctx, $container, options); super(ctx, $container, options);
this.resource = ctx.$injector.get(ResourcesService); this.resource = ctx.$injector.get(ResourcesService);
super.initSettings(options);
this.loadGoogle(() => { this.loadGoogle(() => {
const map = L.map($container, {attributionControl: false, editable: !!options.editablePolygon}).setView(options?.defaultCenterPosition, options?.defaultZoomLevel); const map = L.map($container, {attributionControl: false, editable: !!options.editablePolygon}).setView(options?.defaultCenterPosition, options?.defaultZoomLevel);
(L.gridLayer as any).googleMutant({ (L.gridLayer as any).googleMutant({
@ -42,7 +43,6 @@ export class GoogleMap extends LeafletMap {
}).addTo(map); }).addTo(map);
super.setMap(map); super.setMap(map);
}, options.gmApiKey); }, options.gmApiKey);
super.initSettings(options);
} }
private loadGoogle(callback, apiKey = 'AIzaSyDoEx2kaGz3PxwbI9T7ccTSg5xjdw8Nw8Q') { 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 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); const tileLayer = (L.tileLayer as any).provider(options.mapProviderHere || 'HERE.normalDay', options.credentials);
tileLayer.addTo(map); tileLayer.addTo(map);
super.setMap(map);
super.initSettings(options); 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 { DataSet, DatasourceType, widgetType } from '@shared/models/widget.models';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { WidgetSubscriptionOptions } from '@core/api/widget-api.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 "leaflet-editable/src/Leaflet.Editable";
import {isArray} from "rxjs/internal-compatibility"; import {isArray} from "rxjs/internal-compatibility";
@ -49,8 +49,8 @@ export class ImageMap extends LeafletMap {
this.onResize(true); this.onResize(true);
} else { } else {
this.onResize(); this.onResize();
super.setMap(this.map);
super.initSettings(options); super.initSettings(options);
super.setMap(this.map);
} }
}); });
} }
@ -215,7 +215,7 @@ export class ImageMap extends LeafletMap {
convertPosition(expression): L.LatLng { convertPosition(expression): L.LatLng {
const xPos = expression[this.options.xPosKeyName]; const xPos = expression[this.options.xPosKeyName];
const yPos = expression[this.options.yPosKeyName]; 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; return null;
} }
Object.assign(expression, this.posFunction(xPos, yPos)); 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. /// limitations under the License.
/// ///
export * from './tencent-map'; import {
export * from './google-map'; googleMapSettingsSchema, hereMapSettingsSchema, imageMapSettingsSchema,
export * from './here-map'; openstreetMapSettingsSchema,
export * from './image-map'; tencentMapSettingsSchema
export * from './openstreet-map'; } 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 else
tileLayer = (L.tileLayer as any).provider(options.mapProvider || 'OpenStreetMap.Mapnik'); tileLayer = (L.tileLayer as any).provider(options.mapProvider || 'OpenStreetMap.Mapnik');
tileLayer.addTo(map); tileLayer.addTo(map);
super.setMap(map);
super.initSettings(options); 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' attribution: '&copy;2020 Tencent - GS(2018)2236号- Data&copy; NavInfo'
}).addTo(map); }).addTo(map);
txLayer.addTo(map); txLayer.addTo(map);
super.setMap(map);
super.initSettings(options); 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 id: datasource.entityId
}, },
entityName: datasource.entityName, 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.calcLabel();
this.calcTooltip(currentPosition.find(position => position.entityName === this.activeTrip.entityName)); 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); this.mapWidget.map.updatePolylines(this.interpolatedTimeData.map(ds => _.values(ds)), true, this.activeTrip);
if (this.settings.showPolygon) { if (this.settings.showPolygon) {
this.mapWidget.map.updatePolygons(this.interpolatedTimeData); 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 { debounceTime, distinctUntilChanged, tap } from 'rxjs/operators';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { DialogService } from '@core/services/dialog.service'; import { DialogService } from '@core/services/dialog.service';
import { deepClone } from '@core/utils'; import { deepClone, isUndefined } from '@core/utils';
import { import {
DashboardStateDialogComponent, DashboardStateDialogComponent,
DashboardStateDialogData DashboardStateDialogData
@ -198,8 +198,9 @@ export class ManageDashboardStatesDialogComponent extends
root: state.root, root: state.root,
layouts: state.layouts layouts: state.layouts
}; };
if (prevStateId) { if (prevStateId && prevStateId !== state.id) {
this.states[prevStateId] = newState; delete this.states[prevStateId];
this.states[state.id] = newState;
} else { } else {
this.states[state.id] = newState; this.states[state.id] = newState;
} }
@ -210,6 +211,19 @@ export class ManageDashboardStatesDialogComponent extends
otherState.root = false; 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(); 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 { EntityId } from '@shared/models/id/entity-id';
import { EntityService } from '@core/http/entity.service'; import { EntityService } from '@core/http/entity.service';
import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { getCurrentAuthUser } from '@core/auth/auth.selectors';
import { Authority } from '@shared/models/authority.enum';
@Component({ @Component({
selector: 'tb-entity-autocomplete', selector: 'tb-entity-autocomplete',
@ -196,6 +198,18 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit
this.noEntitiesMatchingText = 'customer.no-customers-matching'; this.noEntitiesMatchingText = 'customer.no-customers-matching';
this.entityRequiredText = 'customer.default-customer-required'; this.entityRequiredText = 'customer.default-customer-required';
break; 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(); const currentEntity = this.getCurrentEntity();
@ -328,6 +342,13 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit
return EntityType.TENANT; return EntityType.TENANT;
} else if (entityType === AliasEntityType.CURRENT_USER) { } else if (entityType === AliasEntityType.CURRENT_USER) {
return EntityType.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; return entityType;
} }

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

@ -28,7 +28,8 @@
<tb-entity-autocomplete <tb-entity-autocomplete
fxFlex fxFlex
*ngIf="modelValue.entityType && modelValue.entityType !== AliasEntityType.CURRENT_TENANT *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" [required]="required"
[entityType]="modelValue.entityType" [entityType]="modelValue.entityType"
formControlName="entityId"> 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() { ngOnInit() {
this.entitySelectFormGroup.get('entityType').valueChanges.subscribe( this.entitySelectFormGroup.get('entityType').valueChanges.subscribe(
(value) => { (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.modelValue.id = NULL_UUID;
} }
this.updateView(value, this.modelValue.id); this.updateView(value, this.modelValue.id);
@ -147,7 +148,8 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte
}; };
if (this.modelValue.entityType && (this.modelValue.id || if (this.modelValue.entityType && (this.modelValue.id ||
this.modelValue.entityType === AliasEntityType.CURRENT_TENANT || 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); this.propagateChange(this.modelValue);
} else { } else {
this.propagateChange(null); this.propagateChange(null);

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

@ -51,7 +51,8 @@ export enum EntityType {
export enum AliasEntityType { export enum AliasEntityType {
CURRENT_CUSTOMER = 'CURRENT_CUSTOMER', CURRENT_CUSTOMER = 'CURRENT_CUSTOMER',
CURRENT_TENANT = 'CURRENT_TENANT', CURRENT_TENANT = 'CURRENT_TENANT',
CURRENT_USER = 'CURRENT_USER' CURRENT_USER = 'CURRENT_USER',
CURRENT_USER_OWNER = 'CURRENT_USER_OWNER'
} }
export interface EntityTypeTranslation { export interface EntityTypeTranslation {
@ -237,6 +238,13 @@ export const entityTypeTranslations = new Map<EntityType | AliasEntityType, Enti
type: 'entity.type-current-user', type: 'entity.type-current-user',
list: '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, type: EntityKeyType.ENTITY_FIELD,
key: 'label' key: 'label'
},
{
type: EntityKeyType.ENTITY_FIELD,
key: 'additionalInfo'
} }
]; ];
@ -609,7 +613,18 @@ export function entityDataToEntityInfo(entityData: EntityData): EntityInfo {
} else { } else {
entityInfo.label = ''; 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; return entityInfo;
} }

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

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

Loading…
Cancel
Save