2121import com .google .common .base .Splitter ;
2222import com .google .common .collect .Iterables ;
2323import com .google .common .collect .Lists ;
24+ import com .google .common .collect .Maps ;
2425import com .google .common .collect .Sets ;
2526import com .google .common .net .MediaType ;
27+ import com .google .gson .Gson ;
2628import com .google .inject .Inject ;
2729import lib .*;
2830import lib .security .RestPermissions ;
3537import org .graylog2 .restclient .models .*;
3638import org .graylog2 .restclient .models .api .results .DateHistogramResult ;
3739import 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 ;
3843import play .mvc .Result ;
3944import views .helpers .Permissions ;
4045
4146import java .io .IOException ;
4247import java .util .List ;
48+ import java .util .Map ;
4349import java .util .Set ;
4450
4551public 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 ()) {
0 commit comments