Skip to content

Commit

Permalink
Implement QuickValue improvements (#4205)
Browse files Browse the repository at this point in the history
* Fix linting errors

* Migrate FieldQuickValuesStore to js/reflux

* First stab at making QuickValues configurable

- Add a config form that can be toggled
- Make order, limit and table size configurable

There are still problems with the ordering in the data table, at least.

* Fix sorting when bottom N is configured

* Add stacking support to the /terms endpoints

* Incorporate PR feedback

- Add "Cancel" button to QuickValues configuration
- Fix horizontal scrolling in QuickValues configuration form
- Remove unneeded custom event handler for radio buttons
- Add label for radio buttons
- Remove now unused FormsUtils#inputEvent()

* Add UI for stacked fields in quick values

* Remove debug console.log

* Refactor config handling in QuickValuesVisualization

This puts the "sortOrder", "dataTableTitle", "dataTableLimit" and "limit"
props into the existing "config" prop. This makes it way easier to
handle both usages of the QuickValuesVisualization, on dashboards and
on the search page.

* Adjust dashboard handling for new config options in QuickValues

* Implement review feedback

- Move stylesheet code into .css files
- Remove unneeded "e.preventDefault()"
- Avoid Input component nesting and use FromGroup and ControlLabel
- Use ButtonToolbar instead of manually adding spaces

* Remove custom error handler
  • Loading branch information
bernd authored and edmundoa committed Oct 5, 2017
1 parent ac21a44 commit 1bbd7f4
Show file tree
Hide file tree
Showing 18 changed files with 534 additions and 132 deletions.
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Reflux from 'reflux';

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

export default FieldQuickValuesActions;
Original file line number Diff line number Diff line change
@@ -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;
}
Loading

0 comments on commit 1bbd7f4

Please sign in to comment.