mirror of
https://github.com/Graylog2/graylog2-server.git
synced 2026-03-13 09:32:21 +08:00
Changing delimiter char for field/decorator in Scripting API (#25155)
* adding risk score * move risk score slicing into enterprise * fix field name * adding changelog * using ".." temporarily to separate field/decorator * using pipe symbol th separate field/decorator * settling on # to separate field/decorator * adjusting test * Update pr-25155.toml * Update pr-25155.toml * adjusting test * adjusting test
This commit is contained in:
4
changelog/unreleased/pr-25155.toml
Normal file
4
changelog/unreleased/pr-25155.toml
Normal file
@@ -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"]
|
||||
@@ -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": [
|
||||
|
||||
@@ -70,7 +70,7 @@ public class EventsSearchService extends AbstractEventsSearchService {
|
||||
return new EventsFilterBuilder(parameters).build();
|
||||
}
|
||||
|
||||
private Set<String> allowedEventStreams(Subject subject) {
|
||||
public Set<String> 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<Object> result) {
|
||||
public Slice mapAggregationResultsToSlice(final String slicingColumn, final List<Object> result) {
|
||||
return new Slice(result.getFirst().toString(), null, Integer.valueOf(result.getLast().toString()));
|
||||
}
|
||||
|
||||
// the alert can either be true or false
|
||||
List<Slice> handleAlertColumn(final List<Slice> 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<Slice> handlePriorityColumn(final List<Slice> 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<Slice> addMissingOptions(final List<Slice> slices, final String slicingColumn) {
|
||||
public List<Slice> addMissingOptions(final List<Slice> slices, final String slicingColumn) {
|
||||
return switch (slicingColumn) {
|
||||
case FIELD_ALERT -> handleAlertColumn(slices);
|
||||
case FIELD_PRIORITY -> handlePriorityColumn(slices);
|
||||
|
||||
@@ -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<NumberRange> ranges) {
|
||||
this(fieldName, Optional.empty(), Optional.empty(), Optional.empty(), Optional.of(ranges));
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public String fieldName() {
|
||||
|
||||
@@ -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<String> parts = Splitter.on(".")
|
||||
final List<String> 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user