Skip to content
This repository was archived by the owner on Jan 8, 2019. It is now read-only.

Commit e42d1f9

Browse files
committed
Merge branch 'issue777'
2 parents 04fa6a8 + 185aecb commit e42d1f9

15 files changed

+168
-41
lines changed

app/assets/javascripts/dashboards/dashboards.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,8 +154,18 @@ $(document).ready(function() {
154154

155155
$(".reloading", widget).show();
156156

157+
// XXX: This is just the fixed resolution of the Rickshaw graph as defined in "search_result_chart.js"
158+
var resolution = 800;
159+
160+
var url;
161+
if (resolution) {
162+
url = appPrefixed('/a/dashboards/' + dashboardId + '/widgets/' + widgetId + '/resolution/' + resolution + '/value');
163+
} else {
164+
url = appPrefixed('/a/dashboards/' + dashboardId + '/widgets/' + widgetId + '/value');
165+
}
166+
157167
$.ajax({
158-
url: appPrefixed('/a/dashboards/' + dashboardId + '/widgets/' + widgetId + '/value'),
168+
url: url,
159169
type: 'GET',
160170
success: function(data) {
161171
// Pass to widget specific function to display actual value(s).

app/assets/javascripts/universalsearch.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,18 @@ $(document).ready(function() {
3939
$("#universalsearch-fields", $(this)).val(searchViewState.getFieldsString());
4040
});
4141

42+
$(".universalsearch-form").on("submit", function() {
43+
var width = $(document).width();
44+
$("input[name='width']", $(this)).val(width);
45+
});
46+
47+
$(".added-width-search-link").on("click", function() {
48+
var width = $(document).width();
49+
var href = $(this).attr("href");
50+
href = URI(href).addSearch("width", width).toString();
51+
$(this).attr("href", href);
52+
});
53+
4254
// initialize the user readable dates on page load
4355
$("#universalsearch .timerange-selector-container .absolute input[type='hidden']").each(function() {
4456
var input = $("input[type='text']", $(this).parent());
@@ -149,13 +161,14 @@ $(document).ready(function() {
149161

150162
// Saved search selected. Get details and send to page that redirects to the actual search.
151163
$("#saved-searches-selector").on("change", function(e) {
164+
var width = $(document).width();
152165
var searchId = $("#saved-searches-selector").val();
153166

154167
var container = $(this).closest(".saved-searches-selector-container");
155168
if(!!container.attr("data-stream-id")) {
156-
var url = "/savedsearches/" + encodeURI(searchId) + "/execute?" + "streamId=" + container.attr("data-stream-id");
169+
var url = "/savedsearches/" + encodeURI(searchId) + "/execute?" + "streamId=" + container.attr("data-stream-id") + "&width=" + width;
157170
} else {
158-
var url = "/savedsearches/" + encodeURI(searchId) + "/execute";
171+
var url = "/savedsearches/" + encodeURI(searchId) + "/execute" + "?width=" + width;
159172
}
160173

161174
window.location = appPrefixed(url);

app/controllers/OpenSearchController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
public class OpenSearchController extends BaseController {
2424

2525
public static Result index() {
26-
final String relative = routes.SearchController.index("{searchTerms}", "relative", 3600, "", "", "", "", 1, "", "", "", "").absoluteURL(request());
26+
final String relative = routes.SearchController.index("{searchTerms}", "relative", 3600, "", "", "", "", 1, "", "", "", "", -1).absoluteURL(request());
2727
final String unescaped = relative.replaceAll("%7B", "{").replaceAll("%7D", "}").replaceAll("&", "&");
2828
String content =
2929
"<OpenSearchDescription xmlns=\"http://a9.com/-/spec/opensearch/1.1/\"\n" +

app/controllers/SavedSearchesController.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,14 @@ public class SavedSearchesController extends AuthenticatedController {
3737
@Inject
3838
private SavedSearchService savedSearchService;
3939

40-
public Result execute(String searchId, String streamId) {
40+
public Result execute(String searchId, String streamId, int displayWidth) {
4141
try {
4242
SavedSearch search = savedSearchService.get(searchId);
4343

4444
if(streamId == null || streamId.isEmpty()) {
45-
return redirect(callFromSavedSearch(search, streamId, true));
45+
return redirect(callFromSavedSearch(search, streamId, true, displayWidth));
4646
} else {
47-
return redirect(callFromSavedSearch(search, streamId, true));
47+
return redirect(callFromSavedSearch(search, streamId, true, displayWidth));
4848
}
4949

5050
} catch (IOException e) {
@@ -71,7 +71,7 @@ public Result delete(String searchId) {
7171
}
7272
}
7373

74-
private Call callFromSavedSearch(SavedSearch search, String streamId, boolean includeOriginal) {
74+
private Call callFromSavedSearch(SavedSearch search, String streamId, boolean includeOriginal, int displayWidth) {
7575
int relative = 0;
7676
if (search.getQuery().containsKey("relative")) {
7777
relative = Integer.parseInt((String) search.getQuery().get("relative"));
@@ -115,7 +115,8 @@ private Call callFromSavedSearch(SavedSearch search, String streamId, boolean in
115115
searchId,
116116
"",
117117
"",
118-
fields
118+
fields,
119+
displayWidth
119120
);
120121
} else {
121122
return routes.StreamSearchController.index(
@@ -131,7 +132,8 @@ private Call callFromSavedSearch(SavedSearch search, String streamId, boolean in
131132
searchId,
132133
"",
133134
"",
134-
fields
135+
fields,
136+
displayWidth
135137
);
136138
}
137139
}

app/controllers/SearchController.java

Lines changed: 73 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@
2121
import com.google.common.base.Splitter;
2222
import com.google.common.collect.Iterables;
2323
import com.google.common.collect.Lists;
24+
import com.google.common.collect.Maps;
2425
import com.google.common.collect.Sets;
2526
import com.google.common.net.MediaType;
27+
import com.google.gson.Gson;
2628
import com.google.inject.Inject;
2729
import lib.*;
2830
import lib.security.RestPermissions;
@@ -35,15 +37,22 @@
3537
import org.graylog2.restclient.models.*;
3638
import org.graylog2.restclient.models.api.results.DateHistogramResult;
3739
import org.graylog2.restclient.models.api.results.SearchResult;
40+
import org.joda.time.DateTime;
41+
import org.joda.time.DateTimeConstants;
42+
import org.joda.time.Minutes;
3843
import play.mvc.Result;
3944
import views.helpers.Permissions;
4045

4146
import java.io.IOException;
4247
import java.util.List;
48+
import java.util.Map;
4349
import java.util.Set;
4450

4551
public class SearchController extends AuthenticatedController {
4652

53+
// guess high, so we never have a bad resolution
54+
private static int DEFAULT_ASSUMED_GRAPH_RESOLUTION = 4000;
55+
4756
@Inject
4857
protected UniversalSearch.Factory searchFactory;
4958

@@ -76,7 +85,8 @@ public Result index(String q,
7685
int page,
7786
String savedSearchId,
7887
String sortField, String sortOrder,
79-
String fields) {
88+
String fields,
89+
int displayWidth) {
8090
SearchSort sort = buildSearchSort(sortField, sortOrder);
8191

8292
UniversalSearch search;
@@ -92,25 +102,26 @@ public Result index(String q,
92102
DateHistogramResult histogramResult;
93103
SavedSearch savedSearch;
94104
Set<String> selectedFields = getSelectedFields(fields);
105+
String formattedHistogramResults;
95106
try {
96107
if(savedSearchId != null && !savedSearchId.isEmpty()) {
97108
savedSearch = savedSearchService.get(savedSearchId);
98109
} else {
99110
savedSearch = null;
100111
}
101112

102-
// Histogram interval.
103-
if (interval == null || interval.isEmpty() || !SearchTools.isAllowedDateHistogramInterval(interval)) {
104-
interval = "minute";
105-
}
106-
107113
searchResult = search.search();
108114
if (searchResult.getError() != null) {
109115
return ok(views.html.search.queryerror.render(currentUser(), q, searchResult, savedSearch, fields, null));
110116
}
111117
searchResult.setAllFields(getAllFields());
112118

119+
// histogram resolution (strangely aka interval)
120+
if (interval == null || interval.isEmpty() || !SearchTools.isAllowedDateHistogramInterval(interval)) {
121+
interval = determineHistogramResolution(searchResult);
122+
}
113123
histogramResult = search.dateHistogram(interval);
124+
formattedHistogramResults = formatHistogramResults(histogramResult.getResults(), displayWidth);
114125
} catch (IOException e) {
115126
return status(504, views.html.errors.error.render(ApiClient.ERROR_MSG_IO, e, request()));
116127
} catch (APIException e) {
@@ -119,12 +130,67 @@ public Result index(String q,
119130
}
120131

121132
if (searchResult.getTotalResultCount() > 0) {
122-
return ok(views.html.search.results.render(currentUser(), search, searchResult, histogramResult, q, page, savedSearch, selectedFields, serverNodes.asMap(), null));
133+
return ok(views.html.search.results.render(currentUser(), search, searchResult, histogramResult, formattedHistogramResults, q, page, savedSearch, selectedFields, serverNodes.asMap(), null));
123134
} else {
124135
return ok(views.html.search.noresults.render(currentUser(), q, searchResult, savedSearch, selectedFields, null));
125136
}
126137
}
127138

139+
protected String determineHistogramResolution(final SearchResult searchResult) {
140+
final String interval;
141+
final int queryRangeInMinutes = Minutes.minutesBetween(searchResult.getFromDateTime(), searchResult.getToDateTime()).getMinutes();
142+
final int HOUR = DateTimeConstants.MINUTES_PER_HOUR;
143+
final int DAY = DateTimeConstants.MINUTES_PER_DAY;
144+
final int WEEK = DateTimeConstants.MINUTES_PER_WEEK;
145+
final int MONTH = DAY * 30;
146+
final int YEAR = MONTH * 12;
147+
148+
if (queryRangeInMinutes < DAY / 2) {
149+
interval = "minute";
150+
} else if (queryRangeInMinutes < DAY * 2) {
151+
interval = "hour";
152+
} else if (queryRangeInMinutes < MONTH) {
153+
interval = "day";
154+
} else if (queryRangeInMinutes < MONTH * 6) {
155+
interval = "week";
156+
} else if (queryRangeInMinutes < YEAR * 2) {
157+
interval = "month";
158+
} else if (queryRangeInMinutes < YEAR * 10) {
159+
interval = "quarter";
160+
} else {
161+
interval = "year";
162+
}
163+
return interval;
164+
}
165+
166+
/**
167+
* [{ x: -1893456000, y: 92228531 }, { x: -1577923200, y: 106021568 }]
168+
*
169+
* @return A JSON string representation of the result, suitable for Rickshaw data graphing.
170+
*/
171+
protected String formatHistogramResults(Map<String, Long> histogramResults, int displayWidth) {
172+
final int saneDisplayWidth = (displayWidth == -1 || displayWidth < 100 || displayWidth > DEFAULT_ASSUMED_GRAPH_RESOLUTION) ? DEFAULT_ASSUMED_GRAPH_RESOLUTION : displayWidth;
173+
final List<Map<String, Long>> points = Lists.newArrayList();
174+
175+
// using the absolute value guarantees, that there will always be enough values for the given resolution
176+
final int factor = (saneDisplayWidth != -1 && histogramResults.size() > saneDisplayWidth) ? histogramResults.size() / saneDisplayWidth : 1;
177+
178+
int index = 0;
179+
for (Map.Entry<String, Long> result : histogramResults.entrySet()) {
180+
// TODO: instead of sampling we might consider interpolation (compare DashboardsApiController)
181+
if (index % factor == 0) {
182+
Map<String, Long> point = Maps.newHashMap();
183+
point.put("x", Long.parseLong(result.getKey()));
184+
point.put("y", result.getValue());
185+
186+
points.add(point);
187+
}
188+
index++;
189+
}
190+
191+
return new Gson().toJson(points);
192+
}
193+
128194
protected Set<String> getSelectedFields(String fields) {
129195
Set<String> selectedFields = Sets.newLinkedHashSet();
130196
if (fields != null && !fields.isEmpty()) {

app/controllers/StreamSearchController.java

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ public Result index(String streamId,
3131
int page,
3232
String savedSearchId,
3333
String sortField, String sortOrder,
34-
String fields) {
34+
String fields,
35+
int displayWidth) {
3536
SearchSort sort = buildSearchSort(sortField, sortOrder);
3637

3738
Stream stream;
@@ -71,25 +72,27 @@ public Result index(String streamId,
7172
DateHistogramResult histogramResult;
7273
SavedSearch savedSearch;
7374
Set<String> selectedFields = getSelectedFields(fields);
75+
String formattedHistogramResults;
76+
7477
try {
7578
if(savedSearchId != null && !savedSearchId.isEmpty()) {
7679
savedSearch = savedSearchService.get(savedSearchId);
7780
} else {
7881
savedSearch = null;
7982
}
8083

81-
// Histogram interval.
82-
if (interval == null || interval.isEmpty() || !SearchTools.isAllowedDateHistogramInterval(interval)) {
83-
interval = "minute";
84-
}
85-
8684
searchResult = search.search();
8785
if (searchResult.getError() != null) {
8886
return ok(views.html.search.queryerror.render(currentUser(), q, searchResult, savedSearch, fields, stream));
8987
}
9088
searchResult.setAllFields(getAllFields());
9189

90+
// histogram resolution (strangely aka interval)
91+
if (interval == null || interval.isEmpty() || !SearchTools.isAllowedDateHistogramInterval(interval)) {
92+
interval = determineHistogramResolution(searchResult);
93+
}
9294
histogramResult = search.dateHistogram(interval);
95+
formattedHistogramResults = formatHistogramResults(histogramResult.getResults(), displayWidth);
9396
} catch (IOException e) {
9497
return status(504, views.html.errors.error.render(ApiClient.ERROR_MSG_IO, e, request()));
9598
} catch (APIException e) {
@@ -98,7 +101,7 @@ public Result index(String streamId,
98101
}
99102

100103
if (searchResult.getTotalResultCount() > 0) {
101-
return ok(views.html.search.results.render(currentUser(), search, searchResult, histogramResult, q, page, savedSearch, selectedFields, serverNodes.asMap(), stream));
104+
return ok(views.html.search.results.render(currentUser(), search, searchResult, histogramResult, formattedHistogramResults, q, page, savedSearch, selectedFields, serverNodes.asMap(), stream));
102105
} else {
103106
return ok(views.html.search.noresults.render(currentUser(), q, searchResult, savedSearch, selectedFields, stream));
104107
}

app/controllers/api/DashboardsApiController.java

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,7 @@
4343
import views.helpers.Permissions;
4444

4545
import java.io.IOException;
46-
import java.util.HashMap;
47-
import java.util.List;
48-
import java.util.Map;
46+
import java.util.*;
4947

5048
/**
5149
* @author Lennart Koopmann <[email protected]>
@@ -132,14 +130,15 @@ public Result setWidgetPositions(String dashboardId) {
132130
return ok();
133131
}
134132

135-
public Result widgetValue(String dashboardId, String widgetId) {
133+
public Result widgetValue(String dashboardId, String widgetId, int resolution) {
136134
try {
137135
Dashboard dashboard = dashboardService.get(dashboardId);
138136
DashboardWidget widget = dashboard.getWidget(widgetId);
139137
DashboardWidgetValueResponse widgetValue = widget.getValue(api());
140138

139+
Object resultValue = filterValuesByResolution(resolution, widgetValue.result);
141140
Map<String, Object> result = Maps.newHashMap();
142-
result.put("result", widgetValue.result);
141+
result.put("result", resultValue);
143142
result.put("took_ms", widgetValue.tookMs);
144143
result.put("calculated_at", widgetValue.calculatedAt);
145144
result.put("time_range", widgetValue.computationTimeRange);
@@ -153,6 +152,32 @@ public Result widgetValue(String dashboardId, String widgetId) {
153152
}
154153
}
155154

155+
// in case the widget submitted its resolution, make sure we do not deliver more result values than the widget can display for performance reasons
156+
private Object filterValuesByResolution(final int resolution, final Object resultValue) {
157+
if (resultValue instanceof Map && resolution != -1) {
158+
final Map<?, ?> resultMap = (Map) resultValue;
159+
final int size = resultMap.size();
160+
if (size > resolution) {
161+
// using the absolute value guarantees, that there will always be enough values for the given resolution
162+
final int factor = size / resolution;
163+
// shortcut, no need to copy verbatim
164+
if (factor != 1) {
165+
final Map<Object, Object> filteredResults = new LinkedHashMap<>(resolution);
166+
int index = 0;
167+
for (Map.Entry entry : resultMap.entrySet()) {
168+
// TODO: instead of sampling we might consider interpolation (compare SearchController)
169+
if (index % factor == 0) {
170+
filteredResults.put(entry.getKey(), entry.getValue());
171+
}
172+
index++;
173+
}
174+
return filteredResults;
175+
}
176+
}
177+
}
178+
return resultValue;
179+
}
180+
156181
public Result addWidget(String dashboardId) {
157182
try {
158183
final Map<String, String> params = flattenFormUrlEncoded(request().body().asFormUrlEncoded());

app/views/dashboards/show.scala.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ <h2>Widget config</h2>
200200
<a class="btn" href="@routes.MetricsController.ofMasterNode("org.graylog2.dashboards.widgets.*." + widget.getId)">
201201
Show widget metrics
202202
</a>
203-
<a class="btn btn-success" href="@DashboardWidgetRouteHelper.replayRoute(widget)">
203+
<a class="btn btn-success added-width-search-link" href="@DashboardWidgetRouteHelper.replayRoute(widget)">
204204
Replay search
205205
</a>
206206
<button class="btn btn-primary" data-dismiss="modal" aria-hidden="true">Close</button>

app/views/helpers/DashboardWidgetRouteHelper.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ protected static Call prepareNonStreamBoundReplayRoute(String query, TimeRange t
3232
"",
3333
"",
3434
"",
35-
"" // TODO fields
35+
"", // TODO fields
36+
-1
3637
);
3738
}
3839

@@ -50,7 +51,8 @@ protected static Call prepareStreamBoundReplayRoute(String streamId, String quer
5051
"",
5152
"",
5253
"",
53-
"" // TODO fields
54+
"", // TODO fields
55+
-1
5456
);
5557
}
5658
}

0 commit comments

Comments
 (0)