Browse Source

Merge pull request #10175 from dashevchenko/alarmRuleStateFix

Added alarm state handling for IN, NOT_IN types of operation
pull/10219/head
Andrew Shvayka 2 years ago
committed by GitHub
parent
commit
ded647f0cb
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 30
      common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java
  2. 29
      dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java
  3. 26
      dao/src/test/java/org/thingsboard/server/dao/sql/query/EntityKeyMappingTest.java
  4. 7
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java
  5. 92
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/AlarmRuleStateTest.java
  6. 2
      ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.html
  7. 14
      ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.ts
  8. 1
      ui-ngx/src/app/modules/home/components/filter/string-filter-predicate.component.html
  9. 1
      ui-ngx/src/assets/locale/locale.constant-en_US.json

30
common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java

@ -19,7 +19,10 @@ import com.google.common.base.Splitter;
import org.apache.commons.lang3.RandomStringUtils;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;
import java.util.function.Function;
import static org.apache.commons.lang3.StringUtils.repeat;
@ -156,6 +159,10 @@ public class StringUtils {
}
public static boolean equalsAny(String string, String... otherStrings) {
return equalsAny(string, Arrays.asList(otherStrings));
}
public static boolean equalsAny(String string, List<String> otherStrings) {
for (String otherString : otherStrings) {
if (equals(string, otherString)) {
return true;
@ -245,4 +252,27 @@ public class StringUtils {
return string.substring(0, maxLength) + truncationMarkerFunc.apply(truncatedSymbols);
}
public static List<String> splitByCommaWithoutQuotes(String value) {
List<String> splitValues = List.of(value.trim().split("\\s*,\\s*"));
List<String> result = new ArrayList<>();
char lastWayInputValue = '#';
for (String str : splitValues) {
char startWith = str.charAt(0);
char endWith = str.charAt(str.length() - 1);
// if first value is not quote, so we return values after split
if (startWith != '\'' && startWith != '"') return splitValues;
// if value is not in quote, so we return values after split
if (startWith != endWith) return splitValues;
// if different way values, so don't replace quote and return values after split
if (lastWayInputValue != '#' && startWith != lastWayInputValue) return splitValues;
result.add(str.substring(1, str.length() - 1));
lastWayInputValue = startWith;
}
return result;
}
}

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

@ -47,6 +47,8 @@ import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.thingsboard.server.common.data.StringUtils.splitByCommaWithoutQuotes;
@Data
public class EntityKeyMapping {
@ -574,7 +576,7 @@ public class EntityKeyMapping {
switch (stringFilterPredicate.getOperation()) {
case IN:
case NOT_IN:
ctx.addStringListParameter(paramName, getListValuesWithoutQuote(value));
ctx.addStringListParameter(paramName, splitByCommaWithoutQuotes(value));
break;
default:
ctx.addStringParameter(paramName, value);
@ -582,30 +584,7 @@ public class EntityKeyMapping {
return String.format("((%s is not null and %s)", field, stringOperationQuery);
}
protected List<String> getListValuesWithoutQuote(String value) {
List<String> splitValues = List.of(value.trim().split("\\s*,\\s*"));
List<String> result = new ArrayList<>();
char lastWayInputValue = '#';
for (String str : splitValues) {
char startWith = str.charAt(0);
char endWith = str.charAt(str.length() - 1);
// if first value is not quote, so we return values after split
if (startWith != '\'' && startWith != '"') return splitValues;
// if value is not in quote, so we return values after split
if (startWith != endWith) return splitValues;
// if different way values, so don't replace quote and return values after split
if (lastWayInputValue != '#' && startWith != lastWayInputValue) return splitValues;
result.add(str.substring(1, str.length() - 1));
lastWayInputValue = startWith;
}
return result;
}
private String buildNumericPredicateQuery(QueryContext ctx, String field, NumericFilterPredicate numericFilterPredicate) {
private String buildNumericPredicateQuery(QueryContext ctx, String field, NumericFilterPredicate numericFilterPredicate) {
String paramName = getNextParameterName(field);
ctx.addDoubleParameter(paramName, numericFilterPredicate.getValue().getValue());
String numericOperationQuery = "";

26
dao/src/test/java/org/thingsboard/server/dao/sql/query/EntityKeyMappingTest.java

@ -24,6 +24,8 @@ import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
import static org.thingsboard.server.common.data.StringUtils.splitByCommaWithoutQuotes;
@RunWith(SpringRunner.class )
@SpringBootTest(classes = EntityKeyMapping.class)
public class EntityKeyMappingTest {
@ -36,39 +38,39 @@ public class EntityKeyMappingTest {
@Test
public void testSplitToList() {
String value = "device1, device2, device3";
Assert.assertEquals(entityKeyMapping.getListValuesWithoutQuote(value), result);
Assert.assertEquals(splitByCommaWithoutQuotes(value), result);
}
@Test
public void testReplaceSingleQuote() {
String value = "'device1', 'device2', 'device3'";
Assert.assertEquals(entityKeyMapping.getListValuesWithoutQuote(value), result);
Assert.assertEquals(splitByCommaWithoutQuotes(value), result);
}
@Test
public void testReplaceDoubleQuote() {
String value = "\"device1\", \"device2\", \"device3\"";
Assert.assertEquals(entityKeyMapping.getListValuesWithoutQuote(value), result);
Assert.assertEquals(splitByCommaWithoutQuotes(value), result);
}
@Test
public void testSplitWithoutSpace() {
String value = "\"device1\" , \"device2\" , \"device3\"";
Assert.assertEquals(entityKeyMapping.getListValuesWithoutQuote(value), result);
Assert.assertEquals(splitByCommaWithoutQuotes(value), result);
}
@Test
public void testSaveSpacesBetweenString() {
String value = "device 1 , device 2 , device 3";
List<String> result = List.of("device 1", "device 2", "device 3");
Assert.assertEquals(entityKeyMapping.getListValuesWithoutQuote(value), result);
Assert.assertEquals(splitByCommaWithoutQuotes(value), result);
}
@Test
public void testSaveQuoteInString() {
String value = "device ''1 , device \"\"2 , device \"'3";
List<String> result = List.of("device ''1", "device \"\"2", "device \"'3");
Assert.assertEquals(entityKeyMapping.getListValuesWithoutQuote(value), result);
Assert.assertEquals(splitByCommaWithoutQuotes(value), result);
}
@Test
@ -76,28 +78,28 @@ public class EntityKeyMappingTest {
String value = "\"device1\", 'device2', \"device3\"";
List<String> result = List.of("\"device1\"", "'device2'", "\"device3\"");
Assert.assertEquals(entityKeyMapping.getListValuesWithoutQuote(value), result);
Assert.assertEquals(splitByCommaWithoutQuotes(value), result);
value = "'device1', \"device2\", \"device3\"";
result = List.of("'device1'", "\"device2\"", "\"device3\"");
Assert.assertEquals(entityKeyMapping.getListValuesWithoutQuote(value), result);
Assert.assertEquals(splitByCommaWithoutQuotes(value), result);
value = "device1, 'device2', \"device3\"";
result = List.of("device1", "'device2'", "\"device3\"");
Assert.assertEquals(entityKeyMapping.getListValuesWithoutQuote(value), result);
Assert.assertEquals(splitByCommaWithoutQuotes(value), result);
value = "'device1', device2, \"device3\"";
result = List.of("'device1'", "device2", "\"device3\"");
Assert.assertEquals(entityKeyMapping.getListValuesWithoutQuote(value), result);
Assert.assertEquals(splitByCommaWithoutQuotes(value), result);
value = "device1, \"device2\", \"device3\"";
result = List.of("device1", "\"device2\"", "\"device3\"");
Assert.assertEquals(entityKeyMapping.getListValuesWithoutQuote(value), result);
Assert.assertEquals(splitByCommaWithoutQuotes(value), result);
value = "\"device1\", device2, \"device3\"";
result = List.of("\"device1\"", "device2", "\"device3\"");
Assert.assertEquals(entityKeyMapping.getListValuesWithoutQuote(value), result);
Assert.assertEquals(splitByCommaWithoutQuotes(value), result);
}
}

7
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java

@ -50,6 +50,9 @@ import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import static org.thingsboard.server.common.data.StringUtils.equalsAny;
import static org.thingsboard.server.common.data.StringUtils.splitByCommaWithoutQuotes;
@Data
@Slf4j
class AlarmRuleState {
@ -467,6 +470,10 @@ class AlarmRuleState {
return !val.equals(predicateValue);
case NOT_CONTAINS:
return !val.contains(predicateValue);
case IN:
return equalsAny(val, splitByCommaWithoutQuotes(predicateValue));
case NOT_IN:
return !equalsAny(val, splitByCommaWithoutQuotes(predicateValue));
default:
throw new RuntimeException("Operation not supported: " + predicate.getOperation());
}

92
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/AlarmRuleStateTest.java

@ -0,0 +1,92 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.rule.engine.profile;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.thingsboard.server.common.data.device.profile.AlarmCondition;
import org.thingsboard.server.common.data.device.profile.AlarmConditionFilter;
import org.thingsboard.server.common.data.device.profile.AlarmConditionFilterKey;
import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType;
import org.thingsboard.server.common.data.device.profile.AlarmRule;
import org.thingsboard.server.common.data.device.profile.SimpleAlarmConditionSpec;
import org.thingsboard.server.common.data.query.EntityKeyValueType;
import org.thingsboard.server.common.data.query.FilterPredicateValue;
import org.thingsboard.server.common.data.query.StringFilterPredicate;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Stream;
public class AlarmRuleStateTest {
private static Stream<Arguments> testEvalCondition() {
return Stream.of(
Arguments.of(StringFilterPredicate.StringOperation.IN, "test,value", "test", AlarmEvalResult.TRUE),
Arguments.of(StringFilterPredicate.StringOperation.IN, "test,value", "teeeeest", AlarmEvalResult.FALSE),
Arguments.of(StringFilterPredicate.StringOperation.NOT_IN, "test,value", "test", AlarmEvalResult.FALSE),
Arguments.of(StringFilterPredicate.StringOperation.NOT_IN, "test,value", "teeeeest", AlarmEvalResult.TRUE),
Arguments.of(StringFilterPredicate.StringOperation.CONTAINS, "test value", "test value", AlarmEvalResult.TRUE),
Arguments.of(StringFilterPredicate.StringOperation.CONTAINS, "test value", "test", AlarmEvalResult.FALSE),
Arguments.of(StringFilterPredicate.StringOperation.NOT_CONTAINS, "test value", "test", AlarmEvalResult.TRUE),
Arguments.of(StringFilterPredicate.StringOperation.NOT_CONTAINS, "test value", "test value", AlarmEvalResult.FALSE),
Arguments.of(StringFilterPredicate.StringOperation.EQUAL, "test value", "test value", AlarmEvalResult.TRUE),
Arguments.of(StringFilterPredicate.StringOperation.EQUAL, "test value", "test", AlarmEvalResult.FALSE),
Arguments.of(StringFilterPredicate.StringOperation.NOT_EQUAL, "test value", "test", AlarmEvalResult.TRUE),
Arguments.of(StringFilterPredicate.StringOperation.NOT_EQUAL, "test value", "test value", AlarmEvalResult.FALSE),
Arguments.of(StringFilterPredicate.StringOperation.ENDS_WITH, "test value", "some test value", AlarmEvalResult.TRUE),
Arguments.of(StringFilterPredicate.StringOperation.ENDS_WITH, "test value", "some test value2", AlarmEvalResult.FALSE),
Arguments.of(StringFilterPredicate.StringOperation.STARTS_WITH, "test value", "test value attribute", AlarmEvalResult.TRUE),
Arguments.of(StringFilterPredicate.StringOperation.STARTS_WITH, "test value", "test", AlarmEvalResult.FALSE)
);
}
@ParameterizedTest
@MethodSource
public void testEvalCondition(StringFilterPredicate.StringOperation operation, String predicateValue, String attributeValue, AlarmEvalResult evalResult) {
AlarmConditionFilterKey alarmConditionFilterKey = new AlarmConditionFilterKey(AlarmConditionKeyType.ATTRIBUTE, "stringKey");
StringFilterPredicate predicate = new StringFilterPredicate();
predicate.setOperation(operation);
predicate.setValue(new FilterPredicateValue<>(predicateValue));
AlarmConditionFilter alarmConditionFilter = new AlarmConditionFilter();
alarmConditionFilter.setKey(alarmConditionFilterKey);
alarmConditionFilter.setPredicate(predicate);
alarmConditionFilter.setValueType(EntityKeyValueType.STRING);
List<AlarmConditionFilter> condition = new ArrayList<>();
condition.add(alarmConditionFilter);
AlarmCondition alarmCondition = new AlarmCondition();
alarmCondition.setSpec(new SimpleAlarmConditionSpec());
alarmCondition.setCondition(condition);
AlarmRule alarmRule = new AlarmRule();
alarmRule.setCondition(alarmCondition);
AlarmRuleState alarmRuleState = new AlarmRuleState(null, alarmRule, null, null, null);
Set<AlarmConditionFilterKey> entityKeys = new HashSet<>(List.of(alarmConditionFilterKey));
DataSnapshot result = new DataSnapshot(entityKeys);
result.putValue(alarmConditionFilterKey, System.currentTimeMillis(), EntityKeyValue.fromString(attributeValue));
Assertions.assertEquals(evalResult, alarmRuleState.eval(result));
}
}

2
ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.html

@ -41,7 +41,7 @@
</mat-checkbox>
</ng-template>
</div>
<div class="tb-hint" translate>filter.default-value</div>
<div class="tb-hint">{{ hintText | translate }}</div>
</div>
<div fxFlex fxLayout="column" [fxShow]="dynamicMode || onlyUserDynamicSource">
<div formGroupName="dynamicValue" fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">

14
ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.ts

@ -32,7 +32,8 @@ import {
EntityKeyValueType,
FilterPredicateValue,
getDynamicSourcesForAllowUser,
inheritModeForDynamicValueSourceType
inheritModeForDynamicValueSourceType,
StringOperation
} from '@shared/models/query/query.models';
@Component({
@ -81,6 +82,15 @@ export class FilterPredicateValueComponent implements ControlValueAccessor, Vali
return this.onlyUserDynamicSourceValue;
}
@Input()
set operation(operation: StringOperation) {
if (operation && (operation === StringOperation.IN || operation === StringOperation.NOT_IN)) {
this.hintText = 'filter.default-comma-separated-values';
} else {
this.hintText = 'filter.default-value';
}
}
@Input()
valueType: EntityKeyValueType;
@ -98,6 +108,8 @@ export class FilterPredicateValueComponent implements ControlValueAccessor, Vali
inheritMode = false;
hintText = 'filter.default-value';
private propagateChange = null;
private propagateChangePending = false;

1
ui-ngx/src/app/modules/home/components/filter/string-filter-predicate.component.html

@ -31,6 +31,7 @@
[onlyUserDynamicSource]="onlyUserDynamicSource"
fxFlex="70"
[valueType]="valueTypeEnum.STRING"
[operation]="stringFilterPredicateFormGroup.get('operation').value"
formControlName="value">
</tb-filter-predicate-value>
</div>

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

@ -2670,6 +2670,7 @@
"current-user": "Current user",
"current-device": "Current device",
"default-value": "Default value",
"default-comma-separated-values": "Default comma-separated values",
"dynamic-source-type": "Dynamic source type",
"dynamic-value": "Dynamic value",
"no-dynamic-value": "No dynamic value",

Loading…
Cancel
Save