Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement QuickValue improvements #4205

Merged
merged 12 commits into from Oct 5, 2017
Expand Up @@ -16,19 +16,24 @@
*/
package org.graylog2.dashboards.widgets.strategies;

import com.google.common.base.Splitter;
import com.google.common.collect.Maps;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
import org.graylog2.dashboards.widgets.InvalidWidgetConfigurationException;
import org.graylog2.indexer.results.TermsResult;
import org.graylog2.indexer.searches.Searches;
import org.graylog2.indexer.searches.Sorting;
import org.graylog2.plugin.dashboards.widgets.ComputationResult;
import org.graylog2.plugin.dashboards.widgets.WidgetStrategy;
import org.graylog2.plugin.indexer.searches.timeranges.TimeRange;

import javax.annotation.Nullable;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Strings.isNullOrEmpty;

public class QuickvaluesWidgetStrategy implements WidgetStrategy {
Expand All @@ -45,6 +50,9 @@ public interface Factory extends WidgetStrategy.Factory<QuickvaluesWidgetStrateg
private final String field;
private final Searches searches;
private final TimeRange timeRange;
private final String sortOrder;
private final int dataTableLimit;
private final List<String> stackedFields;

@AssistedInject
public QuickvaluesWidgetStrategy(Searches searches, @Assisted Map<String, Object> config, @Assisted TimeRange timeRange, @Assisted String widgetId) throws InvalidWidgetConfigurationException {
Expand All @@ -59,6 +67,27 @@ public QuickvaluesWidgetStrategy(Searches searches, @Assisted Map<String, Object

this.field = (String) config.get("field");
this.streamId = (String) config.get("stream_id");

this.sortOrder = (String) firstNonNull(config.get("sort_order"), "desc");
this.dataTableLimit = (int) firstNonNull(config.get("data_table_limit"), 50);
this.stackedFields = getStackedFields(config.get("stacked_fields"));
}

private static List<String> getStackedFields(@Nullable Object value) {
final String stackedFieldsString = (String) firstNonNull(value, "");
return Splitter.on(',').trimResults().omitEmptyStrings().splitToList(stackedFieldsString);
}

private static Sorting.Direction getSortingDirection(String sort) {
if (isNullOrEmpty(sort)) {
return Sorting.Direction.DESC;
}

try {
return Sorting.Direction.valueOf(sort.toUpperCase(Locale.ENGLISH));
} catch (Exception e) {
return Sorting.Direction.DESC;
}
}

@Override
Expand All @@ -68,7 +97,8 @@ public ComputationResult compute() {
filter = "streams:" + streamId;
}

final TermsResult terms = searches.terms(field, 50, query, filter, this.timeRange);
final Sorting.Direction sortDirection = getSortingDirection(sortOrder);
final TermsResult terms = searches.terms(field, stackedFields, dataTableLimit, query, filter, this.timeRange, sortDirection);

Map<String, Object> result = Maps.newHashMap();
result.put("terms", terms.getTerms());
Expand Down
Expand Up @@ -40,6 +40,8 @@
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.script.Script;
import org.elasticsearch.script.ScriptService;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.filter.FilterAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramBuilder;
Expand Down Expand Up @@ -76,6 +78,7 @@
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
Expand Down Expand Up @@ -274,15 +277,37 @@ public SearchResult search(SearchesConfig config) {
return new SearchResult(hits, searchResult.getTotal(), indexRanges, config.query(), requestBuilder.toString(), tookMsFromSearchResult(searchResult));
}

public TermsResult terms(String field, int size, String query, String filter, TimeRange range, Sorting.Direction sorting) {
public TermsResult terms(String field, List<String> stackedFields, int size, String query, String filter, TimeRange range, Sorting.Direction sorting) {
final Terms.Order termsOrder = sorting == Sorting.Direction.DESC ? Terms.Order.count(false) : Terms.Order.count(true);

final SearchSourceBuilder searchSourceBuilder = filteredSearchRequest(query, filter, range)
.aggregation(AggregationBuilders.terms(AGG_TERMS)
.field(field)
.size(size > 0 ? size : 50)
.order(termsOrder))
.aggregation(AggregationBuilders.missing("missing")
final SearchSourceBuilder searchSourceBuilder = filteredSearchRequest(query, filter, range);

if (stackedFields.isEmpty()) {
searchSourceBuilder.aggregation(AggregationBuilders.terms(AGG_TERMS)
.field(field)
.size(size > 0 ? size : 50)
.order(termsOrder));
} else {
// If the methods gets stacked fields, we have to use scripting to concatenate the fields.
// There is currently no other way to do this. (as of ES 5.6)
final StringBuilder scriptStringBuilder = new StringBuilder();

// Add the main field
scriptStringBuilder.append("doc['").append(field).append("'].value");

// Add all other fields
stackedFields.forEach(f -> {
scriptStringBuilder.append(" + ' - ' + ");
scriptStringBuilder.append("doc['").append(f).append("'].value");
});

searchSourceBuilder.aggregation(AggregationBuilders.terms(AGG_TERMS)
.script(new Script(scriptStringBuilder.toString(), ScriptService.ScriptType.INLINE, "painless", null))
.size(size > 0 ? size : 50)
.order(termsOrder));
}

searchSourceBuilder.aggregation(AggregationBuilders.missing("missing")
.field(field));

final Set<String> affectedIndices = determineAffectedIndices(range, filter);
Expand Down Expand Up @@ -311,6 +336,10 @@ public TermsResult terms(String field, int size, String query, String filter, Ti
);
}

public TermsResult terms(String field, int size, String query, String filter, TimeRange range, Sorting.Direction sorting) {
return terms(field, Collections.emptyList(), size, query, filter, range, sorting);
}

public TermsResult terms(String field, int size, String query, String filter, TimeRange range) {
return terms(field, size, query, filter, range, Sorting.Direction.DESC);
}
Expand Down
Expand Up @@ -179,13 +179,17 @@ public TermsResult termsAbsolute(
@QueryParam("field") @NotEmpty String field,
@ApiParam(name = "query", value = "Query (Lucene syntax)", required = true)
@QueryParam("query") @NotEmpty String query,
@ApiParam(name = "stacked_fields", value = "Fields to stack", required = false) @QueryParam("stacked_fields") String stackedFieldsParam,
@ApiParam(name = "size", value = "Maximum number of terms to return", required = false) @QueryParam("size") int size,
@ApiParam(name = "from", value = "Timerange start. See search method description for date format", required = true) @QueryParam("from") String from,
@ApiParam(name = "to", value = "Timerange end. See search method description for date format", required = true) @QueryParam("to") String to,
@ApiParam(name = "filter", value = "Filter", required = false) @QueryParam("filter") String filter) {
@ApiParam(name = "filter", value = "Filter", required = false) @QueryParam("filter") String filter,
@ApiParam(name = "order", value = "Sorting (field:asc / field:desc)", required = false) @QueryParam("order") String order) {
checkSearchPermission(filter, RestPermissions.SEARCHES_ABSOLUTE);

return buildTermsResult(searches.terms(field, size, query, filter, buildAbsoluteTimeRange(from, to)));
final List<String> stackedFields = splitStackedFields(stackedFieldsParam);
final Sorting sortOrder = buildSorting(order);
return buildTermsResult(searches.terms(field, stackedFields, size, query, filter, buildAbsoluteTimeRange(from, to), sortOrder.getDirection()));
}

@GET
Expand Down
Expand Up @@ -205,12 +205,16 @@ public TermsResult termsKeyword(
@QueryParam("field") @NotEmpty String field,
@ApiParam(name = "query", value = "Query (Lucene syntax)", required = true)
@QueryParam("query") @NotEmpty String query,
@ApiParam(name = "stacked_fields", value = "Fields to stack", required = false) @QueryParam("stacked_fields") String stackedFieldsParam,
@ApiParam(name = "size", value = "Maximum number of terms to return", required = false) @QueryParam("size") int size,
@ApiParam(name = "keyword", value = "Range keyword", required = true) @QueryParam("keyword") String keyword,
@ApiParam(name = "filter", value = "Filter", required = false) @QueryParam("filter") String filter) {
@ApiParam(name = "filter", value = "Filter", required = false) @QueryParam("filter") String filter,
@ApiParam(name = "order", value = "Sorting (field:asc / field:desc)", required = false) @QueryParam("order") String order) {
checkSearchPermission(filter, RestPermissions.SEARCHES_KEYWORD);

return buildTermsResult(searches.terms(field, size, query, filter, buildKeywordTimeRange(keyword)));
final List<String> stackedFields = splitStackedFields(stackedFieldsParam);
final Sorting sortOrder = buildSorting(order);
return buildTermsResult(searches.terms(field, stackedFields, size, query, filter, buildKeywordTimeRange(keyword), sortOrder.getDirection()));
}

@GET
Expand Down
Expand Up @@ -177,14 +177,16 @@ public TermsResult termsRelative(
@QueryParam("field") @NotEmpty String field,
@ApiParam(name = "query", value = "Query (Lucene syntax)", required = true)
@QueryParam("query") @NotEmpty String query,
@ApiParam(name = "stacked_fields", value = "Fields to stack", required = false) @QueryParam("stacked_fields") String stackedFieldsParam,
@ApiParam(name = "size", value = "Maximum number of terms to return", required = false) @QueryParam("size") int size,
@ApiParam(name = "range", value = "Relative timeframe to search in. See search method description.", required = true) @QueryParam("range") int range,
@ApiParam(name = "filter", value = "Filter", required = false) @QueryParam("filter") String filter,
@ApiParam(name = "order", value = "Sorting (field:asc / field:desc)", required = false) @QueryParam("order") String order) {
checkSearchPermission(filter, RestPermissions.SEARCHES_RELATIVE);

final List<String> stackedFields = splitStackedFields(stackedFieldsParam);
final Sorting sortOrder = buildSorting(order);
return buildTermsResult(searches.terms(field, size, query, filter, buildRelativeTimeRange(range), sortOrder.getDirection()));
return buildTermsResult(searches.terms(field, stackedFields, size, query, filter, buildRelativeTimeRange(range), sortOrder.getDirection()));
}

@GET
Expand Down
Expand Up @@ -52,6 +52,7 @@
import javax.ws.rs.ForbiddenException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
Expand Down Expand Up @@ -331,4 +332,10 @@ protected org.graylog2.plugin.indexer.searches.timeranges.TimeRange restrictTime
return AbsoluteRange.create(from, to);
}

protected List<String> splitStackedFields(String stackedFieldsParam) {
if (stackedFieldsParam == null || stackedFieldsParam.isEmpty()) {
return Collections.emptyList();
}
return Splitter.on(',').trimResults().omitEmptyStrings().splitToList(stackedFieldsParam);
}
}
@@ -0,0 +1,7 @@
import Reflux from 'reflux';

const FieldQuickValuesActions = Reflux.createActions({
get: { asyncResult: true },
});

export default FieldQuickValuesActions;
@@ -0,0 +1,14 @@
:local(.optionsFormWrapper) {
margin-top: 10px;
}

:local(.spinnerWrapper) {
max-height: 400px;
margin-top: 10px;
}

:local(.visualizationWrapper) {
max-height: 400px;
overflow: auto;
margin-top: 10px;
}