Browse Source

Merge pull request #10145 from dashevchenko/queueStatsEntity

Entity for queue statistics
pull/10489/head
Andrew Shvayka 3 years ago
committed by GitHub
parent
commit
b276d5e3da
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 111
      application/src/main/data/json/demo/dashboards/rule_engine_statistics.json
  2. 22
      application/src/main/data/upgrade/3.6.3/schema_update.sql
  3. 6
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  4. 6
      application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
  5. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessor.java
  6. 14
      application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java
  7. 11
      application/src/main/java/org/thingsboard/server/service/entitiy/asset/profile/DefaultTbAssetProfileService.java
  8. 47
      application/src/main/java/org/thingsboard/server/service/stats/DefaultRuleEngineStatisticsService.java
  9. 3
      application/src/test/java/org/thingsboard/server/controller/AssetControllerTest.java
  10. 21
      application/src/test/java/org/thingsboard/server/controller/BaseQueueControllerTest.java
  11. 51
      application/src/test/java/org/thingsboard/server/controller/EntityQueryControllerTest.java
  12. 37
      common/dao-api/src/main/java/org/thingsboard/server/dao/queue/QueueStatsService.java
  13. 3
      common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java
  14. 2
      common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java
  15. 43
      common/data/src/main/java/org/thingsboard/server/common/data/id/QueueStatsId.java
  16. 39
      common/data/src/main/java/org/thingsboard/server/common/data/queue/QueueStats.java
  17. 1
      common/proto/src/main/proto/queue.proto
  18. 1
      dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
  19. 8
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  20. 69
      dao/src/main/java/org/thingsboard/server/dao/model/sql/QueueStatsEntity.java
  21. 90
      dao/src/main/java/org/thingsboard/server/dao/queue/BaseQueueStatsService.java
  22. 32
      dao/src/main/java/org/thingsboard/server/dao/queue/QueueStatsDao.java
  23. 4
      dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetDataValidator.java
  24. 40
      dao/src/main/java/org/thingsboard/server/dao/service/validator/QueueStatsDataValidator.java
  25. 2
      dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java
  26. 3
      dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java
  27. 2
      dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java
  28. 5
      dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java
  29. 66
      dao/src/main/java/org/thingsboard/server/dao/sql/queue/JpaQueueStatsDao.java
  30. 39
      dao/src/main/java/org/thingsboard/server/dao/sql/queue/QueueStatsRepository.java
  31. 6
      dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java
  32. 9
      dao/src/main/resources/sql/schema-entities.sql
  33. 134
      dao/src/test/java/org/thingsboard/server/dao/service/QueueStatsServiceTest.java
  34. 3
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java
  35. 4
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/TenantIdLoader.java
  36. 10
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/TenantIdLoaderTest.java
  37. 5
      ui-ngx/src/app/core/http/entity.service.ts
  38. 2
      ui-ngx/src/app/modules/home/components/profile/asset-profile.component.html
  39. 4
      ui-ngx/src/app/modules/home/components/profile/asset-profile.component.ts
  40. 9
      ui-ngx/src/app/modules/home/pages/asset-profile/asset-profiles-table-config.resolver.ts
  41. 2
      ui-ngx/src/app/shared/models/asset.models.ts
  42. 8
      ui-ngx/src/app/shared/models/entity-type.models.ts
  43. 10
      ui-ngx/src/app/shared/models/entity.models.ts
  44. 3
      ui-ngx/src/app/shared/models/query/query.models.ts
  45. 72
      ui-ngx/src/assets/dashboard/api_usage.json
  46. 6
      ui-ngx/src/assets/locale/locale.constant-en_US.json

111
application/src/main/data/json/demo/dashboards/rule_engine_statistics.json

@ -13,6 +13,7 @@
"datasources": [ "datasources": [
{ {
"type": "entity", "type": "entity",
"entityAliasId": "140f23dd-e3a0-ed98-6189-03c49d2d8018",
"dataKeys": [ "dataKeys": [
{ {
"name": "ruleEngineException", "name": "ruleEngineException",
@ -51,7 +52,59 @@
"_hash": 0.7255162989552142 "_hash": 0.7255162989552142
} }
], ],
"entityAliasId": "140f23dd-e3a0-ed98-6189-03c49d2d8018" "alarmFilterConfig": {
"statusList": [
"ACTIVE"
]
},
"latestDataKeys": [
{
"name": "queueName",
"type": "entityField",
"label": "Queue name",
"color": "#ffc107",
"settings": {
"show": false,
"order": null,
"useCellStyleFunction": false,
"cellStyleFunction": "",
"useCellContentFunction": false,
"cellContentFunction": "",
"defaultColumnVisibility": "visible",
"columnSelectionToDisplay": "enabled"
},
"_hash": 0.8104572478982748,
"aggregationType": null,
"units": null,
"decimals": null,
"funcBody": null,
"usePostProcessing": null,
"postFuncBody": null
},
{
"name": "serviceId",
"type": "entityField",
"label": "Service Id",
"color": "#607d8b",
"settings": {
"show": false,
"order": null,
"useCellStyleFunction": false,
"cellStyleFunction": "",
"useCellContentFunction": false,
"cellContentFunction": "",
"defaultColumnVisibility": "visible",
"columnSelectionToDisplay": "enabled"
},
"_hash": 0.38329217099945034,
"aggregationType": null,
"units": null,
"decimals": null,
"funcBody": null,
"usePostProcessing": null,
"postFuncBody": null
}
]
} }
], ],
"timewindow": { "timewindow": {
@ -71,7 +124,9 @@
"settings": { "settings": {
"showTimestamp": true, "showTimestamp": true,
"displayPagination": true, "displayPagination": true,
"defaultPageSize": 10 "defaultPageSize": 10,
"enableSearch": true,
"enableSelectColumnDisplay": true
}, },
"title": "Exceptions", "title": "Exceptions",
"dropShadow": true, "dropShadow": true,
@ -89,7 +144,10 @@
"iconColor": "rgba(0, 0, 0, 0.87)", "iconColor": "rgba(0, 0, 0, 0.87)",
"iconSize": "24px", "iconSize": "24px",
"titleTooltip": "", "titleTooltip": "",
"displayTimewindow": true "displayTimewindow": true,
"configMode": "basic",
"titleFont": null,
"titleColor": null
}, },
"id": "5eb79712-5c24-3060-7e4f-6af36b8f842d", "id": "5eb79712-5c24-3060-7e4f-6af36b8f842d",
"typeFullFqn": "system.cards.timeseries_table" "typeFullFqn": "system.cards.timeseries_table"
@ -329,7 +387,25 @@
"statusList": [ "statusList": [
"ACTIVE" "ACTIVE"
] ]
} },
"latestDataKeys": [
{
"name": "queueName",
"type": "entityField",
"label": "Queue name",
"color": "#ffc107",
"settings": {},
"_hash": 0.8012481564934415
},
{
"name": "serviceId",
"type": "entityField",
"label": "Service Id",
"color": "#607d8b",
"settings": {},
"_hash": 0.0724871638610094
}
]
} }
], ],
"timewindow": { "timewindow": {
@ -724,7 +800,25 @@
"statusList": [ "statusList": [
"ACTIVE" "ACTIVE"
] ]
} },
"latestDataKeys": [
{
"name": "queueName",
"type": "entityField",
"label": "Queue name",
"color": "#f44336",
"settings": {},
"_hash": 0.7242351292118758
},
{
"name": "serviceId",
"type": "entityField",
"label": "Service Id",
"color": "#ffc107",
"settings": {},
"_hash": 0.3347262075244206
}
]
} }
], ],
"timewindow": { "timewindow": {
@ -1004,12 +1098,9 @@
"id": "140f23dd-e3a0-ed98-6189-03c49d2d8018", "id": "140f23dd-e3a0-ed98-6189-03c49d2d8018",
"alias": "TbServiceQueues", "alias": "TbServiceQueues",
"filter": { "filter": {
"type": "assetType", "type": "entityType",
"resolveMultiple": true, "resolveMultiple": true,
"assetNameFilter": "", "entityType": "QUEUE_STATS"
"assetTypes": [
"TbServiceQueue"
]
} }
} }
}, },

22
application/src/main/data/upgrade/3.6.3/schema_update.sql

@ -112,3 +112,25 @@ ALTER TABLE oauth2_params
ADD COLUMN IF NOT EXISTS edge_enabled boolean DEFAULT false; ADD COLUMN IF NOT EXISTS edge_enabled boolean DEFAULT false;
-- OAUTH2 PARAMS ALTER TABLE END -- OAUTH2 PARAMS ALTER TABLE END
-- QUEUE STATS UPDATE START
CREATE TABLE IF NOT EXISTS queue_stats (
id uuid NOT NULL CONSTRAINT queue_stats_pkey PRIMARY KEY,
created_time bigint NOT NULL,
tenant_id uuid NOT NULL,
queue_name varchar(255) NOT NULL,
service_id varchar(255) NOT NULL,
CONSTRAINT queue_stats_name_unq_key UNIQUE (tenant_id, queue_name, service_id)
);
INSERT INTO queue_stats
SELECT id, created_time, tenant_id, substring(name FROM 1 FOR position('_' IN name) - 1) AS queue_name,
substring(name FROM position('_' IN name) + 1) AS service_id
FROM asset
WHERE type = 'TbServiceQueue' and name LIKE '%\_%';
DELETE FROM asset WHERE type='TbServiceQueue';
DELETE FROM asset_profile WHERE name ='TbServiceQueue';
-- QUEUE STATS UPDATE END

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

@ -79,6 +79,7 @@ import org.thingsboard.server.dao.notification.NotificationTargetService;
import org.thingsboard.server.dao.notification.NotificationTemplateService; import org.thingsboard.server.dao.notification.NotificationTemplateService;
import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.queue.QueueService;
import org.thingsboard.server.dao.queue.QueueStatsService;
import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.resource.ResourceService; import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.rule.RuleChainService;
@ -447,6 +448,11 @@ public class ActorSystemContext {
@Getter @Getter
private QueueService queueService; private QueueService queueService;
@Lazy
@Autowired(required = false)
@Getter
private QueueStatsService queueStatsService;
@Lazy @Lazy
@Autowired(required = false) @Autowired(required = false)
@Getter @Getter

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

@ -96,6 +96,7 @@ import org.thingsboard.server.dao.notification.NotificationTargetService;
import org.thingsboard.server.dao.notification.NotificationTemplateService; import org.thingsboard.server.dao.notification.NotificationTemplateService;
import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.queue.QueueService;
import org.thingsboard.server.dao.queue.QueueStatsService;
import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.resource.ResourceService; import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.rule.RuleChainService;
@ -768,6 +769,11 @@ class DefaultTbContext implements TbContext {
return mainCtx.getQueueService(); return mainCtx.getQueueService();
} }
@Override
public QueueStatsService getQueueStatsService() {
return mainCtx.getQueueStatsService();
}
@Override @Override
public EventLoopGroup getSharedEventLoop() { public EventLoopGroup getSharedEventLoop() {
return mainCtx.getSharedEventLoopGroupService().getSharedEventLoopGroup(); return mainCtx.getSharedEventLoopGroupService().getSharedEventLoopGroup();

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessor.java

@ -32,7 +32,6 @@ import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.msg.TbMsgType;
import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.dao.asset.BaseAssetService;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg; import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
@ -115,7 +114,7 @@ public abstract class AssetEdgeProcessor extends BaseAssetProcessor implements A
case ASSIGNED_TO_CUSTOMER: case ASSIGNED_TO_CUSTOMER:
case UNASSIGNED_FROM_CUSTOMER: case UNASSIGNED_FROM_CUSTOMER:
Asset asset = assetService.findAssetById(edgeEvent.getTenantId(), assetId); Asset asset = assetService.findAssetById(edgeEvent.getTenantId(), assetId);
if (asset != null && !BaseAssetService.TB_SERVICE_QUEUE.equals(asset.getType())) { if (asset != null) {
UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction());
AssetUpdateMsg assetUpdateMsg = ((AssetMsgConstructor) AssetUpdateMsg assetUpdateMsg = ((AssetMsgConstructor)
assetMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)).constructAssetUpdatedMsg(msgType, asset); assetMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)).constructAssetUpdatedMsg(msgType, asset);

14
application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java

@ -22,10 +22,8 @@ import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
@ -33,30 +31,18 @@ import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.service.entitiy.AbstractTbEntityService; import org.thingsboard.server.service.entitiy.AbstractTbEntityService;
import org.thingsboard.server.service.profile.TbAssetProfileCache;
import static org.thingsboard.server.dao.asset.BaseAssetService.TB_SERVICE_QUEUE;
@Service @Service
@AllArgsConstructor @AllArgsConstructor
public class DefaultTbAssetService extends AbstractTbEntityService implements TbAssetService { public class DefaultTbAssetService extends AbstractTbEntityService implements TbAssetService {
private final AssetService assetService; private final AssetService assetService;
private final TbAssetProfileCache assetProfileCache;
@Override @Override
public Asset save(Asset asset, User user) throws Exception { public Asset save(Asset asset, User user) throws Exception {
ActionType actionType = asset.getId() == null ? ActionType.ADDED : ActionType.UPDATED; ActionType actionType = asset.getId() == null ? ActionType.ADDED : ActionType.UPDATED;
TenantId tenantId = asset.getTenantId(); TenantId tenantId = asset.getTenantId();
try { try {
if (TB_SERVICE_QUEUE.equals(asset.getType())) {
throw new ThingsboardException("Unable to save asset with type " + TB_SERVICE_QUEUE, ThingsboardErrorCode.BAD_REQUEST_PARAMS);
} else if (asset.getAssetProfileId() != null) {
AssetProfile assetProfile = assetProfileCache.get(tenantId, asset.getAssetProfileId());
if (assetProfile != null && TB_SERVICE_QUEUE.equals(assetProfile.getName())) {
throw new ThingsboardException("Unable to save asset with profile " + TB_SERVICE_QUEUE, ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}
}
Asset savedAsset = checkNotNull(assetService.saveAsset(asset)); Asset savedAsset = checkNotNull(assetService.saveAsset(asset));
autoCommit(user, savedAsset.getId()); autoCommit(user, savedAsset.getId());
logEntityActionService.logEntityAction(tenantId, savedAsset.getId(), savedAsset, asset.getCustomerId(), logEntityActionService.logEntityAction(tenantId, savedAsset.getId(), savedAsset, asset.getCustomerId(),

11
application/src/main/java/org/thingsboard/server/service/entitiy/asset/profile/DefaultTbAssetProfileService.java

@ -22,7 +22,6 @@ import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.asset.AssetProfile; import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.AssetProfileId; import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
@ -30,8 +29,6 @@ import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.AbstractTbEntityService; import org.thingsboard.server.service.entitiy.AbstractTbEntityService;
import static org.thingsboard.server.dao.asset.BaseAssetService.TB_SERVICE_QUEUE;
@Service @Service
@TbCoreComponent @TbCoreComponent
@AllArgsConstructor @AllArgsConstructor
@ -45,14 +42,6 @@ public class DefaultTbAssetProfileService extends AbstractTbEntityService implem
ActionType actionType = assetProfile.getId() == null ? ActionType.ADDED : ActionType.UPDATED; ActionType actionType = assetProfile.getId() == null ? ActionType.ADDED : ActionType.UPDATED;
TenantId tenantId = assetProfile.getTenantId(); TenantId tenantId = assetProfile.getTenantId();
try { try {
if (TB_SERVICE_QUEUE.equals(assetProfile.getName())) {
throw new ThingsboardException("Unable to save asset profile with name " + TB_SERVICE_QUEUE, ThingsboardErrorCode.BAD_REQUEST_PARAMS);
} else if (assetProfile.getId() != null) {
AssetProfile foundAssetProfile = assetProfileService.findAssetProfileById(tenantId, assetProfile.getId());
if (foundAssetProfile != null && TB_SERVICE_QUEUE.equals(foundAssetProfile.getName())) {
throw new ThingsboardException("Updating asset profile with name " + TB_SERVICE_QUEUE + " is prohibited!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}
}
AssetProfile savedAssetProfile = checkNotNull(assetProfileService.saveAssetProfile(assetProfile)); AssetProfile savedAssetProfile = checkNotNull(assetProfileService.saveAssetProfile(assetProfile));
autoCommit(user, savedAssetProfile.getId()); autoCommit(user, savedAssetProfile.getId());
logEntityActionService.logEntityAction(tenantId, savedAssetProfile.getId(), savedAssetProfile, logEntityActionService.logEntityAction(tenantId, savedAssetProfile.getId(), savedAssetProfile,

47
application/src/main/java/org/thingsboard/server/service/stats/DefaultRuleEngineStatisticsService.java

@ -21,15 +21,15 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.id.QueueStatsId;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.JsonDataEntry; import org.thingsboard.server.common.data.kv.JsonDataEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.queue.QueueStats;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.queue.QueueStatsService;
import org.thingsboard.server.dao.usagerecord.ApiLimitService; import org.thingsboard.server.dao.usagerecord.ApiLimitService;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.queue.util.TbRuleEngineComponent; import org.thingsboard.server.queue.util.TbRuleEngineComponent;
@ -52,7 +52,6 @@ import java.util.stream.Collectors;
@RequiredArgsConstructor @RequiredArgsConstructor
public class DefaultRuleEngineStatisticsService implements RuleEngineStatisticsService { public class DefaultRuleEngineStatisticsService implements RuleEngineStatisticsService {
public static final String TB_SERVICE_QUEUE = "TbServiceQueue";
public static final String RULE_ENGINE_EXCEPTION = "ruleEngineException"; public static final String RULE_ENGINE_EXCEPTION = "ruleEngineException";
public static final FutureCallback<Integer> CALLBACK = new FutureCallback<Integer>() { public static final FutureCallback<Integer> CALLBACK = new FutureCallback<Integer>() {
@Override @Override
@ -68,10 +67,10 @@ public class DefaultRuleEngineStatisticsService implements RuleEngineStatisticsS
private final TbServiceInfoProvider serviceInfoProvider; private final TbServiceInfoProvider serviceInfoProvider;
private final TelemetrySubscriptionService tsService; private final TelemetrySubscriptionService tsService;
private final AssetService assetService; private final QueueStatsService queueStatsService;
private final ApiLimitService apiLimitService; private final ApiLimitService apiLimitService;
private final Lock lock = new ReentrantLock(); private final Lock lock = new ReentrantLock();
private final ConcurrentMap<TenantQueueKey, AssetId> tenantQueueAssets = new ConcurrentHashMap<>(); private final ConcurrentMap<TenantQueueKey, QueueStatsId> tenantQueueStats = new ConcurrentHashMap<>();
@Value("${queue.rule-engine.stats.max-error-message-length:4096}") @Value("${queue.rule-engine.stats.max-error-message-length:4096}")
private int maxErrorMessageLength; private int maxErrorMessageLength;
@ -82,7 +81,7 @@ public class DefaultRuleEngineStatisticsService implements RuleEngineStatisticsS
ruleEngineStats.getTenantStats().forEach((id, stats) -> { ruleEngineStats.getTenantStats().forEach((id, stats) -> {
try { try {
TenantId tenantId = TenantId.fromUUID(id); TenantId tenantId = TenantId.fromUUID(id);
AssetId serviceAssetId = getServiceAssetId(tenantId, queueName); QueueStatsId queueStatsId = getQueueStatsId(tenantId, queueName);
if (stats.getTotalMsgCounter().get() > 0) { if (stats.getTotalMsgCounter().get() > 0) {
List<TsKvEntry> tsList = stats.getCounters().entrySet().stream() List<TsKvEntry> tsList = stats.getCounters().entrySet().stream()
.map(kv -> new BasicTsKvEntry(ts, new LongDataEntry(kv.getKey(), (long) kv.getValue().get()))) .map(kv -> new BasicTsKvEntry(ts, new LongDataEntry(kv.getKey(), (long) kv.getValue().get())))
@ -90,7 +89,7 @@ public class DefaultRuleEngineStatisticsService implements RuleEngineStatisticsS
if (!tsList.isEmpty()) { if (!tsList.isEmpty()) {
long ttl = apiLimitService.getLimit(tenantId, DefaultTenantProfileConfiguration::getQueueStatsTtlDays); long ttl = apiLimitService.getLimit(tenantId, DefaultTenantProfileConfiguration::getQueueStatsTtlDays);
ttl = TimeUnit.DAYS.toSeconds(ttl); ttl = TimeUnit.DAYS.toSeconds(ttl);
tsService.saveAndNotifyInternal(tenantId, serviceAssetId, tsList, ttl, CALLBACK); tsService.saveAndNotifyInternal(tenantId, queueStatsId, tsList, ttl, CALLBACK);
} }
} }
} catch (Exception e) { } catch (Exception e) {
@ -104,7 +103,7 @@ public class DefaultRuleEngineStatisticsService implements RuleEngineStatisticsS
TsKvEntry tsKv = new BasicTsKvEntry(e.getTs(), new JsonDataEntry(RULE_ENGINE_EXCEPTION, e.toJsonString(maxErrorMessageLength))); TsKvEntry tsKv = new BasicTsKvEntry(e.getTs(), new JsonDataEntry(RULE_ENGINE_EXCEPTION, e.toJsonString(maxErrorMessageLength)));
long ttl = apiLimitService.getLimit(tenantId, DefaultTenantProfileConfiguration::getRuleEngineExceptionsTtlDays); long ttl = apiLimitService.getLimit(tenantId, DefaultTenantProfileConfiguration::getRuleEngineExceptionsTtlDays);
ttl = TimeUnit.DAYS.toSeconds(ttl); ttl = TimeUnit.DAYS.toSeconds(ttl);
tsService.saveAndNotifyInternal(tenantId, getServiceAssetId(tenantId, queueName), Collections.singletonList(tsKv), ttl, CALLBACK); tsService.saveAndNotifyInternal(tenantId, getQueueStatsId(tenantId, queueName), Collections.singletonList(tsKv), ttl, CALLBACK);
} catch (Exception e2) { } catch (Exception e2) {
if (!"Asset is referencing to non-existent tenant!".equalsIgnoreCase(e2.getMessage())) { if (!"Asset is referencing to non-existent tenant!".equalsIgnoreCase(e2.getMessage())) {
log.debug("[{}] Failed to store the statistics", tenantId, e2); log.debug("[{}] Failed to store the statistics", tenantId, e2);
@ -113,30 +112,30 @@ public class DefaultRuleEngineStatisticsService implements RuleEngineStatisticsS
}); });
} }
private AssetId getServiceAssetId(TenantId tenantId, String queueName) { private QueueStatsId getQueueStatsId(TenantId tenantId, String queueName) {
TenantQueueKey key = new TenantQueueKey(tenantId, queueName); TenantQueueKey key = new TenantQueueKey(tenantId, queueName);
AssetId assetId = tenantQueueAssets.get(key); QueueStatsId queueStatsId = tenantQueueStats.get(key);
if (assetId == null) { if (queueStatsId == null) {
lock.lock(); lock.lock();
try { try {
assetId = tenantQueueAssets.get(key); queueStatsId = tenantQueueStats.get(key);
if (assetId == null) { if (queueStatsId == null) {
Asset asset = assetService.findAssetByTenantIdAndName(tenantId, queueName + "_" + serviceInfoProvider.getServiceId()); QueueStats queueStats = queueStatsService.findByTenantIdAndNameAndServiceId(tenantId, queueName , serviceInfoProvider.getServiceId());
if (asset == null) { if (queueStats == null) {
asset = new Asset(); queueStats = new QueueStats();
asset.setTenantId(tenantId); queueStats.setTenantId(tenantId);
asset.setName(queueName + "_" + serviceInfoProvider.getServiceId()); queueStats.setQueueName(queueName);
asset.setType(TB_SERVICE_QUEUE); queueStats.setServiceId(serviceInfoProvider.getServiceId());
asset = assetService.saveAsset(asset); queueStats = queueStatsService.save(tenantId, queueStats);
} }
assetId = asset.getId(); queueStatsId = queueStats.getId();
tenantQueueAssets.put(key, assetId); tenantQueueStats.put(key, queueStatsId);
} }
} finally { } finally {
lock.unlock(); lock.unlock();
} }
} }
return assetId; return queueStatsId;
} }
@Data @Data

3
application/src/test/java/org/thingsboard/server/controller/AssetControllerTest.java

@ -50,7 +50,6 @@ import org.thingsboard.server.dao.asset.AssetDao;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.service.stats.DefaultRuleEngineStatisticsService;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -567,8 +566,6 @@ public class AssetControllerTest extends AbstractControllerTest {
savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
ActionType.ADDED, cntEntity, cntEntity, cntEntity); ActionType.ADDED, cntEntity, cntEntity, cntEntity);
loadedAssets.removeIf(asset -> asset.getType().equals(DefaultRuleEngineStatisticsService.TB_SERVICE_QUEUE));
assets.sort(idComparator); assets.sort(idComparator);
loadedAssets.sort(idComparator); loadedAssets.sort(idComparator);

21
application/src/test/java/org/thingsboard/server/controller/BaseQueueControllerTest.java

@ -25,7 +25,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.TestPropertySource;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry;
@ -34,12 +33,13 @@ import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.queue.ProcessingStrategy; import org.thingsboard.server.common.data.queue.ProcessingStrategy;
import org.thingsboard.server.common.data.queue.ProcessingStrategyType; import org.thingsboard.server.common.data.queue.ProcessingStrategyType;
import org.thingsboard.server.common.data.queue.Queue; import org.thingsboard.server.common.data.queue.Queue;
import org.thingsboard.server.common.data.queue.QueueStats;
import org.thingsboard.server.common.data.queue.SubmitStrategy; import org.thingsboard.server.common.data.queue.SubmitStrategy;
import org.thingsboard.server.common.data.queue.SubmitStrategyType; import org.thingsboard.server.common.data.queue.SubmitStrategyType;
import org.thingsboard.server.common.msg.queue.RuleEngineException; import org.thingsboard.server.common.msg.queue.RuleEngineException;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.stats.StatsFactory; import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.queue.QueueStatsService;
import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.dao.timeseries.TimeseriesDao; import org.thingsboard.server.dao.timeseries.TimeseriesDao;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
@ -50,6 +50,7 @@ import org.thingsboard.server.service.queue.processing.TbRuleEngineProcessingRes
import org.thingsboard.server.service.stats.DefaultRuleEngineStatisticsService; import org.thingsboard.server.service.stats.DefaultRuleEngineStatisticsService;
import org.thingsboard.server.service.stats.RuleEngineStatisticsService; import org.thingsboard.server.service.stats.RuleEngineStatisticsService;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -66,7 +67,6 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.dao.asset.BaseAssetService.TB_SERVICE_QUEUE;
@DaoSqlTest @DaoSqlTest
@TestPropertySource(properties = { @TestPropertySource(properties = {
@ -81,7 +81,7 @@ public class BaseQueueControllerTest extends AbstractControllerTest {
@SpyBean @SpyBean
private TimeseriesDao timeseriesDao; private TimeseriesDao timeseriesDao;
@Autowired @Autowired
private AssetService assetService; private QueueStatsService queueStatsService;
@Test @Test
public void testQueueWithServiceTypeRE() throws Exception { public void testQueueWithServiceTypeRE() throws Exception {
@ -176,16 +176,17 @@ public class BaseQueueControllerTest extends AbstractControllerTest {
}); });
ruleEngineStatisticsService.reportQueueStats(System.currentTimeMillis(), testStats); ruleEngineStatisticsService.reportQueueStats(System.currentTimeMillis(), testStats);
Asset serviceAsset = assetService.findAssetsByTenantIdAndType(tenantId, TB_SERVICE_QUEUE, new PageLink(100)).getData() List<QueueStats> queueStatsList = queueStatsService.findByTenantId(tenantId);
.stream().filter(asset -> asset.getName().startsWith(queue.getName())) assertThat(queueStatsList).hasSize(1);
.findFirst().get(); QueueStats queueStats = queueStatsList.get(0);
assertThat(queueStats.getQueueName()).isEqualTo(queue.getName());
ArgumentCaptor<Long> ttlCaptor = ArgumentCaptor.forClass(Long.class); ArgumentCaptor<Long> ttlCaptor = ArgumentCaptor.forClass(Long.class);
verify(timeseriesDao).save(eq(tenantId), eq(serviceAsset.getId()), argThat(tsKvEntry -> { verify(timeseriesDao).save(eq(tenantId), eq(queueStats.getId()), argThat(tsKvEntry -> {
return tsKvEntry.getKey().equals(TbRuleEngineConsumerStats.SUCCESSFUL_MSGS) && return tsKvEntry.getKey().equals(TbRuleEngineConsumerStats.SUCCESSFUL_MSGS) &&
tsKvEntry.getLongValue().get().equals(5L); tsKvEntry.getLongValue().get().equals(5L);
}), ttlCaptor.capture()); }), ttlCaptor.capture());
verify(timeseriesDao).save(eq(tenantId), eq(serviceAsset.getId()), argThat(tsKvEntry -> { verify(timeseriesDao).save(eq(tenantId), eq(queueStats.getId()), argThat(tsKvEntry -> {
return tsKvEntry.getKey().equals(TbRuleEngineConsumerStats.FAILED_MSGS) && return tsKvEntry.getKey().equals(TbRuleEngineConsumerStats.FAILED_MSGS) &&
tsKvEntry.getLongValue().get().equals(5L); tsKvEntry.getLongValue().get().equals(5L);
}), ttlCaptor.capture()); }), ttlCaptor.capture());
@ -193,7 +194,7 @@ public class BaseQueueControllerTest extends AbstractControllerTest {
assertThat(usedTtl).isEqualTo(TimeUnit.DAYS.toSeconds(queueStatsTtlDays)); assertThat(usedTtl).isEqualTo(TimeUnit.DAYS.toSeconds(queueStatsTtlDays));
}); });
verify(timeseriesDao).save(eq(tenantId), eq(serviceAsset.getId()), argThat(tsKvEntry -> { verify(timeseriesDao).save(eq(tenantId), eq(queueStats.getId()), argThat(tsKvEntry -> {
return tsKvEntry.getKey().equals(DefaultRuleEngineStatisticsService.RULE_ENGINE_EXCEPTION) && return tsKvEntry.getKey().equals(DefaultRuleEngineStatisticsService.RULE_ENGINE_EXCEPTION) &&
tsKvEntry.getJsonValue().get().equals(ruleEngineException.toJsonString(0)); tsKvEntry.getJsonValue().get().equals(ruleEngineException.toJsonString(0));
}), ttlCaptor.capture()); }), ttlCaptor.capture());

51
application/src/test/java/org/thingsboard/server/controller/EntityQueryControllerTest.java

@ -22,11 +22,13 @@ import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.ResultActions;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
@ -51,10 +53,13 @@ import org.thingsboard.server.common.data.query.FilterPredicateValue;
import org.thingsboard.server.common.data.query.KeyFilter; import org.thingsboard.server.common.data.query.KeyFilter;
import org.thingsboard.server.common.data.query.NumericFilterPredicate; import org.thingsboard.server.common.data.query.NumericFilterPredicate;
import org.thingsboard.server.common.data.query.TsValue; import org.thingsboard.server.common.data.query.TsValue;
import org.thingsboard.server.common.data.queue.QueueStats;
import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.queue.QueueStatsService;
import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.service.DaoSqlTest;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -69,6 +74,9 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
private Tenant savedTenant; private Tenant savedTenant;
private User tenantAdmin; private User tenantAdmin;
@Autowired
private QueueStatsService queueStatsService;
@Before @Before
public void beforeTest() throws Exception { public void beforeTest() throws Exception {
loginSysAdmin(); loginSysAdmin();
@ -593,4 +601,47 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
assertThat(getErrorMessage(result)).contains("Invalid").contains("sort property"); assertThat(getErrorMessage(result)).contains("Invalid").contains("sort property");
} }
@Test
public void testFindQueueStatsEntitiesByQuery() throws Exception {
List<QueueStats> queueStatsList = new ArrayList<>();
for (int i = 0; i < 97; i++) {
QueueStats queueStats = new QueueStats();
queueStats.setQueueName(StringUtils.randomAlphabetic(5));
queueStats.setServiceId(StringUtils.randomAlphabetic(5));
queueStats.setTenantId(savedTenant.getTenantId());
queueStatsList.add(queueStatsService.save(savedTenant.getId(), queueStats));
Thread.sleep(1);
}
EntityTypeFilter entityTypeFilter = new EntityTypeFilter();
entityTypeFilter.setEntityType(EntityType.QUEUE_STATS);
EntityDataSortOrder sortOrder = new EntityDataSortOrder(
new EntityKey(EntityKeyType.ENTITY_FIELD, "queueName"), EntityDataSortOrder.Direction.ASC
);
EntityDataPageLink pageLink = new EntityDataPageLink(10, 0, null, sortOrder);
List<EntityKey> entityFields = Arrays.asList(new EntityKey(EntityKeyType.ENTITY_FIELD, "queueName"),
new EntityKey(EntityKeyType.ENTITY_FIELD, "serviceId"));
EntityDataQuery query = new EntityDataQuery(entityTypeFilter, pageLink, entityFields, null, null);
PageData<EntityData> data =
doPostWithTypedResponse("/api/entitiesQuery/find", query, new TypeReference<PageData<EntityData>>() {
});
Assert.assertEquals(97, data.getTotalElements());
Assert.assertEquals(10, data.getTotalPages());
Assert.assertTrue(data.hasNext());
Assert.assertEquals(10, data.getData().size());
data.getData().forEach(entityData -> {
assertThat(entityData.getLatest().get(EntityKeyType.ENTITY_FIELD).get("queueName")).asString().isNotBlank();
assertThat(entityData.getLatest().get(EntityKeyType.ENTITY_FIELD).get("serviceId")).asString().isNotBlank();
});
EntityCountQuery countQuery = new EntityCountQuery(entityTypeFilter);
Long count = doPostWithResponse("/api/entitiesQuery/count", countQuery, Long.class);
Assert.assertEquals(97, count.longValue());
}
} }

37
common/dao-api/src/main/java/org/thingsboard/server/dao/queue/QueueStatsService.java

@ -0,0 +1,37 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.queue;
import org.thingsboard.server.common.data.id.QueueStatsId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.queue.QueueStats;
import org.thingsboard.server.dao.entity.EntityDaoService;
import java.util.List;
public interface QueueStatsService extends EntityDaoService {
QueueStats save(TenantId tenantId, QueueStats queueStats);
QueueStats findQueueStatsById(TenantId tenantId, QueueStatsId queueStatsId);
QueueStats findByTenantIdAndNameAndServiceId(TenantId tenantId, String queueName, String serviceId);
List<QueueStats> findByTenantId(TenantId tenantId);
void deleteByTenantId(TenantId tenantId);
}

3
common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java

@ -59,7 +59,8 @@ public enum EntityType {
NOTIFICATION_TEMPLATE (30), NOTIFICATION_TEMPLATE (30),
NOTIFICATION_REQUEST (31), NOTIFICATION_REQUEST (31),
NOTIFICATION (32), NOTIFICATION (32),
NOTIFICATION_RULE (33); NOTIFICATION_RULE (33),
QUEUE_STATS(34);
@Getter @Getter
private final int protoNumber; // Corresponds to EntityTypeProto private final int protoNumber; // Corresponds to EntityTypeProto

2
common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java

@ -103,6 +103,8 @@ public class EntityIdFactory {
return new NotificationTemplateId(uuid); return new NotificationTemplateId(uuid);
case NOTIFICATION: case NOTIFICATION:
return new NotificationId(uuid); return new NotificationId(uuid);
case QUEUE_STATS:
return new QueueStatsId(uuid);
} }
throw new IllegalArgumentException("EntityType " + type + " is not supported!"); throw new IllegalArgumentException("EntityType " + type + " is not supported!");
} }

43
common/data/src/main/java/org/thingsboard/server/common/data/id/QueueStatsId.java

@ -0,0 +1,43 @@
/**
* Copyright © 2016-2024 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.id;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import org.thingsboard.server.common.data.EntityType;
import java.util.UUID;
public class QueueStatsId extends UUIDBased implements EntityId {
private static final long serialVersionUID = 1L;
@JsonCreator
public QueueStatsId(@JsonProperty("id") UUID id) {
super(id);
}
public static QueueStatsId fromString(String queueId) {
return new QueueStatsId(UUID.fromString(queueId));
}
@Schema(required = true, description = "string", example = "QUEUE_STATS", allowableValues = "QUEUE_STATS")
@Override
public EntityType getEntityType() {
return EntityType.QUEUE_STATS;
}
}

39
common/data/src/main/java/org/thingsboard/server/common/data/queue/QueueStats.java

@ -0,0 +1,39 @@
/**
* Copyright © 2016-2024 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.queue;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.QueueStatsId;
@EqualsAndHashCode(callSuper = true)
@Data
public class QueueStats extends BaseData<QueueStatsId> implements HasTenantId {
private TenantId tenantId;
private String queueName;
private String serviceId;
public QueueStats() {
}
public QueueStats(QueueStatsId id) {
super(id);
}
}

1
common/proto/src/main/proto/queue.proto

@ -54,6 +54,7 @@ enum EntityTypeProto {
NOTIFICATION_REQUEST = 31; NOTIFICATION_REQUEST = 31;
NOTIFICATION = 32; NOTIFICATION = 32;
NOTIFICATION_RULE = 33; NOTIFICATION_RULE = 33;
QUEUE_STATS = 34;
} }
/** /**

1
dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java

@ -75,7 +75,6 @@ public class BaseAssetService extends AbstractCachedEntityService<AssetCacheKey,
public static final String INCORRECT_ASSET_PROFILE_ID = "Incorrect assetProfileId "; public static final String INCORRECT_ASSET_PROFILE_ID = "Incorrect assetProfileId ";
public static final String INCORRECT_CUSTOMER_ID = "Incorrect customerId "; public static final String INCORRECT_CUSTOMER_ID = "Incorrect customerId ";
public static final String INCORRECT_ASSET_ID = "Incorrect assetId "; public static final String INCORRECT_ASSET_ID = "Incorrect assetId ";
public static final String TB_SERVICE_QUEUE = "TbServiceQueue";
@Autowired @Autowired
private AssetDao assetDao; private AssetDao assetDao;

8
dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java

@ -611,6 +611,14 @@ public class ModelConstants {
public static final String QUEUE_TABLE_NAME = "queue"; public static final String QUEUE_TABLE_NAME = "queue";
public static final String QUEUE_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY; public static final String QUEUE_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY;
/**
* Tenant queue stats constants.
*/
public static final String QUEUE_STATS_TABLE_NAME = "queue_stats";
public static final String QUEUE_STATS_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
public static final String QUEUE_STATS_QUEUE_NAME_PROPERTY = "queue_name";
public static final String QUEUE_STATS_SERVICE_ID_PROPERTY = "service_id";
/** /**
* Notification constants * Notification constants
*/ */

69
dao/src/main/java/org/thingsboard/server/dao/model/sql/QueueStatsEntity.java

@ -0,0 +1,69 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.model.sql;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.id.QueueStatsId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.queue.QueueStats;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.model.BaseSqlEntity;
import org.thingsboard.server.dao.model.ModelConstants;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import java.util.UUID;
@Data
@EqualsAndHashCode(callSuper = true)
@Entity
@Table(name = ModelConstants.QUEUE_STATS_TABLE_NAME)
public class QueueStatsEntity extends BaseSqlEntity<QueueStats> {
@Column(name = ModelConstants.QUEUE_STATS_TENANT_ID_PROPERTY)
private UUID tenantId;
@Column(name = ModelConstants.QUEUE_STATS_QUEUE_NAME_PROPERTY)
private String queueName;
@Column(name = ModelConstants.QUEUE_STATS_SERVICE_ID_PROPERTY)
private String serviceId;
public QueueStatsEntity() {
}
public QueueStatsEntity(QueueStats queueStats) {
if (queueStats.getId() != null) {
this.setId(queueStats.getId().getId());
}
this.setCreatedTime(queueStats.getCreatedTime());
this.tenantId = DaoUtil.getId(queueStats.getTenantId());
this.queueName = queueStats.getQueueName();
this.serviceId = queueStats.getServiceId();
}
@Override
public QueueStats toData() {
QueueStats queueStats = new QueueStats(new QueueStatsId(getUuid()));
queueStats.setCreatedTime(createdTime);
queueStats.setTenantId(new TenantId(tenantId));
queueStats.setQueueName(queueName);
queueStats.setServiceId(serviceId);
return queueStats;
}
}

90
dao/src/main/java/org/thingsboard/server/dao/queue/BaseQueueStatsService.java

@ -0,0 +1,90 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.queue;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.HasId;
import org.thingsboard.server.common.data.id.QueueStatsId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.queue.QueueStats;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.service.DataValidator;
import java.util.List;
import java.util.Optional;
import static org.thingsboard.server.dao.service.Validator.validateId;
@Service("QueueStatsDaoService")
@Slf4j
@RequiredArgsConstructor
public class BaseQueueStatsService extends AbstractEntityService implements QueueStatsService {
public static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
private final QueueStatsDao queueStatsDao;
private final DataValidator<QueueStats> queueStatsValidator;
@Override
public QueueStats save(TenantId tenantId, QueueStats queueStats) {
log.trace("Executing save [{}]", queueStats);
queueStatsValidator.validate(queueStats, QueueStats::getTenantId);
return queueStatsDao.save(tenantId, queueStats);
}
@Override
public QueueStats findQueueStatsById(TenantId tenantId, QueueStatsId queueStatsId) {
log.trace("Executing findQueueStatsById [{}]", queueStatsId);
validateId(queueStatsId, "Incorrect queueStatsId " + queueStatsId);
return queueStatsDao.findById(tenantId, queueStatsId.getId());
}
@Override
public QueueStats findByTenantIdAndNameAndServiceId(TenantId tenantId, String queueName, String serviceId) {
log.trace("Executing findByTenantIdAndNameAndServiceId, tenantId: [{}], queueName: [{}], serviceId: [{}]", tenantId, queueName, serviceId);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
return queueStatsDao.findByTenantIdQueueNameAndServiceId(tenantId, queueName, serviceId);
}
@Override
public List<QueueStats> findByTenantId(TenantId tenantId) {
log.trace("Executing findByTenantId, tenantId: [{}]", tenantId);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
return queueStatsDao.findByTenantId(tenantId);
}
@Override
public void deleteByTenantId(TenantId tenantId) {
log.trace("Executing deleteByTenantId, tenantId [{}]", tenantId);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
queueStatsDao.deleteByTenantId(tenantId);
}
@Override
public Optional<HasId<?>> findEntity(TenantId tenantId, EntityId entityId) {
return Optional.ofNullable(findQueueStatsById(tenantId, new QueueStatsId(entityId.getId())));
}
@Override
public EntityType getEntityType() {
return EntityType.QUEUE_STATS;
}
}

32
dao/src/main/java/org/thingsboard/server/dao/queue/QueueStatsDao.java

@ -0,0 +1,32 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.queue;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.queue.QueueStats;
import org.thingsboard.server.dao.Dao;
import java.util.List;
public interface QueueStatsDao extends Dao<QueueStats> {
QueueStats findByTenantIdQueueNameAndServiceId(TenantId tenantId, String queueName, String serviceId);
List<QueueStats> findByTenantId(TenantId tenantId);
void deleteByTenantId(TenantId tenantId);
}

4
dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetDataValidator.java

@ -47,9 +47,7 @@ public class AssetDataValidator extends DataValidator<Asset> {
@Override @Override
protected void validateCreate(TenantId tenantId, Asset asset) { protected void validateCreate(TenantId tenantId, Asset asset) {
if (!BaseAssetService.TB_SERVICE_QUEUE.equals(asset.getType())) { validateNumberOfEntitiesPerTenant(tenantId, EntityType.ASSET);
validateNumberOfEntitiesPerTenant(tenantId, EntityType.ASSET);
}
} }
@Override @Override

40
dao/src/main/java/org/thingsboard/server/dao/service/validator/QueueStatsDataValidator.java

@ -0,0 +1,40 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.service.validator;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.queue.QueueStats;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
@Component
public class QueueStatsDataValidator extends DataValidator<QueueStats> {
@Override
protected void validateDataImpl(TenantId tenantId, QueueStats queueStats) {
if (queueStats.getTenantId() == null) {
throw new DataValidationException("Tenant id should be specified!.");
}
if (queueStats.getQueueName() == null) {
throw new DataValidationException("Queue name should be specified!.");
}
if (StringUtils.isEmpty(queueStats.getServiceId())) {
throw new DataValidationException("Service id should be specified!.");
}
}
}

2
dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java

@ -189,7 +189,7 @@ public interface AssetRepository extends JpaRepository<AssetEntity, UUID>, Expor
@Param("searchText") String searchText, @Param("searchText") String searchText,
Pageable pageable); Pageable pageable);
Long countByTenantIdAndTypeIsNot(UUID tenantId, String type); Long countByTenantId(UUID tenantId);
@Query("SELECT externalId FROM AssetEntity WHERE id = :id") @Query("SELECT externalId FROM AssetEntity WHERE id = :id")
UUID getExternalIdById(@Param("id") UUID id); UUID getExternalIdById(@Param("id") UUID id);

3
dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java

@ -43,7 +43,6 @@ import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import static org.thingsboard.server.dao.DaoUtil.convertTenantEntityInfosToDto; import static org.thingsboard.server.dao.DaoUtil.convertTenantEntityInfosToDto;
import static org.thingsboard.server.dao.asset.BaseAssetService.TB_SERVICE_QUEUE;
/** /**
* Created by Valerii Sosliuk on 5/19/2017. * Created by Valerii Sosliuk on 5/19/2017.
@ -244,7 +243,7 @@ public class JpaAssetDao extends JpaAbstractDao<AssetEntity, Asset> implements A
@Override @Override
public Long countByTenantId(TenantId tenantId) { public Long countByTenantId(TenantId tenantId) {
return assetRepository.countByTenantIdAndTypeIsNot(tenantId.getId(), TB_SERVICE_QUEUE); return assetRepository.countByTenantId(tenantId.getId());
} }
@Override @Override

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

@ -244,6 +244,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
entityTableMap.put(EntityType.DEVICE_PROFILE, "device_profile"); entityTableMap.put(EntityType.DEVICE_PROFILE, "device_profile");
entityTableMap.put(EntityType.ASSET_PROFILE, "asset_profile"); entityTableMap.put(EntityType.ASSET_PROFILE, "asset_profile");
entityTableMap.put(EntityType.TENANT_PROFILE, "tenant_profile"); entityTableMap.put(EntityType.TENANT_PROFILE, "tenant_profile");
entityTableMap.put(EntityType.QUEUE_STATS, "queue_stats");
entityNameColumns.put(EntityType.DEVICE, "name"); entityNameColumns.put(EntityType.DEVICE, "name");
entityNameColumns.put(EntityType.CUSTOMER, "title"); entityNameColumns.put(EntityType.CUSTOMER, "title");
@ -262,6 +263,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
entityNameColumns.put(EntityType.TB_RESOURCE, "search_text"); entityNameColumns.put(EntityType.TB_RESOURCE, "search_text");
entityNameColumns.put(EntityType.EDGE, "name"); entityNameColumns.put(EntityType.EDGE, "name");
entityNameColumns.put(EntityType.QUEUE, "name"); entityNameColumns.put(EntityType.QUEUE, "name");
entityNameColumns.put(EntityType.QUEUE_STATS, "queue_name");
} }
public static EntityType[] RELATION_QUERY_ENTITY_TYPES = new EntityType[]{ public static EntityType[] RELATION_QUERY_ENTITY_TYPES = new EntityType[]{

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

@ -75,6 +75,8 @@ public class EntityKeyMapping {
public static final String PHONE = "phone"; public static final String PHONE = "phone";
public static final String ADDITIONAL_INFO = "additionalInfo"; public static final String ADDITIONAL_INFO = "additionalInfo";
public static final String RELATED_PARENT_ID = "parentId"; public static final String RELATED_PARENT_ID = "parentId";
public static final String QUEUE_NAME = "queueName";
public static final String SERVICE_ID = "serviceId";
public static final List<String> typedEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, ADDITIONAL_INFO); public static final List<String> typedEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, ADDITIONAL_INFO);
public static final List<String> widgetEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME); public static final List<String> widgetEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME);
@ -106,6 +108,7 @@ public class EntityKeyMapping {
allowedEntityFieldMap.put(EntityType.API_USAGE_STATE, apiUsageStateEntityFields); allowedEntityFieldMap.put(EntityType.API_USAGE_STATE, apiUsageStateEntityFields);
allowedEntityFieldMap.put(EntityType.DEVICE_PROFILE, Set.of(CREATED_TIME, NAME, TYPE)); allowedEntityFieldMap.put(EntityType.DEVICE_PROFILE, Set.of(CREATED_TIME, NAME, TYPE));
allowedEntityFieldMap.put(EntityType.ASSET_PROFILE, Set.of(CREATED_TIME, NAME)); allowedEntityFieldMap.put(EntityType.ASSET_PROFILE, Set.of(CREATED_TIME, NAME));
allowedEntityFieldMap.put(EntityType.QUEUE_STATS, new HashSet<>(Arrays.asList(CREATED_TIME, QUEUE_NAME, SERVICE_ID)));
entityFieldColumnMap.put(CREATED_TIME, ModelConstants.CREATED_TIME_PROPERTY); entityFieldColumnMap.put(CREATED_TIME, ModelConstants.CREATED_TIME_PROPERTY);
entityFieldColumnMap.put(ENTITY_TYPE, ModelConstants.ENTITY_TYPE_PROPERTY); entityFieldColumnMap.put(ENTITY_TYPE, ModelConstants.ENTITY_TYPE_PROPERTY);
@ -126,6 +129,8 @@ public class EntityKeyMapping {
entityFieldColumnMap.put(PHONE, ModelConstants.PHONE_PROPERTY); entityFieldColumnMap.put(PHONE, ModelConstants.PHONE_PROPERTY);
entityFieldColumnMap.put(ADDITIONAL_INFO, ModelConstants.ADDITIONAL_INFO_PROPERTY); entityFieldColumnMap.put(ADDITIONAL_INFO, ModelConstants.ADDITIONAL_INFO_PROPERTY);
entityFieldColumnMap.put(RELATED_PARENT_ID, "parent_id"); entityFieldColumnMap.put(RELATED_PARENT_ID, "parent_id");
entityFieldColumnMap.put(QUEUE_NAME, ModelConstants.QUEUE_STATS_QUEUE_NAME_PROPERTY);
entityFieldColumnMap.put(SERVICE_ID, ModelConstants.QUEUE_STATS_SERVICE_ID_PROPERTY);
Map<String, String> contactBasedAliases = new HashMap<>(); Map<String, String> contactBasedAliases = new HashMap<>();
contactBasedAliases.put(NAME, TITLE); contactBasedAliases.put(NAME, TITLE);

66
dao/src/main/java/org/thingsboard/server/dao/sql/queue/JpaQueueStatsDao.java

@ -0,0 +1,66 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.sql.queue;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.queue.QueueStats;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.model.sql.QueueStatsEntity;
import org.thingsboard.server.dao.queue.QueueStatsDao;
import org.thingsboard.server.dao.sql.JpaAbstractDao;
import org.thingsboard.server.dao.util.SqlDao;
import java.util.List;
import java.util.UUID;
@Slf4j
@Component
@SqlDao
public class JpaQueueStatsDao extends JpaAbstractDao<QueueStatsEntity, QueueStats> implements QueueStatsDao {
@Autowired
private QueueStatsRepository queueStatsRepository;
@Override
protected Class<QueueStatsEntity> getEntityClass() {
return QueueStatsEntity.class;
}
@Override
protected JpaRepository<QueueStatsEntity, UUID> getRepository() {
return queueStatsRepository;
}
@Override
public QueueStats findByTenantIdQueueNameAndServiceId(TenantId tenantId, String queueName, String serviceId) {
return DaoUtil.getData(queueStatsRepository.findByTenantIdAndQueueNameAndServiceId(tenantId.getId(), queueName, serviceId));
}
@Override
public List<QueueStats> findByTenantId(TenantId tenantId) {
return DaoUtil.convertDataList(queueStatsRepository.findByTenantId(tenantId.getId()));
}
@Override
public void deleteByTenantId(TenantId tenantId) {
queueStatsRepository.deleteByTenantId(tenantId.getId());
}
}

39
dao/src/main/java/org/thingsboard/server/dao/sql/queue/QueueStatsRepository.java

@ -0,0 +1,39 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.sql.queue;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.dao.model.sql.QueueStatsEntity;
import java.util.List;
import java.util.UUID;
public interface QueueStatsRepository extends JpaRepository<QueueStatsEntity, UUID> {
QueueStatsEntity findByTenantIdAndQueueNameAndServiceId(UUID tenantId, String queueName, String serviceId);
List<QueueStatsEntity> findByTenantId(UUID tenantId);
@Transactional
@Modifying
@Query("DELETE FROM QueueStatsEntity t WHERE t.tenantId = :tenantId")
void deleteByTenantId(@Param("tenantId") UUID tenantId);
}

6
dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java

@ -49,6 +49,7 @@ import org.thingsboard.server.dao.notification.NotificationTargetService;
import org.thingsboard.server.dao.notification.NotificationTemplateService; import org.thingsboard.server.dao.notification.NotificationTemplateService;
import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.queue.QueueService;
import org.thingsboard.server.dao.queue.QueueStatsService;
import org.thingsboard.server.dao.resource.ResourceService; import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.rpc.RpcService; import org.thingsboard.server.dao.rpc.RpcService;
import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.rule.RuleChainService;
@ -131,6 +132,10 @@ public class TenantServiceImpl extends AbstractCachedEntityService<TenantId, Ten
@Autowired @Autowired
private QueueService queueService; private QueueService queueService;
@Lazy
@Autowired
private QueueStatsService queueStatsService;
@Autowired @Autowired
private AdminSettingsService adminSettingsService; private AdminSettingsService adminSettingsService;
@ -259,6 +264,7 @@ public class TenantServiceImpl extends AbstractCachedEntityService<TenantId, Ten
.entity(tenant).entityId(tenantId).build()); .entity(tenant).entityId(tenantId).build());
relationService.deleteEntityRelations(tenantId, tenantId); relationService.deleteEntityRelations(tenantId, tenantId);
alarmService.deleteEntityAlarmRecordsByTenantId(tenantId); alarmService.deleteEntityAlarmRecordsByTenantId(tenantId);
queueStatsService.deleteByTenantId(tenantId);
} }
@Override @Override

9
dao/src/main/resources/sql/schema-entities.sql

@ -884,3 +884,12 @@ CREATE TABLE IF NOT EXISTS alarm_types (
CONSTRAINT tenant_id_type_unq_key UNIQUE (tenant_id, type), CONSTRAINT tenant_id_type_unq_key UNIQUE (tenant_id, type),
CONSTRAINT fk_entity_tenant_id FOREIGN KEY (tenant_id) REFERENCES tenant(id) ON DELETE CASCADE CONSTRAINT fk_entity_tenant_id FOREIGN KEY (tenant_id) REFERENCES tenant(id) ON DELETE CASCADE
); );
CREATE TABLE IF NOT EXISTS queue_stats (
id uuid NOT NULL CONSTRAINT queue_stats_pkey PRIMARY KEY,
created_time bigint NOT NULL,
tenant_id uuid NOT NULL,
queue_name varchar(255) NOT NULL,
service_id varchar(255) NOT NULL,
CONSTRAINT queue_stats_name_unq_key UNIQUE (tenant_id, queue_name, service_id)
);

134
dao/src/test/java/org/thingsboard/server/dao/service/QueueStatsServiceTest.java

@ -0,0 +1,134 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.service;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.jupiter.api.Assertions;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.queue.QueueStats;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.queue.QueueStatsService;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
@DaoSqlTest
public class QueueStatsServiceTest extends AbstractServiceTest {
@Autowired
QueueStatsService queueStatsService;
private TenantId tenantId;
@Before
public void before() throws NoSuchFieldException, IllegalAccessException {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = tenantService.saveTenant(tenant);
Assert.assertNotNull(savedTenant);
tenantId = savedTenant.getId();
}
@After
public void after() {
tenantService.deleteTenant(tenantId);
}
@Test
public void testSaveQueueStats() {
QueueStats queueStats = new QueueStats();
queueStats.setTenantId(tenantId);
String queueName = StringUtils.randomAlphabetic(8);
queueStats.setQueueName(queueName);
queueStats.setServiceId(StringUtils.randomAlphabetic(8));
QueueStats savedQueueStats = queueStatsService.save(tenantId, queueStats);
Assert.assertNotNull(savedQueueStats);
Assert.assertNotNull(savedQueueStats.getId());
Assert.assertTrue(savedQueueStats.getCreatedTime() > 0);
Assert.assertEquals(queueStats.getTenantId(), savedQueueStats.getTenantId());
Assert.assertEquals(savedQueueStats.getQueueName(), queueStats.getQueueName());
QueueStats retrievedQueueStatsById = queueStatsService.findQueueStatsById(tenantId, savedQueueStats.getId());
Assert.assertEquals(retrievedQueueStatsById.getQueueName(), queueName);
String secondQueueName = StringUtils.randomAlphabetic(8);
queueStats.setQueueName(secondQueueName);
QueueStats savedQueueStats2 = queueStatsService.save(tenantId, queueStats);
QueueStats retrievedQueueStatsById2 = queueStatsService.findQueueStatsById(tenantId, savedQueueStats2.getId());
Assert.assertEquals(retrievedQueueStatsById2.getQueueName(), secondQueueName);
List<QueueStats> queueStatsList = queueStatsService.findByTenantId(tenantId);
Assert.assertEquals(2, queueStatsList.size());
assertThat(queueStatsList).containsOnly(retrievedQueueStatsById, retrievedQueueStatsById2);
queueStatsService.deleteByTenantId(tenantId);
QueueStats retrievedQueueStatsAfterDelete = queueStatsService.findQueueStatsById(tenantId, savedQueueStats.getId());
Assert.assertNull(retrievedQueueStatsAfterDelete);
}
@Test
public void testSaveWithNullQueueName() {
QueueStats queueStats = new QueueStats();
queueStats.setTenantId(tenantId);
queueStats.setQueueName(null);
queueStats.setServiceId(StringUtils.randomAlphabetic(8));
Assertions.assertThrows(DataValidationException.class, () -> {
queueStatsService.save(tenantId, queueStats);
});
}
@Test
public void testSaveWithNullServiceId() {
QueueStats queueStats = new QueueStats();
queueStats.setTenantId(tenantId);
queueStats.setQueueName(StringUtils.randomAlphabetic(8));
queueStats.setServiceId(null);
Assertions.assertThrows(DataValidationException.class, () -> {
queueStatsService.save(tenantId, queueStats);
});
}
@Test
public void testFindByTenantIdAndNameAndServiceId() {
QueueStats queueStats = new QueueStats();
queueStats.setTenantId(tenantId);
queueStats.setQueueName(StringUtils.randomAlphabetic(8));
queueStats.setServiceId(StringUtils.randomAlphabetic(8));
QueueStats savedQueueStats = queueStatsService.save(tenantId, queueStats);
QueueStats queueStats2 = new QueueStats();
queueStats2.setTenantId(tenantId);
queueStats2.setQueueName(StringUtils.randomAlphabetic(8));
queueStats2.setServiceId(StringUtils.randomAlphabetic(8));
queueStatsService.save(tenantId, queueStats2);
QueueStats retrievedQueueStatsById = queueStatsService.findByTenantIdAndNameAndServiceId(tenantId, queueStats.getQueueName(), queueStats.getServiceId());
assertThat(retrievedQueueStatsById).isEqualTo(savedQueueStats);
}
}

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

@ -68,6 +68,7 @@ import org.thingsboard.server.dao.notification.NotificationTargetService;
import org.thingsboard.server.dao.notification.NotificationTemplateService; import org.thingsboard.server.dao.notification.NotificationTemplateService;
import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.queue.QueueService;
import org.thingsboard.server.dao.queue.QueueStatsService;
import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.resource.ResourceService; import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.rule.RuleChainService;
@ -315,6 +316,8 @@ public interface TbContext {
QueueService getQueueService(); QueueService getQueueService();
QueueStatsService getQueueStatsService();
ListeningExecutor getMailExecutor(); ListeningExecutor getMailExecutor();
ListeningExecutor getSmsExecutor(); ListeningExecutor getSmsExecutor();

4
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/TenantIdLoader.java

@ -35,6 +35,7 @@ import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.id.NotificationTemplateId; import org.thingsboard.server.common.data.id.NotificationTemplateId;
import org.thingsboard.server.common.data.id.OtaPackageId; import org.thingsboard.server.common.data.id.OtaPackageId;
import org.thingsboard.server.common.data.id.QueueId; import org.thingsboard.server.common.data.id.QueueId;
import org.thingsboard.server.common.data.id.QueueStatsId;
import org.thingsboard.server.common.data.id.RpcId; import org.thingsboard.server.common.data.id.RpcId;
import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.RuleNodeId;
@ -141,6 +142,9 @@ public class TenantIdLoader {
case NOTIFICATION_RULE: case NOTIFICATION_RULE:
tenantEntity = ctx.getNotificationRuleService().findNotificationRuleById(ctxTenantId, new NotificationRuleId(id)); tenantEntity = ctx.getNotificationRuleService().findNotificationRuleById(ctxTenantId, new NotificationRuleId(id));
break; break;
case QUEUE_STATS:
tenantEntity = ctx.getQueueStatsService().findQueueStatsById(ctxTenantId, new QueueStatsId(id));
break;
default: default:
throw new RuntimeException("Unexpected entity type: " + entityId.getEntityType()); throw new RuntimeException("Unexpected entity type: " + entityId.getEntityType());
} }

10
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/TenantIdLoaderTest.java

@ -56,6 +56,7 @@ import org.thingsboard.server.common.data.notification.rule.NotificationRule;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget; import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate; import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
import org.thingsboard.server.common.data.queue.Queue; import org.thingsboard.server.common.data.queue.Queue;
import org.thingsboard.server.common.data.queue.QueueStats;
import org.thingsboard.server.common.data.rpc.Rpc; import org.thingsboard.server.common.data.rpc.Rpc;
import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.rule.RuleNode;
@ -73,6 +74,7 @@ import org.thingsboard.server.dao.notification.NotificationTargetService;
import org.thingsboard.server.dao.notification.NotificationTemplateService; import org.thingsboard.server.dao.notification.NotificationTemplateService;
import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.queue.QueueService;
import org.thingsboard.server.dao.queue.QueueStatsService;
import org.thingsboard.server.dao.resource.ResourceService; import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.user.UserService;
@ -135,6 +137,8 @@ public class TenantIdLoaderTest {
private NotificationRequestService notificationRequestService; private NotificationRequestService notificationRequestService;
@Mock @Mock
private NotificationRuleService notificationRuleService; private NotificationRuleService notificationRuleService;
@Mock
private QueueStatsService queueStatsService;
private TenantId tenantId; private TenantId tenantId;
private TenantProfileId tenantProfileId; private TenantProfileId tenantProfileId;
@ -352,6 +356,12 @@ public class TenantIdLoaderTest {
when(ctx.getNotificationRuleService()).thenReturn(notificationRuleService); when(ctx.getNotificationRuleService()).thenReturn(notificationRuleService);
doReturn(notificationRule).when(notificationRuleService).findNotificationRuleById(eq(tenantId), any()); doReturn(notificationRule).when(notificationRuleService).findNotificationRuleById(eq(tenantId), any());
break; break;
case QUEUE_STATS:
QueueStats queueStats = new QueueStats();
queueStats.setTenantId(tenantId);
when(ctx.getQueueStatsService()).thenReturn(queueStatsService);
doReturn(queueStats).when(queueStatsService).findQueueStatsById(eq(tenantId), any());
break;
default: default:
throw new RuntimeException("Unexpected originator EntityType " + entityType); throw new RuntimeException("Unexpected originator EntityType " + entityType);
} }

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

@ -716,6 +716,7 @@ export class EntityService {
entityTypes.push(EntityType.CUSTOMER); entityTypes.push(EntityType.CUSTOMER);
entityTypes.push(EntityType.USER); entityTypes.push(EntityType.USER);
entityTypes.push(EntityType.DASHBOARD); entityTypes.push(EntityType.DASHBOARD);
entityTypes.push(EntityType.QUEUE_STATS);
if (authState.edgesSupportEnabled) { if (authState.edgesSupportEnabled) {
entityTypes.push(EntityType.EDGE); entityTypes.push(EntityType.EDGE);
} }
@ -795,6 +796,10 @@ export class EntityService {
case EntityType.API_USAGE_STATE: case EntityType.API_USAGE_STATE:
entityFieldKeys.push(entityFields.name.keyName); entityFieldKeys.push(entityFields.name.keyName);
break; break;
case EntityType.QUEUE_STATS:
entityFieldKeys.push(entityFields.queueName.keyName);
entityFieldKeys.push(entityFields.serviceId.keyName);
break;
} }
return query ? entityFieldKeys.filter((entityField) => entityField.toLowerCase().indexOf(query) === 0) : entityFieldKeys; return query ? entityFieldKeys.filter((entityField) => entityField.toLowerCase().indexOf(query) === 0) : entityFieldKeys;
} }

2
ui-ngx/src/app/modules/home/components/profile/asset-profile.component.html

@ -31,7 +31,7 @@
<button mat-raised-button color="primary" <button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)" [disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'setDefault')" (click)="onEntityAction($event, 'setDefault')"
[fxShow]="!isEdit && !entity?.default && entity?.name !== TB_SERVICE_QUEUE"> [fxShow]="!isEdit && !entity?.default">
{{'asset-profile.set-default' | translate }} {{'asset-profile.set-default' | translate }}
</button> </button>
<button mat-raised-button color="primary" <button mat-raised-button color="primary"

4
ui-ngx/src/app/modules/home/components/profile/asset-profile.component.ts

@ -27,7 +27,7 @@ import { RuleChainId } from '@shared/models/id/rule-chain-id';
import { ServiceType } from '@shared/models/queue.models'; import { ServiceType } from '@shared/models/queue.models';
import { EntityId } from '@shared/models/id/entity-id'; import { EntityId } from '@shared/models/id/entity-id';
import { DashboardId } from '@shared/models/id/dashboard-id'; import { DashboardId } from '@shared/models/id/dashboard-id';
import { AssetProfile, TB_SERVICE_QUEUE } from '@shared/models/asset.models'; import { AssetProfile } from '@shared/models/asset.models';
import { RuleChainType } from '@shared/models/rule-chain.models'; import { RuleChainType } from '@shared/models/rule-chain.models';
@Component({ @Component({
@ -46,8 +46,6 @@ export class AssetProfileComponent extends EntityComponent<AssetProfile> {
edgeRuleChainType = RuleChainType.EDGE; edgeRuleChainType = RuleChainType.EDGE;
TB_SERVICE_QUEUE = TB_SERVICE_QUEUE;
assetProfileId: EntityId; assetProfileId: EntityId;
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,

9
ui-ngx/src/app/modules/home/pages/asset-profile/asset-profiles-table-config.resolver.ts

@ -31,7 +31,7 @@ import { DialogService } from '@core/services/dialog.service';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { ImportExportService } from '@shared/import-export/import-export.service'; import { ImportExportService } from '@shared/import-export/import-export.service';
import { HomeDialogsService } from '@home/dialogs/home-dialogs.service'; import { HomeDialogsService } from '@home/dialogs/home-dialogs.service';
import { AssetProfile, TB_SERVICE_QUEUE } from '@shared/models/asset.models'; import { AssetProfile } from '@shared/models/asset.models';
import { AssetProfileService } from '@core/http/asset-profile.service'; import { AssetProfileService } from '@core/http/asset-profile.service';
import { AssetProfileComponent } from '@home/components/profile/asset-profile.component'; import { AssetProfileComponent } from '@home/components/profile/asset-profile.component';
import { AssetProfileTabsComponent } from './asset-profile-tabs.component'; import { AssetProfileTabsComponent } from './asset-profile-tabs.component';
@ -78,7 +78,7 @@ export class AssetProfilesTableConfigResolver implements Resolve<EntityTableConf
{ {
name: this.translate.instant('asset-profile.set-default'), name: this.translate.instant('asset-profile.set-default'),
icon: 'flag', icon: 'flag',
isEnabled: (assetProfile) => !assetProfile.default && TB_SERVICE_QUEUE !== assetProfile.name, isEnabled: (assetProfile) => !assetProfile.default,
onAction: ($event, entity) => this.setDefaultAssetProfile($event, entity) onAction: ($event, entity) => this.setDefaultAssetProfile($event, entity)
} }
); );
@ -94,9 +94,8 @@ export class AssetProfilesTableConfigResolver implements Resolve<EntityTableConf
this.config.saveEntity = assetProfile => this.assetProfileService.saveAssetProfile(assetProfile); this.config.saveEntity = assetProfile => this.assetProfileService.saveAssetProfile(assetProfile);
this.config.deleteEntity = id => this.assetProfileService.deleteAssetProfile(id.id); this.config.deleteEntity = id => this.assetProfileService.deleteAssetProfile(id.id);
this.config.onEntityAction = action => this.onAssetProfileAction(action); this.config.onEntityAction = action => this.onAssetProfileAction(action);
this.config.deleteEnabled = (assetProfile) => assetProfile && !assetProfile.default && TB_SERVICE_QUEUE !== assetProfile.name; this.config.deleteEnabled = (assetProfile) => assetProfile && !assetProfile.default;
this.config.entitySelectionEnabled = (assetProfile) => assetProfile && !assetProfile.default && TB_SERVICE_QUEUE !== assetProfile.name; this.config.entitySelectionEnabled = (assetProfile) => assetProfile && !assetProfile.default;
this.config.detailsReadonly = (assetProfile) => assetProfile && TB_SERVICE_QUEUE === assetProfile.name;
this.config.addActionDescriptors = this.configureAddActions(); this.config.addActionDescriptors = this.configureAddActions();
} }

2
ui-ngx/src/app/shared/models/asset.models.ts

@ -24,8 +24,6 @@ import { RuleChainId } from '@shared/models/id/rule-chain-id';
import { DashboardId } from '@shared/models/id/dashboard-id'; import { DashboardId } from '@shared/models/id/dashboard-id';
import { EntityInfoData, HasTenantId } from '@shared/models/entity.models'; import { EntityInfoData, HasTenantId } from '@shared/models/entity.models';
export const TB_SERVICE_QUEUE = 'TbServiceQueue';
export interface AssetProfile extends BaseData<AssetProfileId>, HasTenantId, ExportableEntity<AssetProfileId> { export interface AssetProfile extends BaseData<AssetProfileId>, HasTenantId, ExportableEntity<AssetProfileId> {
tenantId?: TenantId; tenantId?: TenantId;
name: string; name: string;

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

@ -39,6 +39,7 @@ export enum EntityType {
OTA_PACKAGE = 'OTA_PACKAGE', OTA_PACKAGE = 'OTA_PACKAGE',
RPC = 'RPC', RPC = 'RPC',
QUEUE = 'QUEUE', QUEUE = 'QUEUE',
QUEUE_STATS = 'QUEUE_STATS',
NOTIFICATION = 'NOTIFICATION', NOTIFICATION = 'NOTIFICATION',
NOTIFICATION_REQUEST = 'NOTIFICATION_REQUEST', NOTIFICATION_REQUEST = 'NOTIFICATION_REQUEST',
NOTIFICATION_RULE = 'NOTIFICATION_RULE', NOTIFICATION_RULE = 'NOTIFICATION_RULE',
@ -362,6 +363,13 @@ export const entityTypeTranslations = new Map<EntityType | AliasEntityType, Enti
selectedEntities: 'queue.selected-queues' selectedEntities: 'queue.selected-queues'
} }
], ],
[
EntityType.QUEUE_STATS,
{
type: 'entity.type-queue-stats',
typePlural: 'entity.type-queues-stats'
}
],
[ [
EntityType.NOTIFICATION, EntityType.NOTIFICATION,
{ {

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

@ -161,6 +161,16 @@ export const entityFields: {[fieldName: string]: EntityField} = {
keyName: 'label', keyName: 'label',
name: 'entity-field.label', name: 'entity-field.label',
value: 'label' value: 'label'
},
queueName: {
keyName: 'queueName',
name: 'entity-field.queue-name',
value: 'queueName'
},
serviceId: {
keyName: 'serviceId',
name: 'entity-field.service-id',
value: 'serviceId'
} }
}; };

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

@ -885,6 +885,9 @@ export function entityDataToEntityInfo(entityData: EntityData): EntityInfo {
} catch (e) {} } catch (e) {}
} }
} }
if (fields.queueName && fields.serviceId) {
entityInfo.name = fields.queueName.value + '_' + fields.serviceId.value;
}
} }
return entityInfo; return entityInfo;
} }

72
ui-ngx/src/assets/dashboard/api_usage.json

@ -14,6 +14,7 @@
"datasources": [ "datasources": [
{ {
"type": "entity", "type": "entity",
"entityAliasId": "2e4c97b0-257a-a1b9-690c-141d9bf2ec6f",
"dataKeys": [ "dataKeys": [
{ {
"name": "ruleEngineException", "name": "ruleEngineException",
@ -52,7 +53,29 @@
"_hash": 0.7255162989552142 "_hash": 0.7255162989552142
} }
], ],
"entityAliasId": "2e4c97b0-257a-a1b9-690c-141d9bf2ec6f" "alarmFilterConfig": {
"statusList": [
"ACTIVE"
]
},
"latestDataKeys": [
{
"name": "queueName",
"type": "entityField",
"label": "Queue name",
"color": "#ffc107",
"settings": {},
"_hash": 0.6889245277142959
},
{
"name": "serviceId",
"type": "entityField",
"label": "Service Id",
"color": "#607d8b",
"settings": {},
"_hash": 0.7972226575450785
}
]
} }
], ],
"timewindow": { "timewindow": {
@ -7690,7 +7713,25 @@
"statusList": [ "statusList": [
"ACTIVE" "ACTIVE"
] ]
} },
"latestDataKeys": [
{
"name": "queueName",
"type": "entityField",
"label": "Queue name",
"color": "#ffc107",
"settings": {},
"_hash": 0.7021721434431745
},
{
"name": "serviceId",
"type": "entityField",
"label": "Service Id",
"color": "#607d8b",
"settings": {},
"_hash": 0.5924381120750077
}
]
} }
], ],
"timewindow": { "timewindow": {
@ -8080,7 +8121,25 @@
"statusList": [ "statusList": [
"ACTIVE" "ACTIVE"
] ]
} },
"latestDataKeys": [
{
"name": "queueName",
"type": "entityField",
"label": "Queue name",
"color": "#f44336",
"settings": {},
"_hash": 0.7066844328378095
},
{
"name": "serviceId",
"type": "entityField",
"label": "Service Id",
"color": "#ffc107",
"settings": {},
"_hash": 0.1371570237026627
}
]
} }
], ],
"timewindow": { "timewindow": {
@ -8674,12 +8733,9 @@
"id": "2e4c97b0-257a-a1b9-690c-141d9bf2ec6f", "id": "2e4c97b0-257a-a1b9-690c-141d9bf2ec6f",
"alias": "TbServiceQueues", "alias": "TbServiceQueues",
"filter": { "filter": {
"type": "assetType", "type": "entityType",
"resolveMultiple": true, "resolveMultiple": true,
"assetNameFilter": "", "entityType": "QUEUE_STATS"
"assetTypes": [
"TbServiceQueue"
]
} }
} }
}, },

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

@ -2245,6 +2245,8 @@
"type-ota-package": "OTA package", "type-ota-package": "OTA package",
"type-rpc": "RPC", "type-rpc": "RPC",
"type-queue": "Queue", "type-queue": "Queue",
"type-queue-stats": "Queue statistics",
"type-queues-stats": "Queues statistics",
"type-notification": "Notification", "type-notification": "Notification",
"type-notification-rule": "Notification rule", "type-notification-rule": "Notification rule",
"type-notification-rules": "Notification rules", "type-notification-rules": "Notification rules",
@ -2272,7 +2274,9 @@
"address2": "Address 2", "address2": "Address 2",
"zip": "Zip", "zip": "Zip",
"phone": "Phone", "phone": "Phone",
"label": "Label" "label": "Label",
"queue-name": "Queue name",
"service-id": "Service Id"
}, },
"entity-view": { "entity-view": {
"entity-view": "Entity view", "entity-view": "Entity view",

Loading…
Cancel
Save