Skip to content

Commit 6a141c9

Browse files
ahornaceVladimir Kotal
authored andcommitted
Inform user if only partial suggestions data were returned
1 parent 9fccc1b commit 6a141c9

File tree

7 files changed

+99
-28
lines changed

7 files changed

+99
-28
lines changed

src/org/opensolaris/opengrok/web/api/v1/controller/SuggesterController.java

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.apache.lucene.search.Query;
2828
import org.hibernate.validator.constraints.NotBlank;
2929
import org.opengrok.suggest.LookupResultItem;
30+
import org.opengrok.suggest.Suggester.Suggestions;
3031
import org.opengrok.suggest.SuggesterUtils;
3132
import org.opensolaris.opengrok.configuration.RuntimeEnvironment;
3233
import org.opensolaris.opengrok.configuration.SuggesterConfig;
@@ -111,14 +112,15 @@ public Result getSuggestions(@Valid @BeanParam final SuggesterQueryData data) th
111112
throw new WebApplicationException(Response.Status.NOT_FOUND);
112113
}
113114

114-
List<LookupResultItem> suggestedItems = suggester.getSuggestions(
115+
Suggestions suggestions = suggester.getSuggestions(
115116
suggesterData.getProjects(), suggesterData.getSuggesterQuery(), suggesterData.getQuery());
116117

117118
Instant end = Instant.now();
118119

119120
long timeInMs = Duration.between(start, end).toMillis();
120121

121-
return new Result(suggestedItems, suggesterData.getIdentifier(), suggesterData.getSuggesterQueryFieldText(), timeInMs);
122+
return new Result(suggestions.getItems(), suggesterData.getIdentifier(),
123+
suggesterData.getSuggesterQueryFieldText(), timeInMs, suggestions.isPartialResult());
122124
}
123125

124126
private void modifyDataBasedOnConfiguration(final SuggesterData data, final SuggesterConfig config) {
@@ -274,16 +276,20 @@ private static class Result {
274276

275277
private String queryText;
276278

279+
private boolean partialResult;
280+
277281
Result(
278282
final List<LookupResultItem> suggestions,
279283
final String identifier,
280284
final String queryText,
281-
final long time
285+
final long time,
286+
final boolean partialResult
282287
) {
283288
this.suggestions = suggestions;
284289
this.identifier = identifier;
285290
this.queryText = queryText;
286291
this.time = time;
292+
this.partialResult = partialResult;
287293
}
288294

289295
public long getTime() {
@@ -317,6 +323,14 @@ public String getQueryText() {
317323
public void setQueryText(String queryText) {
318324
this.queryText = queryText;
319325
}
326+
327+
public boolean isPartialResult() {
328+
return partialResult;
329+
}
330+
331+
public void setPartialResult(boolean partialResult) {
332+
this.partialResult = partialResult;
333+
}
320334
}
321335

322336
private static class TermIncrementData {

src/org/opensolaris/opengrok/web/api/v1/suggester/provider/service/SuggesterService.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
import org.apache.lucene.index.Term;
2626
import org.apache.lucene.search.Query;
2727
import org.apache.lucene.util.BytesRef;
28-
import org.opengrok.suggest.LookupResultItem;
28+
import org.opengrok.suggest.Suggester.Suggestions;
2929
import org.opengrok.suggest.query.SuggesterQuery;
3030

3131
import java.util.Collection;
@@ -43,7 +43,7 @@ public interface SuggesterService {
4343
* considered to be complex because it takes more resources to find the proper suggestions.
4444
* @return suggestions to be displayed
4545
*/
46-
List<LookupResultItem> getSuggestions(Collection<String> projects, SuggesterQuery suggesterQuery, Query query);
46+
Suggestions getSuggestions(Collection<String> projects, SuggesterQuery suggesterQuery, Query query);
4747

4848
/**
4949
* Refreshes the suggester based on the new configuration.

src/org/opensolaris/opengrok/web/api/v1/suggester/provider/service/impl/SuggesterServiceImpl.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@
3030
import org.apache.lucene.index.Term;
3131
import org.apache.lucene.search.Query;
3232
import org.apache.lucene.util.BytesRef;
33-
import org.opengrok.suggest.LookupResultItem;
3433
import org.opengrok.suggest.Suggester;
3534
import org.opengrok.suggest.Suggester.NamedIndexDir;
3635
import org.opengrok.suggest.Suggester.NamedIndexReader;
36+
import org.opengrok.suggest.Suggester.Suggestions;
3737
import org.opengrok.suggest.query.SuggesterQuery;
3838
import org.opensolaris.opengrok.configuration.Configuration;
3939
import org.opensolaris.opengrok.configuration.Project;
@@ -99,7 +99,7 @@ public static SuggesterService getInstance() {
9999

100100
/** {@inheritDoc} */
101101
@Override
102-
public List<LookupResultItem> getSuggestions(
102+
public Suggestions getSuggestions(
103103
final Collection<String> projects,
104104
final SuggesterQuery suggesterQuery,
105105
final Query query
@@ -108,7 +108,7 @@ public List<LookupResultItem> getSuggestions(
108108
lock.readLock().lock();
109109
try {
110110
if (suggester == null) {
111-
return Collections.emptyList();
111+
return new Suggestions(Collections.emptyList(), true);
112112
}
113113
List<NamedIndexReader> namedReaders = getNamedIndexReaders(projects, superIndexSearchers);
114114

suggester/src/main/java/org/opengrok/suggest/Suggester.java

Lines changed: 61 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
import java.util.concurrent.ConcurrentHashMap;
5151
import java.util.concurrent.ExecutorService;
5252
import java.util.concurrent.Executors;
53+
import java.util.concurrent.Future;
5354
import java.util.concurrent.TimeUnit;
5455
import java.util.logging.Level;
5556
import java.util.logging.Logger;
@@ -87,7 +88,7 @@ public final class Suggester implements Closeable {
8788

8889
/**
8990
* @param suggesterDir directory under which the suggester data should be created
90-
* @param resultSize maximum number of suggestions that should be returned
91+
* @param resultSize maximum number of items that should be returned
9192
* @param awaitTerminationTime how much time to wait for suggester to initialize
9293
* @param allowMostPopular specifies if the most popular completion is enabled
9394
* @param projectsEnabled specifies if the OpenGrok projects are enabled
@@ -281,13 +282,13 @@ public void remove(final Iterable<String> names) {
281282
* @param query query on which the suggestions depend
282283
* @return suggestions
283284
*/
284-
public List<LookupResultItem> search(
285+
public Suggestions search(
285286
final List<NamedIndexReader> indexReaders,
286287
final SuggesterQuery suggesterQuery,
287288
final Query query
288289
) {
289290
if (indexReaders == null || suggesterQuery == null) {
290-
return Collections.emptyList();
291+
return new Suggestions(Collections.emptyList(), true);
291292
}
292293

293294
List<NamedIndexReader> readers = indexReaders;
@@ -296,28 +297,33 @@ public List<LookupResultItem> search(
296297
indexReaders.get(0).getReader()));
297298
}
298299

299-
List<LookupResultItem> results;
300+
Suggestions suggestions;
300301
if (!SuggesterUtils.isComplexQuery(query, suggesterQuery)) { // use WFST for lone prefix
301-
results = prefixLookup(readers, (SuggesterPrefixQuery) suggesterQuery);
302+
suggestions = prefixLookup(readers, (SuggesterPrefixQuery) suggesterQuery);
302303
} else {
303-
results = complexLookup(readers, suggesterQuery, query);
304+
suggestions = complexLookup(readers, suggesterQuery, query);
304305
}
305306

306-
return SuggesterUtils.combineResults(results, resultSize);
307+
return new Suggestions(SuggesterUtils.combineResults(suggestions.items, resultSize),
308+
suggestions.partialResult);
307309
}
308310

309-
private List<LookupResultItem> prefixLookup(
311+
private Suggestions prefixLookup(
310312
final List<NamedIndexReader> readers,
311313
final SuggesterPrefixQuery suggesterQuery
312314
) {
313-
return readers.parallelStream().flatMap(namedIndexReader -> {
315+
BooleanWrapper partialResult = new BooleanWrapper();
316+
317+
List<LookupResultItem> results = readers.parallelStream().flatMap(namedIndexReader -> {
314318
SuggesterProjectData data = projectData.get(namedIndexReader.name);
315319
if (data == null) {
316320
logger.log(Level.FINE, "{0} not yet initialized", namedIndexReader.name);
321+
partialResult.value = true;
317322
return Stream.empty();
318323
}
319324
boolean gotLock = data.tryLock();
320325
if (!gotLock) { // do not wait for rebuild
326+
partialResult.value = true;
321327
return Stream.empty();
322328
}
323329

@@ -331,9 +337,11 @@ private List<LookupResultItem> prefixLookup(
331337
data.unlock();
332338
}
333339
}).collect(Collectors.toList());
340+
341+
return new Suggestions(results, partialResult.value);
334342
}
335343

336-
private List<LookupResultItem> complexLookup(
344+
private Suggestions complexLookup(
337345
final List<NamedIndexReader> readers,
338346
final SuggesterQuery suggesterQuery,
339347
final Query query
@@ -344,13 +352,17 @@ private List<LookupResultItem> complexLookup(
344352
searchTasks.add(new SuggesterSearchTask(ir, query, suggesterQuery, results));
345353
}
346354

355+
List<Future<Void>> futures;
347356
try {
348-
executorService.invokeAll(searchTasks, timeThreshold, TimeUnit.MILLISECONDS);
357+
futures = executorService.invokeAll(searchTasks, timeThreshold, TimeUnit.MILLISECONDS);
349358
} catch (InterruptedException e) {
350359
logger.log(Level.WARNING, "Interrupted while invoking suggester search", e);
351360
Thread.currentThread().interrupt();
361+
return new Suggestions(Collections.emptyList(), true);
352362
}
353363

364+
boolean partialResult = futures.stream().anyMatch(Future::isCancelled);
365+
354366
// wait for tasks to finish
355367
for (SuggesterSearchTask searchTask : searchTasks) {
356368
if (!searchTask.started) {
@@ -360,7 +372,7 @@ private List<LookupResultItem> complexLookup(
360372
while (!searchTask.finished) {
361373
}
362374
}
363-
return results;
375+
return new Suggestions(results, partialResult);
364376
}
365377

366378
/**
@@ -369,20 +381,26 @@ private List<LookupResultItem> complexLookup(
369381
* @param query query that was used to perform the search
370382
*/
371383
public void onSearch(final Iterable<String> projects, final Query query) {
372-
if (!allowMostPopular) {
384+
if (!allowMostPopular || projects == null) {
373385
return;
374386
}
375387
try {
376388
List<Term> terms = SuggesterUtils.intoTerms(query);
377389

378390
if (!projectsEnabled) {
379391
for (Term t : terms) {
380-
projectData.get(PROJECTS_DISABLED_KEY).incrementSearchCount(t);
392+
SuggesterProjectData data = projectData.get(PROJECTS_DISABLED_KEY);
393+
if (data != null) {
394+
data.incrementSearchCount(t);
395+
}
381396
}
382397
} else {
383398
for (String project : projects) {
384399
for (Term t : terms) {
385-
projectData.get(project).incrementSearchCount(t);
400+
SuggesterProjectData data = projectData.get(project);
401+
if (data != null) {
402+
data.incrementSearchCount(t);
403+
}
386404
}
387405
}
388406
}
@@ -529,6 +547,28 @@ public Void call() {
529547
}
530548
}
531549

550+
/**
551+
* Result suggestions data.
552+
*/
553+
public static class Suggestions {
554+
555+
private final List<LookupResultItem> items;
556+
private final boolean partialResult;
557+
558+
public Suggestions(final List<LookupResultItem> items, final boolean partialResult) {
559+
this.items = items;
560+
this.partialResult = partialResult;
561+
}
562+
563+
public List<LookupResultItem> getItems() {
564+
return items;
565+
}
566+
567+
public boolean isPartialResult() {
568+
return partialResult;
569+
}
570+
}
571+
532572
/**
533573
* Model classes for holding project name and path to ist index directory.
534574
*/
@@ -612,4 +652,10 @@ public String toString() {
612652

613653
}
614654

655+
private static class BooleanWrapper {
656+
657+
private volatile boolean value;
658+
659+
}
660+
615661
}

suggester/src/test/java/org/opengrok/suggest/SuggesterTest.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ public void testSimpleSuggestions() throws IOException {
161161
Suggester.NamedIndexReader ir = t.getNamedIndexReader();
162162

163163
List<LookupResultItem> res = t.s.search(Collections.singletonList(ir),
164-
new SuggesterPrefixQuery(new Term("test", "t")), null);
164+
new SuggesterPrefixQuery(new Term("test", "t")), null).getItems();
165165

166166
assertThat(res.stream().map(LookupResultItem::getPhrase).collect(Collectors.toList()),
167167
containsInAnyOrder("term1", "term2", "term3"));
@@ -180,7 +180,7 @@ public void testRefresh() throws IOException {
180180
Suggester.NamedIndexReader ir = t.getNamedIndexReader();
181181

182182
List<LookupResultItem> res = t.s.search(Collections.singletonList(ir),
183-
new SuggesterPrefixQuery(new Term("test", "a")), null);
183+
new SuggesterPrefixQuery(new Term("test", "a")), null).getItems();
184184

185185
assertThat(res.stream().map(LookupResultItem::getPhrase).collect(Collectors.toList()),
186186
containsInAnyOrder("a1", "a2"));
@@ -206,7 +206,7 @@ public void testIndexChangedWhileOffline() throws IOException {
206206
Suggester.NamedIndexReader ir = t.getNamedIndexReader();
207207

208208
List<LookupResultItem> res = t.s.search(Collections.singletonList(ir),
209-
new SuggesterPrefixQuery(new Term("test", "a")), null);
209+
new SuggesterPrefixQuery(new Term("test", "a")), null).getItems();
210210

211211
assertThat(res.stream().map(LookupResultItem::getPhrase).collect(Collectors.toList()),
212212
containsInAnyOrder("a1", "a2"));
@@ -231,7 +231,7 @@ public void testComplexQuerySearch() throws IOException {
231231
SuggesterTestData t = initSuggester();
232232

233233
List<LookupResultItem> res = t.s.search(Collections.singletonList(t.getNamedIndexReader()),
234-
new SuggesterWildcardQuery(new Term("test", "*1")), null);
234+
new SuggesterWildcardQuery(new Term("test", "*1")), null).getItems();
235235

236236
assertThat(res.stream().map(LookupResultItem::getPhrase).collect(Collectors.toList()),
237237
contains("term1"));

test/org/opensolaris/opengrok/web/api/v1/controller/SuggesterControllerTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ public static class Result {
6767
public List<ResultItem> suggestions;
6868
public String identifier;
6969
public String queryText;
70+
public boolean partialResult;
7071
}
7172

7273
public static class ResultItem {

web/js/utils-0.0.23.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1781,6 +1781,7 @@ function initAutocompleteForField(inputId, field, config, dataFunction, errorEle
17811781
var text;
17821782
var identifier;
17831783
var time;
1784+
var partialResult;
17841785

17851786
var input = $("#" + inputId);
17861787

@@ -1804,6 +1805,7 @@ function initAutocompleteForField(inputId, field, config, dataFunction, errorEle
18041805
text = data.queryText;
18051806
identifier = data.identifier;
18061807
time = data.time;
1808+
partialResult = data.partialResult;
18071809

18081810
response(data.suggestions);
18091811
},
@@ -1835,9 +1837,17 @@ function initAutocompleteForField(inputId, field, config, dataFunction, errorEle
18351837
if (config.showTime) {
18361838
$("<li>", {
18371839
class: "ui-state-disabled",
1840+
style: 'padding-left: 5px;',
18381841
text: time + 'ms'
18391842
}).appendTo(ul);
18401843
}
1844+
if (partialResult) {
1845+
$("<li>", {
1846+
class: "ui-state-disabled",
1847+
style: 'padding-left: 5px;',
1848+
text: 'Partial result due to timeout'
1849+
}).appendTo(ul);
1850+
}
18411851
};
18421852
},
18431853
focus: function (event, ui) {
@@ -1859,7 +1869,7 @@ function initAutocompleteForField(inputId, field, config, dataFunction, errorEle
18591869
// error occurred
18601870
return;
18611871
}
1862-
if (ui.content.length === 0) {
1872+
if (ui.content.length === 0 && !partialResult) {
18631873
var noMatchesFoundResult = {phrase: 'No matches found', selectable: false};
18641874
ui.content.push(noMatchesFoundResult);
18651875
}

0 commit comments

Comments
 (0)