[ML] Add description to ML filters (#31330)

This adds a `description` to ML filters in order
to allow users to describe their filters in a human
readable form which is also editable (filter updates
to be added shortly).
This commit is contained in:
Dimitris Athanasiou 2018-06-14 16:52:32 +01:00 committed by GitHub
parent f7a0cafe55
commit 9b293275af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 75 additions and 37 deletions

View File

@ -5,6 +5,7 @@
*/ */
package org.elasticsearch.xpack.core.ml.job.config; package org.elasticsearch.xpack.core.ml.job.config;
import org.elasticsearch.Version;
import org.elasticsearch.common.Nullable; import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamInput;
@ -30,6 +31,7 @@ public class MlFilter implements ToXContentObject, Writeable {
public static final ParseField TYPE = new ParseField("type"); public static final ParseField TYPE = new ParseField("type");
public static final ParseField ID = new ParseField("filter_id"); public static final ParseField ID = new ParseField("filter_id");
public static final ParseField DESCRIPTION = new ParseField("description");
public static final ParseField ITEMS = new ParseField("items"); public static final ParseField ITEMS = new ParseField("items");
// For QueryPage // For QueryPage
@ -43,27 +45,38 @@ public class MlFilter implements ToXContentObject, Writeable {
parser.declareString((builder, s) -> {}, TYPE); parser.declareString((builder, s) -> {}, TYPE);
parser.declareString(Builder::setId, ID); parser.declareString(Builder::setId, ID);
parser.declareStringOrNull(Builder::setDescription, DESCRIPTION);
parser.declareStringArray(Builder::setItems, ITEMS); parser.declareStringArray(Builder::setItems, ITEMS);
return parser; return parser;
} }
private final String id; private final String id;
private final String description;
private final List<String> items; private final List<String> items;
public MlFilter(String id, List<String> items) { public MlFilter(String id, String description, List<String> items) {
this.id = Objects.requireNonNull(id, ID.getPreferredName() + " must not be null"); this.id = Objects.requireNonNull(id, ID.getPreferredName() + " must not be null");
this.description = description;
this.items = Objects.requireNonNull(items, ITEMS.getPreferredName() + " must not be null"); this.items = Objects.requireNonNull(items, ITEMS.getPreferredName() + " must not be null");
} }
public MlFilter(StreamInput in) throws IOException { public MlFilter(StreamInput in) throws IOException {
id = in.readString(); id = in.readString();
if (in.getVersion().onOrAfter(Version.V_6_4_0)) {
description = in.readOptionalString();
} else {
description = null;
}
items = Arrays.asList(in.readStringArray()); items = Arrays.asList(in.readStringArray());
} }
@Override @Override
public void writeTo(StreamOutput out) throws IOException { public void writeTo(StreamOutput out) throws IOException {
out.writeString(id); out.writeString(id);
if (out.getVersion().onOrAfter(Version.V_6_4_0)) {
out.writeOptionalString(description);
}
out.writeStringArray(items.toArray(new String[items.size()])); out.writeStringArray(items.toArray(new String[items.size()]));
} }
@ -71,6 +84,9 @@ public class MlFilter implements ToXContentObject, Writeable {
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(); builder.startObject();
builder.field(ID.getPreferredName(), id); builder.field(ID.getPreferredName(), id);
if (description != null) {
builder.field(DESCRIPTION.getPreferredName(), description);
}
builder.field(ITEMS.getPreferredName(), items); builder.field(ITEMS.getPreferredName(), items);
if (params.paramAsBoolean(MlMetaIndex.INCLUDE_TYPE_KEY, false)) { if (params.paramAsBoolean(MlMetaIndex.INCLUDE_TYPE_KEY, false)) {
builder.field(TYPE.getPreferredName(), FILTER_TYPE); builder.field(TYPE.getPreferredName(), FILTER_TYPE);
@ -83,6 +99,10 @@ public class MlFilter implements ToXContentObject, Writeable {
return id; return id;
} }
public String getDescription() {
return description;
}
public List<String> getItems() { public List<String> getItems() {
return new ArrayList<>(items); return new ArrayList<>(items);
} }
@ -98,12 +118,12 @@ public class MlFilter implements ToXContentObject, Writeable {
} }
MlFilter other = (MlFilter) obj; MlFilter other = (MlFilter) obj;
return id.equals(other.id) && items.equals(other.items); return id.equals(other.id) && Objects.equals(description, other.description) && items.equals(other.items);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(id, items); return Objects.hash(id, description, items);
} }
public String documentId() { public String documentId() {
@ -114,30 +134,45 @@ public class MlFilter implements ToXContentObject, Writeable {
return DOCUMENT_ID_PREFIX + filterId; return DOCUMENT_ID_PREFIX + filterId;
} }
public static Builder builder(String filterId) {
return new Builder().setId(filterId);
}
public static class Builder { public static class Builder {
private String id; private String id;
private String description;
private List<String> items = Collections.emptyList(); private List<String> items = Collections.emptyList();
private Builder() {}
public Builder setId(String id) { public Builder setId(String id) {
this.id = id; this.id = id;
return this; return this;
} }
private Builder() {}
@Nullable @Nullable
public String getId() { public String getId() {
return id; return id;
} }
public Builder setDescription(String description) {
this.description = description;
return this;
}
public Builder setItems(List<String> items) { public Builder setItems(List<String> items) {
this.items = items; this.items = items;
return this; return this;
} }
public Builder setItems(String... items) {
this.items = Arrays.asList(items);
return this;
}
public MlFilter build() { public MlFilter build() {
return new MlFilter(id, items); return new MlFilter(id, description, items);
} }
} }
} }

View File

@ -9,6 +9,7 @@ import org.elasticsearch.test.AbstractStreamableTestCase;
import org.elasticsearch.xpack.core.ml.action.GetFiltersAction.Response; import org.elasticsearch.xpack.core.ml.action.GetFiltersAction.Response;
import org.elasticsearch.xpack.core.ml.action.util.QueryPage; import org.elasticsearch.xpack.core.ml.action.util.QueryPage;
import org.elasticsearch.xpack.core.ml.job.config.MlFilter; import org.elasticsearch.xpack.core.ml.job.config.MlFilter;
import org.elasticsearch.xpack.core.ml.job.config.MlFilterTests;
import java.util.Collections; import java.util.Collections;
@ -17,9 +18,7 @@ public class GetFiltersActionResponseTests extends AbstractStreamableTestCase<Ge
@Override @Override
protected Response createTestInstance() { protected Response createTestInstance() {
final QueryPage<MlFilter> result; final QueryPage<MlFilter> result;
MlFilter doc = MlFilterTests.createRandom();
MlFilter doc = new MlFilter(
randomAlphaOfLengthBetween(1, 20), Collections.singletonList(randomAlphaOfLengthBetween(1, 20)));
result = new QueryPage<>(Collections.singletonList(doc), 1, MlFilter.RESULTS_FIELD); result = new QueryPage<>(Collections.singletonList(doc), 1, MlFilter.RESULTS_FIELD);
return new Response(result); return new Response(result);
} }

View File

@ -8,10 +8,7 @@ package org.elasticsearch.xpack.core.ml.action;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.test.AbstractStreamableXContentTestCase; import org.elasticsearch.test.AbstractStreamableXContentTestCase;
import org.elasticsearch.xpack.core.ml.action.PutFilterAction.Request; import org.elasticsearch.xpack.core.ml.action.PutFilterAction.Request;
import org.elasticsearch.xpack.core.ml.job.config.MlFilter; import org.elasticsearch.xpack.core.ml.job.config.MlFilterTests;
import java.util.ArrayList;
import java.util.List;
public class PutFilterActionRequestTests extends AbstractStreamableXContentTestCase<Request> { public class PutFilterActionRequestTests extends AbstractStreamableXContentTestCase<Request> {
@ -19,13 +16,7 @@ public class PutFilterActionRequestTests extends AbstractStreamableXContentTestC
@Override @Override
protected Request createTestInstance() { protected Request createTestInstance() {
int size = randomInt(10); return new PutFilterAction.Request(MlFilterTests.createRandom(filterId));
List<String> items = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
items.add(randomAlphaOfLengthBetween(1, 20));
}
MlFilter filter = new MlFilter(filterId, items);
return new PutFilterAction.Request(filter);
} }
@Override @Override
@ -42,5 +33,4 @@ public class PutFilterActionRequestTests extends AbstractStreamableXContentTestC
protected Request doParseInstance(XContentParser parser) { protected Request doParseInstance(XContentParser parser) {
return PutFilterAction.Request.parseRequest(filterId, parser); return PutFilterAction.Request.parseRequest(filterId, parser);
} }
} }

View File

@ -26,12 +26,25 @@ public class MlFilterTests extends AbstractSerializingTestCase<MlFilter> {
@Override @Override
protected MlFilter createTestInstance() { protected MlFilter createTestInstance() {
return createRandom();
}
public static MlFilter createRandom() {
return createRandom(randomAlphaOfLengthBetween(1, 20));
}
public static MlFilter createRandom(String filterId) {
String description = null;
if (randomBoolean()) {
description = randomAlphaOfLength(20);
}
int size = randomInt(10); int size = randomInt(10);
List<String> items = new ArrayList<>(size); List<String> items = new ArrayList<>(size);
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
items.add(randomAlphaOfLengthBetween(1, 20)); items.add(randomAlphaOfLengthBetween(1, 20));
} }
return new MlFilter(randomAlphaOfLengthBetween(1, 20), items); return new MlFilter(filterId, description, items);
} }
@Override @Override
@ -45,13 +58,13 @@ public class MlFilterTests extends AbstractSerializingTestCase<MlFilter> {
} }
public void testNullId() { public void testNullId() {
NullPointerException ex = expectThrows(NullPointerException.class, () -> new MlFilter(null, Collections.emptyList())); NullPointerException ex = expectThrows(NullPointerException.class, () -> new MlFilter(null, "", Collections.emptyList()));
assertEquals(MlFilter.ID.getPreferredName() + " must not be null", ex.getMessage()); assertEquals(MlFilter.ID.getPreferredName() + " must not be null", ex.getMessage());
} }
public void testNullItems() { public void testNullItems() {
NullPointerException ex = NullPointerException ex =
expectThrows(NullPointerException.class, () -> new MlFilter(randomAlphaOfLengthBetween(1, 20), null)); expectThrows(NullPointerException.class, () -> new MlFilter(randomAlphaOfLengthBetween(1, 20), "", null));
assertEquals(MlFilter.ITEMS.getPreferredName() + " must not be null", ex.getMessage()); assertEquals(MlFilter.ITEMS.getPreferredName() + " must not be null", ex.getMessage());
} }

View File

@ -385,8 +385,8 @@ public class JobProviderIT extends MlSingleNodeTestCase {
indexScheduledEvents(events); indexScheduledEvents(events);
List<MlFilter> filters = new ArrayList<>(); List<MlFilter> filters = new ArrayList<>();
filters.add(new MlFilter("fruit", Arrays.asList("apple", "pear"))); filters.add(MlFilter.builder("fruit").setItems("apple", "pear").build());
filters.add(new MlFilter("tea", Arrays.asList("green", "builders"))); filters.add(MlFilter.builder("tea").setItems("green", "builders").build());
indexFilters(filters); indexFilters(filters);
DataCounts earliestCounts = DataCountsTests.createTestInstance(jobId); DataCounts earliestCounts = DataCountsTests.createTestInstance(jobId);

View File

@ -210,7 +210,7 @@ public class JobManagerTests extends ESTestCase {
JobManager jobManager = createJobManager(); JobManager jobManager = createJobManager();
MlFilter filter = new MlFilter("foo_filter", Arrays.asList("a", "b")); MlFilter filter = MlFilter.builder("foo_filter").setItems("a", "b").build();
jobManager.updateProcessOnFilterChanged(filter); jobManager.updateProcessOnFilterChanged(filter);

View File

@ -207,8 +207,8 @@ public class ControlMsgToProcessWriterTests extends ESTestCase {
public void testWriteUpdateFiltersMessage() throws IOException { public void testWriteUpdateFiltersMessage() throws IOException {
ControlMsgToProcessWriter writer = new ControlMsgToProcessWriter(lengthEncodedWriter, 2); ControlMsgToProcessWriter writer = new ControlMsgToProcessWriter(lengthEncodedWriter, 2);
MlFilter filter1 = new MlFilter("filter_1", Arrays.asList("a")); MlFilter filter1 = MlFilter.builder("filter_1").setItems("a").build();
MlFilter filter2 = new MlFilter("filter_2", Arrays.asList("b", "c")); MlFilter filter2 = MlFilter.builder("filter_2").setItems("b", "c").build();
writer.writeUpdateFiltersMessage(Arrays.asList(filter1, filter2)); writer.writeUpdateFiltersMessage(Arrays.asList(filter1, filter2));

View File

@ -220,8 +220,8 @@ public class FieldConfigWriterTests extends ESTestCase {
AnalysisConfig.Builder builder = new AnalysisConfig.Builder(Collections.singletonList(d)); AnalysisConfig.Builder builder = new AnalysisConfig.Builder(Collections.singletonList(d));
analysisConfig = builder.build(); analysisConfig = builder.build();
filters.add(new MlFilter("filter_1", Arrays.asList("a", "b"))); filters.add(MlFilter.builder("filter_1").setItems("a", "b").build());
filters.add(new MlFilter("filter_2", Arrays.asList("c", "d"))); filters.add(MlFilter.builder("filter_2").setItems("c", "d").build());
writer = mock(OutputStreamWriter.class); writer = mock(OutputStreamWriter.class);
createFieldConfigWriter().write(); createFieldConfigWriter().write();

View File

@ -10,7 +10,6 @@ import org.elasticsearch.xpack.core.ml.job.config.MlFilter;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -28,8 +27,8 @@ public class MlFilterWriterTests extends ESTestCase {
public void testWrite() throws IOException { public void testWrite() throws IOException {
List<MlFilter> filters = new ArrayList<>(); List<MlFilter> filters = new ArrayList<>();
filters.add(new MlFilter("filter_1", Arrays.asList("a", "b"))); filters.add(MlFilter.builder("filter_1").setItems("a", "b").build());
filters.add(new MlFilter("filter_2", Arrays.asList("c", "d"))); filters.add(MlFilter.builder("filter_2").setItems("c", "d").build());
StringBuilder buffer = new StringBuilder(); StringBuilder buffer = new StringBuilder();
new MlFilterWriter(filters, buffer).write(); new MlFilterWriter(filters, buffer).write();

View File

@ -32,6 +32,7 @@ setup:
filter_id: filter-foo2 filter_id: filter-foo2
body: > body: >
{ {
"description": "This filter has a description",
"items": ["123", "lmnop"] "items": ["123", "lmnop"]
} }
@ -76,6 +77,7 @@ setup:
- match: - match:
filters.1: filters.1:
filter_id: "filter-foo2" filter_id: "filter-foo2"
description: "This filter has a description"
items: ["123", "lmnop"] items: ["123", "lmnop"]
- do: - do:

View File

@ -120,7 +120,7 @@ public class DetectionRulesIT extends MlNativeAutodetectIntegTestCase {
} }
public void testScope() throws Exception { public void testScope() throws Exception {
MlFilter safeIps = new MlFilter("safe_ips", Arrays.asList("111.111.111.111", "222.222.222.222")); MlFilter safeIps = MlFilter.builder("safe_ips").setItems("111.111.111.111", "222.222.222.222").build();
assertThat(putMlFilter(safeIps), is(true)); assertThat(putMlFilter(safeIps), is(true));
DetectionRule rule = new DetectionRule.Builder(RuleScope.builder().include("ip", "safe_ips")).build(); DetectionRule rule = new DetectionRule.Builder(RuleScope.builder().include("ip", "safe_ips")).build();
@ -178,7 +178,7 @@ public class DetectionRulesIT extends MlNativeAutodetectIntegTestCase {
assertThat(records.get(0).getOverFieldValue(), equalTo("333.333.333.333")); assertThat(records.get(0).getOverFieldValue(), equalTo("333.333.333.333"));
// Now let's update the filter // Now let's update the filter
MlFilter updatedFilter = new MlFilter(safeIps.getId(), Collections.singletonList("333.333.333.333")); MlFilter updatedFilter = MlFilter.builder(safeIps.getId()).setItems("333.333.333.333").build();
assertThat(putMlFilter(updatedFilter), is(true)); assertThat(putMlFilter(updatedFilter), is(true));
// Wait until the notification that the process was updated is indexed // Wait until the notification that the process was updated is indexed
@ -229,7 +229,7 @@ public class DetectionRulesIT extends MlNativeAutodetectIntegTestCase {
public void testScopeAndCondition() throws IOException { public void testScopeAndCondition() throws IOException {
// We have 2 IPs and they're both safe-listed. // We have 2 IPs and they're both safe-listed.
List<String> ips = Arrays.asList("111.111.111.111", "222.222.222.222"); List<String> ips = Arrays.asList("111.111.111.111", "222.222.222.222");
MlFilter safeIps = new MlFilter("safe_ips", ips); MlFilter safeIps = MlFilter.builder("safe_ips").setItems(ips).build();
assertThat(putMlFilter(safeIps), is(true)); assertThat(putMlFilter(safeIps), is(true));
// Ignore if ip in safe list AND actual < 10. // Ignore if ip in safe list AND actual < 10.