Added `compare` condition
A simple `condition` that compares a path into the model in the execution context to a value. The comparison is based on the following possible operators: `eq`, `not_eq`, `lt`, `lte`, `gt`, `gte`. The following example shows a `compare` condition that checks if the total hits in the payload is greater or equal to 5. ``` { "compare" : { "ctx.payload.hits.total" : { "gte" : "5" } } } ``` Original commit: elastic/x-pack-elasticsearch@6d4f2bbf10
This commit is contained in:
parent
9e5fa64e03
commit
9aef7bb52b
|
@ -9,6 +9,8 @@ import org.elasticsearch.common.inject.AbstractModule;
|
|||
import org.elasticsearch.common.inject.multibindings.MapBinder;
|
||||
import org.elasticsearch.watcher.condition.always.AlwaysCondition;
|
||||
import org.elasticsearch.watcher.condition.always.AlwaysConditionFactory;
|
||||
import org.elasticsearch.watcher.condition.compare.CompareCondition;
|
||||
import org.elasticsearch.watcher.condition.compare.CompareConditionFactory;
|
||||
import org.elasticsearch.watcher.condition.never.NeverCondition;
|
||||
import org.elasticsearch.watcher.condition.never.NeverConditionFactory;
|
||||
import org.elasticsearch.watcher.condition.script.ScriptCondition;
|
||||
|
@ -42,6 +44,9 @@ public class ConditionModule extends AbstractModule {
|
|||
bind(AlwaysConditionFactory.class).asEagerSingleton();
|
||||
factoriesBinder.addBinding(AlwaysCondition.TYPE).to(AlwaysConditionFactory.class);
|
||||
|
||||
bind(CompareConditionFactory.class).asEagerSingleton();
|
||||
factoriesBinder.addBinding(CompareCondition.TYPE).to(CompareConditionFactory.class);
|
||||
|
||||
for (Map.Entry<String, Class<? extends ConditionFactory>> entry : factories.entrySet()) {
|
||||
bind(entry.getValue()).asEagerSingleton();
|
||||
factoriesBinder.addBinding(entry.getKey()).to(entry.getValue());
|
||||
|
|
|
@ -0,0 +1,349 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.watcher.condition.compare;
|
||||
|
||||
import org.elasticsearch.common.ParseField;
|
||||
import org.elasticsearch.common.joda.time.DateTime;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.watcher.condition.Condition;
|
||||
import org.elasticsearch.watcher.support.WatcherDateUtils;
|
||||
import org.elasticsearch.watcher.support.xcontent.WatcherXContentUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.elasticsearch.common.joda.time.DateTimeZone.UTC;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class CompareCondition implements Condition {
|
||||
|
||||
public static final String TYPE = "compare";
|
||||
|
||||
private String path;
|
||||
private Op op;
|
||||
private Object value;
|
||||
|
||||
public CompareCondition(String path, Op op, Object value) {
|
||||
this.path = path;
|
||||
this.op = op;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String type() {
|
||||
return TYPE;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public Op getOp() {
|
||||
return op;
|
||||
}
|
||||
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
CompareCondition condition = (CompareCondition) o;
|
||||
|
||||
if (!path.equals(condition.path)) return false;
|
||||
if (op != condition.op) return false;
|
||||
return !(value != null ? !value.equals(condition.value) : condition.value != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = path.hashCode();
|
||||
result = 31 * result + op.hashCode();
|
||||
result = 31 * result + (value != null ? value.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
return builder.startObject()
|
||||
.startObject(path)
|
||||
.field(op.id(), value)
|
||||
.endObject()
|
||||
.endObject();
|
||||
}
|
||||
|
||||
public static CompareCondition parse(String watchId, XContentParser parser) throws IOException {
|
||||
if (parser.currentToken() != XContentParser.Token.START_OBJECT) {
|
||||
throw new CompareConditionException("could not parse [{}] condition for watch [{}]. expected an object but found [{}] instead", TYPE, watchId, parser.currentToken());
|
||||
}
|
||||
String path = null;
|
||||
Object value = null;
|
||||
Op op = null;
|
||||
|
||||
XContentParser.Token token;
|
||||
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||
if (token == XContentParser.Token.FIELD_NAME) {
|
||||
path = parser.currentName();
|
||||
} else if (path == null) {
|
||||
throw new CompareConditionException("could not parse [{}] condition for watch [{}]. expected a field indicating the compared path, but found [{}] instead", TYPE, watchId, token);
|
||||
} else if (token == XContentParser.Token.START_OBJECT) {
|
||||
token = parser.nextToken();
|
||||
if (token != XContentParser.Token.FIELD_NAME) {
|
||||
throw new CompareConditionException("could not parse [{}] condition for watch [{}]. expected a field indicating the comparison operator, but found [{}] instead", TYPE, watchId, token);
|
||||
}
|
||||
try {
|
||||
op = Op.resolve(parser.currentName());
|
||||
} catch (IllegalArgumentException iae) {
|
||||
throw new CompareConditionException("could not parse [{}] condition for watch [{}]. unknown comparison operator [{}]", TYPE, watchId, parser.currentName());
|
||||
}
|
||||
token = parser.nextToken();
|
||||
if (!op.supportsStructures() && !token.isValue() && token != XContentParser.Token.VALUE_NULL) {
|
||||
throw new CompareConditionException("could not parse [{}] condition for watch [{}]. compared value for [{}] with operation [{}] must either be a numeric, string, boolean or null value, but found [{}] instead", TYPE, watchId, path, op.name().toLowerCase(Locale.ROOT), token);
|
||||
}
|
||||
value = WatcherXContentUtils.readValue(parser, token);
|
||||
token = parser.nextToken();
|
||||
if (token != XContentParser.Token.END_OBJECT) {
|
||||
throw new CompareConditionException("could not parse [{}] condition for watch [{}]. expected end of path object, but found [{}] instead", TYPE, watchId, token);
|
||||
}
|
||||
} else {
|
||||
throw new CompareConditionException("could not parse [{}] condition for watch [{}]. expected an object for field [{}] but found [{}] instead", TYPE, watchId, path, token);
|
||||
}
|
||||
}
|
||||
|
||||
return new CompareCondition(path, op, value);
|
||||
}
|
||||
|
||||
public static class Result extends Condition.Result {
|
||||
|
||||
private final Object resolveValue;
|
||||
|
||||
Result(Object resolveValue, boolean met) {
|
||||
super(TYPE, met);
|
||||
this.resolveValue = resolveValue;
|
||||
}
|
||||
|
||||
public Object getResolveValue() {
|
||||
return resolveValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
return builder.startObject()
|
||||
.field(Field.MET.getPreferredName(), met)
|
||||
.field(Field.RESOLVED_VALUE.getPreferredName(), resolveValue)
|
||||
.endObject();
|
||||
}
|
||||
|
||||
public static Result parse(String watchId, XContentParser parser) throws IOException {
|
||||
Object resolvedValue = null;
|
||||
boolean foundResolvedValue = false;
|
||||
Boolean met = null;
|
||||
|
||||
String currentFieldName = null;
|
||||
XContentParser.Token token;
|
||||
while((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||
if (token == XContentParser.Token.FIELD_NAME) {
|
||||
currentFieldName = parser.currentName();
|
||||
} else if (currentFieldName == null) {
|
||||
throw new CompareConditionException("could not parse condition result [{}] for watcher [{}]. expected a field but found [{}] instead", TYPE, watchId, token);
|
||||
} else if (token == XContentParser.Token.VALUE_BOOLEAN) {
|
||||
if (Field.MET.match(currentFieldName)) {
|
||||
met = parser.booleanValue();
|
||||
} else {
|
||||
throw new CompareConditionException("could not parse condition result [{}] for watcher [{}]. unexpected boolean field [{}]", TYPE, watchId, currentFieldName);
|
||||
}
|
||||
} else if (Field.RESOLVED_VALUE.match(currentFieldName)) {
|
||||
resolvedValue = WatcherXContentUtils.readValue(parser, token);
|
||||
foundResolvedValue = true;
|
||||
} else {
|
||||
throw new CompareConditionException("could not parse condition result [{}] for watcher [{}]. unexpected field [{}]", TYPE, watchId, currentFieldName);
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundResolvedValue) {
|
||||
throw new CompareConditionException("could not parse condition result [{}] for watcher [{}]. missing required field [{}]", TYPE, watchId, Field.RESOLVED_VALUE.getPreferredName());
|
||||
}
|
||||
|
||||
return new Result(resolvedValue, met);
|
||||
}
|
||||
}
|
||||
|
||||
public enum Op {
|
||||
|
||||
EQ() {
|
||||
@Override
|
||||
public boolean eval(Object v1, Object v2) {
|
||||
Integer compVal = compare(v1, v2);
|
||||
return compVal != null && compVal == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsStructures() {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
NOT_EQ() {
|
||||
@Override
|
||||
public boolean eval(Object v1, Object v2) {
|
||||
Integer compVal = compare(v1, v2);
|
||||
return compVal == null || compVal != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsStructures() {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
LT() {
|
||||
@Override
|
||||
public boolean eval(Object v1, Object v2) {
|
||||
Integer compVal = compare(v1, v2);
|
||||
return compVal != null && compVal < 0;
|
||||
}
|
||||
},
|
||||
LTE() {
|
||||
@Override
|
||||
public boolean eval(Object v1, Object v2) {
|
||||
Integer compVal = compare(v1, v2);
|
||||
return compVal != null && compVal <= 0;
|
||||
}
|
||||
},
|
||||
GT() {
|
||||
@Override
|
||||
public boolean eval(Object v1, Object v2) {
|
||||
Integer compVal = compare(v1, v2);
|
||||
return compVal != null && compVal > 0;
|
||||
}
|
||||
},
|
||||
GTE() {
|
||||
@Override
|
||||
public boolean eval(Object v1, Object v2) {
|
||||
Integer compVal = compare(v1, v2);
|
||||
return compVal != null && compVal >= 0;
|
||||
}
|
||||
};
|
||||
|
||||
public abstract boolean eval(Object v1, Object v2);
|
||||
|
||||
public boolean supportsStructures() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// this method performs lenient comparison, potentially between different types. The second argument
|
||||
// type (v2) determines the type of comparison (this is because the second argument is configured by the
|
||||
// user while the first argument is the dynamic path that is evaluated at runtime. That is, if the user configures
|
||||
// a number, it expects a number, therefore the comparison will be based on numeric comparison). If the
|
||||
// comparison is numeric, other types (e.g. strings) will converted to numbers if possible, if not, the comparison
|
||||
// will fail and `false` will be returned.
|
||||
//
|
||||
// may return `null` indicating v1 simply doesn't equal v2 (without any order association)
|
||||
static Integer compare(Object v1, Object v2) {
|
||||
if (Objects.equals(v1, v2)) {
|
||||
return 0;
|
||||
}
|
||||
if (v1 == null || v2 == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// special case for numbers. If v1 is not a number, we'll try to convert it to a number
|
||||
if (v2 instanceof Number) {
|
||||
if (!(v1 instanceof Number)) {
|
||||
try {
|
||||
v1 = Double.valueOf(String.valueOf(v1));
|
||||
} catch (NumberFormatException nfe) {
|
||||
// could not convert to number
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return ((Number) v1).doubleValue() > ((Number) v2).doubleValue() ? 1 :
|
||||
((Number) v1).doubleValue() < ((Number) v2).doubleValue() ? -1 : 0;
|
||||
}
|
||||
|
||||
// special case for strings. If v1 is not a string, we'll convert it to a string
|
||||
if (v2 instanceof String) {
|
||||
v1 = String.valueOf(v1);
|
||||
return ((String) v1).compareTo((String) v2);
|
||||
}
|
||||
|
||||
// special case for date/times. If v1 is not a dateTime, we'll try to convert it to a datetime
|
||||
if (v2 instanceof DateTime) {
|
||||
if (v1 instanceof DateTime) {
|
||||
return ((DateTime) v1).compareTo((DateTime) v2);
|
||||
}
|
||||
if (v1 instanceof String) {
|
||||
try {
|
||||
v1 = WatcherDateUtils.parseDate((String) v1);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
} else if (v1 instanceof Number){
|
||||
v1 = new DateTime(((Number) v1).longValue(), UTC);
|
||||
} else {
|
||||
// cannot convert to date...
|
||||
return null;
|
||||
}
|
||||
return ((DateTime) v1).compareTo((DateTime) v2);
|
||||
}
|
||||
|
||||
if (v1.getClass() != v2.getClass() || Comparable.class.isAssignableFrom(v1.getClass())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return ((Comparable) v1).compareTo(v2);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public String id() {
|
||||
return name().toLowerCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
public static Op resolve(String id) {
|
||||
return Op.valueOf(id.toUpperCase(Locale.ROOT));
|
||||
}
|
||||
}
|
||||
|
||||
public static class Builder implements Condition.Builder<CompareCondition> {
|
||||
|
||||
private String path;
|
||||
private Op op;
|
||||
private Object value;
|
||||
|
||||
public Builder(String path, Op op, Object value) {
|
||||
this.path = path;
|
||||
this.op = op;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public CompareCondition build() {
|
||||
return new CompareCondition(path, op, value);
|
||||
}
|
||||
}
|
||||
|
||||
public static class EvaluationException extends CompareConditionException {
|
||||
|
||||
public EvaluationException(String msg, Object... args) {
|
||||
super(msg, args);
|
||||
}
|
||||
|
||||
public EvaluationException(String msg, Throwable cause, Object... args) {
|
||||
super(msg, cause, args);
|
||||
}
|
||||
}
|
||||
|
||||
interface Field extends Condition.Field {
|
||||
ParseField RESOLVED_VALUE = new ParseField("resolved_value");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.watcher.condition.compare;
|
||||
|
||||
import org.elasticsearch.watcher.condition.ConditionException;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class CompareConditionException extends ConditionException {
|
||||
|
||||
public CompareConditionException(String msg, Object... args) {
|
||||
super(msg, args);
|
||||
}
|
||||
|
||||
public CompareConditionException(String msg, Throwable cause, Object... args) {
|
||||
super(msg, cause, args);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.watcher.condition.compare;
|
||||
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.joda.DateMathParser;
|
||||
import org.elasticsearch.common.logging.Loggers;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.watcher.condition.ConditionFactory;
|
||||
import org.elasticsearch.watcher.support.WatcherDateUtils;
|
||||
import org.elasticsearch.watcher.support.clock.Clock;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class CompareConditionFactory extends ConditionFactory<CompareCondition, CompareCondition.Result, ExecutableCompareCondition> {
|
||||
|
||||
private final Clock clock;
|
||||
|
||||
@Inject
|
||||
public CompareConditionFactory(Settings settings, Clock clock) {
|
||||
super(Loggers.getLogger(ExecutableCompareCondition.class, settings));
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String type() {
|
||||
return CompareCondition.TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompareCondition parseCondition(String watchId, XContentParser parser) throws IOException {
|
||||
return CompareCondition.parse(watchId, parser);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompareCondition.Result parseResult(String watchId, XContentParser parser) throws IOException {
|
||||
return CompareCondition.Result.parse(watchId, parser);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutableCompareCondition createExecutable(CompareCondition condition) {
|
||||
return new ExecutableCompareCondition(condition, conditionLogger, clock);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.watcher.condition.compare;
|
||||
|
||||
import org.elasticsearch.common.logging.ESLogger;
|
||||
import org.elasticsearch.watcher.condition.ExecutableCondition;
|
||||
import org.elasticsearch.watcher.execution.WatchExecutionContext;
|
||||
import org.elasticsearch.watcher.support.Variables;
|
||||
import org.elasticsearch.watcher.support.WatcherDateUtils;
|
||||
import org.elasticsearch.watcher.support.clock.Clock;
|
||||
import org.elasticsearch.watcher.support.xcontent.MapPath;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.elasticsearch.common.joda.time.DateTimeZone.UTC;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class ExecutableCompareCondition extends ExecutableCondition<CompareCondition, CompareCondition.Result> {
|
||||
|
||||
static final Pattern DATE_MATH_PATTERN = Pattern.compile("<\\{(.+)\\}>");
|
||||
static final Pattern PATH_PATTERN = Pattern.compile("\\{\\{(.+)\\}\\}");
|
||||
|
||||
|
||||
private final Clock clock;
|
||||
|
||||
public ExecutableCompareCondition(CompareCondition condition, ESLogger logger, Clock clock) {
|
||||
super(condition, logger);
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompareCondition.Result execute(WatchExecutionContext ctx) throws IOException {
|
||||
Map<String, Object> model = Variables.createCtxModel(ctx, ctx.payload());
|
||||
|
||||
Object configuredValue = condition.getValue();
|
||||
|
||||
if (configuredValue instanceof String) {
|
||||
|
||||
// checking if the given value is a date math expression
|
||||
Matcher matcher = DATE_MATH_PATTERN.matcher((String) configuredValue);
|
||||
if (matcher.matches()) {
|
||||
String dateMath = matcher.group(1);
|
||||
configuredValue = WatcherDateUtils.parseDateMath(dateMath, UTC, clock);
|
||||
} else {
|
||||
// checking if the given value is a path expression
|
||||
matcher = PATH_PATTERN.matcher((String) configuredValue);
|
||||
if (matcher.matches()) {
|
||||
String configuredPath = matcher.group(1);
|
||||
configuredValue = MapPath.eval(configuredPath, model);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Object resolvedValue = MapPath.eval(condition.getPath(), model);
|
||||
|
||||
return new CompareCondition.Result(resolvedValue, condition.getOp().eval(resolvedValue, configuredValue));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.watcher.support.xcontent;
|
||||
|
||||
import org.elasticsearch.common.Strings;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class MapPath {
|
||||
|
||||
private MapPath() {
|
||||
}
|
||||
|
||||
public static <T> T eval(String path, Map<String, Object> map) {
|
||||
return (T) eval(path, (Object) map);
|
||||
}
|
||||
|
||||
private static Object eval(String path, Object ctx) {
|
||||
String[] parts = Strings.splitStringToArray(path, '.');
|
||||
StringBuilder resolved = new StringBuilder();
|
||||
for (String part : parts) {
|
||||
if (ctx == null) {
|
||||
return null;
|
||||
}
|
||||
if (ctx instanceof Map) {
|
||||
ctx = ((Map) ctx).get(part);
|
||||
if (resolved.length() != 0) {
|
||||
resolved.append(".");
|
||||
}
|
||||
resolved.append(part);
|
||||
} else if (ctx instanceof List) {
|
||||
try {
|
||||
int index = Integer.parseInt(part);
|
||||
ctx = ((List) ctx).get(index);
|
||||
if (resolved.length() != 0) {
|
||||
resolved.append(".");
|
||||
}
|
||||
resolved.append(part);
|
||||
} catch (NumberFormatException nfe) {
|
||||
return null;
|
||||
}
|
||||
} else if (ctx.getClass().isArray()) {
|
||||
try {
|
||||
int index = Integer.parseInt(part);
|
||||
ctx = Array.get(ctx, index);
|
||||
if (resolved.length() != 0) {
|
||||
resolved.append(".");
|
||||
}
|
||||
resolved.append(part);
|
||||
} catch (NumberFormatException nfe) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.watcher.support.xcontent;
|
||||
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class WatcherXContentUtils {
|
||||
|
||||
private WatcherXContentUtils() {
|
||||
}
|
||||
|
||||
// TODO open this up in core
|
||||
public static List<Object> readList(XContentParser parser, XContentParser.Token token) throws IOException {
|
||||
ArrayList<Object> list = new ArrayList<>();
|
||||
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
|
||||
list.add(readValue(parser, token));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
// TODO open this up in core
|
||||
public static Object readValue(XContentParser parser, XContentParser.Token token) throws IOException {
|
||||
if (token == XContentParser.Token.VALUE_NULL) {
|
||||
return null;
|
||||
} else if (token == XContentParser.Token.VALUE_STRING) {
|
||||
return parser.text();
|
||||
} else if (token == XContentParser.Token.VALUE_NUMBER) {
|
||||
XContentParser.NumberType numberType = parser.numberType();
|
||||
if (numberType == XContentParser.NumberType.INT) {
|
||||
return parser.intValue();
|
||||
} else if (numberType == XContentParser.NumberType.LONG) {
|
||||
return parser.longValue();
|
||||
} else if (numberType == XContentParser.NumberType.FLOAT) {
|
||||
return parser.floatValue();
|
||||
} else if (numberType == XContentParser.NumberType.DOUBLE) {
|
||||
return parser.doubleValue();
|
||||
}
|
||||
} else if (token == XContentParser.Token.VALUE_BOOLEAN) {
|
||||
return parser.booleanValue();
|
||||
} else if (token == XContentParser.Token.START_OBJECT) {
|
||||
return parser.map();
|
||||
} else if (token == XContentParser.Token.START_ARRAY) {
|
||||
return readList(parser, token);
|
||||
} else if (token == XContentParser.Token.VALUE_EMBEDDED_OBJECT) {
|
||||
return parser.binaryValue();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -11,7 +11,6 @@ import org.elasticsearch.common.collect.Tuple;
|
|||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
import org.elasticsearch.common.xcontent.*;
|
||||
import org.elasticsearch.common.xcontent.support.XContentMapValues;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
@ -59,7 +58,7 @@ public class XContentSource implements ToXContent {
|
|||
* @return The extracted value or {@code null} if no value is associated with the given path
|
||||
*/
|
||||
public <T> T getValue(String path) {
|
||||
return (T) XContentMapValues.extractValue(path, getAsMap());
|
||||
return (T) MapPath.eval(path, getAsMap());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -128,6 +128,20 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"condition" : {
|
||||
"type" : "object",
|
||||
"dynamic" : true,
|
||||
"properties" : {
|
||||
"compare" : {
|
||||
"type" : "object",
|
||||
"enabled" : false
|
||||
},
|
||||
"script" : {
|
||||
"type" : "object",
|
||||
"enabled" : false
|
||||
}
|
||||
}
|
||||
},
|
||||
"transform" : {
|
||||
"type" : "object",
|
||||
"dynamic" : true,
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.watcher.condition.compare;
|
||||
|
||||
import org.elasticsearch.action.search.SearchResponse;
|
||||
import org.elasticsearch.action.search.ShardSearchFailure;
|
||||
import org.elasticsearch.common.text.StringText;
|
||||
import org.elasticsearch.search.SearchShardTarget;
|
||||
import org.elasticsearch.search.aggregations.AggregationBuilders;
|
||||
import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogram;
|
||||
import org.elasticsearch.search.aggregations.bucket.histogram.Histogram;
|
||||
import org.elasticsearch.search.internal.InternalSearchHit;
|
||||
import org.elasticsearch.search.internal.InternalSearchHits;
|
||||
import org.elasticsearch.search.internal.InternalSearchResponse;
|
||||
import org.elasticsearch.watcher.execution.WatchExecutionContext;
|
||||
import org.elasticsearch.watcher.support.clock.SystemClock;
|
||||
import org.elasticsearch.watcher.test.AbstractWatcherIntegrationTests;
|
||||
import org.elasticsearch.watcher.watch.Payload;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.elasticsearch.watcher.test.WatcherTestUtils.mockExecutionContext;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class CompareConditionSearchTests extends AbstractWatcherIntegrationTests {
|
||||
|
||||
@Test
|
||||
public void testExecute_withAggs() throws Exception {
|
||||
|
||||
client().admin().indices().prepareCreate("my-index")
|
||||
.addMapping("my-type", "_timestamp", "enabled=true")
|
||||
.get();
|
||||
|
||||
client().prepareIndex("my-index", "my-type").setTimestamp("2005-01-01T00:00").setSource("{}").get();
|
||||
client().prepareIndex("my-index", "my-type").setTimestamp("2005-01-01T00:10").setSource("{}").get();
|
||||
client().prepareIndex("my-index", "my-type").setTimestamp("2005-01-01T00:20").setSource("{}").get();
|
||||
client().prepareIndex("my-index", "my-type").setTimestamp("2005-01-01T00:30").setSource("{}").get();
|
||||
refresh();
|
||||
|
||||
SearchResponse response = client().prepareSearch("my-index")
|
||||
.addAggregation(AggregationBuilders.dateHistogram("rate").field("_timestamp").interval(DateHistogram.Interval.HOUR).order(Histogram.Order.COUNT_DESC))
|
||||
.get();
|
||||
|
||||
ExecutableCompareCondition condition = new ExecutableCompareCondition(new CompareCondition("ctx.payload.aggregations.rate.buckets.0.doc_count", CompareCondition.Op.GTE, 5), logger, SystemClock.INSTANCE);
|
||||
|
||||
WatchExecutionContext ctx = mockExecutionContext("_name", new Payload.XContent(response));
|
||||
assertThat(condition.execute(ctx).met(), is(false));
|
||||
|
||||
client().prepareIndex("my-index", "my-type").setTimestamp("2005-01-01T00:40").setSource("{}").get();
|
||||
refresh();
|
||||
|
||||
response = client().prepareSearch("my-index")
|
||||
.addAggregation(AggregationBuilders.dateHistogram("rate").field("_timestamp").interval(DateHistogram.Interval.HOUR).order(Histogram.Order.COUNT_DESC))
|
||||
.get();
|
||||
|
||||
ctx = mockExecutionContext("_name", new Payload.XContent(response));
|
||||
assertThat(condition.execute(ctx).met(), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_accessHits() throws Exception {
|
||||
ExecutableCompareCondition condition = new ExecutableCompareCondition(new CompareCondition("ctx.payload.hits.hits.0._score", CompareCondition.Op.EQ, 1), logger, SystemClock.INSTANCE);
|
||||
InternalSearchHit hit = new InternalSearchHit(0, "1", new StringText("type"), null);
|
||||
hit.score(1f);
|
||||
hit.shard(new SearchShardTarget("a", "a", 0));
|
||||
|
||||
InternalSearchResponse internalSearchResponse = new InternalSearchResponse(new InternalSearchHits(new InternalSearchHit[]{hit}, 1l, 1f), null, null, null, false, null);
|
||||
SearchResponse response = new SearchResponse(internalSearchResponse, "", 3, 3, 500l, new ShardSearchFailure[0]);
|
||||
|
||||
WatchExecutionContext ctx = mockExecutionContext("_watch_name", new Payload.XContent(response));
|
||||
assertThat(condition.execute(ctx).met(), is(true));
|
||||
hit.score(2f);
|
||||
when(ctx.payload()).thenReturn(new Payload.XContent(response));
|
||||
assertThat(condition.execute(ctx).met(), is(false));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,286 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.watcher.condition.compare;
|
||||
|
||||
import com.carrotsearch.randomizedtesting.annotations.Repeat;
|
||||
import com.carrotsearch.randomizedtesting.annotations.Seed;
|
||||
import org.elasticsearch.common.collect.ImmutableList;
|
||||
import org.elasticsearch.common.collect.ImmutableMap;
|
||||
import org.elasticsearch.common.joda.time.DateTime;
|
||||
import org.elasticsearch.common.settings.ImmutableSettings;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.common.xcontent.json.JsonXContent;
|
||||
import org.elasticsearch.test.ElasticsearchTestCase;
|
||||
import org.elasticsearch.watcher.condition.compare.CompareCondition.Op;
|
||||
import org.elasticsearch.watcher.execution.WatchExecutionContext;
|
||||
import org.elasticsearch.watcher.support.clock.ClockMock;
|
||||
import org.elasticsearch.watcher.support.clock.SystemClock;
|
||||
import org.elasticsearch.watcher.watch.Payload;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
|
||||
import static org.elasticsearch.watcher.test.WatcherTestUtils.mockExecutionContext;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
|
||||
/**
|
||||
*/
|
||||
@Seed("9E70A915296AA3F2:FAA20587D7DCA86B")
|
||||
public class CompareConditionTests extends ElasticsearchTestCase {
|
||||
|
||||
@Test
|
||||
public void testOpEval_EQ() throws Exception {
|
||||
assertThat(Op.EQ.eval(null, null), is(true));
|
||||
assertThat(Op.EQ.eval(4, 3.0), is(false));
|
||||
assertThat(Op.EQ.eval(3, 3.0), is(true));
|
||||
assertThat(Op.EQ.eval(2, new Float(3.0)), is(false));
|
||||
assertThat(Op.EQ.eval(3, null), is(false));
|
||||
assertThat(Op.EQ.eval(2, "2"), is(true)); // comparing as strings
|
||||
assertThat(Op.EQ.eval(3, "4"), is(false)); // comparing as strings
|
||||
assertThat(Op.EQ.eval(3, "a"), is(false)); // comparing as strings
|
||||
assertThat(Op.EQ.eval("3", 3), is(true)); // comparing as numbers
|
||||
assertThat(Op.EQ.eval("a", "aa"), is(false));
|
||||
assertThat(Op.EQ.eval("a", "a"), is(true));
|
||||
assertThat(Op.EQ.eval("aa", "ab"), is(false));
|
||||
assertThat(Op.EQ.eval(ImmutableMap.of("k", "v"), ImmutableMap.of("k", "v")), is(true));
|
||||
assertThat(Op.EQ.eval(ImmutableMap.of("k", "v"), ImmutableMap.of("k1", "v1")), is(false));
|
||||
assertThat(Op.EQ.eval(ImmutableList.of("k", "v"), ImmutableList.of("k", "v")), is(true));
|
||||
assertThat(Op.EQ.eval(ImmutableList.of("k", "v"), ImmutableList.of("k1", "v1")), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOpEval_NOT_EQ() throws Exception {
|
||||
assertThat(Op.NOT_EQ.eval(null, null), is(false));
|
||||
assertThat(Op.NOT_EQ.eval(4, 3.0), is(true));
|
||||
assertThat(Op.NOT_EQ.eval(3, 3.0), is(false));
|
||||
assertThat(Op.NOT_EQ.eval(2, new Float(3.0)), is(true));
|
||||
assertThat(Op.NOT_EQ.eval(3, null), is(true));
|
||||
assertThat(Op.NOT_EQ.eval(2, "2"), is(false)); // comparing as strings
|
||||
assertThat(Op.NOT_EQ.eval(3, "4"), is(true)); // comparing as strings
|
||||
assertThat(Op.NOT_EQ.eval(3, "a"), is(true)); // comparing as strings
|
||||
assertThat(Op.NOT_EQ.eval("3", 3), is(false)); // comparing as numbers
|
||||
assertThat(Op.NOT_EQ.eval("a", "aa"), is(true));
|
||||
assertThat(Op.NOT_EQ.eval("a", "a"), is(false));
|
||||
assertThat(Op.NOT_EQ.eval("aa", "ab"), is(true));
|
||||
assertThat(Op.NOT_EQ.eval(ImmutableMap.of("k", "v"), ImmutableMap.of("k", "v")), is(false));
|
||||
assertThat(Op.NOT_EQ.eval(ImmutableMap.of("k", "v"), ImmutableMap.of("k1", "v1")), is(true));
|
||||
assertThat(Op.NOT_EQ.eval(ImmutableList.of("k", "v"), ImmutableList.of("k", "v")), is(false));
|
||||
assertThat(Op.NOT_EQ.eval(ImmutableList.of("k", "v"), ImmutableList.of("k1", "v1")), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOpEval_GTE() throws Exception {
|
||||
assertThat(Op.GTE.eval(4, 3.0), is(true));
|
||||
assertThat(Op.GTE.eval(3, 3.0), is(true));
|
||||
assertThat(Op.GTE.eval(2, new Float(3.0)), is(false));
|
||||
assertThat(Op.GTE.eval(3, null), is(false));
|
||||
assertThat(Op.GTE.eval(3, "2"), is(true)); // comparing as strings
|
||||
assertThat(Op.GTE.eval(3, "4"), is(false)); // comparing as strings
|
||||
assertThat(Op.GTE.eval(3, "a"), is(false)); // comparing as strings
|
||||
assertThat(Op.GTE.eval("4", 3), is(true)); // comparing as numbers
|
||||
assertThat(Op.GTE.eval("a", "aa"), is(false));
|
||||
assertThat(Op.GTE.eval("a", "a"), is(true));
|
||||
assertThat(Op.GTE.eval("aa", "ab"), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOpEval_GT() throws Exception {
|
||||
assertThat(Op.GT.eval(4, 3.0), is(true));
|
||||
assertThat(Op.GT.eval(3, 3.0), is(false));
|
||||
assertThat(Op.GT.eval(2, new Float(3.0)), is(false));
|
||||
assertThat(Op.GT.eval(3, null), is(false));
|
||||
assertThat(Op.GT.eval(3, "2"), is(true)); // comparing as strings
|
||||
assertThat(Op.GT.eval(3, "4"), is(false)); // comparing as strings
|
||||
assertThat(Op.GT.eval(3, "a"), is(false)); // comparing as strings
|
||||
assertThat(Op.GT.eval("4", 3), is(true)); // comparing as numbers
|
||||
assertThat(Op.GT.eval("a", "aa"), is(false));
|
||||
assertThat(Op.GT.eval("a", "a"), is(false));
|
||||
assertThat(Op.GT.eval("aa", "ab"), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOpEval_LTE() throws Exception {
|
||||
assertThat(Op.LTE.eval(4, 3.0), is(false));
|
||||
assertThat(Op.LTE.eval(3, 3.0), is(true));
|
||||
assertThat(Op.LTE.eval(2, new Float(3.0)), is(true));
|
||||
assertThat(Op.LTE.eval(3, null), is(false));
|
||||
assertThat(Op.LTE.eval(3, "2"), is(false)); // comparing as strings
|
||||
assertThat(Op.LTE.eval(3, "4"), is(true)); // comparing as strings
|
||||
assertThat(Op.LTE.eval(3, "a"), is(true)); // comparing as strings
|
||||
assertThat(Op.LTE.eval("4", 3), is(false)); // comparing as numbers
|
||||
assertThat(Op.LTE.eval("a", "aa"), is(true));
|
||||
assertThat(Op.LTE.eval("a", "a"), is(true));
|
||||
assertThat(Op.LTE.eval("aa", "ab"), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOpEval_LT() throws Exception {
|
||||
assertThat(Op.LT.eval(4, 3.0), is(false));
|
||||
assertThat(Op.LT.eval(3, 3.0), is(false));
|
||||
assertThat(Op.LT.eval(2, new Float(3.0)), is(true));
|
||||
assertThat(Op.LT.eval(3, null), is(false));
|
||||
assertThat(Op.LT.eval(3, "2"), is(false)); // comparing as strings
|
||||
assertThat(Op.LT.eval(3, "4"), is(true)); // comparing as strings
|
||||
assertThat(Op.LT.eval(3, "a"), is(true)); // comparing as strings
|
||||
assertThat(Op.LT.eval("4", 3), is(false)); // comparing as numbers
|
||||
assertThat(Op.LT.eval("a", "aa"), is(true));
|
||||
assertThat(Op.LT.eval("a", "a"), is(false));
|
||||
assertThat(Op.LT.eval("aa", "ab"), is(true));
|
||||
}
|
||||
|
||||
@Test @Repeat(iterations = 10)
|
||||
public void testExecute() throws Exception {
|
||||
Op op = randomFrom(Op.values());
|
||||
int value = randomInt(10);
|
||||
int payloadValue = randomInt(10);
|
||||
boolean met = op.eval(payloadValue, value);
|
||||
|
||||
ExecutableCompareCondition condition = new ExecutableCompareCondition(new CompareCondition("ctx.payload.value", op, value), logger, SystemClock.INSTANCE);
|
||||
WatchExecutionContext ctx = mockExecutionContext("_name", new Payload.Simple("value", payloadValue));
|
||||
assertThat(condition.execute(ctx).met(), is(met));
|
||||
}
|
||||
|
||||
@Test @Repeat(iterations = 10)
|
||||
public void testExecute_DateMath() throws Exception {
|
||||
ClockMock clock = new ClockMock();
|
||||
boolean met = randomBoolean();
|
||||
Op op = met ? randomFrom(Op.GT, Op.GTE, Op.NOT_EQ) : randomFrom(Op.LT, Op.LTE, Op.EQ);
|
||||
String value = "<{now-1d}>";
|
||||
DateTime payloadValue = clock.now();
|
||||
|
||||
ExecutableCompareCondition condition = new ExecutableCompareCondition(new CompareCondition("ctx.payload.value", op, value), logger, clock);
|
||||
WatchExecutionContext ctx = mockExecutionContext("_name", new Payload.Simple("value", payloadValue));
|
||||
assertThat(condition.execute(ctx).met(), is(met));
|
||||
}
|
||||
|
||||
@Test @Repeat(iterations = 5)
|
||||
public void testExecute_Path() throws Exception {
|
||||
ClockMock clock = new ClockMock();
|
||||
boolean met = randomBoolean();
|
||||
Op op = met ? Op.EQ : Op.NOT_EQ;
|
||||
String value = "{{ctx.payload.value}}";
|
||||
Object payloadValue = new Object();
|
||||
|
||||
ExecutableCompareCondition condition = new ExecutableCompareCondition(new CompareCondition("ctx.payload.value", op, value), logger, clock);
|
||||
WatchExecutionContext ctx = mockExecutionContext("_name", new Payload.Simple("value", payloadValue));
|
||||
assertThat(condition.execute(ctx).met(), is(met));
|
||||
}
|
||||
|
||||
|
||||
@Test @Repeat(iterations = 10)
|
||||
public void testParse_Valid() throws Exception {
|
||||
Op op = randomFrom(Op.values());
|
||||
Object value = randomFrom("value", 1, null);
|
||||
CompareConditionFactory factory = new CompareConditionFactory(ImmutableSettings.EMPTY, SystemClock.INSTANCE);
|
||||
XContentBuilder builder = jsonBuilder();
|
||||
builder.startObject();
|
||||
builder.startObject("key1.key2");
|
||||
builder.field(op.name().toLowerCase(Locale.ROOT), value);
|
||||
builder.endObject();
|
||||
builder.endObject();
|
||||
|
||||
XContentParser parser = JsonXContent.jsonXContent.createParser(builder.bytes());
|
||||
parser.nextToken();
|
||||
|
||||
CompareCondition condition = factory.parseCondition("_id", parser);
|
||||
|
||||
assertThat(condition, notNullValue());
|
||||
assertThat(condition.getPath(), is("key1.key2"));
|
||||
assertThat(condition.getOp(), is(op));
|
||||
assertThat(condition.getValue(), is(value));
|
||||
}
|
||||
|
||||
@Test(expected = CompareConditionException.class)
|
||||
public void testParse_InValid_NoOperationBody() throws Exception {
|
||||
CompareConditionFactory factory = new CompareConditionFactory(ImmutableSettings.EMPTY, SystemClock.INSTANCE);
|
||||
XContentBuilder builder = jsonBuilder();
|
||||
builder.startObject();
|
||||
builder.startObject("key1.key2");
|
||||
builder.endObject();
|
||||
builder.endObject();
|
||||
|
||||
XContentParser parser = JsonXContent.jsonXContent.createParser(builder.bytes());
|
||||
factory.parseCondition("_id", parser);
|
||||
}
|
||||
|
||||
@Test(expected = CompareConditionException.class)
|
||||
public void testParse_InValid_UnknownOp() throws Exception {
|
||||
Object value = randomFrom("value", 1, null);
|
||||
CompareConditionFactory factory = new CompareConditionFactory(ImmutableSettings.EMPTY, SystemClock.INSTANCE);
|
||||
XContentBuilder builder = jsonBuilder();
|
||||
builder.startObject();
|
||||
builder.startObject("key1.key2");
|
||||
builder.field("foobar", value);
|
||||
builder.endObject();
|
||||
builder.endObject();
|
||||
|
||||
XContentParser parser = JsonXContent.jsonXContent.createParser(builder.bytes());
|
||||
parser.nextToken();
|
||||
|
||||
factory.parseCondition("_id", parser);
|
||||
}
|
||||
|
||||
@Test(expected = CompareConditionException.class) @Repeat(iterations = 10)
|
||||
public void testParse_InValid_WrongValueForOp() throws Exception {
|
||||
Object value = randomFrom(ImmutableList.of("1", "2"), ImmutableMap.of("key", "value"));
|
||||
String op = randomFrom("lt", "lte", "gt", "gte");
|
||||
CompareConditionFactory factory = new CompareConditionFactory(ImmutableSettings.EMPTY, SystemClock.INSTANCE);
|
||||
XContentBuilder builder = jsonBuilder();
|
||||
builder.startObject();
|
||||
builder.startObject("key1.key2");
|
||||
builder.field(op, value);
|
||||
builder.endObject();
|
||||
builder.endObject();
|
||||
|
||||
XContentParser parser = JsonXContent.jsonXContent.createParser(builder.bytes());
|
||||
parser.nextToken();
|
||||
|
||||
factory.parseCondition("_id", parser);
|
||||
}
|
||||
|
||||
|
||||
@Test @Repeat(iterations = 10)
|
||||
public void testParse_Result_Valid() throws Exception {
|
||||
CompareConditionFactory factory = new CompareConditionFactory(ImmutableSettings.EMPTY, SystemClock.INSTANCE);
|
||||
|
||||
boolean met = randomBoolean();
|
||||
Object resolvedValue = randomFrom("1", 5, null, ImmutableList.of("1", "2"), ImmutableMap.of("key", "value"));
|
||||
|
||||
XContentBuilder builder = jsonBuilder();
|
||||
builder.startObject();
|
||||
builder.field("met", met);
|
||||
builder.field("resolved_value", resolvedValue);
|
||||
builder.endObject();
|
||||
|
||||
XContentParser parser = JsonXContent.jsonXContent.createParser(builder.bytes());
|
||||
parser.nextToken();
|
||||
|
||||
CompareCondition.Result result = factory.parseResult("_id", parser);
|
||||
assertThat(result, notNullValue());
|
||||
assertThat(result.met(), is(met));
|
||||
assertThat(result.getResolveValue(), is(resolvedValue));
|
||||
}
|
||||
|
||||
@Test(expected = CompareConditionException.class)
|
||||
public void testParse_Result_Invalid_MissingResolvedValue() throws Exception {
|
||||
CompareConditionFactory factory = new CompareConditionFactory(ImmutableSettings.EMPTY, SystemClock.INSTANCE);
|
||||
|
||||
boolean met = randomBoolean();
|
||||
|
||||
XContentBuilder builder = jsonBuilder();
|
||||
builder.startObject();
|
||||
builder.field("met", met);
|
||||
builder.endObject();
|
||||
|
||||
XContentParser parser = JsonXContent.jsonXContent.createParser(builder.bytes());
|
||||
parser.nextToken();
|
||||
|
||||
factory.parseResult("_id", parser);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.watcher.support.xcontent;
|
||||
|
||||
import com.carrotsearch.randomizedtesting.annotations.Repeat;
|
||||
import org.elasticsearch.common.collect.ImmutableList;
|
||||
import org.elasticsearch.common.collect.ImmutableMap;
|
||||
import org.elasticsearch.test.ElasticsearchTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.nullValue;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class MapPathTests extends ElasticsearchTestCase {
|
||||
|
||||
@Test
|
||||
public void testEval() throws Exception {
|
||||
Map<String, Object> map = ImmutableMap.<String, Object>builder()
|
||||
.put("key", "value")
|
||||
.build();
|
||||
|
||||
assertThat(MapPath.eval("key", map), is((Object) "value"));
|
||||
assertThat(MapPath.eval("key1", map), nullValue());
|
||||
}
|
||||
|
||||
@Test @Repeat(iterations = 5)
|
||||
public void testEval_List() throws Exception {
|
||||
List list = ImmutableList.of(1, 2, 3, 4);
|
||||
Map<String, Object> map = ImmutableMap.<String, Object>builder()
|
||||
.put("key", list)
|
||||
.build();
|
||||
|
||||
int index = randomInt(3);
|
||||
assertThat(MapPath.eval("key." + index, map), is(list.get(index)));
|
||||
}
|
||||
|
||||
@Test @Repeat(iterations = 5)
|
||||
public void testEval_Array() throws Exception {
|
||||
int[] array = new int[] { 1, 2, 3, 4 };
|
||||
Map<String, Object> map = ImmutableMap.<String, Object>builder()
|
||||
.put("key", array)
|
||||
.build();
|
||||
|
||||
int index = randomInt(3);
|
||||
assertThat(((Number) MapPath.eval("key." + index, map)).intValue(), is(array[index]));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEval_Map() throws Exception {
|
||||
Map<String, Object> map = ImmutableMap.<String, Object>builder()
|
||||
.put("a", ImmutableMap.of("b", "val"))
|
||||
.build();
|
||||
|
||||
assertThat(MapPath.eval("a.b", map), is((Object) "val"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testEval_Mixed() throws Exception {
|
||||
Map<String, Object> map = ImmutableMap.<String, Object>builder()
|
||||
.put("a", ImmutableMap.builder()
|
||||
.put("b", ImmutableList.builder()
|
||||
.add(ImmutableList.builder()
|
||||
.add(ImmutableMap.builder()
|
||||
.put("c", "val")
|
||||
.build())
|
||||
.build())
|
||||
.build())
|
||||
.build())
|
||||
.build();
|
||||
|
||||
assertThat(MapPath.eval("", map), is((Object) map));
|
||||
assertThat(MapPath.eval("a.b.0.0.c", map), is((Object) "val"));
|
||||
assertThat(MapPath.eval("a.b.0.0.c.d", map), nullValue());
|
||||
assertThat(MapPath.eval("a.b.0.0.d", map), nullValue());
|
||||
assertThat(MapPath.eval("a.b.c", map), nullValue());
|
||||
|
||||
}
|
||||
}
|
|
@ -144,11 +144,8 @@ public class EmailSecretsIntegrationTests extends AbstractWatcherIntegrationTest
|
|||
.get();
|
||||
assertThat(executeResponse, notNullValue());
|
||||
contentSource = executeResponse.getSource();
|
||||
value = contentSource.getValue("execution_result.actions.email.success");
|
||||
assertThat(value, instanceOf(List.class));
|
||||
List<Boolean> values = (List<Boolean>) value;
|
||||
assertThat(values, hasSize(1));
|
||||
assertThat(values, hasItem(Boolean.TRUE));
|
||||
value = contentSource.getValue("execution_result.actions.0.email.success");
|
||||
assertThat((Boolean) value, is(true));
|
||||
|
||||
if (!latch.await(5, TimeUnit.SECONDS)) {
|
||||
fail("waiting too long for the email to be sent");
|
||||
|
|
|
@ -32,7 +32,6 @@ import org.junit.Before;
|
|||
import org.junit.Test;
|
||||
|
||||
import java.net.BindException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.elasticsearch.common.joda.time.DateTimeZone.UTC;
|
||||
|
@ -226,24 +225,18 @@ public class HttpSecretsIntegrationTests extends AbstractWatcherIntegrationTests
|
|||
assertThat(executeResponse, notNullValue());
|
||||
contentSource = executeResponse.getSource();
|
||||
|
||||
value = contentSource.getValue("execution_result.actions.webhook.response.status");
|
||||
assertThat(value, instanceOf(List.class));
|
||||
value = contentSource.getValue("execution_result.actions.0.webhook.response.status");
|
||||
assertThat(value, notNullValue());
|
||||
List<Number> values = (List<Number>) value;
|
||||
assertThat(values, hasSize(1));
|
||||
assertThat(values, hasItem(200));
|
||||
assertThat(value, instanceOf(Number.class));
|
||||
assertThat(((Number) value).intValue(), is(200));
|
||||
|
||||
value = contentSource.getValue("execution_result.actions.webhook.request.auth.username");
|
||||
value = contentSource.getValue("execution_result.actions.0.webhook.request.auth.username");
|
||||
assertThat(value, notNullValue());
|
||||
assertThat(value, instanceOf(List.class));
|
||||
values = (List<Number>) value;
|
||||
assertThat(values, hasSize(1)); // the auth username exists
|
||||
assertThat(value, instanceOf(String.class));
|
||||
assertThat((String) value, is(USERNAME)); // the auth username exists
|
||||
|
||||
value = contentSource.getValue("execution_result.actions.webhook.request.auth.password");
|
||||
assertThat(value, notNullValue());
|
||||
assertThat(value, instanceOf(List.class));
|
||||
values = (List<Number>) value;
|
||||
assertThat(values, hasSize(0)); // but the auth password was filtered out
|
||||
value = contentSource.getValue("execution_result.actions.0.webhook.request.auth.password");
|
||||
assertThat(value, nullValue()); // but the auth password was filtered out
|
||||
|
||||
RecordedRequest request = webServer.takeRequest();
|
||||
assertThat(request.getHeader("Authorization"), equalTo(ApplicableBasicAuth.headerValue(USERNAME, PASSWORD.toCharArray())));
|
||||
|
|
|
@ -33,7 +33,6 @@ import java.net.BindException;
|
|||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
|
||||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures;
|
||||
import static org.elasticsearch.watcher.client.WatchSourceBuilders.watchBuilder;
|
||||
|
@ -120,15 +119,13 @@ public class WebhookHttpsIntegrationTests extends AbstractWatcherIntegrationTest
|
|||
.get();
|
||||
assertNoFailures(response);
|
||||
XContentSource source = new XContentSource(response.getHits().getAt(0).sourceRef());
|
||||
List<String> bodies = source.getValue("execution_result.actions.webhook.response.body");
|
||||
assertThat(bodies, notNullValue());
|
||||
assertThat(bodies, hasSize(1));
|
||||
assertThat(bodies, hasItem("body"));
|
||||
String body = source.getValue("execution_result.actions.0.webhook.response.body");
|
||||
assertThat(body, notNullValue());
|
||||
assertThat(body, is("body"));
|
||||
|
||||
List<Number> statuses = source.getValue("execution_result.actions.webhook.response.status");
|
||||
assertThat(statuses, notNullValue());
|
||||
assertThat(statuses, hasSize(1));
|
||||
assertThat(statuses, hasItem(200));
|
||||
Number status = source.getValue("execution_result.actions.0.webhook.response.status");
|
||||
assertThat(status, notNullValue());
|
||||
assertThat(status.intValue(), is(200));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -26,7 +26,6 @@ import org.junit.Before;
|
|||
import org.junit.Test;
|
||||
|
||||
import java.net.BindException;
|
||||
import java.util.List;
|
||||
|
||||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures;
|
||||
import static org.elasticsearch.watcher.client.WatchSourceBuilders.watchBuilder;
|
||||
|
@ -98,12 +97,12 @@ public class WebhookIntegrationTests extends AbstractWatcherIntegrationTests {
|
|||
|
||||
assertNoFailures(response);
|
||||
XContentSource source = new XContentSource(response.getHits().getAt(0).getSourceRef());
|
||||
List<String> bodies = source.getValue("execution_result.actions.webhook.response.body");
|
||||
assertThat(bodies, notNullValue());
|
||||
assertThat(bodies, hasItem("body"));
|
||||
List<Number> statuses = source.getValue("execution_result.actions.webhook.response.status");
|
||||
assertThat(statuses, notNullValue());
|
||||
assertThat(statuses, hasItem(200));
|
||||
String body = source.getValue("execution_result.actions.0.webhook.response.body");
|
||||
assertThat(body, notNullValue());
|
||||
assertThat(body, is("body"));
|
||||
Number status = source.getValue("execution_result.actions.0.webhook.response.status");
|
||||
assertThat(status, notNullValue());
|
||||
assertThat(status.intValue(), is(200));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
package org.elasticsearch.watcher.watch;
|
||||
|
||||
import com.carrotsearch.ant.tasks.junit4.dependencies.com.google.common.collect.ImmutableSet;
|
||||
import com.carrotsearch.randomizedtesting.annotations.Repeat;
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.common.collect.ImmutableList;
|
||||
import org.elasticsearch.common.collect.ImmutableMap;
|
||||
|
@ -42,6 +41,10 @@ import org.elasticsearch.watcher.condition.ExecutableCondition;
|
|||
import org.elasticsearch.watcher.condition.always.AlwaysCondition;
|
||||
import org.elasticsearch.watcher.condition.always.AlwaysConditionFactory;
|
||||
import org.elasticsearch.watcher.condition.always.ExecutableAlwaysCondition;
|
||||
import org.elasticsearch.watcher.condition.compare.CompareCondition;
|
||||
import org.elasticsearch.watcher.condition.compare.CompareCondition.Op;
|
||||
import org.elasticsearch.watcher.condition.compare.CompareConditionFactory;
|
||||
import org.elasticsearch.watcher.condition.compare.ExecutableCompareCondition;
|
||||
import org.elasticsearch.watcher.condition.script.ExecutableScriptCondition;
|
||||
import org.elasticsearch.watcher.condition.script.ScriptCondition;
|
||||
import org.elasticsearch.watcher.condition.script.ScriptConditionFactory;
|
||||
|
@ -127,7 +130,7 @@ public class WatchTests extends ElasticsearchTestCase {
|
|||
logger = Loggers.getLogger(WatchTests.class);
|
||||
}
|
||||
|
||||
@Test @Repeat(iterations = 20)
|
||||
@Test //@Repeat(iterations = 20)
|
||||
public void testParser_SelfGenerated() throws Exception {
|
||||
|
||||
TransformRegistry transformRegistry = transformRegistry();
|
||||
|
@ -314,10 +317,12 @@ public class WatchTests extends ElasticsearchTestCase {
|
|||
}
|
||||
|
||||
private ExecutableCondition randomCondition() {
|
||||
String type = randomFrom(ScriptCondition.TYPE, AlwaysCondition.TYPE);
|
||||
String type = randomFrom(ScriptCondition.TYPE, AlwaysCondition.TYPE, CompareCondition.TYPE);
|
||||
switch (type) {
|
||||
case ScriptCondition.TYPE:
|
||||
return new ExecutableScriptCondition(new ScriptCondition(Script.inline("_script").build()), logger, scriptService);
|
||||
case CompareCondition.TYPE:
|
||||
return new ExecutableCompareCondition(new CompareCondition("_path", randomFrom(Op.values()), randomFrom(5, "3")), logger, SystemClock.INSTANCE);
|
||||
default:
|
||||
return new ExecutableAlwaysCondition(logger);
|
||||
}
|
||||
|
@ -329,6 +334,9 @@ public class WatchTests extends ElasticsearchTestCase {
|
|||
case ScriptCondition.TYPE:
|
||||
parsers.put(ScriptCondition.TYPE, new ScriptConditionFactory(settings, scriptService));
|
||||
return new ConditionRegistry(parsers.build());
|
||||
case CompareCondition.TYPE:
|
||||
parsers.put(CompareCondition.TYPE, new CompareConditionFactory(settings, SystemClock.INSTANCE));
|
||||
return new ConditionRegistry(parsers.build());
|
||||
default:
|
||||
parsers.put(AlwaysCondition.TYPE, new AlwaysConditionFactory(settings));
|
||||
return new ConditionRegistry(parsers.build());
|
||||
|
|
Loading…
Reference in New Issue