From adbb07d79c87cc9f40f8d8602f399d6781763f04 Mon Sep 17 00:00:00 2001 From: Jan Heise Date: Mon, 9 Mar 2026 14:36:34 +0100 Subject: [PATCH] Improve structure and documentation of the Events/Alerts table slicing functionality (#25142) * fixed imports * improving structure, adding comments * adding changelog * fix changelog * remove documentation that is not sensible from method comments * adding a not equals query, adding a special "null" case * reverting change on Test class * remove whitespace * revert change * moving asset querying to enterprise --- changelog/unreleased/pr-25142.toml | 4 ++ .../events/search/EventsSearchService.java | 66 ++++++++++--------- 2 files changed, 38 insertions(+), 32 deletions(-) create mode 100644 changelog/unreleased/pr-25142.toml diff --git a/changelog/unreleased/pr-25142.toml b/changelog/unreleased/pr-25142.toml new file mode 100644 index 0000000000..ac2ec45804 --- /dev/null +++ b/changelog/unreleased/pr-25142.toml @@ -0,0 +1,4 @@ +type = "a" +message = "Improve structure and documentation of the Events/Alerts table slicing functionality." + +pulls = ["25142"] 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 19d9e7e5a9..2d89f45f6a 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 @@ -22,6 +22,7 @@ import jakarta.inject.Inject; import org.apache.shiro.subject.Subject; import org.graylog.events.event.EventDto; import org.graylog.events.processor.DBEventDefinitionService; +import org.graylog.plugins.views.search.aggregations.MissingBucketConstants; import org.graylog.plugins.views.search.permissions.SearchUser; import org.graylog.plugins.views.search.rest.scriptingapi.ScriptingApiService; import org.graylog.plugins.views.search.rest.scriptingapi.mapping.QueryFailedException; @@ -41,6 +42,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -81,15 +83,42 @@ public class EventsSearchService extends AbstractEventsSearchService { .collect(Collectors.toSet()); } + /** + * returns the slices for the slice-by functionality for the Alerts/Events table. Used in the core Events/Alerts table + */ public Slices slices(final EventsSlicesRequest request, final Subject subject, final SearchUser searchUser) { - // we cover two use cases, if you only want the slices from the resultset that will also be shown in the entity table, we re-use query and timerange for that. Otherwise, we query "all" + // we cover two use cases by the include_all flag: if you only want the slices calculated from the resultset that will also be shown in the entity table, we re-use query and timerange for that. Otherwise, we query the table for "all" possible slices final var query = request.includeAll() ? "" : request.query(); final var timeRange = request.includeAll() ? RelativeRange.allTime() : request.timerange(); final var filter = buildFilter(EventsSearchParameters.builder().query(query).timerange(timeRange).filter(request.filter()).build()); final var queryString = filter.isEmpty() ? query : query.isEmpty() ? filter : query + " AND " + filter; - return slices(queryString, timeRange, subject, searchUser, request.sliceColumn(), request.includeAll()); + return this.slices(queryString, timeRange, subject, searchUser, request.sliceColumn(), request.includeAll()); + } + + /** + * Used inside this service for the core Events/Alerts table but also is a provided method, re-used from the enterprise/security part of Graylog + */ + public Slices slices(String query, TimeRange timeRange, Subject subject, SearchUser searchUser, final String slicingColumn, final boolean includeAll) { + try { + final var slices = scriptingApiService.executeAggregation( + new AggregationRequestSpec(query, allowedEventStreams(subject), Set.of(), timeRange, List.of(new Grouping(slicingColumn, Integer.MAX_VALUE)), List.of(new Metric("count", slicingColumn))), + searchUser + ) + .datarows() + .stream() + .map(r -> mapAggregationResultsToSlice(slicingColumn, r)) + .toList(); + + final var filtered = includeAll + ? addMissingOptions(slices, slicingColumn) + : slices.stream().filter(s -> !MissingBucketConstants.MISSING_BUCKET_NAME.equals(s.value())).toList(); + + return new Slices(filtered); + } catch (QueryFailedException e) { + throw new RuntimeException(e); + } } /** @@ -112,11 +141,7 @@ 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 + * So we only need a simple mapping function here. */ public Slice mapAggregationResultsToSlice(final String slicingColumn, final List result) { return new Slice(result.getFirst().toString(), null, Integer.valueOf(result.getLast().toString())); @@ -166,7 +191,8 @@ public class EventsSearchService extends AbstractEventsSearchService { return fixedList; } - public List addMissingOptions(final List slices, final String slicingColumn) { + // when slicing, add missing keys that don't exist in the data but you still want to show with cardinality 0 + private List addMissingOptions(final List slices, final String slicingColumn) { return switch (slicingColumn) { case FIELD_ALERT -> handleAlertColumn(slices); case FIELD_PRIORITY -> handlePriorityColumn(slices); @@ -174,30 +200,6 @@ public class EventsSearchService extends AbstractEventsSearchService { }; } - /** - * finding all slices for a particular column in the Indexer - */ - public Slices slices(String query, TimeRange timeRange, Subject subject, SearchUser searchUser, final String slicingColumn, final boolean includeAll) { - try { - final var slices = scriptingApiService.executeAggregation( - new AggregationRequestSpec(query, allowedEventStreams(subject), Set.of(), timeRange, List.of(new Grouping(slicingColumn, Integer.MAX_VALUE)), List.of(new Metric("count", slicingColumn))), - searchUser - ) - .datarows() - .stream() - .map(r -> mapAggregationResultsToSlice(slicingColumn, r)) - .toList(); - - final var filtered = includeAll - ? addMissingOptions(slices, slicingColumn) - : slices.stream().filter(s -> !"(Empty Value)".equals(s.value())).toList(); - - return new Slices(filtered); - } catch (QueryFailedException e) { - throw new RuntimeException(e); - } - } - public EventsSearchResult search(EventsSearchParameters parameters, Subject subject) { final var eventStreams = allowedEventStreams(subject); if (eventStreams.isEmpty()) {