Browse Source

Merge branch 'develop/3.4' of github.com:thingsboard/thingsboard into js-timeout-fix

pull/6905/head
Volodymyr Babak 4 years ago
parent
commit
e7521d737a
  1. 13
      application/src/main/java/org/thingsboard/server/controller/DashboardController.java
  2. 20
      application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DefaultTbDashboardService.java
  3. 16
      application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPermissions.java
  4. 20
      application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java
  5. 2
      application/src/test/java/org/thingsboard/server/controller/BaseTenantControllerTest.java
  6. 62
      application/src/test/java/org/thingsboard/server/queue/discovery/HashPartitionServiceTest.java
  7. 2
      common/queue/src/main/java/org/thingsboard/server/queue/common/AbstractTbQueueTemplate.java
  8. 6
      common/queue/src/main/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplate.java
  9. 4
      common/queue/src/main/java/org/thingsboard/server/queue/common/DefaultTbQueueResponseTemplate.java
  10. 17
      common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java
  11. 2
      dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java
  12. 26
      msa/js-executor/api/jsInvokeMessageProcessor.js
  13. 6
      msa/js-executor/queue/kafkaTemplate.js
  14. 3
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbAbstractTransformNode.java
  15. 1
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java
  16. 2
      ui-ngx/angular.json
  17. 56
      ui-ngx/package.json
  18. 2
      ui-ngx/src/app/modules/home/components/widget/dynamic-widget.component.ts
  19. 2
      ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts
  20. 1
      ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/google-map.ts
  21. 1
      ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/here-map.ts
  22. 2
      ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts
  23. 1
      ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/openstreet-map.ts
  24. 1
      ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/tencent-map.ts
  25. 3
      ui-ngx/src/app/modules/home/components/widget/lib/settings/map/common-map-settings.component.html
  26. 1
      ui-ngx/src/app/modules/home/components/widget/lib/settings/map/common-map-settings.component.ts
  27. 2
      ui-ngx/src/app/modules/home/models/services.map.ts
  28. 2
      ui-ngx/src/app/modules/home/models/widget-component.models.ts
  29. 3
      ui-ngx/src/app/shared/components/marked-options.service.ts
  30. 1
      ui-ngx/src/app/shared/components/phone-input.component.html
  31. 44
      ui-ngx/src/app/shared/components/phone-input.component.ts
  32. 1
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  33. 4197
      ui-ngx/yarn.lock

13
application/src/main/java/org/thingsboard/server/controller/DashboardController.java

@ -261,7 +261,7 @@ public class DashboardController extends BaseController {
checkParameter(DASHBOARD_ID, strDashboardId);
DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
Dashboard dashboard = checkDashboardId(dashboardId, Operation.ASSIGN_TO_CUSTOMER);
Set<CustomerId> customerIds = customerIdFromStr(strCustomerIds, dashboard);
Set<CustomerId> customerIds = customerIdFromStr(strCustomerIds);
return tbDashboardService.updateDashboardCustomers(dashboard, customerIds, getCurrentUser());
}
@ -281,7 +281,7 @@ public class DashboardController extends BaseController {
checkParameter(DASHBOARD_ID, strDashboardId);
DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
Dashboard dashboard = checkDashboardId(dashboardId, Operation.ASSIGN_TO_CUSTOMER);
Set<CustomerId> customerIds = customerIdFromStr(strCustomerIds, dashboard);
Set<CustomerId> customerIds = customerIdFromStr(strCustomerIds);
return tbDashboardService.addDashboardCustomers(dashboard, customerIds, getCurrentUser());
}
@ -301,7 +301,7 @@ public class DashboardController extends BaseController {
checkParameter(DASHBOARD_ID, strDashboardId);
DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
Dashboard dashboard = checkDashboardId(dashboardId, Operation.UNASSIGN_FROM_CUSTOMER);
Set<CustomerId> customerIds = customerIdFromStr(strCustomerIds, dashboard);
Set<CustomerId> customerIds = customerIdFromStr(strCustomerIds);
return tbDashboardService.removeDashboardCustomers(dashboard, customerIds, getCurrentUser());
}
@ -704,14 +704,11 @@ public class DashboardController extends BaseController {
}
}
private Set<CustomerId> customerIdFromStr(String[] strCustomerIds, Dashboard dashboard) {
private Set<CustomerId> customerIdFromStr(String[] strCustomerIds) {
Set<CustomerId> customerIds = new HashSet<>();
if (strCustomerIds != null) {
for (String strCustomerId : strCustomerIds) {
CustomerId customerId = new CustomerId(UUID.fromString(strCustomerId));
if (dashboard.isAssignedToCustomer(customerId)) {
customerIds.add(customerId);
}
customerIds.add(new CustomerId(UUID.fromString(strCustomerId)));
}
}
return customerIds;

20
application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DefaultTbDashboardService.java

@ -183,11 +183,17 @@ public class DefaultTbDashboardService extends AbstractTbEntityService implement
TenantId tenantId = dashboard.getTenantId();
DashboardId dashboardId = dashboard.getId();
try {
if (customerIds.isEmpty()) {
Set<CustomerId> addedCustomerIds = new HashSet<>();
for (CustomerId customerId : customerIds) {
if (!dashboard.isAssignedToCustomer(customerId)) {
addedCustomerIds.add(customerId);
}
}
if (addedCustomerIds.isEmpty()) {
return dashboard;
} else {
Dashboard savedDashboard = null;
for (CustomerId customerId : customerIds) {
for (CustomerId customerId : addedCustomerIds) {
savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(tenantId, dashboardId, customerId));
ShortCustomerInfo customerInfo = savedDashboard.getAssignedCustomerInfo(customerId);
notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, dashboardId, customerId, savedDashboard,
@ -207,11 +213,17 @@ public class DefaultTbDashboardService extends AbstractTbEntityService implement
TenantId tenantId = dashboard.getTenantId();
DashboardId dashboardId = dashboard.getId();
try {
if (customerIds.isEmpty()) {
Set<CustomerId> removedCustomerIds = new HashSet<>();
for (CustomerId customerId : customerIds) {
if (dashboard.isAssignedToCustomer(customerId)) {
removedCustomerIds.add(customerId);
}
}
if (removedCustomerIds.isEmpty()) {
return dashboard;
} else {
Dashboard savedDashboard = null;
for (CustomerId customerId : customerIds) {
for (CustomerId customerId : removedCustomerIds) {
ShortCustomerInfo customerInfo = dashboard.getAssignedCustomerInfo(customerId);
savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(tenantId, dashboardId, customerId));
notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, dashboardId, customerId, savedDashboard,

16
application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPermissions.java

@ -42,6 +42,7 @@ public class CustomerUserPermissions extends AbstractPermissions {
put(Resource.WIDGET_TYPE, widgetsPermissionChecker);
put(Resource.EDGE, customerEntityPermissionChecker);
put(Resource.RPC, rpcPermissionChecker);
put(Resource.DEVICE_PROFILE, deviceProfilePermissionChecker);
}
private static final PermissionChecker customerAlarmPermissionChecker = new PermissionChecker() {
@ -152,4 +153,19 @@ public class CustomerUserPermissions extends AbstractPermissions {
return user.getTenantId().equals(entity.getTenantId());
}
};
private static final PermissionChecker deviceProfilePermissionChecker = new PermissionChecker.GenericPermissionChecker(Operation.READ) {
@Override
@SuppressWarnings("unchecked")
public boolean hasPermission(SecurityUser user, Operation operation, EntityId entityId, HasTenantId entity) {
if (!super.hasPermission(user, operation, entityId, entity)) {
return false;
}
if (entity.getTenantId() == null || entity.getTenantId().isNullUid()) {
return true;
}
return user.getTenantId().equals(entity.getTenantId());
}
};
}

20
application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java

@ -28,6 +28,7 @@ import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileInfo;
@ -170,6 +171,25 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController
Assert.assertEquals(savedDeviceProfile.getId(), foundDeviceProfileInfo.getId());
Assert.assertEquals(savedDeviceProfile.getName(), foundDeviceProfileInfo.getName());
Assert.assertEquals(savedDeviceProfile.getType(), foundDeviceProfileInfo.getType());
Customer customer = new Customer();
customer.setTitle("Customer");
customer.setTenantId(savedTenant.getId());
Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
User customerUser = new User();
customerUser.setAuthority(Authority.CUSTOMER_USER);
customerUser.setTenantId(savedTenant.getId());
customerUser.setCustomerId(savedCustomer.getId());
customerUser.setEmail("customer2@thingsboard.org");
createUserAndLogin(customerUser, "customer");
foundDeviceProfileInfo = doGet("/api/deviceProfileInfo/" + savedDeviceProfile.getId().getId().toString(), DeviceProfileInfo.class);
Assert.assertNotNull(foundDeviceProfileInfo);
Assert.assertEquals(savedDeviceProfile.getId(), foundDeviceProfileInfo.getId());
Assert.assertEquals(savedDeviceProfile.getName(), foundDeviceProfileInfo.getName());
Assert.assertEquals(savedDeviceProfile.getType(), foundDeviceProfileInfo.getType());
}
@Test

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

@ -476,7 +476,7 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest {
queueConfiguration.setName(queueName);
queueConfiguration.setTopic("tb_rule_engine." + queueName.toLowerCase());
queueConfiguration.setPollInterval(25);
queueConfiguration.setPartitions(new Random().nextInt(100));
queueConfiguration.setPartitions(1 + new Random().nextInt(99));
queueConfiguration.setConsumerPerPartition(true);
queueConfiguration.setPackProcessingTimeout(2000);
SubmitStrategy submitStrategy = new SubmitStrategy();

62
application/src/test/java/org/thingsboard/server/service/cluster/routing/HashPartitionServiceTest.java → application/src/test/java/org/thingsboard/server/queue/discovery/HashPartitionServiceTest.java

@ -13,9 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.cluster.routing;
package org.thingsboard.server.queue.discovery;
import com.datastax.driver.core.utils.UUIDs;
import com.datastax.oss.driver.api.core.uuid.Uuids;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.junit.Assert;
import org.junit.Before;
@ -29,17 +31,16 @@ import org.thingsboard.server.common.data.id.TenantId;
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.queue.discovery.HashPartitionService;
import org.thingsboard.server.queue.discovery.QueueRoutingInfoService;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.queue.discovery.TenantRoutingInfoService;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static org.mockito.Mockito.mock;
@ -111,15 +112,56 @@ public class HashPartitionServiceTest {
map.put(partition, map.getOrDefault(partition, 0) + 1);
}
List<Map.Entry<Integer, Integer>> data = map.entrySet().stream().sorted(Comparator.comparingInt(Map.Entry::getValue)).collect(Collectors.toList());
checkDispersion(start, map, ITERATIONS, 1.0);
}
@SneakyThrows
@Test
public void testDispersionOnResolveByPartitionIdx() {
int serverCount = 5;
int tenantCount = 1000;
int queueCount = 3;
int partitionCount = 3;
List<TransportProtos.ServiceInfo> services = new ArrayList<>();
for (int i = 0; i < serverCount; i++) {
services.add(TransportProtos.ServiceInfo.newBuilder().setServiceId("RE-" + i).build());
}
long start = System.currentTimeMillis();
Map<String, Integer> map = new HashMap<>();
services.forEach(s -> map.put(s.getServiceId(), 0));
Random random = new Random();
long ts = new SimpleDateFormat("dd-MM-yyyy").parse("06-12-2016").getTime() - TimeUnit.DAYS.toMillis(tenantCount);
for (int tenantIndex = 0; tenantIndex < tenantCount; tenantIndex++) {
TenantId tenantId = new TenantId(UUIDs.startOf(ts));
ts += TimeUnit.DAYS.toMillis(1) + random.nextInt(1000);
for (int queueIndex = 0; queueIndex < queueCount; queueIndex++) {
QueueKey queueKey = new QueueKey(ServiceType.TB_RULE_ENGINE, "queue" + queueIndex, tenantId);
for (int partition = 0; partition < partitionCount; partition++) {
TransportProtos.ServiceInfo serviceInfo = clusterRoutingService.resolveByPartitionIdx(services, queueKey, partition);
String serviceId = serviceInfo.getServiceId();
map.put(serviceId, map.get(serviceId) + 1);
}
}
}
checkDispersion(start, map, tenantCount * queueCount * partitionCount, 10.0);
}
private <T> void checkDispersion(long start, Map<T, Integer> map, int iterations, double maxDiffPercent) {
List<Map.Entry<T, Integer>> data = map.entrySet().stream().sorted(Comparator.comparingInt(Map.Entry::getValue)).collect(Collectors.toList());
long end = System.currentTimeMillis();
double diff = (data.get(data.size() - 1).getValue() - data.get(0).getValue());
double diffPercent = (diff / ITERATIONS) * 100.0;
double ideal = ((double) iterations) / map.size();
double diff = Math.max(data.get(data.size() - 1).getValue() - ideal, ideal - data.get(0).getValue());
double diffPercent = (diff / ideal) * 100.0;
System.out.println("Time: " + (end - start) + " Diff: " + diff + "(" + String.format("%f", diffPercent) + "%)");
Assert.assertTrue(diffPercent < 0.5);
for (Map.Entry<Integer, Integer> entry : data) {
for (Map.Entry<T, Integer> entry : data) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
Assert.assertTrue(diffPercent < maxDiffPercent);
}
}

2
common/queue/src/main/java/org/thingsboard/server/queue/common/AbstractTbQueueTemplate.java

@ -22,7 +22,7 @@ import java.util.UUID;
public class AbstractTbQueueTemplate {
protected static final String REQUEST_ID_HEADER = "requestId";
protected static final String RESPONSE_TOPIC_HEADER = "responseTopic";
protected static final String REQUEST_TIME = "requestTime";
protected static final String EXPIRE_TS_HEADER = "expireTs";
protected byte[] uuidToBytes(UUID uuid) {
ByteBuffer buf = ByteBuffer.allocate(16);

6
common/queue/src/main/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplate.java

@ -56,6 +56,7 @@ public class DefaultTbQueueRequestTemplate<Request extends TbQueueMsg, Response
final boolean internalExecutor;
final ExecutorService executor;
final long maxRequestTimeoutNs;
final long maxRequestTimeout;
final long maxPendingRequests;
final long pollInterval;
volatile boolean stopped = false;
@ -76,6 +77,7 @@ public class DefaultTbQueueRequestTemplate<Request extends TbQueueMsg, Response
this.requestTemplate = requestTemplate;
this.responseTemplate = responseTemplate;
this.maxRequestTimeoutNs = TimeUnit.MILLISECONDS.toNanos(maxRequestTimeout);
this.maxRequestTimeout = maxRequestTimeout;
this.maxPendingRequests = maxPendingRequests;
this.pollInterval = pollInterval;
this.internalExecutor = (executor == null);
@ -170,7 +172,7 @@ public class DefaultTbQueueRequestTemplate<Request extends TbQueueMsg, Response
log.error("[{}] Missing requestId in header and body", response);
} else {
requestId = bytesToUuid(requestIdHeader);
log.trace("[{}] Response received: {}", requestId, String.valueOf(response).replace("\n", " ")); //TODO remove overhead
log.trace("[{}] Response received: {}", requestId, response);
ResponseMetaData<Response> expectedResponse = pendingRequests.remove(requestId);
if (expectedResponse == null) {
log.debug("[{}] Invalid or stale request, response: {}", requestId, String.valueOf(response).replace("\n", " "));
@ -216,7 +218,7 @@ public class DefaultTbQueueRequestTemplate<Request extends TbQueueMsg, Response
UUID requestId = UUID.randomUUID();
request.getHeaders().put(REQUEST_ID_HEADER, uuidToBytes(requestId));
request.getHeaders().put(RESPONSE_TOPIC_HEADER, stringToBytes(responseTemplate.getTopic()));
request.getHeaders().put(REQUEST_TIME, longToBytes(getCurrentTimeMs()));
request.getHeaders().put(EXPIRE_TS_HEADER, longToBytes(getCurrentTimeMs() + maxRequestTimeout));
long currentClockNs = getCurrentClockNs();
SettableFuture<Response> future = SettableFuture.create();
ResponseMetaData<Response> responseMetaData = new ResponseMetaData<>(currentClockNs + requestTimeoutNs, future, currentClockNs, requestTimeoutNs);

4
common/queue/src/main/java/org/thingsboard/server/queue/common/DefaultTbQueueResponseTemplate.java

@ -97,8 +97,8 @@ public class DefaultTbQueueResponseTemplate<Request extends TbQueueMsg, Response
requests.forEach(request -> {
long currentTime = System.currentTimeMillis();
long requestTime = bytesToLong(request.getHeaders().get(REQUEST_TIME));
if (requestTime + requestTimeout >= currentTime) {
long expireTs = bytesToLong(request.getHeaders().get(EXPIRE_TS_HEADER));
if (expireTs >= currentTime) {
byte[] requestIdHeader = request.getHeaders().get(REQUEST_ID_HEADER);
if (requestIdHeader == null) {
log.error("[{}] Missing requestId in header", request);

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

@ -34,6 +34,7 @@ import org.thingsboard.server.queue.discovery.event.ServiceListChangedEvent;
import org.thingsboard.server.queue.util.AfterStartUp;
import javax.annotation.PostConstruct;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@ -254,7 +255,7 @@ public class HashPartitionService implements PartitionService {
myPartitions = new ConcurrentHashMap<>();
partitionSizesMap.forEach((queueKey, size) -> {
for (int i = 0; i < size; i++) {
ServiceInfo serviceInfo = resolveByPartitionIdx(queueServicesMap.get(queueKey), i);
ServiceInfo serviceInfo = resolveByPartitionIdx(queueServicesMap.get(queueKey), queueKey, i);
if (currentService.equals(serviceInfo)) {
myPartitions.computeIfAbsent(queueKey, key -> new ArrayList<>()).add(i);
}
@ -434,11 +435,21 @@ public class HashPartitionService implements PartitionService {
}
}
private ServiceInfo resolveByPartitionIdx(List<ServiceInfo> servers, Integer partitionIdx) {
protected ServiceInfo resolveByPartitionIdx(List<ServiceInfo> servers, QueueKey queueKey, int partition) {
if (servers == null || servers.isEmpty()) {
return null;
}
return servers.get(partitionIdx % servers.size());
if (!ServiceType.TB_RULE_ENGINE.equals(queueKey.getType()) || TenantId.SYS_TENANT_ID.equals(queueKey.getTenantId())) {
return servers.get(partition % servers.size());
} else {
int hash = hashFunction.newHasher().putLong(queueKey.getTenantId().getId().getMostSignificantBits())
.putLong(queueKey.getTenantId().getId().getLeastSignificantBits())
.putString(queueKey.getQueueName(), StandardCharsets.UTF_8)
.hash().asInt();
return servers.get(Math.abs((hash + partition) % servers.size()));
}
}
public static HashFunction forName(String name) {

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

@ -543,6 +543,8 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
ctx.addUuidParameter("permissions_customer_id", ctx.getCustomerId().getId());
if (ctx.getEntityType() == EntityType.CUSTOMER) {
return "e.tenant_id=:permissions_tenant_id and e.id=:permissions_customer_id";
} else if (ctx.getEntityType() == EntityType.API_USAGE_STATE) {
return "e.tenant_id=:permissions_tenant_id and e.entity_id=:permissions_customer_id";
} else {
return "e.tenant_id=:permissions_tenant_id and e.customer_id=:permissions_customer_id";
}

26
msa/js-executor/api/jsInvokeMessageProcessor.js

@ -21,6 +21,7 @@ const TIMEOUT_ERROR = 2;
const UNRECOGNIZED = -1;
const config = require('config'),
Long = require('long'),
logger = require('../config/logger')._logger('JsInvokeMessageProcessor'),
Utils = require('./utils'),
JsExecutor = require('./jsExecutor');
@ -49,6 +50,7 @@ JsInvokeMessageProcessor.prototype.onJsInvokeMessage = function (message) {
var tStart = performance.now();
let requestId;
let responseTopic;
let expireTs;
let headers;
let request;
let buf;
@ -59,6 +61,20 @@ JsInvokeMessageProcessor.prototype.onJsInvokeMessage = function (message) {
requestId = Utils.UUIDFromBuffer(buf);
buf = Buffer.from(headers.data['responseTopic']);
responseTopic = buf.toString('utf8');
buf = Buffer.from(headers.data['expireTs']);
expireTs = Long.fromBytes(buf, false, false).toNumber();
const now = Date.now();
// if (logger.isDebugEnabled()) {
// logger.debug('expireTs is %s, buf is %s. Now is %s, ms to expire left %s', expireTs, buf.toString('hex'), now, expireTs - now)
// }
if (expireTs && expireTs <= now) {
if (logger.isDebugEnabled()) {
logger.debug('Message expired! expireTs is %s, buf is %s. Now is %s, ms to expire left %s', expireTs, buf.toString('hex'), now, expireTs - now)
}
return;
}
logger.debug('[%s] Received request, responseTopic: [%s]', requestId, responseTopic);
@ -140,7 +156,7 @@ JsInvokeMessageProcessor.prototype.processInvokeRequest = function (requestId, r
(result) => {
var invokeResponse = createInvokeResponse(result, true);
logger.debug('[%s] Sending success invoke response, scriptId: [%s]', requestId, scriptId);
this.sendResponse(requestId, responseTopic, headers, scriptId, null, invokeResponse);
this.sendResponse(requestId, responseTopic, headers, scriptId, undefined, invokeResponse);
},
(err) => {
var errorCode;
@ -151,14 +167,14 @@ JsInvokeMessageProcessor.prototype.processInvokeRequest = function (requestId, r
}
var invokeResponse = createInvokeResponse("", false, errorCode, err);
logger.debug('[%s] Sending failed invoke response, scriptId: [%s], errorCode: [%s]', requestId, scriptId, errorCode);
this.sendResponse(requestId, responseTopic, headers, scriptId, null, invokeResponse);
this.sendResponse(requestId, responseTopic, headers, scriptId, undefined, invokeResponse);
}
)
},
(err) => {
var invokeResponse = createInvokeResponse("", false, COMPILATION_ERROR, err);
logger.debug('[%s] Sending failed invoke response, scriptId: [%s], errorCode: [%s]', requestId, scriptId, COMPILATION_ERROR);
this.sendResponse(requestId, responseTopic, headers, scriptId, null, invokeResponse);
this.sendResponse(requestId, responseTopic, headers, scriptId, undefined, invokeResponse);
}
);
}
@ -176,7 +192,7 @@ JsInvokeMessageProcessor.prototype.processReleaseRequest = function (requestId,
}
var releaseResponse = createReleaseResponse(scriptId, true);
logger.debug('[%s] Sending success release response, scriptId: [%s]', requestId, scriptId);
this.sendResponse(requestId, responseTopic, headers, scriptId, null, null, releaseResponse);
this.sendResponse(requestId, responseTopic, headers, scriptId, undefined, undefined, releaseResponse);
}
JsInvokeMessageProcessor.prototype.sendResponse = function (requestId, responseTopic, headers, scriptId, compileResponse, invokeResponse, releaseResponse) {
@ -276,7 +292,7 @@ function createReleaseResponse(scriptId, success) {
function parseJsErrorDetails(err) {
if (!err) {
return '';
return undefined;
}
var details = err.name + ': ' + err.message;
if (err.stack) {

6
msa/js-executor/queue/kafkaTemplate.js

@ -65,15 +65,15 @@ async function pushMessageToSendLater(message) {
function sendLoopWithLinger() {
if (sendLoopInstance) {
clearTimeout(sendLoopInstance);
} else {
logger.debug("Starting new send loop with linger [%s]", linger)
// } else {
// logger.debug("Starting new send loop with linger [%s]", linger)
}
sendLoopInstance = setTimeout(sendMessagesAsBatch, linger);
}
async function sendMessagesAsBatch(isImmediately) {
if (sendLoopInstance) {
logger.debug("sendMessagesAsBatch: Clear sendLoop scheduler. Starting new send loop with linger [%s]", linger);
// logger.debug("sendMessagesAsBatch: Clear sendLoop scheduler. Starting new send loop with linger [%s]", linger);
clearTimeout(sendLoopInstance);
}
sendLoopInstance = null;

3
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbAbstractTransformNode.java

@ -16,6 +16,7 @@
package org.thingsboard.rule.engine.transform;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNode;
@ -49,7 +50,7 @@ public abstract class TbAbstractTransformNode implements TbNode {
withCallback(transform(ctx, msg),
m -> transformSuccess(ctx, msg, m),
t -> transformFailure(ctx, msg, t),
ctx.getDbCallbackExecutor());
MoreExecutors.directExecutor());
}
protected void transformFailure(TbContext ctx, TbMsg msg, Throwable t) {

1
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java

@ -73,7 +73,6 @@ public class TbTransformMsgNodeTest {
when(scriptEngine.executeUpdateAsync(msg)).thenReturn(Futures.immediateFuture(Collections.singletonList(transformedMsg)));
node.onMsg(ctx, msg);
verify(ctx).getDbCallbackExecutor();
ArgumentCaptor<TbMsg> captor = ArgumentCaptor.forClass(TbMsg.class);
verify(ctx).tellSuccess(captor.capture());
TbMsg actualMsg = captor.getValue();

2
ui-ngx/angular.json

@ -91,7 +91,7 @@
"node_modules/tinycolor2/dist/tinycolor-min.js",
"node_modules/split.js/dist/split.min.js",
"node_modules/systemjs/dist/system.min.js",
"node_modules/marked/lib/marked.js",
"node_modules/marked/lib/marked.umd.js",
"node_modules/prismjs/prism.js",
"node_modules/prismjs/components/prism-css.min.js",
"node_modules/prismjs/components/prism-bash.min.js",

56
ui-ngx/package.json

@ -14,33 +14,33 @@
},
"private": true,
"dependencies": {
"@angular/animations": "^12.2.13",
"@angular/animations": "^12.2.16",
"@angular/cdk": "^12.2.13",
"@angular/common": "^12.2.13",
"@angular/compiler": "^12.2.13",
"@angular/core": "^12.2.13",
"@angular/common": "^12.2.16",
"@angular/compiler": "^12.2.16",
"@angular/core": "^12.2.16",
"@angular/flex-layout": "^12.0.0-beta.35",
"@angular/forms": "^12.2.13",
"@angular/forms": "^12.2.16",
"@angular/material": "^12.2.13",
"@angular/platform-browser": "^12.2.13",
"@angular/platform-browser-dynamic": "^12.2.13",
"@angular/router": "^12.2.13",
"@angular/platform-browser": "^12.2.16",
"@angular/platform-browser-dynamic": "^12.2.16",
"@angular/router": "^12.2.16",
"@auth0/angular-jwt": "^5.0.2",
"@date-io/date-fns": "^2.11.0",
"@flowjs/flow.js": "^2.14.1",
"@flowjs/ngx-flow": "~0.4.6",
"@geoman-io/leaflet-geoman-free": "^2.11.4",
"@geoman-io/leaflet-geoman-free": "~2.11.4",
"@juggle/resize-observer": "^3.3.1",
"@mat-datetimepicker/core": "~7.0.1",
"@material-ui/core": "^4.12.3",
"@material-ui/icons": "^4.11.2",
"@material-ui/pickers": "^3.3.10",
"@material-ui/core": "4.12.3",
"@material-ui/icons": "4.11.2",
"@material-ui/pickers": "3.3.10",
"@ngrx/effects": "^12.5.1",
"@ngrx/store": "^12.5.1",
"@ngrx/store-devtools": "^12.5.1",
"@ngx-translate/core": "^13.0.0",
"@ngx-translate/http-loader": "^6.0.0",
"ace-builds": "^1.4.13",
"ace-builds": "1.4.13",
"ace-diff": "^3.0.3",
"angular-gridster2": "~12.1.1",
"angular2-hotkeys": "^2.4.0",
@ -58,7 +58,7 @@
"jstree": "^3.3.12",
"jstree-bootstrap-theme": "^1.0.1",
"jszip": "^3.7.1",
"leaflet": "^1.7.1",
"leaflet": "~1.7.1",
"leaflet-polylinedecorator": "^1.6.0",
"leaflet-providers": "^1.13.0",
"leaflet.gridlayer.googlemutant": "^0.13.4",
@ -73,7 +73,7 @@
"ngx-drag-drop": "^2.0.0",
"ngx-flowchart": "https://github.com/thingsboard/ngx-flowchart.git#release/1.0.0",
"ngx-hm-carousel": "^2.0.1",
"ngx-markdown": "^12.0.1",
"ngx-markdown": "^12.1.0",
"ngx-sharebuttons": "^9.0.0",
"ngx-translate-messageformat-compiler": "^4.11.0",
"objectpath": "^2.0.0",
@ -81,10 +81,10 @@
"prop-types": "^15.7.2",
"qrcode": "^1.5.0",
"raphael": "^2.3.0",
"rc-select": "~13.2.0",
"react": "~17.0.2",
"react-ace": "^9.5.0",
"react-dom": "^17.0.2",
"rc-select": "13.2.1",
"react": "17.0.2",
"react-ace": "9.5.0",
"react-dom": "17.0.2",
"react-dropzone": "^11.4.2",
"reactcss": "^1.2.3",
"rxjs": "~6.6.7",
@ -102,11 +102,11 @@
},
"devDependencies": {
"@angular-builders/custom-webpack": "~12.1.3",
"@angular-devkit/build-angular": "^12.2.13",
"@angular/cli": "^12.2.13",
"@angular/compiler-cli": "^12.2.13",
"@angular/language-service": "^12.2.13",
"@ngtools/webpack": "~12.2.13",
"@angular-devkit/build-angular": "^12.2.17",
"@angular/cli": "^12.2.17",
"@angular/compiler-cli": "^12.2.16",
"@angular/language-service": "^12.2.16",
"@ngtools/webpack": "^12.2.17",
"@types/ace-diff": "^2.1.1",
"@types/canvas-gauges": "^2.1.4",
"@types/flot": "^0.0.32",
@ -125,8 +125,8 @@
"@types/mousetrap": "^1.6.0",
"@types/node": "~15.14.9",
"@types/raphael": "^2.3.2",
"@types/react": "^17.0.37",
"@types/react-dom": "^17.0.11",
"@types/react": "17.0.37",
"@types/react-dom": "17.0.11",
"@types/systemjs": "6.1.1",
"@types/tinycolor2": "^1.4.3",
"@types/tooltipster": "^0.0.31",
@ -151,6 +151,8 @@
"webpack": "^5.64.4"
},
"resolutions": {
"lodash": "~4.17.21"
"lodash": "~4.17.21",
"@types/react": "17.0.37",
"ace-builds": "1.4.13"
}
}

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

@ -36,6 +36,7 @@ import { UserService } from '@core/http/user.service';
import { AttributeService } from '@core/http/attribute.service';
import { EntityRelationService } from '@core/http/entity-relation.service';
import { EntityService } from '@core/http/entity.service';
import { AuthService } from '@core/auth/auth.service';
import { DialogService } from '@core/services/dialog.service';
import { CustomDialogService } from '@home/components/widget/dialog/custom-dialog.service';
import { ResourceService } from '@core/http/resource.service';
@ -75,6 +76,7 @@ export class DynamicWidgetComponent extends PageComponent implements IDynamicWid
this.ctx.attributeService = $injector.get(AttributeService);
this.ctx.entityRelationService = $injector.get(EntityRelationService);
this.ctx.entityService = $injector.get(EntityService);
this.ctx.authService = $injector.get(AuthService);
this.ctx.dialogs = $injector.get(DialogService);
this.ctx.customDialog = $injector.get(CustomDialogService);
this.ctx.resourceService = $injector.get(ResourceService);

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

@ -219,6 +219,7 @@ export interface CommonMapSettings {
defaultZoomLevel: number;
defaultCenterPosition?: string;
disableScrollZooming: boolean;
disableDoubleClickZooming: boolean;
disableZoomControl: boolean;
fitMapBounds: boolean;
useDefaultCenterPosition: boolean;
@ -242,6 +243,7 @@ export const defaultCommonMapSettings: CommonMapSettings = {
defaultZoomLevel: null,
defaultCenterPosition: '0,0',
disableScrollZooming: false,
disableDoubleClickZooming: false,
disableZoomControl: false,
fitMapBounds: true,
useDefaultCenterPosition: false,

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

@ -37,6 +37,7 @@ export class GoogleMap extends LeafletMap {
this.loadGoogle(() => {
const map = L.map($container, {
attributionControl: false,
doubleClickZoom: !this.options.disableDoubleClickZooming,
zoomControl: !this.options.disableZoomControl,
tap: L.Browser.safari && L.Browser.mobile
}).setView(options?.parsedDefaultCenterPosition, options?.defaultZoomLevel || DEFAULT_ZOOM_LEVEL);

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

@ -24,6 +24,7 @@ export class HEREMap extends LeafletMap {
super(ctx, $container, options);
const map = L.map($container, {
tap: L.Browser.safari && L.Browser.mobile,
doubleClickZoom: !this.options.disableDoubleClickZooming,
zoomControl: !this.options.disableZoomControl
}).setView(options?.parsedDefaultCenterPosition, options?.defaultZoomLevel || DEFAULT_ZOOM_LEVEL);
const tileLayer = (L.tileLayer as any).provider(options.mapProviderHere || 'HERE.normalDay', options.credentials);

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

@ -224,7 +224,7 @@ export class ImageMap extends LeafletMap {
maxZoom,
scrollWheelZoom: !this.options.disableScrollZooming,
center,
doubleClickZoom: !this.options.disableZoomControl,
doubleClickZoom: !this.options.disableDoubleClickZooming,
zoomControl: !this.options.disableZoomControl,
zoom: 1,
crs: L.CRS.Simple,

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

@ -23,6 +23,7 @@ export class OpenStreetMap extends LeafletMap {
constructor(ctx: WidgetContext, $container, options: WidgetUnitedMapSettings) {
super(ctx, $container, options);
const map = L.map($container, {
doubleClickZoom: !this.options.disableDoubleClickZooming,
zoomControl: !this.options.disableZoomControl,
tap: L.Browser.safari && L.Browser.mobile
}).setView(options?.parsedDefaultCenterPosition, options?.defaultZoomLevel || DEFAULT_ZOOM_LEVEL);

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

@ -25,6 +25,7 @@ export class TencentMap extends LeafletMap {
super(ctx, $container, options);
const txUrl = 'http://rt{s}.map.gtimg.com/realtimerender?z={z}&x={x}&y={y}&type=vector&style=0';
const map = L.map($container, {
doubleClickZoom: !this.options.disableDoubleClickZooming,
zoomControl: !this.options.disableZoomControl,
tap: L.Browser.safari && L.Browser.mobile
}).setView(options?.parsedDefaultCenterPosition, options?.defaultZoomLevel || DEFAULT_ZOOM_LEVEL);

3
ui-ngx/src/app/modules/home/components/widget/lib/settings/map/common-map-settings.component.html

@ -70,6 +70,9 @@
<mat-slide-toggle formControlName="disableScrollZooming" class="slide-block">
{{ 'widgets.maps.disable-scroll-zooming' | translate }}
</mat-slide-toggle>
<mat-slide-toggle formControlName="disableDoubleClickZooming" class="slide-block">
{{ 'widgets.maps.disable-double-click-zooming' | translate }}
</mat-slide-toggle>
<mat-slide-toggle formControlName="disableZoomControl" class="slide-block">
{{ 'widgets.maps.disable-zoom-control-buttons' | translate }}
</mat-slide-toggle>

1
ui-ngx/src/app/modules/home/components/widget/lib/settings/map/common-map-settings.component.ts

@ -83,6 +83,7 @@ export class CommonMapSettingsComponent extends PageComponent implements OnInit,
defaultZoomLevel: [null, [Validators.min(0), Validators.max(20)]],
defaultCenterPosition: [null, []],
disableScrollZooming: [null, []],
disableDoubleClickZooming: [null, []],
disableZoomControl: [null, []],
fitMapBounds: [null, []],
useDefaultCenterPosition: [null, []],

2
ui-ngx/src/app/modules/home/models/services.map.ts

@ -36,6 +36,7 @@ import { BroadcastService } from '@core/services/broadcast.service';
import { ImportExportService } from '@home/components/import-export/import-export.service';
import { DeviceProfileService } from '@core/http/device-profile.service';
import { OtaPackageService } from '@core/http/ota-package.service';
import { AuthService } from '@core/auth/auth.service';
import { ResourceService } from '@core/http/resource.service';
import { TwoFactorAuthenticationService } from '@core/http/two-factor-authentication.service';
@ -62,6 +63,7 @@ export const ServicesMap = new Map<string, Type<any>>(
['importExport', ImportExportService],
['deviceProfileService', DeviceProfileService],
['otaPackageService', OtaPackageService],
['authService', AuthService],
['resourceService', ResourceService],
['twoFactorAuthenticationService', TwoFactorAuthenticationService]
]

2
ui-ngx/src/app/modules/home/models/widget-component.models.ts

@ -71,6 +71,7 @@ import { EntityRelationService } from '@core/http/entity-relation.service';
import { EntityService } from '@core/http/entity.service';
import { DialogService } from '@core/services/dialog.service';
import { CustomDialogService } from '@home/components/widget/dialog/custom-dialog.service';
import { AuthService } from '@core/auth/auth.service';
import { ResourceService } from '@core/http/resource.service';
import { DatePipe } from '@angular/common';
import { TranslateService } from '@ngx-translate/core';
@ -158,6 +159,7 @@ export class WidgetContext {
}
}
authService: AuthService;
deviceService: DeviceService;
assetService: AssetService;
entityViewService: EntityViewService;

3
ui-ngx/src/app/shared/components/marked-options.service.ts

@ -19,8 +19,7 @@ import { Inject, Injectable } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { DOCUMENT } from '@angular/common';
import { WINDOW } from '@core/services/window.service';
import { Tokenizer } from 'marked';
import * as marked from 'marked';
import { Tokenizer, marked } from 'marked';
import { Clipboard } from '@angular/cdk/clipboard';
const copyCodeBlock = '{:copy-code}';

1
ui-ngx/src/app/shared/components/phone-input.component.html

@ -35,7 +35,6 @@
type="tel"
matInput
placeholder="{{ placeholder | translate }}"
[pattern]="phoneNumberPattern"
(focus)="focus()"
autocomplete="off"
[required]="required">

44
ui-ngx/src/app/shared/components/phone-input.component.ts

@ -30,7 +30,6 @@ import {
import { TranslateService } from '@ngx-translate/core';
import { Country, CountryData } from '@shared/models/country.models';
import examples from 'libphonenumber-js/examples.mobile.json';
import { phoneNumberPattern } from '@shared/models/settings.models';
import { Subscription } from 'rxjs';
import { FloatLabelType, MatFormFieldAppearance } from '@angular/material/form-field/form-field';
@ -82,7 +81,6 @@ export class PhoneInputComponent implements OnInit, ControlValueAccessor, Valida
phonePlaceholder = '+12015550123';
flagIcon: string;
phoneFormGroup: FormGroup;
phoneNumberPattern = phoneNumberPattern;
private isLoading = true;
get isLoad(): boolean {
@ -103,7 +101,8 @@ export class PhoneInputComponent implements OnInit, ControlValueAccessor, Valida
private baseCode = 127397;
private countryCallingCode = '+';
private modelValue: string;
private valueChange$: Subscription = null;
private changeSubscriptions: Subscription[] = [];
private propagateChange = (v: any) => { };
constructor(private translate: TranslateService,
@ -116,7 +115,7 @@ export class PhoneInputComponent implements OnInit, ControlValueAccessor, Valida
}
ngOnInit(): void {
const validators: ValidatorFn[] = [Validators.pattern(phoneNumberPattern), this.validatePhoneNumber()];
const validators: ValidatorFn[] = [(c: FormControl) => Validators.pattern(this.getPhoneNumberPattern())(c), this.validatePhoneNumber()];
if (this.required) {
validators.push(Validators.required);
}
@ -125,12 +124,12 @@ export class PhoneInputComponent implements OnInit, ControlValueAccessor, Valida
phoneNumber: [null, validators]
});
this.valueChange$ = this.phoneFormGroup.get('phoneNumber').valueChanges.subscribe(value => {
this.changeSubscriptions.push(this.phoneFormGroup.get('phoneNumber').valueChanges.subscribe(value => {
this.updateModel();
this.defineCountryFromNumber(value);
});
}));
this.phoneFormGroup.get('country').valueChanges.subscribe(value => {
this.changeSubscriptions.push(this.phoneFormGroup.get('country').valueChanges.subscribe(value => {
if (value) {
const code = this.countryCallingCode;
this.getFlagAndPhoneNumberData(value);
@ -142,21 +141,23 @@ export class PhoneInputComponent implements OnInit, ControlValueAccessor, Valida
}
}
}
});
}));
}
ngOnDestroy() {
if (this.valueChange$) {
this.valueChange$.unsubscribe();
for (const subscription of this.changeSubscriptions) {
subscription.unsubscribe();
}
}
focus() {
const phoneNumber = this.phoneFormGroup.get('phoneNumber');
this.phoneFormGroup.markAsPristine();
this.phoneFormGroup.markAsUntouched();
if (!phoneNumber.value) {
phoneNumber.patchValue(this.countryCallingCode);
phoneNumber.patchValue(this.countryCallingCode, {emitEvent: false});
}
if (phoneNumber.untouched && this.countryCallingCode !== phoneNumber.value) {
phoneNumber.markAsTouched();
phoneNumber.updateValueAndValidity();
}
}
@ -182,7 +183,7 @@ export class PhoneInputComponent implements OnInit, ControlValueAccessor, Valida
validatePhoneNumber(): ValidatorFn {
return (c: FormControl) => {
const phoneNumber = c.value;
if (phoneNumber && this.parsePhoneNumberFromString) {
if (phoneNumber && this.countryCallingCode !== phoneNumber && this.parsePhoneNumberFromString) {
const parsedPhoneNumber = this.parsePhoneNumberFromString(phoneNumber);
if (!parsedPhoneNumber?.isValid() || !parsedPhoneNumber?.isPossible()) {
return {
@ -206,8 +207,13 @@ export class PhoneInputComponent implements OnInit, ControlValueAccessor, Valida
}
}
private getPhoneNumberPattern(): RegExp {
return new RegExp(`^${this.countryCallingCode.replace('+', '\\+')}$|^\\+[1-9]\\d{1,14}$`);
}
validate(): ValidationErrors | null {
return this.phoneFormGroup.get('phoneNumber').valid ? null : {
const phoneNumber = this.phoneFormGroup.get('phoneNumber');
return phoneNumber.valid || phoneNumber.untouched || this.countryCallingCode === phoneNumber.value ? null : {
phoneFormGroup: false
};
}
@ -232,18 +238,18 @@ export class PhoneInputComponent implements OnInit, ControlValueAccessor, Valida
this.modelValue = phoneNumber;
let country = this.defaultCountry;
if (this.parsePhoneNumberFromString) {
country = phoneNumber ? this.parsePhoneNumberFromString(phoneNumber)?.country : this.defaultCountry;
country = phoneNumber ? this.parsePhoneNumberFromString(phoneNumber)?.country || this.defaultCountry : this.defaultCountry;
this.getFlagAndPhoneNumberData(country);
}
this.phoneFormGroup.patchValue({phoneNumber, country}, {emitEvent: !phoneNumber});
this.phoneFormGroup.reset({phoneNumber, country}, {emitEvent: false});
}
private updateModel() {
const phoneNumber = this.phoneFormGroup.get('phoneNumber');
if (phoneNumber.valid && phoneNumber.value) {
if (phoneNumber.valid) {
this.modelValue = phoneNumber.value;
this.propagateChange(this.modelValue);
} else if (phoneNumber.invalid && phoneNumber.value) {
} else {
this.propagateChange(null);
}
}

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

@ -4372,6 +4372,7 @@
"default-map-zoom-level": "Default map zoom level (0 - 20)",
"default-map-center-position": "Default map center position (0,0)",
"disable-scroll-zooming": "Disable scroll zooming",
"disable-double-click-zooming": "Disable double click zooming",
"disable-zoom-control-buttons": "Disable zoom control buttons",
"fit-map-bounds": "Fit map bounds to cover all markers",
"use-default-map-center-position": "Use default map center position",

4197
ui-ngx/yarn.lock

File diff suppressed because it is too large
Loading…
Cancel
Save