Browse Source

created Queue entity, dao and ui

pull/6134/head
YevhenBondarenko 5 years ago
parent
commit
4512185cad
  1. 18
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  2. 94
      application/src/main/java/org/thingsboard/server/controller/QueueController.java
  3. 3
      application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java
  4. 1
      application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java
  5. 1
      application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java
  6. 7
      application/src/test/java/org/thingsboard/server/service/cluster/routing/HashPartitionServiceTest.java
  7. 2
      common/cluster-api/src/main/java/org/thingsboard/server/queue/TbQueueAdmin.java
  8. 2
      common/cluster-api/src/main/java/org/thingsboard/server/queue/TbQueueService.java
  9. 48
      common/dao-api/src/main/java/org/thingsboard/server/dao/queue/QueueService.java
  10. 2
      common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java
  11. 6
      common/data/src/main/java/org/thingsboard/server/common/data/TenantProfile.java
  12. 2
      common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java
  13. 43
      common/data/src/main/java/org/thingsboard/server/common/data/id/QueueId.java
  14. 27
      common/data/src/main/java/org/thingsboard/server/common/data/queue/ProcessingStrategy.java
  15. 36
      common/data/src/main/java/org/thingsboard/server/common/data/queue/ProcessingStrategyType.java
  16. 42
      common/data/src/main/java/org/thingsboard/server/common/data/queue/Queue.java
  17. 24
      common/data/src/main/java/org/thingsboard/server/common/data/queue/SubmitStrategy.java
  18. 20
      common/data/src/main/java/org/thingsboard/server/common/data/queue/SubmitStrategyType.java
  19. 2
      common/queue/src/main/java/org/thingsboard/server/queue/DefaultTbQueueService.java
  20. 8
      common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java
  21. 4
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java
  22. 16
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  23. 105
      dao/src/main/java/org/thingsboard/server/dao/model/sql/QueueEntity.java
  24. 12
      dao/src/main/java/org/thingsboard/server/dao/model/sql/TenantProfileEntity.java
  25. 333
      dao/src/main/java/org/thingsboard/server/dao/queue/BaseQueueService.java
  26. 38
      dao/src/main/java/org/thingsboard/server/dao/queue/QueueDao.java
  27. 86
      dao/src/main/java/org/thingsboard/server/dao/sql/queue/JpaQueueDao.java
  28. 42
      dao/src/main/java/org/thingsboard/server/dao/sql/queue/QueueRepository.java
  29. 5
      dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java
  30. 15
      dao/src/main/resources/sql/schema-entities.sql
  31. 11
      ui-ngx/src/app/app.component.ts
  32. 25
      ui-ngx/src/app/core/http/queue.service.ts
  33. 18
      ui-ngx/src/app/core/services/menu.service.ts
  34. 30
      ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.html
  35. 18
      ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.ts
  36. 20
      ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts
  37. 4
      ui-ngx/src/app/modules/home/pages/admin/admin.module.ts
  38. 182
      ui-ngx/src/app/modules/home/pages/admin/queue/queue.component.html
  39. 59
      ui-ngx/src/app/modules/home/pages/admin/queue/queue.component.scss
  40. 154
      ui-ngx/src/app/modules/home/pages/admin/queue/queue.component.ts
  41. 115
      ui-ngx/src/app/modules/home/pages/admin/queue/queues-table-config.resolver.ts
  42. 4
      ui-ngx/src/app/shared/components/queue/queue-type-list.component.html
  43. 2
      ui-ngx/src/app/shared/components/queue/queue-type-list.component.ts
  44. 3
      ui-ngx/src/app/shared/models/constants.ts
  45. 18
      ui-ngx/src/app/shared/models/entity-type.models.ts
  46. 26
      ui-ngx/src/app/shared/models/id/queue-id.ts
  47. 40
      ui-ngx/src/app/shared/models/queue.models.ts
  48. 2
      ui-ngx/src/app/shared/models/tenant.model.ts
  49. 65
      ui-ngx/src/assets/locale/locale.constant-en_US.json

18
application/src/main/java/org/thingsboard/server/controller/BaseController.java

@ -70,6 +70,7 @@ import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.OtaPackageId;
import org.thingsboard.server.common.data.id.RpcId;
import org.thingsboard.server.common.data.id.QueueId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TbResourceId;
@ -86,6 +87,7 @@ import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.rpc.Rpc;
import org.thingsboard.server.common.data.queue.Queue;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleNode;
@ -108,6 +110,7 @@ import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService;
import org.thingsboard.server.dao.oauth2.OAuth2Service;
import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.queue.QueueService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rpc.RpcService;
import org.thingsboard.server.dao.rule.RuleChainService;
@ -271,6 +274,9 @@ public abstract class BaseController {
@Autowired
protected EntityActionService entityActionService;
@Autowired
protected QueueService queueService;
@Value("${server.log_controller_error_stack_trace}")
@Getter
private boolean logControllerErrorStackTrace;
@ -516,6 +522,9 @@ public abstract class BaseController {
case OTA_PACKAGE:
checkOtaPackageId(new OtaPackageId(entityId.getId()), operation);
return;
case QUEUE:
checkQueueId(new QueueId(entityId.getId()), operation);
return;
default:
throw new IllegalArgumentException("Unsupported entity type: " + entityId.getEntityType());
}
@ -807,7 +816,14 @@ public abstract class BaseController {
}
}
@SuppressWarnings("unchecked")
protected Queue checkQueueId(QueueId queueId, Operation operation) throws ThingsboardException {
validateId(queueId, "Incorrect queueId " + queueId);
Queue queue = queueService.findQueueById(getCurrentUser().getTenantId(), queueId);
checkNotNull(queue);
accessControlService.checkPermission(getCurrentUser(), Resource.QUEUE, operation, queueId, queue);
return queue;
}
protected <I extends EntityId> I emptyId(EntityType entityType) {
return (I) EntityIdFactory.getByTypeAndUuid(entityType, ModelConstants.NULL_UUID);
}

94
application/src/main/java/org/thingsboard/server/controller/QueueController.java

@ -20,17 +20,26 @@ import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.QueueId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.queue.Queue;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.queue.QueueService;
import org.thingsboard.server.queue.TbQueueService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
import java.util.Set;
import java.util.UUID;
import static org.thingsboard.server.controller.ControllerConstants.QUEUE_SERVICE_TYPE_ALLOWABLE_VALUES;
import static org.thingsboard.server.controller.ControllerConstants.QUEUE_SERVICE_TYPE_DESCRIPTION;
@ -42,7 +51,7 @@ import static org.thingsboard.server.controller.ControllerConstants.TENANT_AUTHO
@RequiredArgsConstructor
public class QueueController extends BaseController {
private final QueueService queueService;
private final TbQueueService tbQueueService;
@ApiOperation(value = "Get queue names (getTenantQueuesByServiceType)",
notes = "Returns a set of unique queue names based on service type. " + TENANT_AUTHORITY_PARAGRAPH)
@ -53,7 +62,86 @@ public class QueueController extends BaseController {
@RequestParam String serviceType) throws ThingsboardException {
checkParameter("serviceType", serviceType);
try {
return queueService.getQueuesByServiceType(ServiceType.valueOf(serviceType));
//TODO: replace for using new QueueService
return tbQueueService.getQueuesByServiceType(ServiceType.valueOf(serviceType));
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/tenant/queues", params = {"serviceType", "pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
public PageData<Queue> getTenantQueuesByServiceType(@RequestParam String serviceType,
@RequestParam int pageSize,
@RequestParam int page,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String sortProperty,
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
checkParameter("serviceType", serviceType);
try {
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
ServiceType type = ServiceType.valueOf(serviceType);
switch (type) {
case TB_RULE_ENGINE:
return queueService.findQueuesByTenantId(getTenantId(), pageLink);
default:
return new PageData<>();
}
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/tenant/queues/{queueId}", method = RequestMethod.GET)
@ResponseBody
public Queue getQueueById(@PathVariable("queueId") String queueIdStr) throws ThingsboardException {
checkParameter("queueId", queueIdStr);
try {
QueueId queueId = new QueueId(UUID.fromString(queueIdStr));
return checkNotNull(queueService.findQueueById(getTenantId(), queueId));
} catch (
Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/tenant/queues", params = {"serviceType"}, method = RequestMethod.POST)
@ResponseBody
public Queue saveQueue(@RequestBody Queue queue,
@RequestParam String serviceType) throws ThingsboardException {
checkParameter("serviceType", serviceType);
try {
queue.setTenantId(getCurrentUser().getTenantId());
checkEntity(queue.getId(), queue, Resource.QUEUE);
ServiceType type = ServiceType.valueOf(serviceType);
switch (type) {
case TB_RULE_ENGINE:
queue.setTenantId(getTenantId());
Queue savedQueue = queueService.saveQueue(queue);
checkNotNull(savedQueue);
return savedQueue;
default:
return null;
}
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/tenant/queues/{queueId}", method = RequestMethod.DELETE)
@ResponseBody
public void deleteQueue(@PathVariable("queueId") String queueIdStr) throws ThingsboardException {
checkParameter("queueId", queueIdStr);
try {
QueueId queueId = new QueueId(toUUID(queueIdStr));
checkQueueId(queueId, Operation.DELETE);
queueService.deleteQueue(getTenantId(), queueId);
} catch (Exception e) {
throw handleException(e);
}

3
application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java

@ -40,7 +40,8 @@ public enum Resource {
TB_RESOURCE(EntityType.TB_RESOURCE),
OTA_PACKAGE(EntityType.OTA_PACKAGE),
EDGE(EntityType.EDGE),
RPC(EntityType.RPC);
RPC(EntityType.RPC),
QUEUE(EntityType.QUEUE);
private final EntityType entityType;

1
application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java

@ -39,6 +39,7 @@ public class SysAdminPermissions extends AbstractPermissions {
put(Resource.OAUTH2_CONFIGURATION_TEMPLATE, PermissionChecker.allowAllPermissionChecker);
put(Resource.TENANT_PROFILE, PermissionChecker.allowAllPermissionChecker);
put(Resource.TB_RESOURCE, systemEntityPermissionChecker);
put(Resource.QUEUE, systemEntityPermissionChecker);
}
private static final PermissionChecker systemEntityPermissionChecker = new PermissionChecker() {

1
application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java

@ -45,6 +45,7 @@ public class TenantAdminPermissions extends AbstractPermissions {
put(Resource.OTA_PACKAGE, tenantEntityPermissionChecker);
put(Resource.EDGE, tenantEntityPermissionChecker);
put(Resource.RPC, tenantEntityPermissionChecker);
put(Resource.QUEUE, tenantEntityPermissionChecker);
}
public static final PermissionChecker tenantEntityPermissionChecker = new PermissionChecker() {

7
application/src/test/java/org/thingsboard/server/service/cluster/routing/HashPartitionServiceTest.java

@ -21,13 +21,12 @@ import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.test.util.ReflectionTestUtils;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.queue.QueueService;
import org.thingsboard.server.queue.TbQueueService;
import org.thingsboard.server.queue.discovery.HashPartitionService;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
@ -59,7 +58,7 @@ public class HashPartitionServiceTest {
private TenantRoutingInfoService routingInfoService;
private ApplicationEventPublisher applicationEventPublisher;
private TbQueueRuleEngineSettings ruleEngineSettings;
private QueueService queueService;
private TbQueueService queueService;
private String hashFunctionName = "sha256";
@ -70,7 +69,7 @@ public class HashPartitionServiceTest {
applicationEventPublisher = mock(ApplicationEventPublisher.class);
routingInfoService = mock(TenantRoutingInfoService.class);
ruleEngineSettings = mock(TbQueueRuleEngineSettings.class);
queueService = mock(QueueService.class);
queueService = mock(TbQueueService.class);
clusterRoutingService = new HashPartitionService(discoveryService,
routingInfoService,
applicationEventPublisher,

2
common/cluster-api/src/main/java/org/thingsboard/server/queue/TbQueueAdmin.java

@ -20,4 +20,6 @@ public interface TbQueueAdmin {
void createTopicIfNotExists(String topic);
void destroy();
default void deleteTopic(String topic) { }
}

2
common/cluster-api/src/main/java/org/thingsboard/server/queue/QueueService.java → common/cluster-api/src/main/java/org/thingsboard/server/queue/TbQueueService.java

@ -19,7 +19,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType;
import java.util.Set;
public interface QueueService {
public interface TbQueueService {
Set<String> getQueuesByServiceType(ServiceType serviceType);

48
common/dao-api/src/main/java/org/thingsboard/server/dao/queue/QueueService.java

@ -0,0 +1,48 @@
/**
* Copyright © 2016-2022 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.TenantProfile;
import org.thingsboard.server.common.data.id.QueueId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.queue.Queue;
import java.util.List;
public interface QueueService {
Queue saveQueue(Queue queue);
void deleteQueue(TenantId tenantId, QueueId queueId);
List<Queue> findQueuesByTenantId(TenantId tenantId);
PageData<Queue> findQueuesByTenantId(TenantId tenantId, PageLink pageLink);
List<Queue> findAllMainQueues();
List<Queue> findAllQueues();
Queue findQueueById(TenantId tenantId, QueueId queueId);
Queue findQueueByTenantIdAndName(TenantId tenantId, String name);
Queue createDefaultMainQueue(TenantProfile tenantProfile, TenantId tenantId);
void deleteQueuesByTenantId(TenantId tenantId);
}

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

@ -19,5 +19,5 @@ package org.thingsboard.server.common.data;
* @author Andrew Shvayka
*/
public enum EntityType {
TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW, WIDGETS_BUNDLE, WIDGET_TYPE, TENANT_PROFILE, DEVICE_PROFILE, API_USAGE_STATE, TB_RESOURCE, OTA_PACKAGE, EDGE, RPC;
TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW, WIDGETS_BUNDLE, WIDGET_TYPE, TENANT_PROFILE, DEVICE_PROFILE, API_USAGE_STATE, TB_RESOURCE, OTA_PACKAGE, EDGE, RPC, QUEUE;
}

6
common/data/src/main/java/org/thingsboard/server/common/data/TenantProfile.java

@ -57,7 +57,11 @@ public class TenantProfile extends SearchTextBased<TenantProfileId> implements H
@ApiModelProperty(position = 7, value = "If enabled, will push all messages related to this tenant and processed by the rule engine into separate queue. " +
"Useful for complex microservices deployments, to isolate processing of the data for specific tenants", example = "true")
private boolean isolatedTbRuleEngine;
@ApiModelProperty(position = 8, value = "Complex JSON object that contains profile settings: max devices, max assets, rate limits, etc.")
@ApiModelProperty(position = 8, value = "Max amount of queues configurable by isolated tenant.", example = "5")
private Integer maxNumberOfQueues;
@ApiModelProperty(position = 9, value = "Max amount of partitions per queue configurable by isolated tenant.", example = "10")
private Integer maxNumberOfPartitionsPerQueue;
@ApiModelProperty(position = 10, value = "Complex JSON object that contains profile settings: max devices, max assets, rate limits, etc.")
private transient TenantProfileData profileData;
@JsonIgnore
private byte[] profileDataBytes;

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

@ -77,6 +77,8 @@ public class EntityIdFactory {
return new EdgeId(uuid);
case RPC:
return new RpcId(uuid);
case QUEUE:
return new QueueId(uuid);
}
throw new IllegalArgumentException("EntityType " + type + " is not supported!");
}

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

@ -0,0 +1,43 @@
/**
* Copyright © 2016-2022 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.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.thingsboard.server.common.data.EntityType;
import java.util.UUID;
public class QueueId extends UUIDBased implements EntityId {
private static final long serialVersionUID = 1L;
@JsonCreator
public QueueId(@JsonProperty("id") UUID id) {
super(id);
}
public static QueueId fromString(String queueId) {
return new QueueId(UUID.fromString(queueId));
}
@JsonIgnore
@Override
public EntityType getEntityType() {
return EntityType.QUEUE;
}
}

27
common/data/src/main/java/org/thingsboard/server/common/data/queue/ProcessingStrategy.java

@ -0,0 +1,27 @@
/**
* Copyright © 2016-2022 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;
@Data
public class ProcessingStrategy {
private ProcessingStrategyType type;
private int retries;
private double failurePercentage;
private long pauseBetweenRetries;
private long maxPauseBetweenRetries;
}

36
common/data/src/main/java/org/thingsboard/server/common/data/queue/ProcessingStrategyType.java

@ -0,0 +1,36 @@
/**
* Copyright © 2016-2022 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.
*/
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.queue;
public enum ProcessingStrategyType {
SKIP_ALL_FAILURES, SKIP_ALL_FAILURES_AND_TIMED_OUT, RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT
}

42
common/data/src/main/java/org/thingsboard/server/common/data/queue/Queue.java

@ -0,0 +1,42 @@
/**
* Copyright © 2016-2022 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 org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.id.QueueId;
import org.thingsboard.server.common.data.id.TenantId;
@Data
public class Queue extends BaseData<QueueId> implements HasName, HasTenantId {
private TenantId tenantId;
private String name;
private String topic;
private int pollInterval;
private int partitions;
private long packProcessingTimeout;
private SubmitStrategy submitStrategy;
private ProcessingStrategy processingStrategy;
public Queue() {
}
public Queue(QueueId id) {
super(id);
}
}

24
common/data/src/main/java/org/thingsboard/server/common/data/queue/SubmitStrategy.java

@ -0,0 +1,24 @@
/**
* Copyright © 2016-2022 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;
@Data
public class SubmitStrategy {
private SubmitStrategyType type;
private int batchSize;
}

20
common/data/src/main/java/org/thingsboard/server/common/data/queue/SubmitStrategyType.java

@ -0,0 +1,20 @@
/**
* Copyright © 2016-2022 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;
public enum SubmitStrategyType {
BURST, BATCH, SEQUENTIAL_BY_ORIGINATOR, SEQUENTIAL_BY_TENANT, SEQUENTIAL
}

2
common/queue/src/main/java/org/thingsboard/server/queue/DefaultQueueService.java → common/queue/src/main/java/org/thingsboard/server/queue/DefaultTbQueueService.java

@ -31,7 +31,7 @@ import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class DefaultQueueService implements QueueService {
public class DefaultTbQueueService implements TbQueueService {
private final TbQueueRuleEngineSettings ruleEngineSettings;
private Set<String> ruleEngineQueues;

8
common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java

@ -16,13 +16,11 @@
package org.thingsboard.server.queue.discovery;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.queue.ServiceQueue;
@ -31,7 +29,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.ServiceInfo;
import org.thingsboard.server.queue.QueueService;
import org.thingsboard.server.queue.TbQueueService;
import org.thingsboard.server.queue.discovery.event.ClusterTopologyChangeEvent;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.queue.discovery.event.ServiceListChangedEvent;
@ -66,7 +64,7 @@ public class HashPartitionService implements PartitionService {
private final TbServiceInfoProvider serviceInfoProvider;
private final TenantRoutingInfoService tenantRoutingInfoService;
private final TbQueueRuleEngineSettings tbQueueRuleEngineSettings;
private final QueueService queueService;
private final TbQueueService queueService;
private final ConcurrentMap<ServiceQueue, String> partitionTopics = new ConcurrentHashMap<>();
private final ConcurrentMap<ServiceQueue, Integer> partitionSizes = new ConcurrentHashMap<>();
private final ConcurrentMap<TenantId, TenantRoutingInfo> tenantRoutingInfoMap = new ConcurrentHashMap<>();
@ -85,7 +83,7 @@ public class HashPartitionService implements PartitionService {
TenantRoutingInfoService tenantRoutingInfoService,
ApplicationEventPublisher applicationEventPublisher,
TbQueueRuleEngineSettings tbQueueRuleEngineSettings,
QueueService queueService) {
TbQueueService queueService) {
this.serviceInfoProvider = serviceInfoProvider;
this.tenantRoutingInfoService = tenantRoutingInfoService;
this.applicationEventPublisher = applicationEventPublisher;

4
dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java

@ -82,7 +82,7 @@ import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator;
import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.queue.QueueService;
import org.thingsboard.server.queue.TbQueueService;
import java.util.Arrays;
import java.util.Collections;
@ -115,7 +115,7 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D
}
@Autowired(required = false)
private QueueService queueService;
private TbQueueService queueService;
@Autowired
private DeviceProfileDao deviceProfileDao;

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

@ -128,6 +128,8 @@ public class ModelConstants {
public static final String TENANT_PROFILE_IS_DEFAULT_PROPERTY = "is_default";
public static final String TENANT_PROFILE_ISOLATED_TB_CORE = "isolated_tb_core";
public static final String TENANT_PROFILE_ISOLATED_TB_RULE_ENGINE = "isolated_tb_rule_engine";
public static final String TENANT_PROFILE_MAX_NUMBER_OF_QUEUES = "max_number_of_queues";
public static final String TENANT_PROFILE_MAX_NUMBER_OF_PARTITIONS_PER_QUEUE = "max_number_of_partitions_per_queue";
/**
* Cassandra customer constants.
@ -581,6 +583,20 @@ public class ModelConstants {
public static final String DOUBLE_VALUE_COLUMN = "dbl_v";
public static final String JSON_VALUE_COLUMN = "json_v";
/**
* Queue constants.
*/
public static final String QUEUE_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
public static final String QUEUE_NAME_PROPERTY = "name";
public static final String QUEUE_TOPIC_PROPERTY = "topic";
public static final String QUEUE_POLL_INTERVAL_PROPERTY = "poll_interval";
public static final String QUEUE_PARTITIONS_PROPERTY = "partitions";
public static final String QUEUE_PACK_PROCESSING_TIMEOUT_PROPERTY = "pack_processing_timeout";
public static final String QUEUE_SUBMIT_STRATEGY_PROPERTY = "submit_strategy";
public static final String QUEUE_PROCESSING_STRATEGY_PROPERTY = "processing_strategy";
public static final String QUEUE_COLUMN_FAMILY_NAME = "queue";
protected static final String[] NONE_AGGREGATION_COLUMNS = new String[]{LONG_VALUE_COLUMN, DOUBLE_VALUE_COLUMN, BOOLEAN_VALUE_COLUMN, STRING_VALUE_COLUMN, JSON_VALUE_COLUMN, KEY_COLUMN, TS_COLUMN};
protected static final String[] COUNT_AGGREGATION_COLUMNS = new String[]{count(LONG_VALUE_COLUMN), count(DOUBLE_VALUE_COLUMN), count(BOOLEAN_VALUE_COLUMN), count(STRING_VALUE_COLUMN), count(JSON_VALUE_COLUMN)};

105
dao/src/main/java/org/thingsboard/server/dao/model/sql/QueueEntity.java

@ -0,0 +1,105 @@
/**
* Copyright © 2016-2022 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 com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
import org.thingsboard.server.common.data.id.QueueId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.queue.ProcessingStrategy;
import org.thingsboard.server.common.data.queue.Queue;
import org.thingsboard.server.common.data.queue.SubmitStrategy;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.model.BaseSqlEntity;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.util.mapping.JsonStringType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import java.util.UUID;
@Data
@EqualsAndHashCode(callSuper = true)
@Entity
@TypeDef(name = "json", typeClass = JsonStringType.class)
@Table(name = ModelConstants.QUEUE_COLUMN_FAMILY_NAME)
public class QueueEntity extends BaseSqlEntity<Queue> {
private static final ObjectMapper mapper = new ObjectMapper();
@Column(name = ModelConstants.QUEUE_TENANT_ID_PROPERTY)
private UUID tenantId;
@Column(name = ModelConstants.QUEUE_NAME_PROPERTY)
private String name;
@Column(name = ModelConstants.QUEUE_TOPIC_PROPERTY)
private String topic;
@Column(name = ModelConstants.QUEUE_POLL_INTERVAL_PROPERTY)
private int pollInterval;
@Column(name = ModelConstants.QUEUE_PARTITIONS_PROPERTY)
private int partitions;
@Column(name = ModelConstants.QUEUE_PACK_PROCESSING_TIMEOUT_PROPERTY)
private long packProcessingTimeout;
@Type(type = "json")
@Column(name = ModelConstants.QUEUE_SUBMIT_STRATEGY_PROPERTY)
private JsonNode submitStrategy;
@Type(type = "json")
@Column(name = ModelConstants.QUEUE_PROCESSING_STRATEGY_PROPERTY)
private JsonNode processingStrategy;
public QueueEntity() {
}
public QueueEntity(Queue queue) {
if (queue.getId() != null) {
this.setId(queue.getId().getId());
}
this.createdTime = queue.getCreatedTime();
this.tenantId = DaoUtil.getId(queue.getTenantId());
this.name = queue.getName();
this.topic = queue.getTopic();
this.pollInterval = queue.getPollInterval();
this.partitions = queue.getPartitions();
this.packProcessingTimeout = queue.getPackProcessingTimeout();
this.submitStrategy = mapper.valueToTree(queue.getSubmitStrategy());
this.processingStrategy = mapper.valueToTree(queue.getProcessingStrategy());
}
@Override
public Queue toData() {
Queue queue = new Queue(new QueueId(getUuid()));
queue.setCreatedTime(createdTime);
queue.setTenantId(new TenantId(tenantId));
queue.setName(name);
queue.setTopic(topic);
queue.setPollInterval(pollInterval);
queue.setPartitions(partitions);
queue.setPackProcessingTimeout(packProcessingTimeout);
queue.setSubmitStrategy(mapper.convertValue(this.submitStrategy, SubmitStrategy.class));
queue.setProcessingStrategy(mapper.convertValue(this.processingStrategy, ProcessingStrategy.class));
return queue;
}
}

12
dao/src/main/java/org/thingsboard/server/dao/model/sql/TenantProfileEntity.java

@ -21,13 +21,13 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.id.TenantProfileId;
import org.thingsboard.server.common.data.tenant.profile.TenantProfileData;
import org.thingsboard.server.dao.model.BaseSqlEntity;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.model.SearchTextEntity;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.dao.util.mapping.JsonBinaryType;
import javax.persistence.Column;
@ -59,6 +59,12 @@ public final class TenantProfileEntity extends BaseSqlEntity<TenantProfile> impl
@Column(name = ModelConstants.TENANT_PROFILE_ISOLATED_TB_RULE_ENGINE)
private boolean isolatedTbRuleEngine;
@Column(name = ModelConstants.TENANT_PROFILE_MAX_NUMBER_OF_QUEUES)
private Integer maxNumberOfQueues;
@Column(name = ModelConstants.TENANT_PROFILE_MAX_NUMBER_OF_PARTITIONS_PER_QUEUE)
private Integer maxNumberOfPartitionsPerQueue;
@Type(type = "jsonb")
@Column(name = ModelConstants.TENANT_PROFILE_PROFILE_DATA_PROPERTY, columnDefinition = "jsonb")
private JsonNode profileData;
@ -77,6 +83,8 @@ public final class TenantProfileEntity extends BaseSqlEntity<TenantProfile> impl
this.isDefault = tenantProfile.isDefault();
this.isolatedTbCore = tenantProfile.isIsolatedTbCore();
this.isolatedTbRuleEngine = tenantProfile.isIsolatedTbRuleEngine();
this.maxNumberOfPartitionsPerQueue = tenantProfile.getMaxNumberOfPartitionsPerQueue();
this.maxNumberOfQueues = tenantProfile.getMaxNumberOfQueues();
this.profileData = JacksonUtil.convertValue(tenantProfile.getProfileData(), ObjectNode.class);
}
@ -103,6 +111,8 @@ public final class TenantProfileEntity extends BaseSqlEntity<TenantProfile> impl
tenantProfile.setDefault(isDefault);
tenantProfile.setIsolatedTbCore(isolatedTbCore);
tenantProfile.setIsolatedTbRuleEngine(isolatedTbRuleEngine);
tenantProfile.setMaxNumberOfPartitionsPerQueue(maxNumberOfPartitionsPerQueue);
tenantProfile.setMaxNumberOfQueues(maxNumberOfQueues);
tenantProfile.setProfileData(JacksonUtil.convertValue(profileData, TenantProfileData.class));
return tenantProfile;
}

333
dao/src/main/java/org/thingsboard/server/dao/queue/BaseQueueService.java

@ -0,0 +1,333 @@
/**
* Copyright © 2016-2022 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.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.id.QueueId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.queue.ProcessingStrategy;
import org.thingsboard.server.common.data.queue.ProcessingStrategyType;
import org.thingsboard.server.common.data.queue.Queue;
import org.thingsboard.server.common.data.queue.SubmitStrategy;
import org.thingsboard.server.common.data.queue.SubmitStrategyType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.queue.TbQueueAdmin;
import java.util.List;
@Service
@Slf4j
@RequiredArgsConstructor
public class BaseQueueService extends AbstractEntityService implements QueueService {
@Autowired
private QueueDao queueDao;
@Autowired
private TenantDao tenantDao;
@Autowired
private TbTenantProfileCache tenantProfileCache;
@Autowired(required = false)
private TbQueueAdmin tbQueueAdmin;
// @Autowired(required = false)
// private TbQueueClusterService queueClusterService;
// @Autowired
// private QueueStatsService queueStatsService;
@Override
@Transactional
public Queue saveQueue(Queue queue) {
log.trace("Executing createOrUpdateQueue [{}]", queue);
queueValidator.validate(queue, Queue::getTenantId);
Queue savedQueue;
if (queue.getId() == null) {
savedQueue = createQueue(queue);
} else {
savedQueue = updateQueue(queue);
}
// if (queueClusterService != null) {
// queueClusterService.onQueueChange(savedQueue, null);
// }
return savedQueue;
}
private Queue createQueue(Queue queue) {
Queue createdQueue = queueDao.save(queue.getTenantId(), queue);
if (tbQueueAdmin != null) {
for (int i = 0; i < queue.getPartitions(); i++) {
tbQueueAdmin.createTopicIfNotExists(new TopicPartitionInfo(queue.getTopic(), queue.getTenantId(), i, false).getFullTopicName());
}
}
return createdQueue;
}
private Queue updateQueue(Queue queue) {
Queue oldQueue = queueDao.findById(queue.getTenantId(), queue.getUuidId());
Queue updatedQueue = queueDao.save(queue.getTenantId(), queue);
int oldPartitions = oldQueue.getPartitions();
int currentPartitions = queue.getPartitions();
//TODO: 3.2 remove if partitions can't be deleted.
// if (currentPartitions != oldPartitions && tbQueueAdmin != null) {
// queueClusterService.onQueueDelete(queue, null);
// if (currentPartitions > oldPartitions) {
// log.info("Added [{}] new partitions to [{}] queue", currentPartitions - oldPartitions, queue.getName());
// for (int i = oldPartitions; i < currentPartitions; i++) {
// tbQueueAdmin.createTopicIfNotExists(new TopicPartitionInfo(queue.getTopic(), queue.getTenantId(), i, false).getFullTopicName());
// }
// } else {
// log.info("Removed [{}] partitions from [{}] queue", oldPartitions - currentPartitions, queue.getName());
// for (int i = currentPartitions; i < oldPartitions; i++) {
// tbQueueAdmin.deleteTopic(new TopicPartitionInfo(queue.getTopic(), queue.getTenantId(), i, false).getFullTopicName());
// }
// }
// }
return updatedQueue;
}
@Override
@Transactional
public void deleteQueue(TenantId tenantId, QueueId queueId) {
log.trace("Executing deleteQueue, queueId: [{}]", queueId);
Queue queue = findQueueById(tenantId, queueId);
// if (queueClusterService != null) {
// queueClusterService.onQueueDelete(queue, null);
// }
// queueStatsService.deleteQueueStatsByQueueId(tenantId, queueId);
boolean result = queueDao.removeById(tenantId, queueId.getId());
if (result && tbQueueAdmin != null) {
for (int i = 0; i < queue.getPartitions(); i++) {
String fullTopicName = new TopicPartitionInfo(queue.getTopic(), queue.getTenantId(), i, false).getFullTopicName();
log.debug("Deleting queue [{}]", fullTopicName);
try {
tbQueueAdmin.deleteTopic(fullTopicName);
} catch (Exception e) {
log.error("Failed to delete queue [{}]", fullTopicName);
}
}
}
}
@Override
public List<Queue> findQueuesByTenantId(TenantId tenantId) {
log.trace("Executing findQueues, tenantId: [{}]", tenantId);
return queueDao.findAllByTenantId(getSystemOrIsolatedTenantId(tenantId));
}
@Override
public PageData<Queue> findQueuesByTenantId(TenantId tenantId, PageLink pageLink) {
log.trace("Executing findQueues pageLink [{}]", pageLink);
Validator.validatePageLink(pageLink);
return queueDao.findQueuesByTenantId(getSystemOrIsolatedTenantId(tenantId), pageLink);
}
@Override
public List<Queue> findAllMainQueues() {
log.trace("Executing findAllMainQueues");
return queueDao.findAllMainQueues();
}
@Override
public List<Queue> findAllQueues() {
log.trace("Executing findAllQueues");
return queueDao.findAllQueues();
}
@Override
public Queue findQueueById(TenantId tenantId, QueueId queueId) {
log.trace("Executing findQueueById, queueId: [{}]", queueId);
return queueDao.findById(tenantId, queueId.getId());
}
@Override
public Queue findQueueByTenantIdAndName(TenantId tenantId, String queueName) {
log.trace("Executing findQueueByTenantIdAndName, tenantId: [{}] queueName: [{}]", tenantId, queueName);
return queueDao.findQueueByTenantIdAndName(getSystemOrIsolatedTenantId(tenantId), queueName);
}
@Override
public void deleteQueuesByTenantId(TenantId tenantId) {
Validator.validateId(tenantId, "Incorrect tenant id for delete queues request.");
tenantQueuesRemover.removeEntities(tenantId, tenantId);
}
@Override
@Transactional
public Queue createDefaultMainQueue(TenantProfile tenantProfile, TenantId tenantId) {
Queue mainQueue = new Queue();
mainQueue.setTenantId(tenantId);
mainQueue.setName("Main");
mainQueue.setTopic("tb_rule_engine.main");
mainQueue.setPollInterval(25);
mainQueue.setPartitions(Math.max(tenantProfile.getMaxNumberOfPartitionsPerQueue(), 1));
mainQueue.setPackProcessingTimeout(60000);
SubmitStrategy mainQueueSubmitStrategy = new SubmitStrategy();
mainQueueSubmitStrategy.setType(SubmitStrategyType.BURST);
mainQueueSubmitStrategy.setBatchSize(1000);
mainQueue.setSubmitStrategy(mainQueueSubmitStrategy);
ProcessingStrategy mainQueueProcessingStrategy = new ProcessingStrategy();
mainQueueProcessingStrategy.setType(ProcessingStrategyType.SKIP_ALL_FAILURES);
mainQueueProcessingStrategy.setRetries(3);
mainQueueProcessingStrategy.setFailurePercentage(0);
mainQueueProcessingStrategy.setPauseBetweenRetries(3);
mainQueueProcessingStrategy.setMaxPauseBetweenRetries(3);
mainQueue.setProcessingStrategy(mainQueueProcessingStrategy);
return saveQueue(mainQueue);
}
private DataValidator<Queue> queueValidator =
new DataValidator<>() {
@Override
protected void validateCreate(TenantId tenantId, Queue queue) {
if (queueDao.findQueueByTenantIdAndTopic(tenantId, queue.getTopic()) != null) {
throw new DataValidationException(String.format("Queue with topic: %s already exists!", queue.getTopic()));
}
if (queueDao.findQueueByTenantIdAndName(tenantId, queue.getName()) != null) {
throw new DataValidationException(String.format("Queue with name: %s already exists!", queue.getName()));
}
}
@Override
protected void validateUpdate(TenantId tenantId, Queue queue) {
Queue foundQueue = queueDao.findById(tenantId, queue.getUuidId());
if (queueDao.findById(tenantId, queue.getUuidId()) == null) {
throw new DataValidationException(String.format("Queue with id: %s does not exists!", queue.getId()));
}
if (!foundQueue.getName().equals(queue.getName())) {
throw new DataValidationException("Queue name can't be changed!");
}
if (!foundQueue.getTopic().equals(queue.getTopic())) {
throw new DataValidationException("Queue topic can't be changed!");
}
}
@Override
protected void validateDataImpl(TenantId tenantId, Queue queue) {
if (!tenantId.equals(TenantId.SYS_TENANT_ID)) {
TenantProfile tenantProfile = tenantProfileCache.get(tenantId);
if (!tenantProfile.isIsolatedTbRuleEngine()) {
throw new DataValidationException("Tenant should be isolated!");
}
if (queue.getId() == null) {
List<Queue> existingQueues = findQueuesByTenantId(tenantId);
if (existingQueues.size() >= tenantProfile.getMaxNumberOfQueues()) {
throw new DataValidationException("The limit for creating new queue has been exceeded!");
}
}
if (queue.getPartitions() > tenantProfile.getMaxNumberOfPartitionsPerQueue()) {
throw new DataValidationException(String.format("Queue partitions can't be more then %d", tenantProfile.getMaxNumberOfPartitionsPerQueue()));
}
}
if (StringUtils.isEmpty(queue.getName())) {
throw new DataValidationException("Queue name should be specified!");
}
if (StringUtils.isBlank(queue.getTopic())) {
throw new DataValidationException("Queue topic should be non empty and without spaces!");
}
if (queue.getPollInterval() < 1) {
throw new DataValidationException("Queue poll interval should be more then 0!");
}
if (queue.getPartitions() < 1) {
throw new DataValidationException("Queue partitions should be more then 0!");
}
if (queue.getPackProcessingTimeout() < 1) {
throw new DataValidationException("Queue pack processing timeout should be more then 0!");
}
SubmitStrategy submitStrategy = queue.getSubmitStrategy();
if (submitStrategy == null) {
throw new DataValidationException("Queue submit strategy can't be null!");
}
if (submitStrategy.getType() == null) {
throw new DataValidationException("Queue submit strategy type can't be null!");
}
if (submitStrategy.getType() == SubmitStrategyType.BATCH && submitStrategy.getBatchSize() < 1) {
throw new DataValidationException("Queue submit strategy batch size should be more then 0!");
}
ProcessingStrategy processingStrategy = queue.getProcessingStrategy();
if (processingStrategy == null) {
throw new DataValidationException("Queue processing strategy can't be null!");
}
if (processingStrategy.getType() == null) {
throw new DataValidationException("Queue processing strategy type can't be null!");
}
if (processingStrategy.getRetries() < 0) {
throw new DataValidationException("Queue processing strategy retries can't be less then 0!");
}
if (processingStrategy.getFailurePercentage() < 0 || processingStrategy.getFailurePercentage() > 100) {
throw new DataValidationException("Queue processing strategy failure percentage should be in a range from 0 to 100!");
}
if (processingStrategy.getPauseBetweenRetries() < 0) {
throw new DataValidationException("Queue processing strategy pause between retries can't be less then 0!");
}
if (processingStrategy.getMaxPauseBetweenRetries() < processingStrategy.getPauseBetweenRetries()) {
throw new DataValidationException("Queue processing strategy MAX pause between retries can't be less then pause between retries!");
}
}
};
private PaginatedRemover<TenantId, Queue> tenantQueuesRemover =
new PaginatedRemover<>() {
@Override
protected PageData<Queue> findEntities(TenantId tenantId, TenantId id, PageLink pageLink) {
return queueDao.findQueuesByTenantId(id, pageLink);
}
@Override
protected void removeEntity(TenantId tenantId, Queue entity) {
deleteQueue(tenantId, entity.getId());
}
};
private TenantId getSystemOrIsolatedTenantId(TenantId tenantId) {
if (!tenantId.equals(TenantId.SYS_TENANT_ID)) {
TenantProfile tenantProfile = tenantProfileCache.get(tenantId);
if (tenantProfile.isIsolatedTbRuleEngine()) {
return tenantId;
}
}
return TenantId.SYS_TENANT_ID;
}
}

38
dao/src/main/java/org/thingsboard/server/dao/queue/QueueDao.java

@ -0,0 +1,38 @@
/**
* Copyright © 2016-2022 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.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.queue.Queue;
import org.thingsboard.server.dao.Dao;
import java.util.List;
public interface QueueDao extends Dao<Queue> {
Queue findQueueByTenantIdAndTopic(TenantId tenantId, String topic);
Queue findQueueByTenantIdAndName(TenantId tenantId, String name);
List<Queue> findAllMainQueues();
List<Queue> findAllQueues();
List<Queue> findAllByTenantId(TenantId tenantId);
PageData<Queue> findQueuesByTenantId(TenantId tenantId, PageLink pageLink);
}

86
dao/src/main/java/org/thingsboard/server/dao/sql/queue/JpaQueueDao.java

@ -0,0 +1,86 @@
/**
* Copyright © 2016-2022 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 com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.queue.Queue;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.model.sql.QueueEntity;
import org.thingsboard.server.dao.queue.QueueDao;
import org.thingsboard.server.dao.sql.JpaAbstractDao;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
@Slf4j
@Component
public class JpaQueueDao extends JpaAbstractDao<QueueEntity, Queue> implements QueueDao {
@Autowired
private QueueRepository queueRepository;
@Override
protected Class<QueueEntity> getEntityClass() {
return QueueEntity.class;
}
@Override
protected CrudRepository<QueueEntity, UUID> getCrudRepository() {
return queueRepository;
}
@Override
public Queue findQueueByTenantIdAndTopic(TenantId tenantId, String topic) {
return DaoUtil.getData(queueRepository.findByTenantIdAndTopic(tenantId.getId(), topic));
}
@Override
public Queue findQueueByTenantIdAndName(TenantId tenantId, String name) {
return DaoUtil.getData(queueRepository.findByTenantIdAndName(tenantId.getId(), name));
}
@Override
public List<Queue> findAllByTenantId(TenantId tenantId) {
List<QueueEntity> entities = queueRepository.findByTenantId(tenantId.getId());
return DaoUtil.convertDataList(entities);
}
@Override
public List<Queue> findAllMainQueues() {
List<QueueEntity> entities = Lists.newArrayList(queueRepository.findAllByName("Main"));
return DaoUtil.convertDataList(entities);
}
@Override
public List<Queue> findAllQueues() {
List<QueueEntity> entities = Lists.newArrayList(queueRepository.findAll());
return DaoUtil.convertDataList(entities);
}
@Override
public PageData<Queue> findQueuesByTenantId(TenantId tenantId, PageLink pageLink) {
return DaoUtil.toPageData(queueRepository
.findByTenantId(tenantId.getId(), Objects.toString(pageLink.getTextSearch(), ""), DaoUtil.toPageable(pageLink)));
}
}

42
dao/src/main/java/org/thingsboard/server/dao/sql/queue/QueueRepository.java

@ -0,0 +1,42 @@
/**
* Copyright © 2016-2022 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.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.thingsboard.server.dao.model.sql.QueueEntity;
import java.util.List;
import java.util.UUID;
public interface QueueRepository extends CrudRepository<QueueEntity, UUID> {
QueueEntity findByTenantIdAndTopic(UUID tenantId, String topic);
QueueEntity findByTenantIdAndName(UUID tenantId, String name);
List<QueueEntity> findByTenantId(UUID tenantId);
@Query("SELECT q FROM QueueEntity q WHERE q.tenantId = :tenantId " +
"AND LOWER(q.name) LIKE LOWER(CONCAT(:textSearch, '%'))")
Page<QueueEntity> findByTenantId(@Param("tenantId") UUID tenantId,
@Param("textSearch") String textSearch,
Pageable pageable);
List<QueueEntity> findAllByName(String name);
}

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

@ -37,6 +37,7 @@ import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.queue.QueueService;
import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.rpc.RpcService;
import org.thingsboard.server.dao.rule.RuleChainService;
@ -104,6 +105,9 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe
@Autowired
private RpcService rpcService;
@Autowired
private QueueService queueService;
@Override
public Tenant findTenantById(TenantId tenantId) {
log.trace("Executing findTenantById [{}]", tenantId);
@ -160,6 +164,7 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe
resourceService.deleteResourcesByTenantId(tenantId);
otaPackageService.deleteOtaPackagesByTenantId(tenantId);
rpcService.deleteAllRpcByTenantId(tenantId);
queueService.deleteQueuesByTenantId(tenantId);
tenantDao.removeById(tenantId, tenantId.getId());
deleteEntityRelations(tenantId, tenantId);
}

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

@ -337,6 +337,8 @@ CREATE TABLE IF NOT EXISTS tenant_profile (
is_default boolean,
isolated_tb_core boolean,
isolated_tb_rule_engine boolean,
max_number_of_queues int ,
max_number_of_partitions_per_queue int,
CONSTRAINT tenant_profile_name_unq_key UNIQUE (name)
);
@ -632,6 +634,19 @@ CREATE TABLE IF NOT EXISTS rpc (
status varchar(255) NOT NULL
);
CREATE TABLE IF NOT EXISTS queue(
id uuid NOT NULL CONSTRAINT queue_pkey PRIMARY KEY,
created_time bigint NOT NULL,
tenant_id uuid,
name varchar(255),
topic varchar(255),
poll_interval int,
partitions int,
pack_processing_timeout bigint,
submit_strategy varchar(255),
processing_strategy varchar(255)
);
CREATE OR REPLACE PROCEDURE cleanup_events_by_ttl(IN ttl bigint, IN debug_ttl bigint, INOUT deleted bigint)
LANGUAGE plpgsql AS
$$

11
ui-ngx/src/app/app.component.ts

@ -78,6 +78,17 @@ export class AppComponent implements OnInit {
)
);
this.matIconRegistry.addSvgIconLiteral(
'queues-list',
this.domSanitizer.bypassSecurityTrustHtml(
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">' +
'<path fill="#fff" d="M9 4V2H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h5v-2H4V4h5z"/>' +
'<path fill="#fff" d="M7 18V6h2v12H7zM11 6v12h2V6h-2zM15 20v2h5a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2h-5v2h5v16h-5z"/>' +
'<path fill="#fff" d="M15 18V6h2v12h-2z"/>' +
'</svg>'
)
);
this.storageService.testLocalStorage();
this.setupTranslate();

25
ui-ngx/src/app/core/http/queue.service.ts

@ -18,7 +18,9 @@ import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils';
import { Observable } from 'rxjs';
import { ServiceType } from '@shared/models/queue.models';
import { QueueInfo, ServiceType } from '@shared/models/queue.models';
import { PageLink } from '@shared/models/page/page-link';
import { PageData } from '@shared/models/page/page-data';
@Injectable({
providedIn: 'root'
@ -29,8 +31,27 @@ export class QueueService {
private http: HttpClient
) { }
public getTenantQueuesByServiceType(serviceType: ServiceType, config?: RequestConfig): Observable<Array<string>> {
public getTenantQueuesNamesByServiceType(serviceType: ServiceType, config?: RequestConfig): Observable<Array<string>> {
return this.http.get<Array<string>>(`/api/tenant/queues?serviceType=${serviceType}`,
defaultHttpOptionsFromConfig(config));
}
public getQueueById(queueId: string): Observable<QueueInfo> {
return this.http.get<QueueInfo>(`/api/tenant/queues/${queueId}`);
}
public getTenantQueuesByServiceType(pageLink: PageLink,
serviceType: ServiceType,
config?: RequestConfig): Observable<PageData<QueueInfo>> {
return this.http.get<PageData<QueueInfo>>(`/api/tenant/queues${pageLink.toQuery()}&serviceType=${serviceType}`,
defaultHttpOptionsFromConfig(config));
}
public saveQueue(queue: QueueInfo, serviceType: ServiceType, config?: RequestConfig): Observable<QueueInfo> {
return this.http.post<QueueInfo>(`/api/tenant/queues?serviceType=${serviceType}`, queue, defaultHttpOptionsFromConfig(config));
}
public deleteQueue(queueId: string) {
return this.http.delete(`/api/tenant/queues/${queueId}`);
}
}

18
ui-ngx/src/app/core/services/menu.service.ts

@ -108,7 +108,7 @@ export class MenuService {
name: 'admin.system-settings',
type: 'toggle',
path: '/settings',
height: '240px',
height: '280px',
icon: 'settings',
pages: [
{
@ -152,7 +152,14 @@ export class MenuService {
type: 'link',
path: '/settings/resources-library',
icon: 'folder'
}
},
{
id: guid(),
name: 'admin.queues',
type: 'link',
path: '/settings/queues',
icon: 'swap_calls'
},
]
}
);
@ -220,7 +227,12 @@ export class MenuService {
name: 'resource.resources-library',
icon: 'folder',
path: '/settings/resources-library'
}
},
{
name: 'admin.queues',
icon: 'swap_calls',
path: '/settings/queues'
},
]
}
);

30
ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.html

@ -67,6 +67,36 @@
<div>{{ 'tenant.isolated-tb-rule-engine' | translate }}</div>
<div class="tb-hint">{{'tenant.isolated-tb-rule-engine-details' | translate}}</div>
</mat-checkbox>
<mat-form-field class="mat-block" *ngIf="showQueueParams()">
<mat-label translate>tenant.max-number-of-queues</mat-label>
<input matInput
type="number"
step="1"
min="1"
required
formControlName="maxNumberOfQueues"/>
<mat-error *ngIf="entityForm.get('maxNumberOfQueues').hasError('required')">
{{ 'tenant.max-number-of-queues-required' | translate }}
</mat-error>
<mat-error *ngIf="entityForm.get('maxNumberOfQueues').hasError('min')">
{{ 'tenant.max-number-of-queues-min-length' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block" *ngIf="showQueueParams()">
<mat-label translate>tenant.max-number-of-partitions-per-queue</mat-label>
<input matInput
type="number"
step="1"
min="1"
required
formControlName="maxNumberOfPartitionsPerQueue"/>
<mat-error *ngIf="entityForm.get('maxNumberOfPartitionsPerQueue').hasError('required')">
{{ 'tenant.max-number-of-partitions-per-queue-required' | translate }}
</mat-error>
<mat-error *ngIf="entityForm.get('maxNumberOfPartitionsPerQueue').hasError('min')">
{{ 'tenant.max-number-of-partitions-per-queue-min-length' | translate }}
</mat-error>
</mat-form-field>
</div>
<tb-tenant-profile-data
formControlName="profileData"

18
ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.ts

@ -62,6 +62,8 @@ export class TenantProfileComponent extends EntityComponent<TenantProfile> {
name: [entity ? entity.name : '', [Validators.required, Validators.maxLength(255)]],
isolatedTbCore: [entity ? entity.isolatedTbCore : false, []],
isolatedTbRuleEngine: [entity ? entity.isolatedTbRuleEngine : false, []],
maxNumberOfQueues: [entity ? entity.maxNumberOfQueues : 1, [Validators.required, Validators.min(1)]],
maxNumberOfPartitionsPerQueue: [entity ? entity.maxNumberOfPartitionsPerQueue : 1, [Validators.required, Validators.min(1)]],
profileData: [entity && !this.isAdd ? entity.profileData : {
configuration: createTenantProfileConfiguration(TenantProfileType.DEFAULT)
} as TenantProfileData, []],
@ -74,12 +76,26 @@ export class TenantProfileComponent extends EntityComponent<TenantProfile> {
this.entityForm.patchValue({name: entity.name});
this.entityForm.patchValue({isolatedTbCore: entity.isolatedTbCore});
this.entityForm.patchValue({isolatedTbRuleEngine: entity.isolatedTbRuleEngine});
this.entityForm.patchValue({maxNumberOfQueues: entity.maxNumberOfQueues});
this.entityForm.patchValue({maxNumberOfPartitionsPerQueue: entity.maxNumberOfPartitionsPerQueue});
this.entityForm.patchValue({profileData: !this.isAdd ? entity.profileData : {
configuration: createTenantProfileConfiguration(TenantProfileType.DEFAULT)
} as TenantProfileData});
this.entityForm.patchValue({description: entity.description});
}
showQueueParams(): boolean {
let isolatedTbRuleEngine: boolean = this.entityForm.get('isolatedTbRuleEngine').value;
if (isolatedTbRuleEngine) {
this.entityForm.controls['maxNumberOfQueues'].enable();
this.entityForm.controls['maxNumberOfPartitionsPerQueue'].enable();
} else {
this.entityForm.controls['maxNumberOfQueues'].disable();
this.entityForm.controls['maxNumberOfPartitionsPerQueue'].disable();
}
return isolatedTbRuleEngine;
}
updateFormState() {
if (this.entityForm) {
if (this.isEditValue) {
@ -87,6 +103,8 @@ export class TenantProfileComponent extends EntityComponent<TenantProfile> {
if (!this.isAdd) {
this.entityForm.get('isolatedTbCore').disable({emitEvent: false});
this.entityForm.get('isolatedTbRuleEngine').disable({emitEvent: false});
this.entityForm.get('maxNumberOfQueues').disable({emitEvent: false});
this.entityForm.get('maxNumberOfPartitionsPerQueue').disable({emitEvent: false});
}
} else {
this.entityForm.disable({emitEvent: false});

20
ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts

@ -32,6 +32,7 @@ import { ResourcesLibraryTableConfigResolver } from '@home/pages/admin/resource/
import { EntityDetailsPageComponent } from '@home/components/entity/entity-details-page.component';
import { entityDetailsPageBreadcrumbLabelFunction } from '@home/pages/home-pages.models';
import { BreadCrumbConfig } from '@shared/components/breadcrumb';
import { QueuesTableConfigResolver } from '@home/pages/admin/queue/queues-table-config.resolver';
@Injectable()
export class OAuth2LoginProcessingUrlResolver implements Resolve<string> {
@ -183,6 +184,22 @@ const routes: Routes = [
}
}
]
},
{
path: 'queues',
component: EntitiesTableComponent,
canDeactivate: [ConfirmOnExitGuard],
data: {
auth: [Authority.SYS_ADMIN],
title: 'admin.queues',
breadcrumb: {
label: 'admin.queues',
icon: 'swap_calls'
}
},
resolve: {
entitiesTableConfig: QueuesTableConfigResolver
}
}
]
}
@ -193,7 +210,8 @@ const routes: Routes = [
exports: [RouterModule],
providers: [
OAuth2LoginProcessingUrlResolver,
ResourcesLibraryTableConfigResolver
ResourcesLibraryTableConfigResolver,
QueuesTableConfigResolver
]
})
export class AdminRoutingModule { }

4
ui-ngx/src/app/modules/home/pages/admin/admin.module.ts

@ -28,6 +28,7 @@ import { SmsProviderComponent } from '@home/pages/admin/sms-provider.component';
import { SendTestSmsDialogComponent } from '@home/pages/admin/send-test-sms-dialog.component';
import { HomeSettingsComponent } from '@home/pages/admin/home-settings.component';
import { ResourcesLibraryComponent } from '@home/pages/admin/resource/resources-library.component';
import { QueueComponent} from '@home/pages/admin/queue/queue.component';
@NgModule({
declarations:
@ -39,7 +40,8 @@ import { ResourcesLibraryComponent } from '@home/pages/admin/resource/resources-
SecuritySettingsComponent,
OAuth2SettingsComponent,
HomeSettingsComponent,
ResourcesLibraryComponent
ResourcesLibraryComponent,
QueueComponent
],
imports: [
CommonModule,

182
ui-ngx/src/app/modules/home/pages/admin/queue/queue.component.html

@ -0,0 +1,182 @@
<!--
Copyright © 2016-2022 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.
-->
<div class="tb-details-buttons" fxLayout.xs="column">
<button mat-raised-button color="primary" fxFlex.xs
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'delete')"
[fxShow]="!hideDelete() && !isEdit">
{{'queue.delete' | translate }}
</button>
</div>
<div class="mat-padding" fxLayout="column">
<form [formGroup]="entityForm">
<fieldset [disabled]="(isLoading$ | async) || !isEdit">
<mat-form-field class="mat-block">
<mat-label translate>admin.queue-name</mat-label>
<input matInput formControlName="name" required>
<mat-error *ngIf="entityForm.get('name').hasError('required')">
{{ 'queue.name-required' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>queue.poll-interval</mat-label>
<input type="number" matInput formControlName="pollInterval" required>
<mat-error *ngIf="entityForm.get('pollInterval').hasError('required')">
{{ 'queue.poll-interval-required' | translate }}
</mat-error>
<mat-error *ngIf="entityForm.get('pollInterval').hasError('min') &&
!entityForm.get('pollInterval').hasError('required')">
{{ 'queue.poll-interval-min-value' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>queue.partitions</mat-label>
<input type="number" matInput formControlName="partitions" required>
<mat-error *ngIf="entityForm.get('partitions').hasError('required')">
{{ 'queue.partitions-required' | translate }}
</mat-error>
<mat-error *ngIf="entityForm.get('partitions').hasError('min') &&
!entityForm.get('partitions').hasError('required')">
{{ 'queue.partitions-min-value' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>queue.processing-timeout</mat-label>
<input type="number" matInput formControlName="packProcessingTimeout" required>
<mat-error *ngIf="entityForm.get('packProcessingTimeout').hasError('required')">
{{ 'queue.pack-processing-timeout-required' | translate }}
</mat-error>
<mat-error *ngIf="entityForm.get('packProcessingTimeout').hasError('min') &&
!entityForm.get('packProcessingTimeout').hasError('required')">
{{ 'queue.pack-processing-timeout-min-value' | translate }}
</mat-error>
</mat-form-field>
<div class="mat-accordion-container">
<mat-accordion [multi]="true">
<mat-expansion-panel #panel1 hideToggle>
<mat-expansion-panel-header>
<mat-panel-title>
<mat-label translate>queue.submit-strategy</mat-label>
<mat-icon>{{panel1.expanded ? 'keyboard_arrow_up' : 'keyboard_arrow_down' }}</mat-icon>
</mat-panel-title>
</mat-expansion-panel-header>
<div formGroupName="submitStrategy">
<mat-form-field class="mat-block">
<mat-label translate>queue.submit-strategy</mat-label>
<mat-select formControlName="type" required>
<mat-option *ngFor="let strategy of submitStrategies" [value]="strategy">
{{ strategy }}
</mat-option>
</mat-select>
<mat-error *ngIf="entityForm.get('submitStrategy.type').hasError('required')">
{{ 'queue.submit-strategy-type-required' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block" *ngIf="hideBatchSize">
<mat-label translate>queue.batch-size</mat-label>
<input type="number" matInput formControlName="batchSize" required>
<mat-error *ngIf="entityForm.get('submitStrategy.batchSize').hasError('required')">
{{ 'queue.batch-size-required' | translate }}
</mat-error>
<mat-error *ngIf="entityForm.get('submitStrategy.batchSize').hasError('min') &&
!entityForm.get('submitStrategy.batchSize').hasError('required')">
{{ 'queue.batch-size-min-value' | translate }}
</mat-error>
</mat-form-field>
</div>
</mat-expansion-panel>
<mat-expansion-panel #panel2 hideToggle>
<mat-expansion-panel-header>
<mat-panel-title>
<mat-label translate>queue.processing-strategy</mat-label>
<mat-icon>{{panel2.expanded ? 'keyboard_arrow_up' : 'keyboard_arrow_down' }}</mat-icon>
</mat-panel-title>
</mat-expansion-panel-header>
<div formGroupName="processingStrategy">
<mat-form-field class="mat-block">
<mat-label translate>queue.processing-strategy</mat-label>
<mat-select formControlName="type" required>
<mat-option *ngFor="let strategy of processingStrategies" [value]="strategy">
{{ strategy }}
</mat-option>
</mat-select>
<mat-error *ngIf="entityForm.get('processingStrategy.type').hasError('required')">
{{ 'queue.processing-strategy-type-required' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>queue.retries</mat-label>
<input type="number" matInput formControlName="retries" required>
<mat-error *ngIf="entityForm.get('processingStrategy.retries').hasError('required')">
{{ 'queue.retries-required' | translate }}
</mat-error>
<mat-error *ngIf="entityForm.get('processingStrategy.retries').hasError('min') &&
!entityForm.get('processingStrategy.retries').hasError('required')">
{{ 'queue.retries-min-value' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>queue.failure-percentage</mat-label>
<input type="number" matInput formControlName="failurePercentage" required>
<mat-error *ngIf="entityForm.get('processingStrategy.failurePercentage').hasError('required')">
{{ 'queue.failure-percentage-required' | translate }}
</mat-error>
<mat-error *ngIf="entityForm.get('processingStrategy.failurePercentage').hasError('min') &&
!entityForm.get('processingStrategy.failurePercentage').hasError('required') &&
!entityForm.get('processingStrategy.failurePercentage').hasError('max')">
{{ 'queue.failure-percentage-min-value' | translate }}
</mat-error>
<mat-error *ngIf="entityForm.get('processingStrategy.failurePercentage').hasError('max') &&
!entityForm.get('processingStrategy.failurePercentage').hasError('required') &&
!entityForm.get('processingStrategy.failurePercentage').hasError('min')">
{{ 'queue.failure-percentage-max-value' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>queue.pause-between-retries</mat-label>
<input type="number" matInput formControlName="pauseBetweenRetries" required>
<mat-error *ngIf="entityForm.get('processingStrategy.pauseBetweenRetries').hasError('required')">
{{ 'queue.pause-between-retries-required' | translate }}
</mat-error>
<mat-error *ngIf="entityForm.get('processingStrategy.pauseBetweenRetries').hasError('min') &&
!entityForm.get('processingStrategy.pauseBetweenRetries').hasError('required')">
{{ 'queue.pause-between-retries-min-value' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>queue.max-pause-between-retries</mat-label>
<input type="number" matInput formControlName="maxPauseBetweenRetries" required>
<mat-error *ngIf="entityForm.get('processingStrategy.maxPauseBetweenRetries').hasError('required')">
{{ 'queue.max-pause-between-retries-required' | translate }}
</mat-error>
<mat-error *ngIf="entityForm.get('processingStrategy.maxPauseBetweenRetries').hasError('min') &&
!entityForm.get('processingStrategy.maxPauseBetweenRetries').hasError('required')">
{{ 'queue.max-pause-between-retries-min-value' | translate }}
</mat-error>
</mat-form-field>
</div>
</mat-expansion-panel>
</mat-accordion>
</div>
</fieldset>
</form>
</div>

59
ui-ngx/src/app/modules/home/pages/admin/queue/queue.component.scss

@ -0,0 +1,59 @@
/**
* Copyright © 2016-2022 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.
*/
:host ::ng-deep {
.mat-expansion-panel:not([class*='mat-elevation-z']) {
box-shadow: none;
}
.mat-accordion-container {
margin-bottom: 16px;
}
.mat-expansion-panel {
&:hover {
box-shadow: 0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 5px 5px -3px rgba(0, 0, 0, 0.2);
}
&-header {
height: 56px;
border: 1px solid #f2f2f2;
&-title {
align-items: center;
justify-content: space-between;
margin-right: 0;
}
}
&.mat-expanded {
border: none;
box-shadow: 0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 5px 5px -3px rgba(0, 0, 0, 0.2);
.mat-expansion-panel {
&-content {
margin-top: 20px;
}
&-body {
padding-bottom: 10px;
}
}
}
}
}

154
ui-ngx/src/app/modules/home/pages/admin/queue/queue.component.ts

@ -0,0 +1,154 @@
///
/// Copyright © 2016-2022 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.
///
import { ChangeDetectorRef, Component, Inject } from '@angular/core';
import { EntityType } from '@shared/models/entity-type.models';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { EntityComponent } from '@home/components/entity/entity.component';
import { QueueInfo, QueueProcessingStrategyTypes, QueueSubmitStrategyTypes } from '@shared/models/queue.models';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { TranslateService } from '@ngx-translate/core';
import { EntityTableConfig } from '@home/models/entity/entities-table-config.models';
import set = Reflect.set;
import { distinctUntilChanged } from 'rxjs/operators';
@Component({
selector: 'tb-queue',
templateUrl: './queue.component.html',
styleUrls: ['./queue.component.scss']
})
export class QueueComponent extends EntityComponent<QueueInfo> {
entityForm: FormGroup;
entityType = EntityType;
submitStrategies: string[] = [];
processingStrategies: string[] = [];
QueueSubmitStrategyTypes = QueueSubmitStrategyTypes;
hideBatchSize: boolean = false;
constructor(protected store: Store<AppState>,
protected translate: TranslateService,
@Inject('entity') protected entityValue: QueueInfo,
@Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig<QueueInfo>,
protected cd: ChangeDetectorRef,
public fb: FormBuilder) {
super(store, fb, entityValue, entitiesTableConfigValue, cd);
this.submitStrategies = Object.values(QueueSubmitStrategyTypes);
this.processingStrategies = Object.values(QueueProcessingStrategyTypes);
}
ngOnInit() {
super.ngOnInit();
this.entityForm.get('submitStrategy').get('type').valueChanges.subscribe(() => {
this.submitStrategyTypeChanged();
});
}
buildForm(entity: QueueInfo): FormGroup {
return this.fb.group(
{
name: [entity ? entity.name : '', [Validators.required]],
pollInterval: [
entity && entity.pollInterval ? entity.pollInterval : 25,
[Validators.min(1), Validators.required]
],
partitions: [
entity && entity.partitions ? entity.partitions : 10,
[Validators.min(1), Validators.required]
],
packProcessingTimeout: [
entity && entity.packProcessingTimeout ? entity.packProcessingTimeout : 2000,
[Validators.min(1), Validators.required]
],
submitStrategy: this.fb.group({
type: [entity ? entity.submitStrategy?.type : null, [Validators.required]],
batchSize: [
entity && entity.submitStrategy?.batchSize ? entity.submitStrategy?.batchSize : 1000,
[Validators.min(1), Validators.required]
],
}),
processingStrategy: this.fb.group({
type: [entity ? entity.processingStrategy?.type : null, [Validators.required]],
retries: [
entity && entity.processingStrategy?.retries ? entity.processingStrategy?.retries : 3,
[Validators.min(0), Validators.required]
],
failurePercentage: [
entity && entity.processingStrategy?.failurePercentage ? entity.processingStrategy?.failurePercentage : 0,
[Validators.min(0), Validators.required, Validators.max(100)]
],
pauseBetweenRetries: [
entity && entity.processingStrategy?.pauseBetweenRetries ? entity.processingStrategy?.pauseBetweenRetries : 3,
[Validators.min(1), Validators.required]
],
maxPauseBetweenRetries: [
entity && entity.processingStrategy?.maxPauseBetweenRetries ? entity.processingStrategy?.maxPauseBetweenRetries : 3,
[Validators.min(1), Validators.required]
],
})
}
);
}
hideDelete() {
if (this.entitiesTableConfig) {
return !this.entitiesTableConfig.deleteEnabled(this.entity);
} else {
return false;
}
}
updateForm(entity: QueueInfo) {
this.entityForm.patchValue({
name: entity.name,
pollInterval: entity.pollInterval,
partitions: entity.partitions,
packProcessingTimeout: entity.packProcessingTimeout,
submitStrategy: {
type: entity.submitStrategy?.type,
batchSize: entity.submitStrategy?.batchSize,
},
processingStrategy: {
type: entity.processingStrategy?.type,
retries: entity.processingStrategy?.retries,
failurePercentage: entity.processingStrategy?.failurePercentage,
pauseBetweenRetries: entity.processingStrategy?.pauseBetweenRetries,
maxPauseBetweenRetries: entity.processingStrategy?.maxPauseBetweenRetries,
}
}, {emitEvent: true});
if (!this.isAdd) {
this.entityForm.get('name').disable({emitEvent: false});
}
}
submitStrategyTypeChanged() {
const form = this.entityForm.get("submitStrategy") as FormGroup;
const type: QueueSubmitStrategyTypes = form.get('type').value;
const batchSizeField = form.get('batchSize');
if (type === QueueSubmitStrategyTypes.BATCH) {
batchSizeField.enable();
batchSizeField.patchValue(1000);
this.hideBatchSize = true;
} else {
batchSizeField.patchValue(null);
batchSizeField.disable();
this.hideBatchSize = false;
}
}
}

115
ui-ngx/src/app/modules/home/pages/admin/queue/queues-table-config.resolver.ts

@ -0,0 +1,115 @@
///
/// Copyright © 2016-2022 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.
///
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve } from '@angular/router';
import {
EntityTableColumn,
EntityTableConfig
} from '@home/models/entity/entities-table-config.models';
import { QueueInfo, ServiceType } from '@shared/models/queue.models';
import { select, Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { BroadcastService } from '@core/services/broadcast.service';
import { CustomerService } from '@core/http/customer.service';
import { DialogService } from '@core/services/dialog.service';
import { HomeDialogsService } from '@home/dialogs/home-dialogs.service';
import { map, mergeMap, take } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { EntityType, entityTypeResources, entityTypeTranslations } from '@app/shared/models/entity-type.models';
import { TranslateService } from '@ngx-translate/core';
import { QueueComponent } from './queue.component';
import { QueueService } from '@core/http/queue.service';
import { selectAuthUser } from '@core/auth/auth.selectors';
@Injectable()
export class QueuesTableConfigResolver implements Resolve<EntityTableConfig<QueueInfo>> {
readonly queueType = ServiceType.TB_RULE_ENGINE;
private readonly config: EntityTableConfig<QueueInfo> = new EntityTableConfig<QueueInfo>();
constructor(private store: Store<AppState>,
private broadcast: BroadcastService,
private queueService: QueueService,
private customerService: CustomerService,
private dialogService: DialogService,
private homeDialogs: HomeDialogsService,
private translate: TranslateService) {
this.config.entityType = EntityType.QUEUE;
this.config.entityComponent = QueueComponent;
this.config.entityTranslations = entityTypeTranslations.get(EntityType.QUEUE);
this.config.entityResources = entityTypeResources.get(EntityType.QUEUE);
this.config.deleteEntityTitle = queue => this.translate.instant('queue.delete-queue-title', {queueName: queue.name});
this.config.deleteEntityContent = () => this.translate.instant('queue.delete-queue-text');
this.config.deleteEntitiesTitle = count => this.translate.instant('queue.delete-queues-title', {count});
this.config.deleteEntitiesContent = () => this.translate.instant('queue.delete-queues-text');
}
resolve(route: ActivatedRouteSnapshot): Observable<EntityTableConfig<QueueInfo>> {
this.config.componentsData = {
queueType: this.queueType
};
return this.store.pipe(select(selectAuthUser), take(1)).pipe(
map(() => {
this.config.tableTitle = this.translate.instant('admin.queues');
this.config.columns = this.configureColumns();
this.configureEntityFunctions();
return this.config;
})
);
}
configureColumns(): Array<EntityTableColumn<QueueInfo>> {
return [
new EntityTableColumn<QueueInfo>('name', 'admin.queue-name', '25%'),
new EntityTableColumn<QueueInfo>('partitions', 'admin.queue-partitions', '25%'),
new EntityTableColumn<QueueInfo>('submitStrategy', 'admin.queue-submit-strategy', '25%',
(entity: QueueInfo) => {
return entity.submitStrategy.type;
},
() => ({}),
false
),
new EntityTableColumn<QueueInfo>('processingStrategy', 'admin.queue-processing-strategy', '25%',
(entity: QueueInfo) => {
return entity.processingStrategy.type;
},
() => ({}),
false
)
];
}
configureEntityFunctions(): void {
this.config.entitiesFetchFunction = pageLink => this.queueService.getTenantQueuesByServiceType(pageLink, this.queueType);
this.config.loadEntity = id => this.queueService.getQueueById(id.id);
this.config.saveEntity = queue => this.queueService.saveQueue(this.addTopicForQueue(queue), this.queueType).pipe(
mergeMap((savedQueue) => this.queueService.getQueueById(savedQueue.id.id)
));
this.config.deleteEntity = id => this.queueService.deleteQueue(id.id);
this.config.deleteEnabled = (queue) => queue && queue.name !== 'Main';
}
private addTopicForQueue(queue: QueueInfo): QueueInfo {
const modifiedQueue = Object.assign({}, queue);
modifiedQueue.topic = `tb_rule_engine.${queue.name}`;
return modifiedQueue;
}
}

4
ui-ngx/src/app/shared/components/queue/queue-type-list.component.html

@ -17,7 +17,7 @@
-->
<mat-form-field [formGroup]="queueFormGroup" class="mat-block">
<mat-label>{{ 'queue.name' | translate }}</mat-label>
<input matInput type="text" placeholder="{{ 'queue.select_name' | translate }}"
<input matInput type="text" placeholder="{{ 'queue.select-name' | translate }}"
#queueInput
formControlName="queue"
(focusin)="onFocus()"
@ -43,7 +43,7 @@
</mat-option>
</mat-autocomplete>
<mat-error *ngIf="queueFormGroup.get('queue').hasError('required')">
{{ 'queue.name_required' | translate }}
{{ 'queue.name-required' | translate }}
</mat-error>
<mat-hint class="tb-hint" translate [fxShow]="!disabled">device-profile.select-queue-hint</mat-hint>
</mat-form-field>

2
ui-ngx/src/app/shared/components/queue/queue-type-list.component.ts

@ -175,7 +175,7 @@ export class QueueTypeListComponent implements ControlValueAccessor, OnInit, Aft
getQueues(): Observable<Array<Queue>> {
if (!this.queues) {
this.queues = this.queueService.
getTenantQueuesByServiceType(this.queueType, {ignoreLoading: true}).pipe(
getTenantQueuesNamesByServiceType(this.queueType, {ignoreLoading: true}).pipe(
map((queues) => {
return queues.map((queueName) => {
return { queueName };

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

@ -133,7 +133,8 @@ export const HelpLinks = {
widgetsConfigAlarm: helpBaseUrl + '/docs/user-guide/ui/dashboards#alarm',
widgetsConfigStatic: helpBaseUrl + '/docs/user-guide/ui/dashboards#static',
ruleNodePushToCloud: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/action-nodes/#push-to-cloud',
ruleNodePushToEdge: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/action-nodes/#push-to-edge'
ruleNodePushToEdge: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/action-nodes/#push-to-edge',
queue: helpBaseUrl + '/docs/user-guide/queue'
}
};

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

@ -36,7 +36,8 @@ export enum EntityType {
API_USAGE_STATE = 'API_USAGE_STATE',
TB_RESOURCE = 'TB_RESOURCE',
OTA_PACKAGE = 'OTA_PACKAGE',
RPC = 'RPC'
RPC = 'RPC',
QUEUE = 'QUEUE'
}
export enum AliasEntityType {
@ -306,6 +307,15 @@ export const entityTypeTranslations = new Map<EntityType | AliasEntityType, Enti
search: 'ota-update.search',
selectedEntities: 'ota-update.selected-package'
}
],
[
EntityType.QUEUE,
{
add: 'queue.add',
search: 'queue.search',
details: 'queue.details',
selectedEntities: 'queue.selected-queues'
}
]
]
);
@ -395,6 +405,12 @@ export const entityTypeResources = new Map<EntityType, EntityTypeResource<BaseDa
{
helpLinkId: 'otaUpdates'
}
],
[
EntityType.QUEUE,
{
helpLinkId: 'queue'
}
]
]
);

26
ui-ngx/src/app/shared/models/id/queue-id.ts

@ -0,0 +1,26 @@
///
/// Copyright © 2016-2022 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.
///
import { EntityId } from './entity-id';
import { EntityType } from '@shared/models/entity-type.models';
export class QueueId implements EntityId {
entityType = EntityType.QUEUE;
id: string;
constructor(id: string) {
this.id = id;
}
}

40
ui-ngx/src/app/shared/models/queue.models.ts

@ -14,9 +14,49 @@
/// limitations under the License.
///
import { BaseData, HasId } from '@shared/models/base-data';
import { TenantId } from '@shared/models/id/tenant-id';
import {QueueId} from "@shared/models/id/queue-id";
export enum ServiceType {
TB_CORE = 'TB_CORE',
TB_RULE_ENGINE = 'TB_RULE_ENGINE',
TB_TRANSPORT = 'TB_TRANSPORT',
JS_EXECUTOR = 'JS_EXECUTOR'
}
export enum QueueSubmitStrategyTypes {
SEQUENTIAL_BY_ORIGINATOR = 'SEQUENTIAL_BY_ORIGINATOR',
SEQUENTIAL_BY_TENANT = 'SEQUENTIAL_BY_TENANT',
SEQUENTIAL = 'SEQUENTIAL',
BURST = 'BURST',
BATCH = 'BATCH'
}
export enum QueueProcessingStrategyTypes {
RETRY_FAILED_AND_TIMED_OUT = 'RETRY_FAILED_AND_TIMED_OUT',
SKIP_ALL_FAILURES = 'SKIP_ALL_FAILURES',
SKIP_ALL_FAILURES_AND_TIMED_OUT = 'SKIP_ALL_FAILURES_AND_TIMED_OUT',
RETRY_ALL = 'RETRY_ALL',
RETRY_FAILED = 'RETRY_FAILED',
RETRY_TIMED_OUT = 'RETRY_TIMED_OUT'
}
export interface QueueInfo extends BaseData<QueueId> {
packProcessingTimeout: number;
partitions: number;
pollInterval: number;
processingStrategy: {
type: QueueProcessingStrategyTypes,
retries: number,
failurePercentage: number,
pauseBetweenRetries: number,
maxPauseBetweenRetries: number
};
submitStrategy: {
type: QueueSubmitStrategyTypes,
batchSize: number,
};
tenantId: TenantId;
topic: string;
}

2
ui-ngx/src/app/shared/models/tenant.model.ts

@ -106,6 +106,8 @@ export interface TenantProfile extends BaseData<TenantProfileId> {
default?: boolean;
isolatedTbCore?: boolean;
isolatedTbRuleEngine?: boolean;
maxNumberOfQueues?: number;
maxNumberOfPartitionsPerQueue?: number;
profileData?: TenantProfileData;
}

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

@ -252,7 +252,14 @@
"platform-ios": "iOS",
"all-platforms": "All platforms",
"allowed-platforms": "Allowed platforms"
}
},
"queue-select-name": "Select queue name",
"queue-name": "Name",
"queue-name-required": "Queue name is required!",
"queues": "Queues",
"queue-partitions": "Partitions",
"queue-submit-strategy": "Submit strategy",
"queue-processing-strategy": "Processing strategy"
},
"alarm": {
"alarm": "Alarm",
@ -2670,10 +2677,50 @@
"browser-time": "Browser Time"
},
"queue": {
"select_name": "Select queue name",
"name": "Queue Name",
"name_required": "Queue name is required!",
"no-queues-matching": "No queues matching '{{queue}}' were found."
"select-name": "Select queue name",
"name": "Name",
"name-required": "Queue name is required!",
"topic-required": "Queue topic is required!",
"poll-interval-required": "Poll interval is required!",
"poll-interval-min-value": "Poll interval value can't be less then 1",
"partitions-required": "Partitions is required!",
"partitions-min-value": "Partitions value can't be less then 1",
"pack-processing-timeout-required": "Processing timeout is required",
"pack-processing-timeout-min-value": "Processing timeout value can't be less then 1",
"batch-size-required": "Batch size is required!",
"batch-size-min-value": "Batch size value can't be less then 1",
"retries-required": "Retries is required!",
"retries-min-value": "Retries value can't be negative",
"failure-percentage-required": "Failure percentage is required!",
"failure-percentage-min-value": "Failure percentage value can't be less then 0",
"failure-percentage-max-value": "Failure percentage value can't be more then 100",
"pause-between-retries-required": "Pause between retries is required!",
"pause-between-retries-min-value": "Pause between retries value can't be less then 1",
"max-pause-between-retries-required": "Max pause between retries is required!",
"max-pause-between-retries-min-value": "Max pause between retries value can't be less then 1",
"submit-strategy-type-required": "Submit strategy type is required!",
"processing-strategy-type-required": "Processing strategy type is required!",
"queues": "Queues",
"selected-queues": "{ count, plural, 1 {1 queue} other {# queues} } selected",
"delete-queue-title": "Are you sure you want to delete the queue '{{queueName}}'?",
"delete-queues-title": "Are you sure you want to delete { count, plural, 1 {1 queue} other {# queues} }?",
"delete-queue-text": "Be careful, after the confirmation the queue and all related data will become unrecoverable.",
"delete-queues-text": "After the confirmation all selected queues will be deleted and won't be accessible.",
"search": "Search queue",
"add" : "Add queue",
"details": "Queue details",
"topic": "Topic",
"submit-strategy": "Submit Strategy",
"processing-strategy": "Processing Strategy",
"poll-interval": "Poll interval",
"partitions": "Partitions",
"processing-timeout": "Processing timeout",
"batch-size": "Batch size",
"retries": "Retries (0 - unlimited)",
"failure-percentage": "Failure Percentage",
"pause-between-retries": "Pause between retries",
"max-pause-between-retries": "Maximal pause between retries",
"delete": "Delete queue"
},
"tenant": {
"tenant": "Tenant",
@ -2707,7 +2754,13 @@
"isolated-tb-core": "Processing in isolated ThingsBoard Core container",
"isolated-tb-rule-engine": "Processing in isolated ThingsBoard Rule Engine container",
"isolated-tb-core-details": "Requires separate microservice(s) per isolated Tenant",
"isolated-tb-rule-engine-details": "Requires separate microservice(s) per isolated Tenant"
"isolated-tb-rule-engine-details": "Requires separate microservice(s) per isolated Tenant",
"max-number-of-queues": "Max number of ThingsBoard Rule Engine queues",
"max-number-of-queues-required": "Max number of queues is required",
"max-number-of-queues-min-length": "Max number of queues can't be less then 1",
"max-number-of-partitions-per-queue": "Max number of partitions per ThingsBoard Rule Engine queue",
"max-number-of-partitions-per-queue-required": "Max number of partitions per queue is required",
"max-number-of-partitions-per-queue-min-length": "Max number of partitions per queue can't be less then 1"
},
"tenant-profile": {
"tenant-profile": "Tenant profile",

Loading…
Cancel
Save