Merge branch 'rc' into features/add_tooltip_option_to_show_stack_mode_total_value_on_timeseries_chart_widgets
This commit is contained in:
commit
f3b0cf8ac5
@ -335,6 +335,9 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i
|
|||||||
Edge edge = edgeGrpcSession.getEdge();
|
Edge edge = edgeGrpcSession.getEdge();
|
||||||
TenantId tenantId = edge.getTenantId();
|
TenantId tenantId = edge.getTenantId();
|
||||||
log.info("[{}][{}] edge [{}] connected successfully.", tenantId, edgeGrpcSession.getSessionId(), edgeId);
|
log.info("[{}][{}] edge [{}] connected successfully.", tenantId, edgeGrpcSession.getSessionId(), edgeId);
|
||||||
|
if (sessions.containsKey(edgeId)) {
|
||||||
|
destroySession(sessions.get(edgeId));
|
||||||
|
}
|
||||||
sessions.put(edgeId, edgeGrpcSession);
|
sessions.put(edgeId, edgeGrpcSession);
|
||||||
final Lock newEventLock = sessionNewEventsLocks.computeIfAbsent(edgeId, id -> new ReentrantLock());
|
final Lock newEventLock = sessionNewEventsLocks.computeIfAbsent(edgeId, id -> new ReentrantLock());
|
||||||
newEventLock.lock();
|
newEventLock.lock();
|
||||||
@ -503,7 +506,7 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i
|
|||||||
} finally {
|
} finally {
|
||||||
newEventLock.unlock();
|
newEventLock.unlock();
|
||||||
}
|
}
|
||||||
toRemove.destroy();
|
destroySession(toRemove);
|
||||||
TenantId tenantId = toRemove.getEdge().getTenantId();
|
TenantId tenantId = toRemove.getEdge().getTenantId();
|
||||||
save(tenantId, edgeId, ACTIVITY_STATE, false);
|
save(tenantId, edgeId, ACTIVITY_STATE, false);
|
||||||
long lastDisconnectTs = System.currentTimeMillis();
|
long lastDisconnectTs = System.currentTimeMillis();
|
||||||
@ -516,6 +519,12 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i
|
|||||||
edgeIdServiceIdCache.evict(edgeId);
|
edgeIdServiceIdCache.evict(edgeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void destroySession(EdgeGrpcSession session) {
|
||||||
|
try (session) {
|
||||||
|
session.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void save(TenantId tenantId, EdgeId edgeId, String key, long value) {
|
private void save(TenantId tenantId, EdgeId edgeId, String key, long value) {
|
||||||
log.debug("[{}][{}] Updating long edge telemetry [{}] [{}]", tenantId, edgeId, key, value);
|
log.debug("[{}][{}] Updating long edge telemetry [{}] [{}]", tenantId, edgeId, key, value);
|
||||||
if (persistToTelemetry) {
|
if (persistToTelemetry) {
|
||||||
|
|||||||
@ -165,7 +165,6 @@ public class JobManagerTest extends AbstractControllerTest {
|
|||||||
await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> {
|
await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> {
|
||||||
Job job = findJobById(jobId);
|
Job job = findJobById(jobId);
|
||||||
assertThat(job.getStatus()).isEqualTo(JobStatus.CANCELLED);
|
assertThat(job.getStatus()).isEqualTo(JobStatus.CANCELLED);
|
||||||
assertThat(job.getResult().getSuccessfulCount()).isBetween(1, tasksCount - 1);
|
|
||||||
assertThat(job.getResult().getDiscardedCount()).isBetween(1, tasksCount - 1);
|
assertThat(job.getResult().getDiscardedCount()).isBetween(1, tasksCount - 1);
|
||||||
assertThat(job.getResult().getTotalCount()).isEqualTo(tasksCount);
|
assertThat(job.getResult().getTotalCount()).isEqualTo(tasksCount);
|
||||||
assertThat(job.getResult().getCompletedCount()).isEqualTo(tasksCount);
|
assertThat(job.getResult().getCompletedCount()).isEqualTo(tasksCount);
|
||||||
|
|||||||
@ -36,7 +36,7 @@ public abstract class AbstractEntityProfileNameQueryProcessor<T extends EntityFi
|
|||||||
public AbstractEntityProfileNameQueryProcessor(TenantRepo repo, QueryContext ctx, EdqsQuery query, T filter, EntityType entityType) {
|
public AbstractEntityProfileNameQueryProcessor(TenantRepo repo, QueryContext ctx, EdqsQuery query, T filter, EntityType entityType) {
|
||||||
super(repo, ctx, query, filter, entityType);
|
super(repo, ctx, query, filter, entityType);
|
||||||
entityProfileNames = new HashSet<>(getProfileNames(this.filter));
|
entityProfileNames = new HashSet<>(getProfileNames(this.filter));
|
||||||
pattern = RepositoryUtils.toSqlLikePattern(getEntityNameFilter(filter));
|
pattern = RepositoryUtils.toContainsSqlLikePattern(getEntityNameFilter(filter));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract String getEntityNameFilter(T filter);
|
protected abstract String getEntityNameFilter(T filter);
|
||||||
|
|||||||
@ -43,7 +43,7 @@ public abstract class AbstractEntityProfileQueryProcessor<T extends EntityFilter
|
|||||||
entityProfileIds.add(dp.getId());
|
entityProfileIds.add(dp.getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pattern = RepositoryUtils.toSqlLikePattern(getEntityNameFilter(filter));
|
pattern = RepositoryUtils.toContainsSqlLikePattern(getEntityNameFilter(filter));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract String getEntityNameFilter(T filter);
|
protected abstract String getEntityNameFilter(T filter);
|
||||||
|
|||||||
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.thingsboard.server.edqs.query.processor;
|
package org.thingsboard.server.edqs.query.processor;
|
||||||
|
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.thingsboard.server.common.data.permission.QueryContext;
|
import org.thingsboard.server.common.data.permission.QueryContext;
|
||||||
import org.thingsboard.server.common.data.query.EntityFilter;
|
import org.thingsboard.server.common.data.query.EntityFilter;
|
||||||
@ -106,7 +107,7 @@ public abstract class AbstractRelationQueryProcessor<T extends EntityFilter> ext
|
|||||||
|
|
||||||
private Set<EntityData<?>> getEntitiesSet(RelationsRepo relations) {
|
private Set<EntityData<?>> getEntitiesSet(RelationsRepo relations) {
|
||||||
Set<EntityData<?>> result = new HashSet<>();
|
Set<EntityData<?>> result = new HashSet<>();
|
||||||
Set<UUID> processed = new HashSet<>();
|
Set<RelationSearchTask> processed = new HashSet<>();
|
||||||
Queue<RelationSearchTask> tasks = new LinkedList<>();
|
Queue<RelationSearchTask> tasks = new LinkedList<>();
|
||||||
int maxLvl = getMaxLevel() == 0 ? MAXIMUM_QUERY_LEVEL : Math.max(1, getMaxLevel());
|
int maxLvl = getMaxLevel() == 0 ? MAXIMUM_QUERY_LEVEL : Math.max(1, getMaxLevel());
|
||||||
for (UUID uuid : getRootEntities()) {
|
for (UUID uuid : getRootEntities()) {
|
||||||
@ -114,7 +115,7 @@ public abstract class AbstractRelationQueryProcessor<T extends EntityFilter> ext
|
|||||||
}
|
}
|
||||||
while (!tasks.isEmpty()) {
|
while (!tasks.isEmpty()) {
|
||||||
RelationSearchTask task = tasks.poll();
|
RelationSearchTask task = tasks.poll();
|
||||||
if (processed.add(task.entityId)) {
|
if (processed.add(task)) {
|
||||||
var entityLvl = task.lvl + 1;
|
var entityLvl = task.lvl + 1;
|
||||||
Set<RelationInfo> entities = EntitySearchDirection.FROM.equals(getDirection()) ? relations.getFrom(task.entityId) : relations.getTo(task.entityId);
|
Set<RelationInfo> entities = EntitySearchDirection.FROM.equals(getDirection()) ? relations.getFrom(task.entityId) : relations.getTo(task.entityId);
|
||||||
if (isFetchLastLevelOnly() && entities.isEmpty() && task.previous != null && check(task.previous)) {
|
if (isFetchLastLevelOnly() && entities.isEmpty() && task.previous != null && check(task.previous)) {
|
||||||
@ -157,6 +158,7 @@ public abstract class AbstractRelationQueryProcessor<T extends EntityFilter> ext
|
|||||||
protected abstract boolean check(RelationInfo relationInfo);
|
protected abstract boolean check(RelationInfo relationInfo);
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
@EqualsAndHashCode
|
||||||
private static class RelationSearchTask {
|
private static class RelationSearchTask {
|
||||||
private final UUID entityId;
|
private final UUID entityId;
|
||||||
private final int lvl;
|
private final int lvl;
|
||||||
|
|||||||
@ -30,7 +30,7 @@ public class EntityNameQueryProcessor extends AbstractSimpleQueryProcessor<Entit
|
|||||||
|
|
||||||
public EntityNameQueryProcessor(TenantRepo repo, QueryContext ctx, EdqsQuery query) {
|
public EntityNameQueryProcessor(TenantRepo repo, QueryContext ctx, EdqsQuery query) {
|
||||||
super(repo, ctx, query, (EntityNameFilter) query.getEntityFilter(), ((EntityNameFilter) query.getEntityFilter()).getEntityType());
|
super(repo, ctx, query, (EntityNameFilter) query.getEntityFilter(), ((EntityNameFilter) query.getEntityFilter()).getEntityType());
|
||||||
pattern = RepositoryUtils.toSqlLikePattern(filter.getEntityNameFilter());
|
pattern = RepositoryUtils.toContainsSqlLikePattern(filter.getEntityNameFilter());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -54,7 +54,6 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@ -249,11 +248,11 @@ public class RepositoryUtils {
|
|||||||
}
|
}
|
||||||
return switch (predicate.getOperation()) {
|
return switch (predicate.getOperation()) {
|
||||||
case EQUAL -> value.equals(predicateValue);
|
case EQUAL -> value.equals(predicateValue);
|
||||||
case STARTS_WITH -> value.startsWith(predicateValue);
|
case STARTS_WITH -> toStartsWithSqlLikePattern(predicateValue).matcher(value).matches();
|
||||||
case ENDS_WITH -> value.endsWith(predicateValue);
|
case ENDS_WITH -> toEndsWithSqlLikePattern(predicateValue).matcher(value).matches();
|
||||||
case NOT_EQUAL -> !value.equals(predicateValue);
|
case NOT_EQUAL -> !value.equals(predicateValue);
|
||||||
case CONTAINS -> value.contains(predicateValue);
|
case CONTAINS -> toContainsSqlLikePattern(predicateValue).matcher(value).matches();
|
||||||
case NOT_CONTAINS -> !value.contains(predicateValue);
|
case NOT_CONTAINS -> !toContainsSqlLikePattern(predicateValue).matcher(value).matches();
|
||||||
case IN -> equalsAny(value, splitByCommaWithoutQuotes(predicateValue));
|
case IN -> equalsAny(value, splitByCommaWithoutQuotes(predicateValue));
|
||||||
case NOT_IN -> !equalsAny(value, splitByCommaWithoutQuotes(predicateValue));
|
case NOT_IN -> !equalsAny(value, splitByCommaWithoutQuotes(predicateValue));
|
||||||
};
|
};
|
||||||
@ -304,6 +303,15 @@ public class RepositoryUtils {
|
|||||||
return true;
|
return true;
|
||||||
} else if (filterPredicates.getOperation() == OR) {
|
} else if (filterPredicates.getOperation() == OR) {
|
||||||
for (KeyFilterPredicate filterPredicate : filterPredicates.getPredicates()) {
|
for (KeyFilterPredicate filterPredicate : filterPredicates.getPredicates()) {
|
||||||
|
|
||||||
|
// Emulate the SQL-like behavior of ThingsBoard's Entity Data Query service:
|
||||||
|
// for COMPLEX filters, return no results if filter value is empty
|
||||||
|
if (filterPredicate instanceof StringFilterPredicate stringFilterPredicate) {
|
||||||
|
if (StringUtils.isEmpty(stringFilterPredicate.getValue().getValue())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (simpleKeyFilter.check(value, filterPredicate)) {
|
if (simpleKeyFilter.check(value, filterPredicate)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -314,25 +322,40 @@ public class RepositoryUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Pattern toSqlLikePattern(String nameFilter) {
|
public static Pattern toContainsSqlLikePattern(String filter) {
|
||||||
if (StringUtils.isNotBlank(nameFilter)) {
|
if (StringUtils.isNotBlank(filter)) {
|
||||||
boolean percentSymbolOnStart = nameFilter.startsWith("%");
|
return toSqlLikePattern(filter, ".*", ".*");
|
||||||
boolean percentSymbolOnEnd = nameFilter.endsWith("%");
|
|
||||||
if (percentSymbolOnStart) {
|
|
||||||
nameFilter = nameFilter.substring(1);
|
|
||||||
}
|
|
||||||
if (percentSymbolOnEnd) {
|
|
||||||
nameFilter = nameFilter.substring(0, nameFilter.length() - 1);
|
|
||||||
}
|
|
||||||
if (percentSymbolOnStart || percentSymbolOnEnd) {
|
|
||||||
return Pattern.compile((percentSymbolOnStart ? ".*" : "") + Pattern.quote(nameFilter) + (percentSymbolOnEnd ? ".*" : ""), Pattern.CASE_INSENSITIVE);
|
|
||||||
} else {
|
|
||||||
return Pattern.compile(Pattern.quote(nameFilter) + ".*", Pattern.CASE_INSENSITIVE);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Pattern toStartsWithSqlLikePattern(String filter) {
|
||||||
|
return toSqlLikePattern(filter, "^", ".*");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Pattern toEndsWithSqlLikePattern(String filter) {
|
||||||
|
return toSqlLikePattern(filter, ".*", "$");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Pattern toSqlLikePattern(String value, String prefix, String suffix) {
|
||||||
|
if (value.contains("%") || value.contains("_")) {
|
||||||
|
String regexValue = value
|
||||||
|
.replace("_", ".")
|
||||||
|
.replace("%", ".*");
|
||||||
|
String regex;
|
||||||
|
if ("^".equals(prefix)) {
|
||||||
|
regex = "^" + regexValue + (regexValue.endsWith(".*") ? "" : ".*");
|
||||||
|
} else if ("$".equals(suffix)) {
|
||||||
|
regex = (regexValue.startsWith(".*") ? "" : ".*") + regexValue + "$";
|
||||||
|
} else {
|
||||||
|
regex = (regexValue.startsWith(".*") ? "" : ".*") + regexValue + (regexValue.endsWith(".*") ? "" : ".*");
|
||||||
|
}
|
||||||
|
return Pattern.compile(regex);
|
||||||
|
} else {
|
||||||
|
return Pattern.compile(prefix + Pattern.quote(value) + suffix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface SimpleKeyFilter<T> {
|
public interface SimpleKeyFilter<T> {
|
||||||
|
|
||||||
|
|||||||
@ -70,7 +70,45 @@ public class RepositoryUtilsTest {
|
|||||||
Arguments.of("loranet 123", getNameFilter(StringOperation.IN, "loranet 123, loranet 124"), true),
|
Arguments.of("loranet 123", getNameFilter(StringOperation.IN, "loranet 123, loranet 124"), true),
|
||||||
Arguments.of("loranet 123", getNameFilter(StringOperation.IN, "loranet 125, loranet 126"), false),
|
Arguments.of("loranet 123", getNameFilter(StringOperation.IN, "loranet 125, loranet 126"), false),
|
||||||
Arguments.of("loranet 123", getNameFilter(StringOperation.NOT_IN, "loranet 125, loranet 126"), true),
|
Arguments.of("loranet 123", getNameFilter(StringOperation.NOT_IN, "loranet 125, loranet 126"), true),
|
||||||
Arguments.of("loranet 123", getNameFilter(StringOperation.NOT_IN, "loranet 123, loranet 126"), false)
|
Arguments.of("loranet 123", getNameFilter(StringOperation.NOT_IN, "loranet 123, loranet 126"), false),
|
||||||
|
|
||||||
|
// Basic CONTAINS
|
||||||
|
Arguments.of("loranet 123", getNameFilter(StringOperation.CONTAINS, "%loranet"), true),
|
||||||
|
Arguments.of("loranet 123", getNameFilter(StringOperation.CONTAINS, "loranet%"), true),
|
||||||
|
Arguments.of("loranet 123", getNameFilter(StringOperation.CONTAINS, "%ranet%"), true),
|
||||||
|
Arguments.of("loranet 123", getNameFilter(StringOperation.CONTAINS, "%123"), true),
|
||||||
|
Arguments.of("loranet 123", getNameFilter(StringOperation.CONTAINS, "%loranx%"), false),
|
||||||
|
|
||||||
|
// Basic STARTS_WITH
|
||||||
|
Arguments.of("loranet 123", getNameFilter(StringOperation.STARTS_WITH, "loranet%"), true),
|
||||||
|
Arguments.of("loranet 123", getNameFilter(StringOperation.STARTS_WITH, "lora%"), true),
|
||||||
|
Arguments.of("loranet 123", getNameFilter(StringOperation.STARTS_WITH, "lorax%"), false),
|
||||||
|
|
||||||
|
// Basic ENDS_WITH
|
||||||
|
Arguments.of("loranet 123", getNameFilter(StringOperation.ENDS_WITH, "%123"), true),
|
||||||
|
Arguments.of("loranet 123", getNameFilter(StringOperation.ENDS_WITH, "%23"), true),
|
||||||
|
Arguments.of("loranet 123", getNameFilter(StringOperation.ENDS_WITH, "%124"), false),
|
||||||
|
|
||||||
|
// CONTAINS with _
|
||||||
|
Arguments.of("loranet 123", getNameFilter(StringOperation.CONTAINS, "loranet_123"), true), // '_' = ' '
|
||||||
|
Arguments.of("loranet 123", getNameFilter(StringOperation.CONTAINS, "loranet_12_"), true),
|
||||||
|
Arguments.of("loranet 123", getNameFilter(StringOperation.CONTAINS, "loran_t%"), true),
|
||||||
|
|
||||||
|
// STARTS_WITH with _
|
||||||
|
Arguments.of("loranet 123", getNameFilter(StringOperation.STARTS_WITH, "loranet_"), true),
|
||||||
|
Arguments.of("loranet 123", getNameFilter(StringOperation.STARTS_WITH, "lora__t%"), true),
|
||||||
|
Arguments.of("loranet 123", getNameFilter(StringOperation.STARTS_WITH, "lor_net%"), true),
|
||||||
|
|
||||||
|
// ENDS_WITH with _
|
||||||
|
Arguments.of("loranet 123", getNameFilter(StringOperation.ENDS_WITH, "_23"), true),
|
||||||
|
Arguments.of("loranet 123", getNameFilter(StringOperation.ENDS_WITH, "_2_"), true),
|
||||||
|
Arguments.of("loranet 123", getNameFilter(StringOperation.ENDS_WITH, "_3"), true),
|
||||||
|
|
||||||
|
// Mixed patterns
|
||||||
|
Arguments.of("loranet 123", getNameFilter(StringOperation.CONTAINS, "lora__t 1%"), true),
|
||||||
|
Arguments.of("loranet 123", getNameFilter(StringOperation.CONTAINS, "lora%net%3"), true),
|
||||||
|
Arguments.of("loranet 123", getNameFilter(StringOperation.CONTAINS, "%o_anet%2_3"), false),
|
||||||
|
Arguments.of("loranet 123", getNameFilter(StringOperation.CONTAINS, "lora___ ___"), true)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
2
pom.xml
2
pom.xml
@ -54,7 +54,7 @@
|
|||||||
<jjwt.version>0.12.5</jjwt.version>
|
<jjwt.version>0.12.5</jjwt.version>
|
||||||
<slf4j.version>2.0.17</slf4j.version>
|
<slf4j.version>2.0.17</slf4j.version>
|
||||||
<log4j.version>2.24.3</log4j.version>
|
<log4j.version>2.24.3</log4j.version>
|
||||||
<logback.version>1.5.18</logback.version>
|
<logback.version>1.5.6</logback.version>
|
||||||
<rat.version>0.10</rat.version> <!-- unused -->
|
<rat.version>0.10</rat.version> <!-- unused -->
|
||||||
<cassandra.version>4.17.0</cassandra.version>
|
<cassandra.version>4.17.0</cassandra.version>
|
||||||
<metrics.version>4.2.25</metrics.version>
|
<metrics.version>4.2.25</metrics.version>
|
||||||
|
|||||||
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
-->
|
-->
|
||||||
<section [formGroup]="credentialsConfigFormGroup" class="flex flex-col">
|
<section [formGroup]="credentialsConfigFormGroup" class="flex flex-col">
|
||||||
<mat-expansion-panel class="tb-credentials-config-panel-group">
|
<mat-expansion-panel class="tb-credentials-config-panel-group" expanded="true">
|
||||||
<mat-expansion-panel-header>
|
<mat-expansion-panel-header>
|
||||||
<mat-panel-title translate>rule-node-config.credentials</mat-panel-title>
|
<mat-panel-title translate>rule-node-config.credentials</mat-panel-title>
|
||||||
<mat-panel-description>
|
<mat-panel-description>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user