Browse Source

Merge branch 'master' of https://github.com/thingsboard/thingsboard into fix/ws

# Conflicts:
#	application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java
pull/4187/head
YevhenBondarenko 5 years ago
parent
commit
d91c52487f
  1. 2
      application/pom.xml
  2. 4
      application/src/main/data/json/system/widget_bundles/cards.json
  3. 1
      application/src/main/java/org/thingsboard/server/controller/AuthController.java
  4. 20
      application/src/main/java/org/thingsboard/server/controller/UserController.java
  5. 2
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  6. 2
      application/src/main/java/org/thingsboard/server/service/sms/AbstractSmsSender.java
  7. 15
      application/src/main/java/org/thingsboard/server/service/sms/twilio/TwilioSmsSender.java
  8. 54
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java
  9. 267
      application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java
  10. 315
      application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractSubCtx.java
  11. 3
      application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java
  12. 55
      application/src/main/java/org/thingsboard/server/service/subscription/TbEntityCountSubCtx.java
  13. 3
      application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubscriptionService.java
  14. 35
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java
  15. 3
      application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryWebSocketService.java
  16. 6
      application/src/main/java/org/thingsboard/server/service/telemetry/cmd/TelemetryPluginCmdsWrapper.java
  17. 8
      application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AlarmDataUpdate.java
  18. 33
      application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/CmdUpdate.java
  19. 5
      application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/CmdUpdateType.java
  20. 21
      application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/DataUpdate.java
  21. 35
      application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityCountCmd.java
  22. 25
      application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityCountUnsubscribeCmd.java
  23. 57
      application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityCountUpdate.java
  24. 8
      application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityDataUpdate.java
  25. 2
      application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/UnsubscribeCmd.java
  26. 31
      application/src/test/java/org/thingsboard/server/controller/BaseEntityQueryControllerTest.java
  27. 99
      application/src/test/java/org/thingsboard/server/controller/BaseWebsocketApiTest.java
  28. 2
      application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java
  29. 2
      common/actor/pom.xml
  30. 2
      common/dao-api/pom.xml
  31. 2
      common/data/pom.xml
  32. 4
      common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetSearchQuery.java
  33. 4
      common/data/src/main/java/org/thingsboard/server/common/data/device/DeviceSearchQuery.java
  34. 4
      common/data/src/main/java/org/thingsboard/server/common/data/entityview/EntityViewSearchQuery.java
  35. 9
      common/data/src/main/java/org/thingsboard/server/common/data/query/AbstractDataQuery.java
  36. 4
      common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmDataQuery.java
  37. 14
      common/data/src/main/java/org/thingsboard/server/common/data/query/EntityCountQuery.java
  38. 4
      common/data/src/main/java/org/thingsboard/server/common/data/query/EntityDataQuery.java
  39. 1
      common/data/src/main/java/org/thingsboard/server/common/data/query/EntityFilter.java
  40. 1
      common/data/src/main/java/org/thingsboard/server/common/data/query/EntityFilterType.java
  41. 30
      common/data/src/main/java/org/thingsboard/server/common/data/query/EntityTypeFilter.java
  42. 5
      common/data/src/main/java/org/thingsboard/server/common/data/query/RelationsQueryFilter.java
  43. 2
      common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelationsQuery.java
  44. 2
      common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationEntityTypeFilter.java
  45. 2
      common/message/pom.xml
  46. 2
      common/pom.xml
  47. 2
      common/queue/pom.xml
  48. 2
      common/stats/pom.xml
  49. 2
      common/transport/coap/pom.xml
  50. 2
      common/transport/http/pom.xml
  51. 2
      common/transport/mqtt/pom.xml
  52. 2
      common/transport/pom.xml
  53. 2
      common/transport/transport-api/pom.xml
  54. 2
      common/util/pom.xml
  55. 2
      dao/pom.xml
  56. 10
      dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java
  57. 84
      dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java
  58. 25
      dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java
  59. 10
      dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java
  60. 54
      dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceTest.java
  61. 6
      dao/src/test/java/org/thingsboard/server/dao/service/BaseRelationServiceTest.java
  62. 2
      msa/black-box-tests/pom.xml
  63. 2
      msa/js-executor/package.json
  64. 2
      msa/js-executor/pom.xml
  65. 2
      msa/pom.xml
  66. 2
      msa/tb-node/pom.xml
  67. 2
      msa/tb/pom.xml
  68. 2
      msa/transport/coap/pom.xml
  69. 2
      msa/transport/http/pom.xml
  70. 2
      msa/transport/mqtt/pom.xml
  71. 2
      msa/transport/pom.xml
  72. 2
      msa/web-ui/package.json
  73. 2
      msa/web-ui/pom.xml
  74. 4
      netty-mqtt/pom.xml
  75. 2
      pom.xml
  76. 2
      rest-client/pom.xml
  77. 2
      rule-engine/pom.xml
  78. 2
      rule-engine/rule-engine-api/pom.xml
  79. 2
      rule-engine/rule-engine-components/pom.xml
  80. 4
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/data/RelationsQuery.java
  81. 6
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttrNodeConfiguration.java
  82. 6
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeConfiguration.java
  83. 2
      rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js
  84. 2
      tools/pom.xml
  85. 2
      transport/coap/pom.xml
  86. 2
      transport/http/pom.xml
  87. 2
      transport/mqtt/pom.xml
  88. 2
      transport/pom.xml
  89. 3
      ui-ngx/package.json
  90. 2
      ui-ngx/pom.xml
  91. 21
      ui-ngx/src/app/core/api/alias-controller.ts
  92. 10
      ui-ngx/src/app/core/api/data-aggregator.ts
  93. 99
      ui-ngx/src/app/core/api/entity-data-subscription.ts
  94. 23
      ui-ngx/src/app/core/api/entity-data.service.ts
  95. 1
      ui-ngx/src/app/core/api/widget-api.models.ts
  96. 17
      ui-ngx/src/app/core/api/widget-subscription.ts
  97. 15
      ui-ngx/src/app/core/http/entity.service.ts
  98. 4
      ui-ngx/src/app/core/services/item-buffer.service.ts
  99. 3
      ui-ngx/src/app/core/services/utils.service.ts
  100. 3
      ui-ngx/src/app/core/utils.ts

2
application/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.3.0-SNAPSHOT</version>
<version>3.2.2-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>application</artifactId>

4
application/src/main/data/json/system/widget_bundles/cards.json

@ -47,7 +47,7 @@
"resources": [],
"templateHtml": "<tb-timeseries-table-widget \n [ctx]=\"ctx\">\n</tb-timeseries-table-widget>",
"templateCss": "",
"controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.timeseriesTableWidget.onDataUpdated();\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}",
"controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.timeseriesTableWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n ignoreDataUpdateOnIntervalTick: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}",
"settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"TimeseriesTableSettings\",\n \"properties\": {\n \"showTimestamp\": {\n \"title\": \"Display timestamp column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"showMilliseconds\": {\n \"title\": \"Display timestamp milliseconds\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n }, \n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"hideEmptyLines\": {\n \"title\": \"Hide empty lines\",\n \"type\": \"boolean\",\n \"default\": false\n }\n },\n \"required\": []\n },\n \"form\": [\n \"showTimestamp\",\n \"showMilliseconds\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"hideEmptyLines\"\n ]\n}",
"dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\"\n }\n ]\n}",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature °C\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = (value + 60)/120 * 100;\\n var color = tinycolor.mix('blue', 'red', amount = percent);\\n color.setAlpha(.5);\\n return {\\n paddingLeft: '20px',\\n color: '#ffffff',\\n background: color.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\"},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity, %\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = value;\\n var backgroundColor = tinycolor('blue');\\n backgroundColor.setAlpha(value/100);\\n var color = 'blue';\\n if (value > 50) {\\n color = 'white';\\n }\\n \\n return {\\n paddingLeft: '20px',\\n color: color,\\n background: backgroundColor.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 5) {\\n\\tvalue = 5;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":60000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"showTimestamp\":true,\"displayPagination\":true,\"defaultPageSize\":10},\"title\":\"Timeseries table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"showTitleIcon\":false,\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\"}"
@ -134,4 +134,4 @@
}
}
]
}
}

1
application/src/main/java/org/thingsboard/server/controller/AuthController.java

@ -215,6 +215,7 @@ public class AuthController extends BaseController {
User user = userService.findUserById(TenantId.SYS_TENANT_ID, credentials.getUserId());
UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, user.getEmail());
SecurityUser securityUser = new SecurityUser(user, credentials.isEnabled(), principal);
userService.setUserCredentialsEnabled(user.getTenantId(), user.getId(), true);
String baseUrl = systemSecurityService.getBaseUrl(user.getTenantId(), user.getCustomerId(), request);
String loginUrl = String.format("%s/login", baseUrl);
String email = user.getEmail();

20
application/src/main/java/org/thingsboard/server/controller/UserController.java

@ -94,12 +94,24 @@ public class UserController extends BaseController {
processDashboardIdFromAdditionalInfo((ObjectNode) user.getAdditionalInfo(), DEFAULT_DASHBOARD);
processDashboardIdFromAdditionalInfo((ObjectNode) user.getAdditionalInfo(), HOME_DASHBOARD);
}
UserCredentials userCredentials = userService.findUserCredentialsByUserId(user.getTenantId(), user.getId());
if(userCredentials.isEnabled()) {
addUserCredentialsEnabled((ObjectNode) user.getAdditionalInfo());
}
return user;
} catch (Exception e) {
throw handleException(e);
}
}
private void addUserCredentialsEnabled(ObjectNode additionalInfo) {
if(!additionalInfo.isNull()) {
if(!additionalInfo.has("userCredentialsEnabled")) {
additionalInfo.put("userCredentialsEnabled", true);
}
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/user/tokenAccessEnabled", method = RequestMethod.GET)
@ResponseBody
@ -193,13 +205,13 @@ public class UserController extends BaseController {
user.getId(), user);
UserCredentials userCredentials = userService.findUserCredentialsByUserId(getCurrentUser().getTenantId(), user.getId());
if (!userCredentials.isEnabled()) {
if (!userCredentials.isEnabled() && userCredentials.getActivateToken() != null) {
String baseUrl = systemSecurityService.getBaseUrl(getTenantId(), getCurrentUser().getCustomerId(), request);
String activateUrl = String.format(ACTIVATE_URL_PATTERN, baseUrl,
userCredentials.getActivateToken());
mailService.sendActivationEmail(activateUrl, email);
} else {
throw new ThingsboardException("User is already active!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
throw new ThingsboardException("User is already activated!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}
} catch (Exception e) {
throw handleException(e);
@ -218,13 +230,13 @@ public class UserController extends BaseController {
User user = checkUserId(userId, Operation.READ);
SecurityUser authUser = getCurrentUser();
UserCredentials userCredentials = userService.findUserCredentialsByUserId(authUser.getTenantId(), user.getId());
if (!userCredentials.isEnabled()) {
if (!userCredentials.isEnabled() && userCredentials.getActivateToken() != null) {
String baseUrl = systemSecurityService.getBaseUrl(getTenantId(), getCurrentUser().getCustomerId(), request);
String activateUrl = String.format(ACTIVATE_URL_PATTERN, baseUrl,
userCredentials.getActivateToken());
return activateUrl;
} else {
throw new ThingsboardException("User is already active!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
throw new ThingsboardException("User is already activated!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}
} catch (Exception e) {
throw handleException(e);

2
application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java

@ -186,7 +186,7 @@ public class ThingsboardInstallService {
log.info("Upgrading ThingsBoard from version 3.2.0 to 3.2.1 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.2.0");
case "3.2.1":
log.info("Upgrading ThingsBoard from version 3.2.1 to 3.3.0 ...");
log.info("Upgrading ThingsBoard from version 3.2.1 to 3.2.2 ...");
if (databaseTsUpgradeService != null) {
databaseTsUpgradeService.upgradeDatabase("3.2.1");
}

2
application/src/main/java/org/thingsboard/server/service/sms/AbstractSmsSender.java

@ -24,7 +24,7 @@ import java.util.regex.Pattern;
@Slf4j
public abstract class AbstractSmsSender implements SmsSender {
private static final Pattern E_164_PHONE_NUMBER_PATTERN = Pattern.compile("^\\+[1-9]\\d{1,14}$");
protected static final Pattern E_164_PHONE_NUMBER_PATTERN = Pattern.compile("^\\+[1-9]\\d{1,14}$");
private static final int MAX_SMS_MESSAGE_LENGTH = 1600;
private static final int MAX_SMS_SEGMENT_LENGTH = 70;

15
application/src/main/java/org/thingsboard/server/service/sms/twilio/TwilioSmsSender.java

@ -19,21 +19,34 @@ import com.twilio.http.TwilioRestClient;
import com.twilio.rest.api.v2010.account.Message;
import com.twilio.type.PhoneNumber;
import org.apache.commons.lang3.StringUtils;
import org.thingsboard.rule.engine.api.sms.exception.SmsParseException;
import org.thingsboard.server.common.data.sms.config.TwilioSmsProviderConfiguration;
import org.thingsboard.rule.engine.api.sms.exception.SmsException;
import org.thingsboard.rule.engine.api.sms.exception.SmsSendException;
import org.thingsboard.server.service.sms.AbstractSmsSender;
import java.util.regex.Pattern;
public class TwilioSmsSender extends AbstractSmsSender {
private static final Pattern PHONE_NUMBERS_SID_MESSAGE_SERVICE_SID = Pattern.compile("^(PN|MG).*$");
private TwilioRestClient twilioRestClient;
private String numberFrom;
private String validatePhoneTwilioNumber(String phoneNumber) throws SmsParseException {
phoneNumber = phoneNumber.trim();
if (!E_164_PHONE_NUMBER_PATTERN.matcher(phoneNumber).matches() && !PHONE_NUMBERS_SID_MESSAGE_SERVICE_SID.matcher(phoneNumber).matches()) {
throw new SmsParseException("Invalid phone number format. Phone number must be in E.164 format/Phone Number's SID/Messaging Service SID.");
}
return phoneNumber;
}
public TwilioSmsSender(TwilioSmsProviderConfiguration config) {
if (StringUtils.isEmpty(config.getAccountSid()) || StringUtils.isEmpty(config.getAccountToken()) || StringUtils.isEmpty(config.getNumberFrom())) {
throw new IllegalArgumentException("Invalid twilio sms provider configuration: accountSid, accountToken and numberFrom should be specified!");
}
this.numberFrom = this.validatePhoneNumber(config.getNumberFrom());
this.numberFrom = this.validatePhoneTwilioNumber(config.getNumberFrom());
this.twilioRestClient = new TwilioRestClient.Builder(config.getAccountSid(), config.getAccountToken()).build();
}

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

@ -54,6 +54,7 @@ import org.thingsboard.server.service.telemetry.TelemetryWebSocketService;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef;
import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataUpdate;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityHistoryCmd;
@ -92,7 +93,7 @@ import java.util.stream.Collectors;
public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubscriptionService {
private static final int DEFAULT_LIMIT = 100;
private final Map<String, Map<Integer, TbAbstractDataSubCtx>> subscriptionsBySessionId = new ConcurrentHashMap<>();
private final Map<String, Map<Integer, TbAbstractSubCtx>> subscriptionsBySessionId = new ConcurrentHashMap<>();
@Autowired
private TelemetryWebSocketService wsService;
@ -202,7 +203,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
//TODO: validate number of dynamic page links against rate limits. Ignore dynamic flag if limit is reached.
TbEntityDataSubCtx finalCtx = ctx;
ScheduledFuture<?> task = scheduler.scheduleWithFixedDelay(
() -> refreshDynamicQuery(tenantId, customerId, finalCtx),
() -> refreshDynamicQuery(finalCtx),
dynamicPageLinkRefreshInterval, dynamicPageLinkRefreshInterval, TimeUnit.SECONDS);
finalCtx.setRefreshTask(task);
}
@ -235,6 +236,26 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
}, wsCallBackExecutor);
}
@Override
public void handleCmd(TelemetryWebSocketSessionRef session, EntityCountCmd cmd) {
TbEntityCountSubCtx ctx = getSubCtx(session.getSessionId(), cmd.getCmdId());
if (ctx == null) {
ctx = createSubCtx(session, cmd);
long start = System.currentTimeMillis();
ctx.fetchData();
long end = System.currentTimeMillis();
stats.getRegularQueryInvocationCnt().incrementAndGet();
stats.getRegularQueryTimeSpent().addAndGet(end - start);
TbEntityCountSubCtx finalCtx = ctx;
ScheduledFuture<?> task = scheduler.scheduleWithFixedDelay(
() -> refreshDynamicQuery(finalCtx),
dynamicPageLinkRefreshInterval, dynamicPageLinkRefreshInterval, TimeUnit.SECONDS);
finalCtx.setRefreshTask(task);
} else {
log.debug("[{}][{}] Received duplicate command: {}", session.getSessionId(), cmd.getCmdId(), cmd);
}
}
@Override
public void handleCmd(TelemetryWebSocketSessionRef session, AlarmDataCmd cmd) {
TbAlarmDataSubCtx ctx = getSubCtx(session.getSessionId(), cmd.getCmdId());
@ -267,7 +288,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
}
}
private void refreshDynamicQuery(TenantId tenantId, CustomerId customerId, TbEntityDataSubCtx finalCtx) {
private void refreshDynamicQuery(TbAbstractSubCtx finalCtx) {
try {
long start = System.currentTimeMillis();
finalCtx.update();
@ -299,7 +320,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
}
private TbEntityDataSubCtx createSubCtx(TelemetryWebSocketSessionRef sessionRef, EntityDataCmd cmd) {
Map<Integer, TbAbstractDataSubCtx> sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>());
Map<Integer, TbAbstractSubCtx> sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>());
TbEntityDataSubCtx ctx = new TbEntityDataSubCtx(serviceId, wsService, entityService, localSubscriptionService,
attributesService, stats, sessionRef, cmd.getCmdId(), maxEntitiesPerDataSubscription);
if (cmd.getQuery() != null) {
@ -309,8 +330,20 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
return ctx;
}
private TbEntityCountSubCtx createSubCtx(TelemetryWebSocketSessionRef sessionRef, EntityCountCmd cmd) {
Map<Integer, TbAbstractSubCtx> sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>());
TbEntityCountSubCtx ctx = new TbEntityCountSubCtx(serviceId, wsService, entityService, localSubscriptionService,
attributesService, stats, sessionRef, cmd.getCmdId());
if (cmd.getQuery() != null) {
ctx.setAndResolveQuery(cmd.getQuery());
}
sessionSubs.put(cmd.getCmdId(), ctx);
return ctx;
}
private TbAlarmDataSubCtx createSubCtx(TelemetryWebSocketSessionRef sessionRef, AlarmDataCmd cmd) {
Map<Integer, TbAbstractDataSubCtx> sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>());
Map<Integer, TbAbstractSubCtx> sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>());
TbAlarmDataSubCtx ctx = new TbAlarmDataSubCtx(serviceId, wsService, entityService, localSubscriptionService,
attributesService, stats, alarmService, sessionRef, cmd.getCmdId(), maxEntitiesPerAlarmSubscription);
ctx.setAndResolveQuery(cmd.getQuery());
@ -319,8 +352,8 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
}
@SuppressWarnings("unchecked")
private <T extends TbAbstractDataSubCtx> T getSubCtx(String sessionId, int cmdId) {
Map<Integer, TbAbstractDataSubCtx> sessionSubs = subscriptionsBySessionId.get(sessionId);
private <T extends TbAbstractSubCtx> T getSubCtx(String sessionId, int cmdId) {
Map<Integer, TbAbstractSubCtx> sessionSubs = subscriptionsBySessionId.get(sessionId);
if (sessionSubs != null) {
return (T) sessionSubs.get(cmdId);
} else {
@ -464,17 +497,16 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
cleanupAndCancel(getSubCtx(sessionId, cmd.getCmdId()));
}
private void cleanupAndCancel(TbAbstractDataSubCtx ctx) {
private void cleanupAndCancel(TbAbstractSubCtx ctx) {
if (ctx != null) {
ctx.cancelTasks();
ctx.clearEntitySubscriptions();
ctx.clearDynamicValueSubscriptions();
ctx.clearSubscriptions();
}
}
@Override
public void cancelAllSessionSubscriptions(String sessionId) {
Map<Integer, TbAbstractDataSubCtx> sessionSubs = subscriptionsBySessionId.remove(sessionId);
Map<Integer, TbAbstractSubCtx> sessionSubs = subscriptionsBySessionId.remove(sessionId);
if (sessionSubs != null) {
sessionSubs.values().forEach(this::cleanupAndCancel);
}

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

@ -15,32 +15,16 @@
*/
package org.thingsboard.server.service.subscription;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.AbstractDataQuery;
import org.thingsboard.server.common.data.query.ComplexFilterPredicate;
import org.thingsboard.server.common.data.query.DynamicValue;
import org.thingsboard.server.common.data.query.DynamicValueSourceType;
import org.thingsboard.server.common.data.query.EntityData;
import org.thingsboard.server.common.data.query.EntityDataPageLink;
import org.thingsboard.server.common.data.query.EntityDataQuery;
import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.FilterPredicateType;
import org.thingsboard.server.common.data.query.KeyFilter;
import org.thingsboard.server.common.data.query.KeyFilterPredicate;
import org.thingsboard.server.common.data.query.SimpleKeyFilterPredicate;
import org.thingsboard.server.common.data.query.TsValue;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.entity.EntityService;
@ -52,140 +36,25 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledFuture;
import java.util.function.Function;
import java.util.stream.Collectors;
@Slf4j
@Data
public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends EntityDataPageLink>> {
public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends EntityDataPageLink>> extends TbAbstractSubCtx<T> {
protected final String serviceId;
protected final SubscriptionServiceStatistics stats;
protected final TelemetryWebSocketService wsService;
protected final EntityService entityService;
protected final TbLocalSubscriptionService localSubscriptionService;
protected final AttributesService attributesService;
protected final TelemetryWebSocketSessionRef sessionRef;
protected final int cmdId;
protected final Map<Integer, EntityId> subToEntityIdMap;
protected final Set<Integer> subToDynamicValueKeySet;
@Getter
protected final Map<DynamicValueKey, List<DynamicValue>> dynamicValues;
@Getter
protected PageData<EntityData> data;
@Getter
@Setter
protected T query;
@Setter
protected volatile ScheduledFuture<?> refreshTask;
public TbAbstractDataSubCtx(String serviceId, TelemetryWebSocketService wsService,
EntityService entityService, TbLocalSubscriptionService localSubscriptionService,
AttributesService attributesService, SubscriptionServiceStatistics stats,
TelemetryWebSocketSessionRef sessionRef, int cmdId) {
this.serviceId = serviceId;
this.wsService = wsService;
this.entityService = entityService;
this.localSubscriptionService = localSubscriptionService;
this.attributesService = attributesService;
this.stats = stats;
this.sessionRef = sessionRef;
this.cmdId = cmdId;
super(serviceId, wsService, entityService, localSubscriptionService, attributesService, stats, sessionRef, cmdId);
this.subToEntityIdMap = new ConcurrentHashMap<>();
this.subToDynamicValueKeySet = ConcurrentHashMap.newKeySet();
this.dynamicValues = new ConcurrentHashMap<>();
}
public void setAndResolveQuery(T query) {
dynamicValues.clear();
this.query = query;
if (query != null && query.getKeyFilters() != null) {
for (KeyFilter filter : query.getKeyFilters()) {
registerDynamicValues(filter.getPredicate());
}
}
resolve(getTenantId(), getCustomerId(), getUserId());
}
public void resolve(TenantId tenantId, CustomerId customerId, UserId userId) {
List<ListenableFuture<DynamicValueKeySub>> futures = new ArrayList<>();
for (DynamicValueKey key : dynamicValues.keySet()) {
switch (key.getSourceType()) {
case CURRENT_TENANT:
futures.add(resolveEntityValue(tenantId, tenantId, key));
break;
case CURRENT_CUSTOMER:
if (customerId != null && !customerId.isNullUid()) {
futures.add(resolveEntityValue(tenantId, customerId, key));
}
break;
case CURRENT_USER:
if (userId != null && !userId.isNullUid()) {
futures.add(resolveEntityValue(tenantId, userId, key));
}
break;
}
}
try {
Map<EntityId, Map<String, DynamicValueKeySub>> tmpSubMap = new HashMap<>();
for (DynamicValueKeySub sub : Futures.successfulAsList(futures).get()) {
tmpSubMap.computeIfAbsent(sub.getEntityId(), tmp -> new HashMap<>()).put(sub.getKey().getSourceAttribute(), sub);
}
for (EntityId entityId : tmpSubMap.keySet()) {
Map<String, Long> keyStates = new HashMap<>();
Map<String, DynamicValueKeySub> dynamicValueKeySubMap = tmpSubMap.get(entityId);
dynamicValueKeySubMap.forEach((k, v) -> keyStates.put(k, v.getLastUpdateTs()));
int subIdx = sessionRef.getSessionSubIdSeq().incrementAndGet();
TbAttributeSubscription sub = TbAttributeSubscription.builder()
.serviceId(serviceId)
.sessionId(sessionRef.getSessionId())
.subscriptionId(subIdx)
.tenantId(sessionRef.getSecurityCtx().getTenantId())
.entityId(entityId)
.updateConsumer((s, subscriptionUpdate) -> dynamicValueSubUpdate(s, subscriptionUpdate, dynamicValueKeySubMap))
.allKeys(false)
.keyStates(keyStates)
.scope(TbAttributeSubscriptionScope.SERVER_SCOPE)
.build();
subToDynamicValueKeySet.add(subIdx);
localSubscriptionService.addSubscription(sub);
}
} catch (InterruptedException | ExecutionException e) {
log.info("[{}][{}][{}] Failed to resolve dynamic values: {}", tenantId, customerId, userId, dynamicValues.keySet());
}
}
private void dynamicValueSubUpdate(String sessionId, TelemetrySubscriptionUpdate subscriptionUpdate,
Map<String, DynamicValueKeySub> dynamicValueKeySubMap) {
Map<String, TsValue> latestUpdate = new HashMap<>();
subscriptionUpdate.getData().forEach((k, v) -> {
Object[] data = (Object[]) v.get(0);
latestUpdate.put(k, new TsValue((Long) data[0], (String) data[1]));
});
boolean invalidateFilter = false;
for (Map.Entry<String, TsValue> entry : latestUpdate.entrySet()) {
String k = entry.getKey();
TsValue tsValue = entry.getValue();
DynamicValueKeySub sub = dynamicValueKeySubMap.get(k);
if (sub.updateValue(tsValue)) {
invalidateFilter = true;
updateDynamicValuesByKey(sub, tsValue);
}
}
if (invalidateFilter) {
update();
}
}
public void fetchData() {
@ -231,104 +100,10 @@ public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends
return data.getData();
}
@Data
private static class DynamicValueKeySub {
private final DynamicValueKey key;
private final EntityId entityId;
private long lastUpdateTs;
private String lastUpdateValue;
boolean updateValue(TsValue value) {
if (value.getTs() > lastUpdateTs && (lastUpdateValue == null || !lastUpdateValue.equals(value.getValue()))) {
this.lastUpdateTs = value.getTs();
this.lastUpdateValue = value.getValue();
return true;
} else {
return false;
}
}
}
private ListenableFuture<DynamicValueKeySub> resolveEntityValue(TenantId tenantId, EntityId entityId, DynamicValueKey key) {
ListenableFuture<Optional<AttributeKvEntry>> entry = attributesService.find(tenantId, entityId,
TbAttributeSubscriptionScope.SERVER_SCOPE.name(), key.getSourceAttribute());
return Futures.transform(entry, attributeOpt -> {
DynamicValueKeySub sub = new DynamicValueKeySub(key, entityId);
if (attributeOpt.isPresent()) {
AttributeKvEntry attribute = attributeOpt.get();
sub.setLastUpdateTs(attribute.getLastUpdateTs());
sub.setLastUpdateValue(attribute.getValueAsString());
updateDynamicValuesByKey(sub, new TsValue(attribute.getLastUpdateTs(), attribute.getValueAsString()));
}
return sub;
}, MoreExecutors.directExecutor());
}
@SuppressWarnings("unchecked")
private void updateDynamicValuesByKey(DynamicValueKeySub sub, TsValue tsValue) {
DynamicValueKey dvk = sub.getKey();
switch (dvk.getPredicateType()) {
case STRING:
dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(tsValue.getValue()));
break;
case NUMERIC:
try {
Double dValue = Double.parseDouble(tsValue.getValue());
dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(dValue));
} catch (NumberFormatException e) {
dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(null));
}
break;
case BOOLEAN:
Boolean bValue = Boolean.parseBoolean(tsValue.getValue());
dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(bValue));
break;
}
}
@SuppressWarnings("unchecked")
private void registerDynamicValues(KeyFilterPredicate predicate) {
switch (predicate.getType()) {
case STRING:
case NUMERIC:
case BOOLEAN:
Optional<DynamicValue> value = getDynamicValueFromSimplePredicate((SimpleKeyFilterPredicate) predicate);
if (value.isPresent()) {
DynamicValue dynamicValue = value.get();
DynamicValueKey key = new DynamicValueKey(
predicate.getType(),
dynamicValue.getSourceType(),
dynamicValue.getSourceAttribute());
dynamicValues.computeIfAbsent(key, tmp -> new ArrayList<>()).add(dynamicValue);
}
break;
case COMPLEX:
((ComplexFilterPredicate) predicate).getPredicates().forEach(this::registerDynamicValues);
}
}
private Optional<DynamicValue<T>> getDynamicValueFromSimplePredicate(SimpleKeyFilterPredicate<T> predicate) {
if (predicate.getValue().getUserValue() == null) {
return Optional.ofNullable(predicate.getValue().getDynamicValue());
} else {
return Optional.empty();
}
}
public String getSessionId() {
return sessionRef.getSessionId();
}
public TenantId getTenantId() {
return sessionRef.getSecurityCtx().getTenantId();
}
public CustomerId getCustomerId() {
return sessionRef.getSecurityCtx().getCustomerId();
}
public UserId getUserId() {
return sessionRef.getSecurityCtx().getId();
@Override
public void clearSubscriptions() {
clearEntitySubscriptions();
super.clearSubscriptions();
}
public void clearEntitySubscriptions() {
@ -340,26 +115,6 @@ public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends
}
}
public void clearDynamicValueSubscriptions() {
if (subToDynamicValueKeySet != null) {
for (Integer subId : subToDynamicValueKeySet) {
localSubscriptionService.cancelSubscription(sessionRef.getSessionId(), subId);
}
subToDynamicValueKeySet.clear();
}
}
public void setRefreshTask(ScheduledFuture<?> task) {
this.refreshTask = task;
}
public void cancelTasks() {
if (this.refreshTask != null) {
log.trace("[{}][{}] Canceling old refresh task", sessionRef.getSessionId(), cmdId);
this.refreshTask.cancel(true);
}
}
public void createSubscriptions(List<EntityKey> keys, boolean resultToLatestValues) {
Map<EntityKeyType, List<EntityKey>> keysByType = getEntityKeyByTypeMap(keys);
for (EntityData entityData : data.getData()) {
@ -459,14 +214,4 @@ public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends
abstract void sendWsMsg(String sessionId, TelemetrySubscriptionUpdate subscriptionUpdate, EntityKeyType keyType, boolean resultToLatestValues);
@Data
private static class DynamicValueKey {
@Getter
private final FilterPredicateType predicateType;
@Getter
private final DynamicValueSourceType sourceType;
@Getter
private final String sourceAttribute;
}
}

315
application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractSubCtx.java

@ -0,0 +1,315 @@
/**
* Copyright © 2016-2021 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.service.subscription;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.query.ComplexFilterPredicate;
import org.thingsboard.server.common.data.query.DynamicValue;
import org.thingsboard.server.common.data.query.DynamicValueSourceType;
import org.thingsboard.server.common.data.query.EntityCountQuery;
import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.FilterPredicateType;
import org.thingsboard.server.common.data.query.KeyFilter;
import org.thingsboard.server.common.data.query.KeyFilterPredicate;
import org.thingsboard.server.common.data.query.SimpleKeyFilterPredicate;
import org.thingsboard.server.common.data.query.TsValue;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketService;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef;
import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledFuture;
@Slf4j
@Data
public abstract class TbAbstractSubCtx<T extends EntityCountQuery> {
protected final String serviceId;
protected final SubscriptionServiceStatistics stats;
protected final TelemetryWebSocketService wsService;
protected final EntityService entityService;
protected final TbLocalSubscriptionService localSubscriptionService;
protected final AttributesService attributesService;
protected final TelemetryWebSocketSessionRef sessionRef;
protected final int cmdId;
protected final Set<Integer> subToDynamicValueKeySet;
@Getter
protected final Map<DynamicValueKey, List<DynamicValue>> dynamicValues;
@Getter
@Setter
protected T query;
@Setter
protected volatile ScheduledFuture<?> refreshTask;
public TbAbstractSubCtx(String serviceId, TelemetryWebSocketService wsService,
EntityService entityService, TbLocalSubscriptionService localSubscriptionService,
AttributesService attributesService, SubscriptionServiceStatistics stats,
TelemetryWebSocketSessionRef sessionRef, int cmdId) {
this.serviceId = serviceId;
this.wsService = wsService;
this.entityService = entityService;
this.localSubscriptionService = localSubscriptionService;
this.attributesService = attributesService;
this.stats = stats;
this.sessionRef = sessionRef;
this.cmdId = cmdId;
this.subToDynamicValueKeySet = ConcurrentHashMap.newKeySet();
this.dynamicValues = new ConcurrentHashMap<>();
}
public void setAndResolveQuery(T query) {
dynamicValues.clear();
this.query = query;
if (query != null && query.getKeyFilters() != null) {
for (KeyFilter filter : query.getKeyFilters()) {
registerDynamicValues(filter.getPredicate());
}
}
resolve(getTenantId(), getCustomerId(), getUserId());
}
public void resolve(TenantId tenantId, CustomerId customerId, UserId userId) {
List<ListenableFuture<DynamicValueKeySub>> futures = new ArrayList<>();
for (DynamicValueKey key : dynamicValues.keySet()) {
switch (key.getSourceType()) {
case CURRENT_TENANT:
futures.add(resolveEntityValue(tenantId, tenantId, key));
break;
case CURRENT_CUSTOMER:
if (customerId != null && !customerId.isNullUid()) {
futures.add(resolveEntityValue(tenantId, customerId, key));
}
break;
case CURRENT_USER:
if (userId != null && !userId.isNullUid()) {
futures.add(resolveEntityValue(tenantId, userId, key));
}
break;
}
}
try {
Map<EntityId, Map<String, DynamicValueKeySub>> tmpSubMap = new HashMap<>();
for (DynamicValueKeySub sub : Futures.successfulAsList(futures).get()) {
tmpSubMap.computeIfAbsent(sub.getEntityId(), tmp -> new HashMap<>()).put(sub.getKey().getSourceAttribute(), sub);
}
for (EntityId entityId : tmpSubMap.keySet()) {
Map<String, Long> keyStates = new HashMap<>();
Map<String, DynamicValueKeySub> dynamicValueKeySubMap = tmpSubMap.get(entityId);
dynamicValueKeySubMap.forEach((k, v) -> keyStates.put(k, v.getLastUpdateTs()));
int subIdx = sessionRef.getSessionSubIdSeq().incrementAndGet();
TbAttributeSubscription sub = TbAttributeSubscription.builder()
.serviceId(serviceId)
.sessionId(sessionRef.getSessionId())
.subscriptionId(subIdx)
.tenantId(sessionRef.getSecurityCtx().getTenantId())
.entityId(entityId)
.updateConsumer((s, subscriptionUpdate) -> dynamicValueSubUpdate(s, subscriptionUpdate, dynamicValueKeySubMap))
.allKeys(false)
.keyStates(keyStates)
.scope(TbAttributeSubscriptionScope.SERVER_SCOPE)
.build();
subToDynamicValueKeySet.add(subIdx);
localSubscriptionService.addSubscription(sub);
}
} catch (InterruptedException | ExecutionException e) {
log.info("[{}][{}][{}] Failed to resolve dynamic values: {}", tenantId, customerId, userId, dynamicValues.keySet());
}
}
private void dynamicValueSubUpdate(String sessionId, TelemetrySubscriptionUpdate subscriptionUpdate,
Map<String, DynamicValueKeySub> dynamicValueKeySubMap) {
Map<String, TsValue> latestUpdate = new HashMap<>();
subscriptionUpdate.getData().forEach((k, v) -> {
Object[] data = (Object[]) v.get(0);
latestUpdate.put(k, new TsValue((Long) data[0], (String) data[1]));
});
boolean invalidateFilter = false;
for (Map.Entry<String, TsValue> entry : latestUpdate.entrySet()) {
String k = entry.getKey();
TsValue tsValue = entry.getValue();
DynamicValueKeySub sub = dynamicValueKeySubMap.get(k);
if (sub.updateValue(tsValue)) {
invalidateFilter = true;
updateDynamicValuesByKey(sub, tsValue);
}
}
if (invalidateFilter) {
update();
}
}
public abstract void fetchData();
protected abstract void update();
public void clearSubscriptions() {
clearDynamicValueSubscriptions();
}
@Data
private static class DynamicValueKeySub {
private final DynamicValueKey key;
private final EntityId entityId;
private long lastUpdateTs;
private String lastUpdateValue;
boolean updateValue(TsValue value) {
if (value.getTs() > lastUpdateTs && (lastUpdateValue == null || !lastUpdateValue.equals(value.getValue()))) {
this.lastUpdateTs = value.getTs();
this.lastUpdateValue = value.getValue();
return true;
} else {
return false;
}
}
}
private ListenableFuture<DynamicValueKeySub> resolveEntityValue(TenantId tenantId, EntityId entityId, DynamicValueKey key) {
ListenableFuture<Optional<AttributeKvEntry>> entry = attributesService.find(tenantId, entityId,
TbAttributeSubscriptionScope.SERVER_SCOPE.name(), key.getSourceAttribute());
return Futures.transform(entry, attributeOpt -> {
DynamicValueKeySub sub = new DynamicValueKeySub(key, entityId);
if (attributeOpt.isPresent()) {
AttributeKvEntry attribute = attributeOpt.get();
sub.setLastUpdateTs(attribute.getLastUpdateTs());
sub.setLastUpdateValue(attribute.getValueAsString());
updateDynamicValuesByKey(sub, new TsValue(attribute.getLastUpdateTs(), attribute.getValueAsString()));
}
return sub;
}, MoreExecutors.directExecutor());
}
@SuppressWarnings("unchecked")
protected void updateDynamicValuesByKey(DynamicValueKeySub sub, TsValue tsValue) {
DynamicValueKey dvk = sub.getKey();
switch (dvk.getPredicateType()) {
case STRING:
dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(tsValue.getValue()));
break;
case NUMERIC:
try {
Double dValue = Double.parseDouble(tsValue.getValue());
dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(dValue));
} catch (NumberFormatException e) {
dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(null));
}
break;
case BOOLEAN:
Boolean bValue = Boolean.parseBoolean(tsValue.getValue());
dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(bValue));
break;
}
}
@SuppressWarnings("unchecked")
private void registerDynamicValues(KeyFilterPredicate predicate) {
switch (predicate.getType()) {
case STRING:
case NUMERIC:
case BOOLEAN:
Optional<DynamicValue> value = getDynamicValueFromSimplePredicate((SimpleKeyFilterPredicate) predicate);
if (value.isPresent()) {
DynamicValue dynamicValue = value.get();
DynamicValueKey key = new DynamicValueKey(
predicate.getType(),
dynamicValue.getSourceType(),
dynamicValue.getSourceAttribute());
dynamicValues.computeIfAbsent(key, tmp -> new ArrayList<>()).add(dynamicValue);
}
break;
case COMPLEX:
((ComplexFilterPredicate) predicate).getPredicates().forEach(this::registerDynamicValues);
}
}
private Optional<DynamicValue<T>> getDynamicValueFromSimplePredicate(SimpleKeyFilterPredicate<T> predicate) {
if (predicate.getValue().getUserValue() == null) {
return Optional.ofNullable(predicate.getValue().getDynamicValue());
} else {
return Optional.empty();
}
}
public String getSessionId() {
return sessionRef.getSessionId();
}
public TenantId getTenantId() {
return sessionRef.getSecurityCtx().getTenantId();
}
public CustomerId getCustomerId() {
return sessionRef.getSecurityCtx().getCustomerId();
}
public UserId getUserId() {
return sessionRef.getSecurityCtx().getId();
}
protected void clearDynamicValueSubscriptions() {
if (subToDynamicValueKeySet != null) {
for (Integer subId : subToDynamicValueKeySet) {
localSubscriptionService.cancelSubscription(sessionRef.getSessionId(), subId);
}
subToDynamicValueKeySet.clear();
}
}
public void setRefreshTask(ScheduledFuture<?> task) {
this.refreshTask = task;
}
public void cancelTasks() {
if (this.refreshTask != null) {
log.trace("[{}][{}] Canceling old refresh task", sessionRef.getSessionId(), cmdId);
this.refreshTask.cancel(true);
}
}
@Data
public static class DynamicValueKey {
@Getter
private final FilterPredicateType predicateType;
@Getter
private final DynamicValueSourceType sourceType;
@Getter
private final String sourceAttribute;
}
}

3
application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java

@ -90,8 +90,7 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx<AlarmDataQuery> {
AlarmDataUpdate update;
if (!entitiesMap.isEmpty()) {
long start = System.currentTimeMillis();
PageData<AlarmData> alarms = alarmService.findAlarmDataByQueryForEntities(getTenantId(), getCustomerId(),
query, getOrderedEntityIds());
PageData<AlarmData> alarms = alarmService.findAlarmDataByQueryForEntities(getTenantId(), getCustomerId(), query, getOrderedEntityIds());
long end = System.currentTimeMillis();
stats.getAlarmQueryInvocationCnt().incrementAndGet();
stats.getAlarmQueryTimeSpent().addAndGet(end - start);

55
application/src/main/java/org/thingsboard/server/service/subscription/TbEntityCountSubCtx.java

@ -0,0 +1,55 @@
/**
* Copyright © 2016-2021 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.service.subscription;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.query.EntityCountQuery;
import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketService;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountUpdate;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate;
import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate;
@Slf4j
public class TbEntityCountSubCtx extends TbAbstractSubCtx<EntityCountQuery> {
private volatile int result;
public TbEntityCountSubCtx(String serviceId, TelemetryWebSocketService wsService, EntityService entityService,
TbLocalSubscriptionService localSubscriptionService, AttributesService attributesService,
SubscriptionServiceStatistics stats, TelemetryWebSocketSessionRef sessionRef, int cmdId) {
super(serviceId, wsService, entityService, localSubscriptionService, attributesService, stats, sessionRef, cmdId);
}
@Override
public void fetchData() {
result = (int) entityService.countEntitiesByQuery(getTenantId(), getCustomerId(), query);
wsService.sendWsMsg(sessionRef.getSessionId(), new EntityCountUpdate(cmdId, result));
}
@Override
protected void update() {
int newCount = (int) entityService.countEntitiesByQuery(getTenantId(), getCustomerId(), query);
if (newCount != result) {
result = newCount;
wsService.sendWsMsg(sessionRef.getSessionId(), new EntityCountUpdate(cmdId, result));
}
}
}

3
application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubscriptionService.java

@ -17,6 +17,7 @@ package org.thingsboard.server.service.subscription;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef;
import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUnsubscribeCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.UnsubscribeCmd;
@ -25,6 +26,8 @@ public interface TbEntityDataSubscriptionService {
void handleCmd(TelemetryWebSocketSessionRef sessionId, EntityDataCmd cmd);
void handleCmd(TelemetryWebSocketSessionRef sessionId, EntityCountCmd cmd);
void handleCmd(TelemetryWebSocketSessionRef sessionId, AlarmDataCmd cmd);
void cancelSubscription(String sessionId, UnsubscribeCmd subscriptionId);

35
application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java

@ -63,7 +63,8 @@ import org.thingsboard.server.service.telemetry.cmd.v1.SubscriptionCmd;
import org.thingsboard.server.service.telemetry.cmd.v1.TelemetryPluginCmd;
import org.thingsboard.server.service.telemetry.cmd.v1.TimeseriesSubscriptionCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.DataUpdate;
import org.thingsboard.server.service.telemetry.cmd.v2.CmdUpdate;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate;
import org.thingsboard.server.service.telemetry.cmd.v2.UnsubscribeCmd;
@ -225,12 +226,18 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
if (cmdsWrapper.getAlarmDataCmds() != null) {
cmdsWrapper.getAlarmDataCmds().forEach(cmd -> handleWsAlarmDataCmd(sessionRef, cmd));
}
if (cmdsWrapper.getEntityCountCmds() != null) {
cmdsWrapper.getEntityCountCmds().forEach(cmd -> handleWsEntityCountCmd(sessionRef, cmd));
}
if (cmdsWrapper.getEntityDataUnsubscribeCmds() != null) {
cmdsWrapper.getEntityDataUnsubscribeCmds().forEach(cmd -> handleWsDataUnsubscribeCmd(sessionRef, cmd));
}
if (cmdsWrapper.getAlarmDataUnsubscribeCmds() != null) {
cmdsWrapper.getAlarmDataUnsubscribeCmds().forEach(cmd -> handleWsDataUnsubscribeCmd(sessionRef, cmd));
}
if (cmdsWrapper.getEntityCountUnsubscribeCmds() != null) {
cmdsWrapper.getEntityCountUnsubscribeCmds().forEach(cmd -> handleWsDataUnsubscribeCmd(sessionRef, cmd));
}
}
} catch (IOException e) {
log.warn("Failed to decode subscription cmd: {}", e.getMessage(), e);
@ -248,6 +255,16 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
}
}
private void handleWsEntityCountCmd(TelemetryWebSocketSessionRef sessionRef, EntityCountCmd cmd) {
String sessionId = sessionRef.getSessionId();
log.debug("[{}] Processing: {}", sessionId, cmd);
if (validateSessionMetadata(sessionRef, cmd.getCmdId(), sessionId)
&& validateSubscriptionCmd(sessionRef, cmd)) {
entityDataSubService.handleCmd(sessionRef, cmd);
}
}
private void handleWsAlarmDataCmd(TelemetryWebSocketSessionRef sessionRef, AlarmDataCmd cmd) {
String sessionId = sessionRef.getSessionId();
log.debug("[{}] Processing: {}", sessionId, cmd);
@ -273,7 +290,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
}
@Override
public void sendWsMsg(String sessionId, DataUpdate update) {
public void sendWsMsg(String sessionId, CmdUpdate update) {
sendWsMsg(sessionId, update.getCmdId(), update);
}
@ -688,6 +705,20 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
return true;
}
private boolean validateSubscriptionCmd(TelemetryWebSocketSessionRef sessionRef, EntityCountCmd cmd) {
if (cmd.getCmdId() < 0) {
TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST,
"Cmd id is negative value!");
sendWsMsg(sessionRef, update);
return false;
} else if (cmd.getQuery() == null) {
TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST, "Query is empty!");
sendWsMsg(sessionRef, update);
return false;
}
return true;
}
private boolean validateSubscriptionCmd(TelemetryWebSocketSessionRef sessionRef, AlarmDataCmd cmd) {
if (cmd.getCmdId() < 0) {
TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST,

3
application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryWebSocketService.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.service.telemetry;
import org.thingsboard.server.service.telemetry.cmd.v2.CmdUpdate;
import org.thingsboard.server.service.telemetry.cmd.v2.DataUpdate;
import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate;
@ -29,6 +30,6 @@ public interface TelemetryWebSocketService {
void sendWsMsg(String sessionId, TelemetrySubscriptionUpdate update);
void sendWsMsg(String sessionId, DataUpdate update);
void sendWsMsg(String sessionId, CmdUpdate update);
}

6
application/src/main/java/org/thingsboard/server/service/telemetry/cmd/TelemetryPluginCmdsWrapper.java

@ -21,6 +21,8 @@ import org.thingsboard.server.service.telemetry.cmd.v1.GetHistoryCmd;
import org.thingsboard.server.service.telemetry.cmd.v1.TimeseriesSubscriptionCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataUnsubscribeCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountUnsubscribeCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUnsubscribeCmd;
@ -46,4 +48,8 @@ public class TelemetryPluginCmdsWrapper {
private List<AlarmDataUnsubscribeCmd> alarmDataUnsubscribeCmds;
private List<EntityCountCmd> entityCountCmds;
private List<EntityCountUnsubscribeCmd> entityCountUnsubscribeCmds;
}

8
application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AlarmDataUpdate.java

@ -18,14 +18,14 @@ package org.thingsboard.server.service.telemetry.cmd.v2;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.AlarmData;
import org.thingsboard.server.common.data.query.EntityData;
import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode;
import java.util.List;
@ToString
public class AlarmDataUpdate extends DataUpdate<AlarmData> {
@Getter
@ -44,8 +44,8 @@ public class AlarmDataUpdate extends DataUpdate<AlarmData> {
}
@Override
public DataUpdateType getDataUpdateType() {
return DataUpdateType.ALARM_DATA;
public CmdUpdateType getCmdUpdateType() {
return CmdUpdateType.ALARM_DATA;
}
@JsonCreator

33
application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/CmdUpdate.java

@ -0,0 +1,33 @@
/**
* Copyright © 2016-2021 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.service.telemetry.cmd.v2;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public abstract class CmdUpdate {
private final int cmdId;
private final int errorCode;
private final String errorMsg;
public abstract CmdUpdateType getCmdUpdateType();
}

5
application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/DataUpdateType.java → application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/CmdUpdateType.java

@ -15,7 +15,8 @@
*/
package org.thingsboard.server.service.telemetry.cmd.v2;
public enum DataUpdateType {
public enum CmdUpdateType {
ENTITY_DATA,
ALARM_DATA
ALARM_DATA,
COUNT_DATA
}

21
application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/DataUpdate.java

@ -15,24 +15,24 @@
*/
package org.thingsboard.server.service.telemetry.cmd.v2;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode;
import java.util.List;
@Data
@AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public abstract class DataUpdate<T> {
public abstract class DataUpdate<T> extends CmdUpdate {
private final int cmdId;
@Getter
private final PageData<T> data;
@Getter
private final List<T> update;
private final int errorCode;
private final String errorMsg;
public DataUpdate(int cmdId, PageData<T> data, List<T> update, int errorCode, String errorMsg) {
super(cmdId, errorCode, errorMsg);
this.data = data;
this.update = update;
}
public DataUpdate(int cmdId, PageData<T> data, List<T> update) {
this(cmdId, data, update, SubscriptionErrorCode.NO_ERROR.getCode(), null);
@ -42,5 +42,4 @@ public abstract class DataUpdate<T> {
this(cmdId, null, null, errorCode, errorMsg);
}
public abstract DataUpdateType getDataUpdateType();
}

35
application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityCountCmd.java

@ -0,0 +1,35 @@
/**
* Copyright © 2016-2021 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.service.telemetry.cmd.v2;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import org.thingsboard.server.common.data.query.EntityCountQuery;
import org.thingsboard.server.common.data.query.EntityDataQuery;
public class EntityCountCmd extends DataCmd {
@Getter
private final EntityCountQuery query;
@JsonCreator
public EntityCountCmd(@JsonProperty("cmdId") int cmdId,
@JsonProperty("query") EntityCountQuery query) {
super(cmdId);
this.query = query;
}
}

25
application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityCountUnsubscribeCmd.java

@ -0,0 +1,25 @@
/**
* Copyright © 2016-2021 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.service.telemetry.cmd.v2;
import lombok.Data;
@Data
public class EntityCountUnsubscribeCmd implements UnsubscribeCmd {
private final int cmdId;
}

57
application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityCountUpdate.java

@ -0,0 +1,57 @@
/**
* Copyright © 2016-2021 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.service.telemetry.cmd.v2;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import lombok.ToString;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.EntityData;
import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode;
import java.util.List;
@ToString
public class EntityCountUpdate extends CmdUpdate {
@Getter
private int count;
public EntityCountUpdate(int cmdId, int count) {
super(cmdId, SubscriptionErrorCode.NO_ERROR.getCode(), null);
this.count = count;
}
public EntityCountUpdate(int cmdId, int errorCode, String errorMsg) {
super(cmdId, errorCode, errorMsg);
}
@Override
public CmdUpdateType getCmdUpdateType() {
return CmdUpdateType.COUNT_DATA;
}
@JsonCreator
public EntityCountUpdate(@JsonProperty("cmdId") int cmdId,
@JsonProperty("count") int count,
@JsonProperty("errorCode") int errorCode,
@JsonProperty("errorMsg") String errorMsg) {
super(cmdId, errorCode, errorMsg);
this.count = count;
}
}

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

@ -16,16 +16,16 @@
package org.thingsboard.server.service.telemetry.cmd.v2;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import lombok.ToString;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.EntityData;
import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode;
import java.util.List;
@ToString
public class EntityDataUpdate extends DataUpdate<EntityData> {
@Getter
@ -41,8 +41,8 @@ public class EntityDataUpdate extends DataUpdate<EntityData> {
}
@Override
public DataUpdateType getDataUpdateType() {
return DataUpdateType.ENTITY_DATA;
public CmdUpdateType getCmdUpdateType() {
return CmdUpdateType.ENTITY_DATA;
}
@JsonCreator

2
application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/UnsubscribeCmd.java

@ -15,8 +15,6 @@
*/
package org.thingsboard.server.service.telemetry.cmd.v2;
import lombok.Data;
public interface UnsubscribeCmd {
int getCmdId();

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

@ -44,6 +44,7 @@ import org.thingsboard.server.common.data.query.EntityDataSortOrder;
import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.EntityListFilter;
import org.thingsboard.server.common.data.query.EntityTypeFilter;
import org.thingsboard.server.common.data.query.FilterPredicateValue;
import org.thingsboard.server.common.data.query.KeyFilter;
import org.thingsboard.server.common.data.query.NumericFilterPredicate;
@ -132,6 +133,14 @@ public abstract class BaseEntityQueryControllerTest extends AbstractControllerTe
count = doPostWithResponse("/api/entitiesQuery/count", countQuery, Long.class);
Assert.assertEquals(97, count.longValue());
EntityTypeFilter filter2 = new EntityTypeFilter();
filter2.setEntityType(EntityType.DEVICE);
EntityCountQuery countQuery2 = new EntityCountQuery(filter2);
Long count2 = doPostWithResponse("/api/entitiesQuery/count", countQuery2, Long.class);
Assert.assertEquals(97, count2.longValue());
}
@Test
@ -198,11 +207,31 @@ public abstract class BaseEntityQueryControllerTest extends AbstractControllerTe
Assert.assertEquals(11, data.getTotalElements());
Assert.assertEquals("Device19", data.getData().get(0).getLatest().get(EntityKeyType.ENTITY_FIELD).get("name").getValue());
EntityTypeFilter filter2 = new EntityTypeFilter();
filter2.setEntityType(EntityType.DEVICE);
EntityDataSortOrder sortOrder2 = new EntityDataSortOrder(
new EntityKey(EntityKeyType.ENTITY_FIELD, "createdTime"), EntityDataSortOrder.Direction.ASC
);
EntityDataPageLink pageLink2 = new EntityDataPageLink(10, 0, null, sortOrder2);
List<EntityKey> entityFields2 = Collections.singletonList(new EntityKey(EntityKeyType.ENTITY_FIELD, "name"));
EntityDataQuery query2 = new EntityDataQuery(filter2, pageLink2, entityFields2, null, null);
PageData<EntityData> data2 =
doPostWithTypedResponse("/api/entitiesQuery/find", query2, new TypeReference<PageData<EntityData>>() {
});
Assert.assertEquals(97, data2.getTotalElements());
Assert.assertEquals(10, data2.getTotalPages());
Assert.assertTrue(data2.hasNext());
Assert.assertEquals(10, data2.getData().size());
}
@Test
public void testFindEntityDataByQueryWithAttributes() throws Exception {
List<Device> devices = new ArrayList<>();
List<Long> temperatures = new ArrayList<>();
List<Long> highTemperatures = new ArrayList<>();

99
application/src/test/java/org/thingsboard/server/controller/BaseWebsocketApiTest.java

@ -35,16 +35,23 @@ import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.DeviceTypeFilter;
import org.thingsboard.server.common.data.query.EntityCountQuery;
import org.thingsboard.server.common.data.query.EntityData;
import org.thingsboard.server.common.data.query.EntityDataPageLink;
import org.thingsboard.server.common.data.query.EntityDataQuery;
import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.EntityKeyValueType;
import org.thingsboard.server.common.data.query.FilterPredicateValue;
import org.thingsboard.server.common.data.query.KeyFilter;
import org.thingsboard.server.common.data.query.NumericFilterPredicate;
import org.thingsboard.server.common.data.query.TsValue;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.service.subscription.TbAttributeSubscriptionScope;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountUpdate;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityHistoryCmd;
@ -243,6 +250,98 @@ public class BaseWebsocketApiTest extends AbstractWebsocketTest {
Assert.assertEquals(new TsValue(dataPoint4.getTs(), dataPoint4.getValueAsString()), tsValues[0]);
}
@Test
public void testEntityCountWsCmd() throws Exception {
Device device = new Device();
device.setName("Device");
device.setType("default");
device.setLabel("testLabel" + (int) (Math.random() * 1000));
device = doPost("/api/device", device, Device.class);
AttributeKvEntry dataPoint1 = new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("temperature", 42L));
sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, Collections.singletonList(dataPoint1));
DeviceTypeFilter dtf1 = new DeviceTypeFilter();
dtf1.setDeviceNameFilter("D");
dtf1.setDeviceType("default");
EntityCountQuery edq1 = new EntityCountQuery(dtf1, Collections.emptyList());
EntityCountCmd cmd1 = new EntityCountCmd(1, edq1);
TelemetryPluginCmdsWrapper wrapper1 = new TelemetryPluginCmdsWrapper();
wrapper1.setEntityCountCmds(Collections.singletonList(cmd1));
wsClient.send(mapper.writeValueAsString(wrapper1));
String msg1 = wsClient.waitForReply();
EntityCountUpdate update1 = mapper.readValue(msg1, EntityCountUpdate.class);
Assert.assertEquals(1, update1.getCmdId());
Assert.assertEquals(1, update1.getCount());
DeviceTypeFilter dtf2 = new DeviceTypeFilter();
dtf2.setDeviceNameFilter("D");
dtf2.setDeviceType("non-existing-device-type");
EntityCountQuery edq2 = new EntityCountQuery(dtf2, Collections.emptyList());
EntityCountCmd cmd2 = new EntityCountCmd(2, edq2);
TelemetryPluginCmdsWrapper wrapper2 = new TelemetryPluginCmdsWrapper();
wrapper2.setEntityCountCmds(Collections.singletonList(cmd2));
wsClient.send(mapper.writeValueAsString(wrapper2));
String msg2 = wsClient.waitForReply();
EntityCountUpdate update2 = mapper.readValue(msg2, EntityCountUpdate.class);
Assert.assertEquals(2, update2.getCmdId());
Assert.assertEquals(0, update2.getCount());
KeyFilter highTemperatureFilter = new KeyFilter();
highTemperatureFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "temperature"));
NumericFilterPredicate predicate = new NumericFilterPredicate();
predicate.setValue(FilterPredicateValue.fromDouble(40));
predicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
highTemperatureFilter.setPredicate(predicate);
highTemperatureFilter.setValueType(EntityKeyValueType.NUMERIC);
DeviceTypeFilter dtf3 = new DeviceTypeFilter();
dtf3.setDeviceNameFilter("D");
dtf3.setDeviceType("default");
EntityCountQuery edq3 = new EntityCountQuery(dtf3, Collections.singletonList(highTemperatureFilter));
EntityCountCmd cmd3 = new EntityCountCmd(3, edq3);
TelemetryPluginCmdsWrapper wrapper3 = new TelemetryPluginCmdsWrapper();
wrapper3.setEntityCountCmds(Collections.singletonList(cmd3));
wsClient.send(mapper.writeValueAsString(wrapper3));
String msg3 = wsClient.waitForReply();
EntityCountUpdate update3 = mapper.readValue(msg3, EntityCountUpdate.class);
Assert.assertEquals(3, update3.getCmdId());
Assert.assertEquals(1, update3.getCount());
KeyFilter highTemperatureFilter2 = new KeyFilter();
highTemperatureFilter2.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "temperature"));
NumericFilterPredicate predicate2 = new NumericFilterPredicate();
predicate2.setValue(FilterPredicateValue.fromDouble(50));
predicate2.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
highTemperatureFilter2.setPredicate(predicate2);
highTemperatureFilter2.setValueType(EntityKeyValueType.NUMERIC);
DeviceTypeFilter dtf4 = new DeviceTypeFilter();
dtf4.setDeviceNameFilter("D");
dtf4.setDeviceType("default");
EntityCountQuery edq4 = new EntityCountQuery(dtf4, Collections.singletonList(highTemperatureFilter2));
EntityCountCmd cmd4 = new EntityCountCmd(4, edq4);
TelemetryPluginCmdsWrapper wrapper4 = new TelemetryPluginCmdsWrapper();
wrapper4.setEntityCountCmds(Collections.singletonList(cmd4));
wsClient.send(mapper.writeValueAsString(wrapper4));
String msg4 = wsClient.waitForReply();
EntityCountUpdate update4 = mapper.readValue(msg4, EntityCountUpdate.class);
Assert.assertEquals(4, update4.getCmdId());
Assert.assertEquals(0, update4.getCount());
}
@Test
public void testEntityDataLatestWidgetFlow() throws Exception {
Device device = new Device();

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

@ -27,7 +27,7 @@ import java.util.Arrays;
@RunWith(ClasspathSuite.class)
@ClasspathSuite.ClassnameFilters({
// "org.thingsboard.server.controller.sql.WebsocketApiSqlTest",
// "org.thingsboard.server.controller.sql.TenantProfileControllerSqlTest",
// "org.thingsboard.server.controller.sql.EntityQueryControllerSqlTest",
"org.thingsboard.server.controller.sql.*Test",
})
public class ControllerSqlTestSuite {

2
common/actor/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.3.0-SNAPSHOT</version>
<version>3.2.2-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/dao-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.3.0-SNAPSHOT</version>
<version>3.2.2-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/data/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.3.0-SNAPSHOT</version>
<version>3.2.2-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

4
common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetSearchQuery.java

@ -19,7 +19,7 @@ import lombok.Data;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
import org.thingsboard.server.common.data.relation.EntityTypeFilter;
import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter;
import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
import java.util.Collections;
@ -39,7 +39,7 @@ public class AssetSearchQuery {
EntityRelationsQuery query = new EntityRelationsQuery();
query.setParameters(parameters);
query.setFilters(
Collections.singletonList(new EntityTypeFilter(relationType == null ? EntityRelation.CONTAINS_TYPE : relationType,
Collections.singletonList(new RelationEntityTypeFilter(relationType == null ? EntityRelation.CONTAINS_TYPE : relationType,
Collections.singletonList(EntityType.ASSET))));
return query;
}

4
common/data/src/main/java/org/thingsboard/server/common/data/device/DeviceSearchQuery.java

@ -19,7 +19,7 @@ import lombok.Data;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
import org.thingsboard.server.common.data.relation.EntityTypeFilter;
import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter;
import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
import java.util.Collections;
@ -36,7 +36,7 @@ public class DeviceSearchQuery {
EntityRelationsQuery query = new EntityRelationsQuery();
query.setParameters(parameters);
query.setFilters(
Collections.singletonList(new EntityTypeFilter(relationType == null ? EntityRelation.CONTAINS_TYPE : relationType,
Collections.singletonList(new RelationEntityTypeFilter(relationType == null ? EntityRelation.CONTAINS_TYPE : relationType,
Collections.singletonList(EntityType.DEVICE))));
return query;
}

4
common/data/src/main/java/org/thingsboard/server/common/data/entityview/EntityViewSearchQuery.java

@ -19,7 +19,7 @@ import lombok.Data;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
import org.thingsboard.server.common.data.relation.EntityTypeFilter;
import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter;
import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
import java.util.Collections;
@ -36,7 +36,7 @@ public class EntityViewSearchQuery {
EntityRelationsQuery query = new EntityRelationsQuery();
query.setParameters(parameters);
query.setFilters(
Collections.singletonList(new EntityTypeFilter(relationType == null ? EntityRelation.CONTAINS_TYPE : relationType,
Collections.singletonList(new RelationEntityTypeFilter(relationType == null ? EntityRelation.CONTAINS_TYPE : relationType,
Collections.singletonList(EntityType.ENTITY_VIEW))));
return query;
}

9
common/data/src/main/java/org/thingsboard/server/common/data/query/AbstractDataQuery.java

@ -29,15 +29,13 @@ public abstract class AbstractDataQuery<T extends EntityDataPageLink> extends En
protected List<EntityKey> entityFields;
@Getter
protected List<EntityKey> latestValues;
@Getter
protected List<KeyFilter> keyFilters;
public AbstractDataQuery() {
super();
}
public AbstractDataQuery(EntityFilter entityFilter) {
super(entityFilter);
public AbstractDataQuery(EntityFilter entityFilter, List<KeyFilter> keyFilters) {
super(entityFilter, keyFilters);
}
public AbstractDataQuery(EntityFilter entityFilter,
@ -45,11 +43,10 @@ public abstract class AbstractDataQuery<T extends EntityDataPageLink> extends En
List<EntityKey> entityFields,
List<EntityKey> latestValues,
List<KeyFilter> keyFilters) {
super(entityFilter);
super(entityFilter, keyFilters);
this.pageLink = pageLink;
this.entityFields = entityFields;
this.latestValues = latestValues;
this.keyFilters = keyFilters;
}
}

4
common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmDataQuery.java

@ -30,8 +30,8 @@ public class AlarmDataQuery extends AbstractDataQuery<AlarmDataPageLink> {
public AlarmDataQuery() {
}
public AlarmDataQuery(EntityFilter entityFilter) {
super(entityFilter);
public AlarmDataQuery(EntityFilter entityFilter, List<KeyFilter> keyFilters) {
super(entityFilter, keyFilters);
}
public AlarmDataQuery(EntityFilter entityFilter, AlarmDataPageLink pageLink, List<EntityKey> entityFields, List<EntityKey> latestValues, List<KeyFilter> keyFilters, List<EntityKey> alarmFields) {

14
common/data/src/main/java/org/thingsboard/server/common/data/query/EntityCountQuery.java

@ -17,14 +17,26 @@ package org.thingsboard.server.common.data.query;
import lombok.Getter;
import java.util.Collections;
import java.util.List;
public class EntityCountQuery {
@Getter
private EntityFilter entityFilter;
public EntityCountQuery() {}
@Getter
protected List<KeyFilter> keyFilters;
public EntityCountQuery() {
}
public EntityCountQuery(EntityFilter entityFilter) {
this(entityFilter, Collections.emptyList());
}
public EntityCountQuery(EntityFilter entityFilter, List<KeyFilter> keyFilters) {
this.entityFilter = entityFilter;
this.keyFilters = keyFilters;
}
}

4
common/data/src/main/java/org/thingsboard/server/common/data/query/EntityDataQuery.java

@ -27,8 +27,8 @@ public class EntityDataQuery extends AbstractDataQuery<EntityDataPageLink> {
public EntityDataQuery() {
}
public EntityDataQuery(EntityFilter entityFilter) {
super(entityFilter);
public EntityDataQuery(EntityFilter entityFilter, List<KeyFilter> keyFilters) {
super(entityFilter, keyFilters);
}
public EntityDataQuery(EntityFilter entityFilter, EntityDataPageLink pageLink, List<EntityKey> entityFields, List<EntityKey> latestValues, List<KeyFilter> keyFilters) {

1
common/data/src/main/java/org/thingsboard/server/common/data/query/EntityFilter.java

@ -29,6 +29,7 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
@JsonSubTypes.Type(value = SingleEntityFilter.class, name = "singleEntity"),
@JsonSubTypes.Type(value = EntityListFilter.class, name = "entityList"),
@JsonSubTypes.Type(value = EntityNameFilter.class, name = "entityName"),
@JsonSubTypes.Type(value = EntityTypeFilter.class, name = "entityType"),
@JsonSubTypes.Type(value = AssetTypeFilter.class, name = "assetType"),
@JsonSubTypes.Type(value = DeviceTypeFilter.class, name = "deviceType"),
@JsonSubTypes.Type(value = EntityViewTypeFilter.class, name = "entityViewType"),

1
common/data/src/main/java/org/thingsboard/server/common/data/query/EntityFilterType.java

@ -19,6 +19,7 @@ public enum EntityFilterType {
SINGLE_ENTITY("singleEntity"),
ENTITY_LIST("entityList"),
ENTITY_NAME("entityName"),
ENTITY_TYPE("entityType"),
ASSET_TYPE("assetType"),
DEVICE_TYPE("deviceType"),
ENTITY_VIEW_TYPE("entityViewType"),

30
common/data/src/main/java/org/thingsboard/server/common/data/query/EntityTypeFilter.java

@ -0,0 +1,30 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.query;
import lombok.Data;
import org.thingsboard.server.common.data.EntityType;
@Data
public class EntityTypeFilter implements EntityFilter {
@Override
public EntityFilterType getType() {
return EntityFilterType.ENTITY_TYPE;
}
private EntityType entityType;
}

5
common/data/src/main/java/org/thingsboard/server/common/data/query/RelationsQueryFilter.java

@ -18,8 +18,7 @@ package org.thingsboard.server.common.data.query;
import lombok.Data;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import org.thingsboard.server.common.data.relation.EntityTypeFilter;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter;
import java.util.List;
@ -33,7 +32,7 @@ public class RelationsQueryFilter implements EntityFilter {
private EntityId rootEntity;
private EntitySearchDirection direction;
private List<EntityTypeFilter> filters;
private List<RelationEntityTypeFilter> filters;
private int maxLevel;
private boolean fetchLastLevelOnly;

2
common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelationsQuery.java

@ -26,6 +26,6 @@ import java.util.List;
public class EntityRelationsQuery {
private RelationsSearchParameters parameters;
private List<EntityTypeFilter> filters;
private List<RelationEntityTypeFilter> filters;
}

2
common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityTypeFilter.java → common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationEntityTypeFilter.java

@ -26,7 +26,7 @@ import java.util.List;
*/
@Data
@AllArgsConstructor
public class EntityTypeFilter {
public class RelationEntityTypeFilter {
private String relationType;

2
common/message/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.3.0-SNAPSHOT</version>
<version>3.2.2-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.3.0-SNAPSHOT</version>
<version>3.2.2-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>common</artifactId>

2
common/queue/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.3.0-SNAPSHOT</version>
<version>3.2.2-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/stats/pom.xml

@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.3.0-SNAPSHOT</version>
<version>3.2.2-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/transport/coap/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard.common</groupId>
<version>3.3.0-SNAPSHOT</version>
<version>3.2.2-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.common.transport</groupId>

2
common/transport/http/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard.common</groupId>
<version>3.3.0-SNAPSHOT</version>
<version>3.2.2-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.common.transport</groupId>

2
common/transport/mqtt/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard.common</groupId>
<version>3.3.0-SNAPSHOT</version>
<version>3.2.2-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.common.transport</groupId>

2
common/transport/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.3.0-SNAPSHOT</version>
<version>3.2.2-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/transport/transport-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard.common</groupId>
<version>3.3.0-SNAPSHOT</version>
<version>3.2.2-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.common.transport</groupId>

2
common/util/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.3.0-SNAPSHOT</version>
<version>3.2.2-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
dao/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.3.0-SNAPSHOT</version>
<version>3.2.2-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>dao</artifactId>

10
dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java

@ -35,7 +35,7 @@ import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntityRelationInfo;
import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import org.thingsboard.server.common.data.relation.EntityTypeFilter;
import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
import org.thingsboard.server.dao.entity.EntityService;
@ -457,7 +457,7 @@ public class BaseRelationService implements RelationService {
//boolean fetchLastLevelOnly = true;
log.trace("Executing findByQuery [{}]", query);
RelationsSearchParameters params = query.getParameters();
final List<EntityTypeFilter> filters = query.getFilters();
final List<RelationEntityTypeFilter> filters = query.getFilters();
if (filters == null || filters.isEmpty()) {
log.debug("Filters are not set [{}]", query);
}
@ -575,8 +575,8 @@ public class BaseRelationService implements RelationService {
};
}
private boolean matchFilters(List<EntityTypeFilter> filters, EntityRelation relation, EntitySearchDirection direction) {
for (EntityTypeFilter filter : filters) {
private boolean matchFilters(List<RelationEntityTypeFilter> filters, EntityRelation relation, EntitySearchDirection direction) {
for (RelationEntityTypeFilter filter : filters) {
if (match(filter, relation, direction)) {
return true;
}
@ -584,7 +584,7 @@ public class BaseRelationService implements RelationService {
return false;
}
private boolean match(EntityTypeFilter filter, EntityRelation relation, EntitySearchDirection direction) {
private boolean match(RelationEntityTypeFilter filter, EntityRelation relation, EntitySearchDirection direction) {
if (StringUtils.isEmpty(filter.getRelationType()) || filter.getRelationType().equals(relation.getType())) {
if (filter.getEntityTypes() == null || filter.getEntityTypes().isEmpty()) {
return true;

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

@ -40,12 +40,13 @@ import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.EntityListFilter;
import org.thingsboard.server.common.data.query.EntityNameFilter;
import org.thingsboard.server.common.data.query.EntitySearchQueryFilter;
import org.thingsboard.server.common.data.query.EntityTypeFilter;
import org.thingsboard.server.common.data.query.EntityViewSearchQueryFilter;
import org.thingsboard.server.common.data.query.EntityViewTypeFilter;
import org.thingsboard.server.common.data.query.RelationsQueryFilter;
import org.thingsboard.server.common.data.query.SingleEntityFilter;
import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import org.thingsboard.server.common.data.relation.EntityTypeFilter;
import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter;
import java.util.Arrays;
import java.util.Collections;
@ -249,18 +250,70 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
public long countEntitiesByQuery(TenantId tenantId, CustomerId customerId, EntityCountQuery query) {
EntityType entityType = resolveEntityType(query.getEntityFilter());
QueryContext ctx = new QueryContext(new QuerySecurityContext(tenantId, customerId, entityType));
ctx.append("select count(e.id) from ");
ctx.append(addEntityTableQuery(ctx, query.getEntityFilter()));
ctx.append(" e where ");
ctx.append(buildEntityWhere(ctx, query.getEntityFilter(), Collections.emptyList()));
return transactionTemplate.execute(status -> {
long startTs = System.currentTimeMillis();
try {
return jdbcTemplate.queryForObject(ctx.getQuery(), ctx, Long.class);
} finally {
queryLog.logQuery(ctx, ctx.getQuery(), System.currentTimeMillis() - startTs);
if (query.getKeyFilters() == null || query.getKeyFilters().isEmpty()) {
ctx.append("select count(e.id) from ");
ctx.append(addEntityTableQuery(ctx, query.getEntityFilter()));
ctx.append(" e where ");
ctx.append(buildEntityWhere(ctx, query.getEntityFilter(), Collections.emptyList()));
return transactionTemplate.execute(status -> {
long startTs = System.currentTimeMillis();
try {
return jdbcTemplate.queryForObject(ctx.getQuery(), ctx, Long.class);
} finally {
queryLog.logQuery(ctx, ctx.getQuery(), System.currentTimeMillis() - startTs);
}
});
} else {
List<EntityKeyMapping> mappings = EntityKeyMapping.prepareEntityCountKeyMapping(query);
List<EntityKeyMapping> selectionMapping = mappings.stream().filter(EntityKeyMapping::isSelection)
.collect(Collectors.toList());
List<EntityKeyMapping> entityFieldsSelectionMapping = selectionMapping.stream().filter(mapping -> !mapping.isLatest())
.collect(Collectors.toList());
List<EntityKeyMapping> filterMapping = mappings.stream().filter(EntityKeyMapping::hasFilter)
.collect(Collectors.toList());
List<EntityKeyMapping> entityFieldsFiltersMapping = filterMapping.stream().filter(mapping -> !mapping.isLatest())
.collect(Collectors.toList());
List<EntityKeyMapping> allLatestMappings = mappings.stream().filter(EntityKeyMapping::isLatest)
.collect(Collectors.toList());
String entityWhereClause = DefaultEntityQueryRepository.this.buildEntityWhere(ctx, query.getEntityFilter(), entityFieldsFiltersMapping);
String latestJoinsCnt = EntityKeyMapping.buildLatestJoins(ctx, query.getEntityFilter(), entityType, allLatestMappings, true);
String entityFieldsSelection = EntityKeyMapping.buildSelections(entityFieldsSelectionMapping, query.getEntityFilter().getType(), entityType);
String entityTypeStr;
if (query.getEntityFilter().getType().equals(EntityFilterType.RELATIONS_QUERY)) {
entityTypeStr = "e.entity_type";
} else {
entityTypeStr = "'" + entityType.name() + "'";
}
});
if (!StringUtils.isEmpty(entityFieldsSelection)) {
entityFieldsSelection = String.format("e.id id, %s entity_type, %s", entityTypeStr, entityFieldsSelection);
} else {
entityFieldsSelection = String.format("e.id id, %s entity_type", entityTypeStr);
}
String fromClauseCount = String.format("from (select %s from (select %s from %s e where %s) entities %s ) result %s",
"entities.*",
entityFieldsSelection,
addEntityTableQuery(ctx, query.getEntityFilter()),
entityWhereClause,
latestJoinsCnt,
"");
String countQuery = String.format("select count(id) %s", fromClauseCount);
return transactionTemplate.execute(status -> {
long startTs = System.currentTimeMillis();
try {
return jdbcTemplate.queryForObject(countQuery, ctx, Long.class);
} finally {
queryLog.logQuery(ctx, ctx.getQuery(), System.currentTimeMillis() - startTs);
}
});
}
}
@Override
@ -436,6 +489,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
case ASSET_SEARCH_QUERY:
case ENTITY_VIEW_SEARCH_QUERY:
case API_USAGE_STATE:
case ENTITY_TYPE:
return "";
default:
throw new RuntimeException("Not implemented!");
@ -521,7 +575,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
boolean single = entityFilter.getFilters() != null && entityFilter.getFilters().size() == 1;
if (entityFilter.getFilters() != null && !entityFilter.getFilters().isEmpty()) {
int entityTypeFilterIdx = 0;
for (EntityTypeFilter etf : entityFilter.getFilters()) {
for (RelationEntityTypeFilter etf : entityFilter.getFilters()) {
String etfCondition = buildEtfCondition(ctx, etf, entityFilter.getDirection(), entityTypeFilterIdx++);
if (!etfCondition.isEmpty()) {
if (noConditions) {
@ -570,7 +624,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
return "( " + selectFields + from + ")";
}
private String buildEtfCondition(QueryContext ctx, EntityTypeFilter etf, EntitySearchDirection direction, int entityTypeFilterIdx) {
private String buildEtfCondition(QueryContext ctx, RelationEntityTypeFilter etf, EntitySearchDirection direction, int entityTypeFilterIdx) {
StringBuilder whereFilter = new StringBuilder();
String relationType = etf.getRelationType();
List<EntityType> entityTypes = etf.getEntityTypes();
@ -676,6 +730,8 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
return ((EntityListFilter) entityFilter).getEntityType();
case ENTITY_NAME:
return ((EntityNameFilter) entityFilter).getEntityType();
case ENTITY_TYPE:
return ((EntityTypeFilter) entityFilter).getEntityType();
case ASSET_TYPE:
case ASSET_SEARCH_QUERY:
return EntityType.ASSET;

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

@ -21,6 +21,7 @@ import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.query.BooleanFilterPredicate;
import org.thingsboard.server.common.data.query.ComplexFilterPredicate;
import org.thingsboard.server.common.data.query.EntityCountQuery;
import org.thingsboard.server.common.data.query.EntityDataQuery;
import org.thingsboard.server.common.data.query.EntityDataSortOrder;
import org.thingsboard.server.common.data.query.EntityFilter;
@ -380,6 +381,30 @@ public class EntityKeyMapping {
return mappings;
}
public static List<EntityKeyMapping> prepareEntityCountKeyMapping(EntityCountQuery query) {
Map<EntityKey, List<KeyFilter>> filters =
query.getKeyFilters() != null ?
query.getKeyFilters().stream().collect(Collectors.groupingBy(KeyFilter::getKey)) : Collections.emptyMap();
int index = 2;
List<EntityKeyMapping> mappings = new ArrayList<>();
if (!filters.isEmpty()) {
for (EntityKey filterField : filters.keySet()) {
EntityKeyMapping mapping = new EntityKeyMapping();
mapping.setIndex(index);
mapping.setAlias(String.format("alias%s", index));
mapping.setKeyFilters(filters.get(filterField));
mapping.setLatest(!filterField.getType().equals(EntityKeyType.ENTITY_FIELD));
mapping.setSelection(false);
mapping.setEntityKey(filterField);
mappings.add(mapping);
index += 1;
}
}
return mappings;
}
private String buildAttributeSelection() {
return buildTimeSeriesOrAttrSelection(true);
}

10
dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java

@ -19,8 +19,10 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
@ -114,6 +116,14 @@ public class JpaPsqlTimeseriesDao extends AbstractChunkedAggregationTimeseriesDa
partitioningRepository.save(psqlPartition);
log.trace("Adding partition to Set: {}", psqlPartition);
partitions.put(psqlPartition.getStart(), psqlPartition);
} catch (DataIntegrityViolationException ex) {
log.trace("Error occurred during partition save:", ex);
if (ex.getCause() instanceof ConstraintViolationException) {
log.warn("Saving partition [{}] rejected. Timeseries data will save to the ts_kv_indefinite (DEFAULT) partition.", psqlPartition.getPartitionDate());
partitions.put(psqlPartition.getStart(), psqlPartition);
} else {
throw new RuntimeException(ex);
}
} finally {
partitionCreationLock.unlock();
}

54
dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceTest.java

@ -15,9 +15,6 @@
*/
package org.thingsboard.server.dao.service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import org.junit.After;
@ -31,25 +28,48 @@ import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.*;
import org.thingsboard.server.common.data.kv.*;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.DoubleDataEntry;
import org.thingsboard.server.common.data.kv.KvEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.*;
import org.thingsboard.server.common.data.query.AssetSearchQueryFilter;
import org.thingsboard.server.common.data.query.DeviceSearchQueryFilter;
import org.thingsboard.server.common.data.query.DeviceTypeFilter;
import org.thingsboard.server.common.data.query.EntityCountQuery;
import org.thingsboard.server.common.data.query.EntityData;
import org.thingsboard.server.common.data.query.EntityDataPageLink;
import org.thingsboard.server.common.data.query.EntityDataQuery;
import org.thingsboard.server.common.data.query.EntityDataSortOrder;
import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.EntityListFilter;
import org.thingsboard.server.common.data.query.FilterPredicateValue;
import org.thingsboard.server.common.data.query.KeyFilter;
import org.thingsboard.server.common.data.query.NumericFilterPredicate;
import org.thingsboard.server.common.data.query.RelationsQueryFilter;
import org.thingsboard.server.common.data.query.StringFilterPredicate;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import org.thingsboard.server.common.data.relation.EntityTypeFilter;
import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.model.sqlts.ts.TsKvEntity;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.dao.util.DaoTestUtil;
import org.thingsboard.server.dao.util.SqlDbType;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
@ -140,13 +160,13 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
long count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
Assert.assertEquals(30, count);
filter.setFilters(Collections.singletonList(new EntityTypeFilter("Contains", Collections.singletonList(EntityType.DEVICE))));
filter.setFilters(Collections.singletonList(new RelationEntityTypeFilter("Contains", Collections.singletonList(EntityType.DEVICE))));
count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
Assert.assertEquals(25, count);
filter.setRootEntity(devices.get(0).getId());
filter.setDirection(EntitySearchDirection.TO);
filter.setFilters(Collections.singletonList(new EntityTypeFilter("Manages", Collections.singletonList(EntityType.TENANT))));
filter.setFilters(Collections.singletonList(new RelationEntityTypeFilter("Manages", Collections.singletonList(EntityType.TENANT))));
count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
Assert.assertEquals(1, count);
@ -208,7 +228,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
RelationsQueryFilter filter = new RelationsQueryFilter();
filter.setRootEntity(tenantId);
filter.setDirection(EntitySearchDirection.FROM);
filter.setFilters(Collections.singletonList(new EntityTypeFilter("Contains", Collections.singletonList(EntityType.DEVICE))));
filter.setFilters(Collections.singletonList(new RelationEntityTypeFilter("Contains", Collections.singletonList(EntityType.DEVICE))));
EntityDataSortOrder sortOrder = new EntityDataSortOrder(
new EntityKey(EntityKeyType.ENTITY_FIELD, "createdTime"), EntityDataSortOrder.Direction.ASC

6
dao/src/test/java/org/thingsboard/server/dao/service/BaseRelationServiceTest.java

@ -26,7 +26,7 @@ import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import org.thingsboard.server.common.data.relation.EntityTypeFilter;
import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
import org.thingsboard.server.dao.exception.DataValidationException;
@ -221,7 +221,7 @@ public abstract class BaseRelationServiceTest extends AbstractServiceTest {
EntityRelationsQuery query = new EntityRelationsQuery();
query.setParameters(new RelationsSearchParameters(assetA, EntitySearchDirection.FROM, -1, false));
query.setFilters(Collections.singletonList(new EntityTypeFilter(EntityRelation.CONTAINS_TYPE, Collections.singletonList(EntityType.ASSET))));
query.setFilters(Collections.singletonList(new RelationEntityTypeFilter(EntityRelation.CONTAINS_TYPE, Collections.singletonList(EntityType.ASSET))));
List<EntityRelation> relations = relationService.findByQuery(SYSTEM_TENANT_ID, query).get();
Assert.assertEquals(3, relations.size());
Assert.assertTrue(relations.contains(relationA));
@ -255,7 +255,7 @@ public abstract class BaseRelationServiceTest extends AbstractServiceTest {
EntityRelationsQuery query = new EntityRelationsQuery();
query.setParameters(new RelationsSearchParameters(assetA, EntitySearchDirection.FROM, -1, false));
query.setFilters(Collections.singletonList(new EntityTypeFilter(EntityRelation.CONTAINS_TYPE, Collections.singletonList(EntityType.ASSET))));
query.setFilters(Collections.singletonList(new RelationEntityTypeFilter(EntityRelation.CONTAINS_TYPE, Collections.singletonList(EntityType.ASSET))));
List<EntityRelation> relations = relationService.findByQuery(SYSTEM_TENANT_ID, query).get();
Assert.assertEquals(2, relations.size());
Assert.assertTrue(relations.contains(relationAB));

2
msa/black-box-tests/pom.xml

@ -21,7 +21,7 @@
<parent>
<groupId>org.thingsboard</groupId>
<version>3.3.0-SNAPSHOT</version>
<version>3.2.2-SNAPSHOT</version>
<artifactId>msa</artifactId>
</parent>
<groupId>org.thingsboard.msa</groupId>

2
msa/js-executor/package.json

@ -1,7 +1,7 @@
{
"name": "thingsboard-js-executor",
"private": true,
"version": "3.3.0",
"version": "3.2.2",
"description": "ThingsBoard JavaScript Executor Microservice",
"main": "server.js",
"bin": "server.js",

2
msa/js-executor/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.3.0-SNAPSHOT</version>
<version>3.2.2-SNAPSHOT</version>
<artifactId>msa</artifactId>
</parent>
<groupId>org.thingsboard.msa</groupId>

2
msa/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.3.0-SNAPSHOT</version>
<version>3.2.2-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>msa</artifactId>

2
msa/tb-node/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.3.0-SNAPSHOT</version>
<version>3.2.2-SNAPSHOT</version>
<artifactId>msa</artifactId>
</parent>
<groupId>org.thingsboard.msa</groupId>

2
msa/tb/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.3.0-SNAPSHOT</version>
<version>3.2.2-SNAPSHOT</version>
<artifactId>msa</artifactId>
</parent>
<groupId>org.thingsboard.msa</groupId>

2
msa/transport/coap/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard.msa</groupId>
<version>3.3.0-SNAPSHOT</version>
<version>3.2.2-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.msa.transport</groupId>

2
msa/transport/http/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard.msa</groupId>
<version>3.3.0-SNAPSHOT</version>
<version>3.2.2-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.msa.transport</groupId>

2
msa/transport/mqtt/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard.msa</groupId>
<version>3.3.0-SNAPSHOT</version>
<version>3.2.2-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.msa.transport</groupId>

2
msa/transport/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.3.0-SNAPSHOT</version>
<version>3.2.2-SNAPSHOT</version>
<artifactId>msa</artifactId>
</parent>
<groupId>org.thingsboard.msa</groupId>

2
msa/web-ui/package.json

@ -1,7 +1,7 @@
{
"name": "thingsboard-web-ui",
"private": true,
"version": "3.3.0",
"version": "3.2.2",
"description": "ThingsBoard Web UI Microservice",
"main": "server.js",
"bin": "server.js",

2
msa/web-ui/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.3.0-SNAPSHOT</version>
<version>3.2.2-SNAPSHOT</version>
<artifactId>msa</artifactId>
</parent>
<groupId>org.thingsboard.msa</groupId>

4
netty-mqtt/pom.xml

@ -19,11 +19,11 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.3.0-SNAPSHOT</version>
<version>3.2.2-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>netty-mqtt</artifactId>
<version>3.3.0-SNAPSHOT</version>
<version>3.2.2-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Netty MQTT Client</name>

2
pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.thingsboard</groupId>
<artifactId>thingsboard</artifactId>
<version>3.3.0-SNAPSHOT</version>
<version>3.2.2-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Thingsboard</name>

2
rest-client/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.3.0-SNAPSHOT</version>
<version>3.2.2-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>rest-client</artifactId>

2
rule-engine/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.3.0-SNAPSHOT</version>
<version>3.2.2-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>rule-engine</artifactId>

2
rule-engine/rule-engine-api/pom.xml

@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.3.0-SNAPSHOT</version>
<version>3.2.2-SNAPSHOT</version>
<artifactId>rule-engine</artifactId>
</parent>
<groupId>org.thingsboard.rule-engine</groupId>

2
rule-engine/rule-engine-components/pom.xml

@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.3.0-SNAPSHOT</version>
<version>3.2.2-SNAPSHOT</version>
<artifactId>rule-engine</artifactId>
</parent>
<groupId>org.thingsboard.rule-engine</groupId>

4
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/data/RelationsQuery.java

@ -17,7 +17,7 @@ package org.thingsboard.rule.engine.data;
import lombok.Data;
import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import org.thingsboard.server.common.data.relation.EntityTypeFilter;
import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter;
import java.util.List;
@ -26,6 +26,6 @@ public class RelationsQuery {
private EntitySearchDirection direction;
private int maxLevel = 1;
private List<EntityTypeFilter> filters;
private List<RelationEntityTypeFilter> filters;
private boolean fetchLastLevelOnly = false;
}

6
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttrNodeConfiguration.java

@ -19,7 +19,7 @@ import lombok.Data;
import org.thingsboard.rule.engine.data.RelationsQuery;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import org.thingsboard.server.common.data.relation.EntityTypeFilter;
import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter;
import java.util.Collections;
import java.util.HashMap;
@ -41,8 +41,8 @@ public class TbGetRelatedAttrNodeConfiguration extends TbGetEntityAttrNodeConfig
RelationsQuery relationsQuery = new RelationsQuery();
relationsQuery.setDirection(EntitySearchDirection.FROM);
relationsQuery.setMaxLevel(1);
EntityTypeFilter entityTypeFilter = new EntityTypeFilter(EntityRelation.CONTAINS_TYPE, Collections.emptyList());
relationsQuery.setFilters(Collections.singletonList(entityTypeFilter));
RelationEntityTypeFilter relationEntityTypeFilter = new RelationEntityTypeFilter(EntityRelation.CONTAINS_TYPE, Collections.emptyList());
relationsQuery.setFilters(Collections.singletonList(relationEntityTypeFilter));
configuration.setRelationsQuery(relationsQuery);
return configuration;

6
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeConfiguration.java

@ -20,7 +20,7 @@ import org.thingsboard.rule.engine.api.NodeConfiguration;
import org.thingsboard.rule.engine.data.RelationsQuery;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import org.thingsboard.server.common.data.relation.EntityTypeFilter;
import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter;
import java.util.Collections;
@ -39,8 +39,8 @@ public class TbChangeOriginatorNodeConfiguration extends TbTransformNodeConfigur
RelationsQuery relationsQuery = new RelationsQuery();
relationsQuery.setDirection(EntitySearchDirection.FROM);
relationsQuery.setMaxLevel(1);
EntityTypeFilter entityTypeFilter = new EntityTypeFilter(EntityRelation.CONTAINS_TYPE, Collections.emptyList());
relationsQuery.setFilters(Collections.singletonList(entityTypeFilter));
RelationEntityTypeFilter relationEntityTypeFilter = new RelationEntityTypeFilter(EntityRelation.CONTAINS_TYPE, Collections.emptyList());
relationsQuery.setFilters(Collections.singletonList(relationEntityTypeFilter));
configuration.setRelationsQuery(relationsQuery);
return configuration;

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

File diff suppressed because one or more lines are too long

2
tools/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.3.0-SNAPSHOT</version>
<version>3.2.2-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>tools</artifactId>

2
transport/coap/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.3.0-SNAPSHOT</version>
<version>3.2.2-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.transport</groupId>

2
transport/http/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.3.0-SNAPSHOT</version>
<version>3.2.2-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.transport</groupId>

2
transport/mqtt/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.3.0-SNAPSHOT</version>
<version>3.2.2-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.transport</groupId>

2
transport/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.3.0-SNAPSHOT</version>
<version>3.2.2-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>transport</artifactId>

3
ui-ngx/package.json

@ -1,6 +1,6 @@
{
"name": "thingsboard",
"version": "3.3.0",
"version": "3.2.2",
"scripts": {
"ng": "ng",
"start": "node --max_old_space_size=8048 ./node_modules/@angular/cli/bin/ng serve --host 0.0.0.0 --open",
@ -68,6 +68,7 @@
"ngx-clipboard": "^13.0.1",
"ngx-color-picker": "^10.1.0",
"ngx-daterangepicker-material": "^4.0.1",
"ngx-drag-drop": "^2.0.0",
"ngx-flowchart": "git://github.com/thingsboard/ngx-flowchart.git#master",
"ngx-hm-carousel": "^2.0.0-rc.1",
"ngx-sharebuttons": "^8.0.1",

2
ui-ngx/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.3.0-SNAPSHOT</version>
<version>3.2.2-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<groupId>org.thingsboard</groupId>

21
ui-ngx/src/app/core/api/alias-controller.ts

@ -16,7 +16,7 @@
import { AliasInfo, IAliasController, StateControllerHolder, StateEntityInfo } from '@core/api/widget-api.models';
import { forkJoin, Observable, of, ReplaySubject, Subject } from 'rxjs';
import { Datasource, DatasourceType } from '@app/shared/models/widget.models';
import { Datasource, DatasourceType, datasourceTypeTranslationMap } from '@app/shared/models/widget.models';
import { deepClone, isEqual } from '@core/utils';
import { EntityService } from '@core/http/entity.service';
import { UtilsService } from '@core/services/utils.service';
@ -27,6 +27,7 @@ import {
defaultEntityDataPageLink, Filter, FilterInfo, filterInfoToKeyFilters, Filters, KeyFilter, singleEntityDataPageLink,
updateDatasourceFromEntityInfo
} from '@shared/models/query/query.models';
import { TranslateService } from '@ngx-translate/core';
export class AliasController implements IAliasController {
@ -50,6 +51,7 @@ export class AliasController implements IAliasController {
constructor(private utils: UtilsService,
private entityService: EntityService,
private translate: TranslateService,
private stateControllerHolder: StateControllerHolder,
private origEntityAliases: EntityAliases,
private origFilters: Filters) {
@ -241,7 +243,7 @@ export class AliasController implements IAliasController {
private resolveDatasource(datasource: Datasource, forceFilter = false): Observable<Datasource> {
const newDatasource = deepClone(datasource);
if (newDatasource.type === DatasourceType.entity) {
if (newDatasource.type === DatasourceType.entity || newDatasource.type === DatasourceType.entityCount) {
if (newDatasource.filterId) {
newDatasource.keyFilters = this.getKeyFilters(newDatasource.filterId);
}
@ -329,16 +331,23 @@ export class AliasController implements IAliasController {
return forkJoin(observables).pipe(
map((result) => {
let functionIndex = 0;
let entityCountIndex = 0;
result.forEach((datasource) => {
if (datasource.type === DatasourceType.function) {
if (datasource.type === DatasourceType.function || datasource.type === DatasourceType.entityCount) {
let name: string;
if (datasource.name && datasource.name.length) {
name = datasource.name;
} else {
functionIndex++;
name = DatasourceType.function;
if (functionIndex > 1) {
if (datasource.type === DatasourceType.function) {
functionIndex++;
} else {
entityCountIndex++;
}
name = this.translate.instant(datasourceTypeTranslationMap.get(datasource.type));
if (datasource.type === DatasourceType.function && functionIndex > 1) {
name += ' ' + functionIndex;
} else if (datasource.type === DatasourceType.entityCount && entityCountIndex > 1) {
name += ' ' + entityCountIndex;
}
}
datasource.name = name;

10
ui-ngx/src/app/core/api/data-aggregator.ts

@ -71,6 +71,7 @@ export class DataAggregator {
private dataReceived = false;
private resetPending = false;
private updatedData = false;
private noAggregation = this.aggregationType === AggregationType.NONE;
private aggregationTimeout = Math.max(this.interval, 1000);
@ -90,7 +91,8 @@ export class DataAggregator {
private timeWindow: number,
private interval: number,
private stateData: boolean,
private utils: UtilsService) {
private utils: UtilsService,
private ignoreDataUpdateOnIntervalTick: boolean) {
this.tsKeyNames.forEach((key) => {
this.dataBuffer[key] = [];
});
@ -140,6 +142,7 @@ export class DataAggregator {
this.elapsed = 0;
this.aggregationTimeout = Math.max(this.interval, 1000);
this.resetPending = true;
this.updatedData = false;
this.intervalTimeoutHandle = setTimeout(this.onInterval.bind(this), this.aggregationTimeout);
}
@ -180,6 +183,7 @@ export class DataAggregator {
this.onInterval(history, detectChanges);
}
}
this.updatedData = true;
}
private onInterval(history?: boolean, detectChanges?: boolean) {
@ -201,8 +205,9 @@ export class DataAggregator {
} else {
this.data = this.updateData();
}
if (this.onDataCb) {
if (this.onDataCb && (!this.ignoreDataUpdateOnIntervalTick || this.updatedData)) {
this.onDataCb(this.data, detectChanges);
this.updatedData = false;
}
if (!history) {
this.intervalTimeoutHandle = setTimeout(this.onInterval.bind(this), this.aggregationTimeout);
@ -223,6 +228,7 @@ export class DataAggregator {
this.lastPrevKvPairData[key] = [aggTimestamp, aggData.aggValue];
}
aggKeyData.delete(aggTimestamp);
this.updatedData = true;
} else if (aggTimestamp <= this.endTs) {
const kvPair: [number, any] = [aggTimestamp, aggData.aggValue];
keyData.push(kvPair);

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

@ -22,12 +22,14 @@ import {
EntityFilter,
EntityKey,
EntityKeyType,
entityKeyTypeToDataKeyType, entityPageDataChanged,
entityKeyTypeToDataKeyType,
entityPageDataChanged,
KeyFilter,
TsValue
} from '@shared/models/query/query.models';
import {
DataKeyType,
EntityCountCmd,
EntityDataCmd,
SubscriptionData,
SubscriptionDataHolder,
@ -42,6 +44,7 @@ import { DataAggregator } from '@core/api/data-aggregator';
import { NULL_UUID } from '@shared/models/id/has-uuid';
import { EntityType } from '@shared/models/entity-type.models';
import { Observable, of, ReplaySubject, Subject } from 'rxjs';
import { EntityId } from '@shared/models/id/entity-id';
import Timeout = NodeJS.Timeout;
declare type DataKeyFunction = (time: number, prevValue: any) => any;
@ -66,6 +69,7 @@ export interface EntityDataSubscriptionOptions {
type: widgetType;
entityFilter?: EntityFilter;
isPaginatedDataSubscription?: boolean;
ignoreDataUpdateOnIntervalTick?: boolean;
pageLink?: EntityDataPageLink;
keyFilters?: Array<KeyFilter>;
additionalKeyFilters?: Array<KeyFilter>;
@ -82,6 +86,7 @@ export class EntityDataSubscription {
private subscriber: TelemetrySubscriber;
private dataCommand: EntityDataCmd;
private subsCommand: EntityDataCmd;
private countCommand: EntityCountCmd;
private attrFields: Array<EntityKey>;
private tsFields: Array<EntityKey>;
@ -91,7 +96,7 @@ export class EntityDataSubscription {
private pageData: PageData<EntityData>;
private subsTw: SubscriptionTimewindow;
private dataAggregators: Array<DataAggregator>;
private dataKeys: {[key: string]: Array<SubscriptionDataKey> | SubscriptionDataKey} = {}
private dataKeys: {[key: string]: Array<SubscriptionDataKey> | SubscriptionDataKey} = {};
private datasourceData: {[index: number]: {[key: string]: DataSetHolder}};
private datasourceOrigData: {[index: number]: {[key: string]: DataSetHolder}};
private entityIdToDataIndex: {[id: string]: number};
@ -125,7 +130,8 @@ export class EntityDataSubscription {
}
}
let key: string;
if (this.datasourceType === DatasourceType.entity || this.entityDataSubscriptionOptions.type === widgetType.timeseries) {
if (this.datasourceType === DatasourceType.entity || this.datasourceType === DatasourceType.entityCount ||
this.entityDataSubscriptionOptions.type === widgetType.timeseries) {
if (this.datasourceType === DatasourceType.function) {
key = `${dataKey.name}_${dataKey.index}_${dataKey.type}`;
} else {
@ -150,7 +156,7 @@ export class EntityDataSubscription {
clearTimeout(this.timer);
this.timer = null;
}
if (this.datasourceType === DatasourceType.entity) {
if (this.datasourceType === DatasourceType.entity || this.datasourceType === DatasourceType.entityCount) {
if (this.subscriber) {
this.subscriber.unsubscribe();
this.subscriber = null;
@ -159,7 +165,7 @@ export class EntityDataSubscription {
if (this.dataAggregators) {
this.dataAggregators.forEach((aggregator) => {
aggregator.destroy();
})
});
this.dataAggregators = null;
}
this.pageData = null;
@ -291,6 +297,79 @@ export class EntityDataSubscription {
totalPages: 1
};
this.onPageData(pageData);
} else if (this.datasourceType === DatasourceType.entityCount) {
this.subscriber = new TelemetrySubscriber(this.telemetryService);
this.countCommand = new EntityCountCmd();
let keyFilters = this.entityDataSubscriptionOptions.keyFilters;
if (this.entityDataSubscriptionOptions.additionalKeyFilters) {
if (keyFilters) {
keyFilters = keyFilters.concat(this.entityDataSubscriptionOptions.additionalKeyFilters);
} else {
keyFilters = this.entityDataSubscriptionOptions.additionalKeyFilters;
}
}
this.countCommand.query = {
entityFilter: this.entityDataSubscriptionOptions.entityFilter,
keyFilters
};
this.subscriber.subscriptionCommands.push(this.countCommand);
const entityId: EntityId = {
id: NULL_UUID,
entityType: null
};
const countKey = this.entityDataSubscriptionOptions.dataKeys[0];
let dataReceived = false;
this.subscriber.entityCount$.subscribe(
(entityCountUpdate) => {
if (!dataReceived) {
const entityData: EntityData = {
entityId,
latest: {
[EntityKeyType.ENTITY_FIELD]: {
name: {
ts: Date.now(),
value: DatasourceType.entityCount
}
},
[EntityKeyType.COUNT]: {
[countKey.name]: {
ts: Date.now(),
value: entityCountUpdate.count + ''
}
}
},
timeseries: {}
};
const pageData: PageData<EntityData> = {
data: [entityData],
hasNext: false,
totalElements: 1,
totalPages: 1
};
this.onPageData(pageData);
dataReceived = true;
} else {
const update: EntityData[] = [{
entityId,
latest: {
[EntityKeyType.COUNT]: {
[countKey.name]: {
ts: Date.now(),
value: entityCountUpdate.count + ''
}
}
},
timeseries: {}
}];
this.onDataUpdate(update);
}
}
);
this.subscriber.subscribe();
}
if (this.entityDataSubscriptionOptions.isPaginatedDataSubscription) {
return of(null);
@ -347,7 +426,7 @@ export class EntityDataSubscription {
limit: this.subsTw.aggregation.limit,
agg: this.subsTw.aggregation.type,
fetchLatestPreviousPoint: this.subsTw.aggregation.stateData
}
};
}
}
} else if (this.entityDataSubscriptionOptions.type === widgetType.latest) {
@ -381,7 +460,7 @@ export class EntityDataSubscription {
if (this.dataAggregators) {
this.dataAggregators.forEach((aggregator) => {
aggregator.destroy();
})
});
}
this.dataAggregators = [];
this.resetData();
@ -419,7 +498,8 @@ export class EntityDataSubscription {
this.datasourceData[dataIndex] = {};
for (const key of Object.keys(this.dataKeys)) {
const dataKey = this.dataKeys[key];
if (this.datasourceType === DatasourceType.entity || this.entityDataSubscriptionOptions.type === widgetType.timeseries) {
if (this.datasourceType === DatasourceType.entity || this.datasourceType === DatasourceType.entityCount ||
this.entityDataSubscriptionOptions.type === widgetType.timeseries) {
const dataKeysList = dataKey as Array<SubscriptionDataKey>;
for (let index = 0; index < dataKeysList.length; index++) {
this.datasourceData[dataIndex][key + '_' + index] = {
@ -671,7 +751,8 @@ export class EntityDataSubscription {
subsTw.aggregation.timeWindow,
subsTw.aggregation.interval,
subsTw.aggregation.stateData,
this.utils
this.utils,
this.entityDataSubscriptionOptions.ignoreDataUpdateOnIntervalTick
);
}

23
ui-ngx/src/app/core/api/entity-data.service.ts

@ -60,7 +60,8 @@ export class EntityDataService {
constructor(private telemetryService: TelemetryWebsocketService,
private utils: UtilsService) {}
public prepareSubscription(listener: EntityDataListener): Observable<EntityDataLoadResult> {
public prepareSubscription(listener: EntityDataListener,
ignoreDataUpdateOnIntervalTick = false): Observable<EntityDataLoadResult> {
const datasource = listener.configDatasource;
listener.subscriptionOptions = this.createSubscriptionOptions(
datasource,
@ -68,7 +69,8 @@ export class EntityDataService {
datasource.pageLink,
datasource.keyFilters,
null,
false);
false,
ignoreDataUpdateOnIntervalTick);
if (datasource.type === DatasourceType.entity && (!datasource.entityFilter || !datasource.pageLink)) {
return of(null);
}
@ -87,7 +89,8 @@ export class EntityDataService {
public subscribeForPaginatedData(listener: EntityDataListener,
pageLink: EntityDataPageLink,
keyFilters: KeyFilter[]): Observable<EntityDataLoadResult> {
keyFilters: KeyFilter[],
ignoreDataUpdateOnIntervalTick = false): Observable<EntityDataLoadResult> {
const datasource = listener.configDatasource;
listener.subscriptionOptions = this.createSubscriptionOptions(
datasource,
@ -95,7 +98,8 @@ export class EntityDataService {
pageLink,
datasource.keyFilters,
keyFilters,
true);
true,
ignoreDataUpdateOnIntervalTick);
if (datasource.type === DatasourceType.entity && (!datasource.entityFilter || !pageLink)) {
listener.dataLoaded(emptyPageData<EntityData>(), [],
listener.configDatasourceIndex, listener.subscriptionOptions.pageLink);
@ -119,7 +123,8 @@ export class EntityDataService {
pageLink: EntityDataPageLink,
keyFilters: KeyFilter[],
additionalKeyFilters: KeyFilter[],
isPaginatedDataSubscription: boolean): EntityDataSubscriptionOptions {
isPaginatedDataSubscription: boolean,
ignoreDataUpdateOnIntervalTick: boolean): EntityDataSubscriptionOptions {
const subscriptionDataKeys: Array<SubscriptionDataKey> = [];
datasource.dataKeys.forEach((dataKey) => {
const subscriptionDataKey: SubscriptionDataKey = {
@ -135,13 +140,17 @@ export class EntityDataService {
dataKeys: subscriptionDataKeys,
type: subscriptionType
};
if (entityDataSubscriptionOptions.datasourceType === DatasourceType.entity) {
if (entityDataSubscriptionOptions.datasourceType === DatasourceType.entity ||
entityDataSubscriptionOptions.datasourceType === DatasourceType.entityCount) {
entityDataSubscriptionOptions.entityFilter = datasource.entityFilter;
entityDataSubscriptionOptions.pageLink = pageLink;
entityDataSubscriptionOptions.keyFilters = keyFilters;
entityDataSubscriptionOptions.additionalKeyFilters = additionalKeyFilters;
if (entityDataSubscriptionOptions.datasourceType === DatasourceType.entity) {
entityDataSubscriptionOptions.pageLink = pageLink;
}
}
entityDataSubscriptionOptions.isPaginatedDataSubscription = isPaginatedDataSubscription;
entityDataSubscriptionOptions.ignoreDataUpdateOnIntervalTick = ignoreDataUpdateOnIntervalTick;
return entityDataSubscriptionOptions;
}
}

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

@ -226,6 +226,7 @@ export interface WidgetSubscriptionOptions {
hasDataPageLink?: boolean;
singleEntity?: boolean;
warnOnPageDataOverflow?: boolean;
ignoreDataUpdateOnIntervalTick?: boolean;
targetDeviceAliasIds?: Array<string>;
targetDeviceIds?: Array<string>;
useDashboardTimewindow?: boolean;

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

@ -83,6 +83,7 @@ export class WidgetSubscription implements IWidgetSubscription {
hasDataPageLink: boolean;
singleEntity: boolean;
warnOnPageDataOverflow: boolean;
ignoreDataUpdateOnIntervalTick: boolean;
datasourcePages: PageData<Datasource>[];
dataPages: PageData<Array<DatasourceData>>[];
@ -200,6 +201,7 @@ export class WidgetSubscription implements IWidgetSubscription {
this.hasDataPageLink = options.hasDataPageLink;
this.singleEntity = options.singleEntity;
this.warnOnPageDataOverflow = options.warnOnPageDataOverflow;
this.ignoreDataUpdateOnIntervalTick = options.ignoreDataUpdateOnIntervalTick;
this.datasourcePages = [];
this.datasources = [];
this.dataPages = [];
@ -423,7 +425,7 @@ export class WidgetSubscription implements IWidgetSubscription {
}
};
this.entityDataListeners.push(listener);
return this.ctx.entityDataService.prepareSubscription(listener);
return this.ctx.entityDataService.prepareSubscription(listener, this.ignoreDataUpdateOnIntervalTick);
});
return forkJoin(resolveResultObservables).pipe(
map((resolveResults) => {
@ -465,7 +467,15 @@ export class WidgetSubscription implements IWidgetSubscription {
entityName = this.targetDeviceName;
}
} else if (this.type === widgetType.alarm) {
if (this.alarms && this.alarms.data.length) {
if (this.alarmSource && this.alarmSource.entityType && this.alarmSource.entityId) {
entityId = {
entityType: this.alarmSource.entityType,
id: this.alarmSource.entityId
};
entityName = this.alarmSource.entityName;
entityLabel = this.alarmSource.entityLabel;
entityDescription = this.alarmSource.entityDescription;
} else if (this.alarms && this.alarms.data.length) {
const data = this.alarms.data[0];
entityId = data.originator;
entityName = data.originatorName;
@ -815,7 +825,8 @@ export class WidgetSubscription implements IWidgetSubscription {
}
};
this.entityDataListeners[datasourceIndex] = entityDataListener;
return this.ctx.entityDataService.subscribeForPaginatedData(entityDataListener, pageLink, keyFilters);
return this.ctx.entityDataService.subscribeForPaginatedData(entityDataListener, pageLink, keyFilters,
this.ignoreDataUpdateOnIntervalTick);
} else {
return of(null);
}

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

@ -481,6 +481,8 @@ export class EntityService {
return entityTypes.indexOf(filter.entityType) > -1 ? true : false;
case AliasFilterType.entityName:
return entityTypes.indexOf(filter.entityType) > -1 ? true : false;
case AliasFilterType.entityType:
return entityTypes.indexOf(filter.entityType) > -1 ? true : false;
case AliasFilterType.stateEntity:
return true;
case AliasFilterType.assetType:
@ -540,6 +542,8 @@ export class EntityService {
return true;
case AliasFilterType.entityName:
return true;
case AliasFilterType.entityType:
return true;
case AliasFilterType.stateEntity:
return true;
case AliasFilterType.assetType:
@ -805,6 +809,9 @@ export class EntityService {
case AliasFilterType.entityName:
result.entityFilter = deepClone(filter);
return of(result);
case AliasFilterType.entityType:
result.entityFilter = deepClone(filter);
return of(result);
case AliasFilterType.stateEntity:
result.stateEntity = true;
if (stateEntityId) {
@ -1099,10 +1106,10 @@ export class EntityService {
dataKeys: []
};
this.prepareEntityFilterFromSubscriptionInfo(datasource, subscriptionInfo);
} else if (subscriptionInfo.type === DatasourceType.function) {
} else if (subscriptionInfo.type === DatasourceType.function || subscriptionInfo.type === DatasourceType.entityCount) {
datasource = {
type: subscriptionInfo.type,
name: subscriptionInfo.name || DatasourceType.function,
name: subscriptionInfo.name || subscriptionInfo.type,
dataKeys: []
};
}
@ -1119,6 +1126,10 @@ export class EntityService {
if (subscriptionInfo.alarmFields) {
this.createDatasourceKeys(subscriptionInfo.alarmFields, DataKeyType.alarm, datasource);
}
if (subscriptionInfo.type === DatasourceType.entityCount) {
const dataKey = this.utils.createKey({ name: 'count'}, DataKeyType.count);
datasource.dataKeys.push(dataKey);
}
}
return datasource;
}

4
ui-ngx/src/app/core/services/item-buffer.service.ts

@ -93,7 +93,7 @@ export class ItemBufferService {
if (widget.config.datasources) {
for (let i = 0; i < widget.config.datasources.length; i++) {
const datasource = widget.config.datasources[i];
if (datasource.type === DatasourceType.entity && datasource.entityAliasId) {
if ((datasource.type === DatasourceType.entity || datasource.type === DatasourceType.entityCount) && datasource.entityAliasId) {
entityAlias = dashboard.configuration.entityAliases[datasource.entityAliasId];
if (entityAlias) {
aliasesInfo.datasourceAliases[i] = this.prepareAliasInfo(entityAlias);
@ -119,7 +119,7 @@ export class ItemBufferService {
if (widget.config.datasources) {
for (let i = 0; i < widget.config.datasources.length; i++) {
const datasource = widget.config.datasources[i];
if (datasource.type === DatasourceType.entity && datasource.filterId) {
if ((datasource.type === DatasourceType.entity || datasource.type === DatasourceType.entityCount) && datasource.filterId) {
filter = dashboard.configuration.filters[datasource.filterId];
if (filter) {
filtersInfo.datasourceFilters[i] = this.prepareFilterInfo(filter);

3
ui-ngx/src/app/core/services/utils.service.ts

@ -27,7 +27,8 @@ import {
deleteNullProperties,
guid,
isDefined,
isDefinedAndNotNull, isString,
isDefinedAndNotNull,
isString,
isUndefined
} from '@core/utils';
import { WindowMessage } from '@shared/models/window-message.model';

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

@ -324,6 +324,9 @@ export function snakeCase(name: string, separator: string): string {
}
export function getDescendantProp(obj: any, path: string): any {
if (obj.hasOwnProperty(path)) {
return obj[path];
}
return path.split('.').reduce((acc, part) => acc && acc[part], obj);
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save