diff --git a/changelog/unreleased/pr-25155.toml b/changelog/unreleased/pr-25155.toml new file mode 100644 index 0000000000..ff311aebde --- /dev/null +++ b/changelog/unreleased/pr-25155.toml @@ -0,0 +1,4 @@ +type = "a" +message = "Changing delimiter char for decorators in ScriptingApi from '.' to '#' to avoid parsing problems when querying a nested field." + +pulls = ["25155"] diff --git a/full-backend-tests/src/test/java/org/graylog/plugins/views/ScriptingApiResourceIT.java b/full-backend-tests/src/test/java/org/graylog/plugins/views/ScriptingApiResourceIT.java index b5740e4c55..b8d5f4da24 100644 --- a/full-backend-tests/src/test/java/org/graylog/plugins/views/ScriptingApiResourceIT.java +++ b/full-backend-tests/src/test/java/org/graylog/plugins/views/ScriptingApiResourceIT.java @@ -108,7 +108,7 @@ public class ScriptingApiResourceIT { { "group_by": [ { - "field": "streams.id" + "field": "streams#id" } ], "metrics": [ @@ -186,7 +186,7 @@ public class ScriptingApiResourceIT { { "group_by": [ { - "field": "streams.title" + "field": "streams#title" } ], "metrics": [ diff --git a/graylog2-server/src/main/java/org/graylog/events/search/EventsSearchService.java b/graylog2-server/src/main/java/org/graylog/events/search/EventsSearchService.java index f6c1cf4c9d..19d9e7e5a9 100644 --- a/graylog2-server/src/main/java/org/graylog/events/search/EventsSearchService.java +++ b/graylog2-server/src/main/java/org/graylog/events/search/EventsSearchService.java @@ -70,7 +70,7 @@ public class EventsSearchService extends AbstractEventsSearchService { return new EventsFilterBuilder(parameters).build(); } - private Set allowedEventStreams(Subject subject) { + public Set allowedEventStreams(Subject subject) { final var eventStreams = defaultEventStreams(); if (subject.isPermitted(RestPermissions.STREAMS_READ)) { return eventStreams; @@ -113,28 +113,29 @@ public class EventsSearchService extends AbstractEventsSearchService { /** * In the Open Source part, we only map priority and type, both of which are augmented in the FE regarding the title. * So we only need a simple mapping function + * * @param slicingColumn * @param result * @return Slice */ - Slice mapAggregationResultsToSlice(final String slicingColumn, final List result) { + public Slice mapAggregationResultsToSlice(final String slicingColumn, final List result) { return new Slice(result.getFirst().toString(), null, Integer.valueOf(result.getLast().toString())); } // the alert can either be true or false List handleAlertColumn(final List slices) { - if(slices.size() == 2) { + if (slices.size() == 2) { return slices; } - final var TRUE = new Slice( "true", null, 0); - final var FALSE = new Slice( "false", null, 0); + final var TRUE = new Slice("true", null, 0); + final var FALSE = new Slice("false", null, 0); - if(slices.isEmpty()) { + if (slices.isEmpty()) { return List.of(TRUE, FALSE); } - if(slices.getFirst().value().equals("true")) { + if (slices.getFirst().value().equals("true")) { return List.of(slices.getFirst(), FALSE); } else { return List.of(TRUE, slices.getFirst()); @@ -143,16 +144,16 @@ public class EventsSearchService extends AbstractEventsSearchService { // priority can be 1 (low) to 4 (critical), see EventDefinitionPriority.ts List handlePriorityColumn(final List slices) { - if(slices.size() == 4) { + if (slices.size() == 4) { return slices; } - final var LOW = new Slice( "1", null, 0); - final var MEDIUM = new Slice( "2", null, 0); - final var HIGH = new Slice( "3", null, 0); - final var CRITICAL = new Slice( "4", null, 0); + final var LOW = new Slice("1", null, 0); + final var MEDIUM = new Slice("2", null, 0); + final var HIGH = new Slice("3", null, 0); + final var CRITICAL = new Slice("4", null, 0); - if(slices.isEmpty()) { + if (slices.isEmpty()) { return List.of(LOW, MEDIUM, HIGH, CRITICAL); } @@ -165,7 +166,7 @@ public class EventsSearchService extends AbstractEventsSearchService { return fixedList; } - private List addMissingOptions(final List slices, final String slicingColumn) { + public List addMissingOptions(final List slices, final String slicingColumn) { return switch (slicingColumn) { case FIELD_ALERT -> handleAlertColumn(slices); case FIELD_PRIORITY -> handlePriorityColumn(slices); diff --git a/graylog2-server/src/main/java/org/graylog/plugins/views/search/rest/scriptingapi/request/Grouping.java b/graylog2-server/src/main/java/org/graylog/plugins/views/search/rest/scriptingapi/request/Grouping.java index c8ad29c085..707a7b4601 100644 --- a/graylog2-server/src/main/java/org/graylog/plugins/views/search/rest/scriptingapi/request/Grouping.java +++ b/graylog2-server/src/main/java/org/graylog/plugins/views/search/rest/scriptingapi/request/Grouping.java @@ -66,6 +66,11 @@ public record Grouping(@JsonProperty("field") @Valid @NotBlank String fieldName, this(fieldName, Optional.of(limit), Optional.empty(), Optional.empty(), Optional.empty()); } + public Grouping(@JsonProperty("field") @Valid @NotBlank String fieldName, + @JsonProperty("ranges") List ranges) { + this(fieldName, Optional.empty(), Optional.empty(), Optional.empty(), Optional.of(ranges)); + } + @Deprecated @Override public String fieldName() { diff --git a/graylog2-server/src/main/java/org/graylog/plugins/views/search/rest/scriptingapi/request/RequestedField.java b/graylog2-server/src/main/java/org/graylog/plugins/views/search/rest/scriptingapi/request/RequestedField.java index eac0c7bb90..5fcf4515ab 100644 --- a/graylog2-server/src/main/java/org/graylog/plugins/views/search/rest/scriptingapi/request/RequestedField.java +++ b/graylog2-server/src/main/java/org/graylog/plugins/views/search/rest/scriptingapi/request/RequestedField.java @@ -16,16 +16,17 @@ */ package org.graylog.plugins.views.search.rest.scriptingapi.request; -import com.fasterxml.jackson.annotation.JsonCreator; import com.google.common.base.Splitter; import javax.annotation.Nullable; import java.util.List; public record RequestedField(String name, @Nullable String decorator) { + // according to https://discuss.elastic.co/t/legal-character-set-for-field-names/190796, # seems to be a good separator char + public static String DECORATOR_SEPARATOR = "#"; public static RequestedField parse(String value) { - final List parts = Splitter.on(".") + final List parts = Splitter.on(DECORATOR_SEPARATOR) .limit(2) .trimResults() .omitEmptyStrings() @@ -43,7 +44,7 @@ public record RequestedField(String name, @Nullable String decorator) { if (decorator == null) { return name; } else { - return name + "." + decorator; + return name + DECORATOR_SEPARATOR + decorator; } } diff --git a/graylog2-server/src/test/java/org/graylog/plugins/views/search/rest/scriptingapi/mapping/TabularResponseCreatorTest.java b/graylog2-server/src/test/java/org/graylog/plugins/views/search/rest/scriptingapi/mapping/TabularResponseCreatorTest.java index 70d46d43b6..43050b4ff0 100644 --- a/graylog2-server/src/test/java/org/graylog/plugins/views/search/rest/scriptingapi/mapping/TabularResponseCreatorTest.java +++ b/graylog2-server/src/test/java/org/graylog/plugins/views/search/rest/scriptingapi/mapping/TabularResponseCreatorTest.java @@ -40,7 +40,7 @@ class TabularResponseCreatorTest { (field) -> Objects.equals(field.decorator(), "uppercase"), (field, o, searchUser) -> String.valueOf(o).toUpperCase(Locale.ROOT)) ); - final Object decorated = creator.decorate(decorators, RequestedField.parse("myfield.uppercase"), "my-value", TestSearchUser.builder().build()); + final Object decorated = creator.decorate(decorators, RequestedField.parse("myfield#uppercase"), "my-value", TestSearchUser.builder().build()); Assertions.assertThat(decorated).isEqualTo("MY-VALUE"); } @@ -53,7 +53,7 @@ class TabularResponseCreatorTest { (field, o, searchUser) -> String.valueOf(o).toUpperCase(Locale.ROOT)) ); - Assertions.assertThatThrownBy(() -> creator.decorate(decorators, RequestedField.parse("myfield.lowercase"), "my-value", TestSearchUser.builder().build())) + Assertions.assertThatThrownBy(() -> creator.decorate(decorators, RequestedField.parse("myfield#lowercase"), "my-value", TestSearchUser.builder().build())) .isInstanceOf(UnsupportedOperationException.class) .hasMessageContaining("Unsupported property 'lowercase' on field 'myfield'"); } @@ -71,7 +71,7 @@ class TabularResponseCreatorTest { (field, o, searchUser) -> String.valueOf(o).toLowerCase(Locale.ROOT)) ); - Assertions.assertThatThrownBy(() -> creator.decorate(decorators, RequestedField.parse("myfield.lowercase"), "my-value", TestSearchUser.builder().build())) + Assertions.assertThatThrownBy(() -> creator.decorate(decorators, RequestedField.parse("myfield#lowercase"), "my-value", TestSearchUser.builder().build())) .isInstanceOf(UnsupportedOperationException.class) .hasMessageContaining("Found more decorators supporting 'lowercase' on field 'myfield', this is not supported operation."); } diff --git a/graylog2-server/src/test/java/org/graylog/plugins/views/search/rest/scriptingapi/request/RequestedFieldTest.java b/graylog2-server/src/test/java/org/graylog/plugins/views/search/rest/scriptingapi/request/RequestedFieldTest.java index f122b99e78..d7efb8cb8d 100644 --- a/graylog2-server/src/test/java/org/graylog/plugins/views/search/rest/scriptingapi/request/RequestedFieldTest.java +++ b/graylog2-server/src/test/java/org/graylog/plugins/views/search/rest/scriptingapi/request/RequestedFieldTest.java @@ -22,11 +22,11 @@ import org.junit.jupiter.api.Test; class RequestedFieldTest { @Test void testParsing() { - final RequestedField streamId = RequestedField.parse("streams.id"); + final RequestedField streamId = RequestedField.parse("streams#id"); Assertions.assertThat(streamId.name()).isEqualTo("streams"); Assertions.assertThat(streamId.decorator()).isEqualTo("id"); - final RequestedField streamName = RequestedField.parse("streams.name"); + final RequestedField streamName = RequestedField.parse("streams#name"); Assertions.assertThat(streamName.name()).isEqualTo("streams"); Assertions.assertThat(streamName.decorator()).isEqualTo("name"); diff --git a/graylog2-server/src/test/java/org/graylog/plugins/views/search/rest/scriptingapi/response/decorators/IdDecoratorTest.java b/graylog2-server/src/test/java/org/graylog/plugins/views/search/rest/scriptingapi/response/decorators/IdDecoratorTest.java index 4877861ad8..7be21f932c 100644 --- a/graylog2-server/src/test/java/org/graylog/plugins/views/search/rest/scriptingapi/response/decorators/IdDecoratorTest.java +++ b/graylog2-server/src/test/java/org/graylog/plugins/views/search/rest/scriptingapi/response/decorators/IdDecoratorTest.java @@ -27,9 +27,9 @@ class IdDecoratorTest { @Test void accept() { final FieldDecorator decorator = new IdDecorator(); - Assertions.assertThat(decorator.accept(RequestedField.parse("streams.id"))).isTrue(); - Assertions.assertThat(decorator.accept(RequestedField.parse("gl2_source_input.id"))).isTrue(); - Assertions.assertThat(decorator.accept(RequestedField.parse("gl2_source_node.id"))).isTrue(); + Assertions.assertThat(decorator.accept(RequestedField.parse("streams#id"))).isTrue(); + Assertions.assertThat(decorator.accept(RequestedField.parse("gl2_source_input#id"))).isTrue(); + Assertions.assertThat(decorator.accept(RequestedField.parse("gl2_source_node#id"))).isTrue(); // default is to decorate as title, we don't want IDs Assertions.assertThat(decorator.accept(RequestedField.parse("gl2_source_node"))).isFalse(); @@ -41,7 +41,7 @@ class IdDecoratorTest { void decorate() { final FieldDecorator decorator = new IdDecorator(); final SearchUser searchUser = TestSearchUser.builder().build(); - Assertions.assertThat(decorator.decorate(RequestedField.parse("streams.id"), "123", searchUser)) + Assertions.assertThat(decorator.decorate(RequestedField.parse("streams#id"), "123", searchUser)) .isEqualTo("123"); } } diff --git a/graylog2-server/src/test/java/org/graylog/plugins/views/search/rest/scriptingapi/response/decorators/TitleDecoratorTest.java b/graylog2-server/src/test/java/org/graylog/plugins/views/search/rest/scriptingapi/response/decorators/TitleDecoratorTest.java index c2ac07761f..1e91044ec0 100644 --- a/graylog2-server/src/test/java/org/graylog/plugins/views/search/rest/scriptingapi/response/decorators/TitleDecoratorTest.java +++ b/graylog2-server/src/test/java/org/graylog/plugins/views/search/rest/scriptingapi/response/decorators/TitleDecoratorTest.java @@ -33,19 +33,19 @@ class TitleDecoratorTest { void testPermitted() { final FieldDecorator decorator = new TitleDecorator((request, permissions) -> EntitiesTitleResponse.EMPTY_RESPONSE); Assertions.assertThat(decorator.accept(RequestedField.parse("streams"))).isTrue(); - Assertions.assertThat(decorator.accept(RequestedField.parse("streams.title"))).isTrue(); + Assertions.assertThat(decorator.accept(RequestedField.parse("streams#title"))).isTrue(); Assertions.assertThat(decorator.accept(RequestedField.parse("gl2_source_input"))).isTrue(); - Assertions.assertThat(decorator.accept(RequestedField.parse("gl2_source_input.title"))).isTrue(); + Assertions.assertThat(decorator.accept(RequestedField.parse("gl2_source_input#title"))).isTrue(); // For IDs we have a different decorator Assertions.assertThat(decorator.accept(RequestedField.parse("streams.id"))).isFalse(); - Assertions.assertThat(decorator.accept(RequestedField.parse("gl2_source_input.id"))).isFalse(); + Assertions.assertThat(decorator.accept(RequestedField.parse("gl2_source_input#id"))).isFalse(); // unknown decorator - Assertions.assertThat(decorator.accept(RequestedField.parse("gl2_source_input.uppercase"))).isFalse(); + Assertions.assertThat(decorator.accept(RequestedField.parse("gl2_source_input#uppercase"))).isFalse(); // other fields and entities are not supported Assertions.assertThat(decorator.accept(RequestedField.parse("http_response_code"))).isFalse(); - Assertions.assertThat(decorator.accept(RequestedField.parse("http_response_code.title"))).isFalse(); + Assertions.assertThat(decorator.accept(RequestedField.parse("http_response_code#title"))).isFalse(); } @Test