in backend/app/services/index/ElasticsearchPages.scala [166:251]
private def getPagesWithNextAndPreviousHighlights(indexName: String, uri: Uri, highlightQuery: Option[String], highlightFields: List[HighlightField], pagesInViewport: List[Page]): Attempt[List[Page]] = {
highlightQuery match {
case None =>
Attempt.Right(List.empty)
case Some(q) =>
val firstPageInViewport = pagesInViewport.minBy(_.page).page
val lastPageInViewport = pagesInViewport.maxBy(_.page).page
val query = buildQuery(q)
val documentFilter = termQuery(PagesFields.resourceId, uri.value)
execute {
multi(
// The first page to contain highlights
search(indexName)
.sortByFieldAsc(PagesFields.page)
.highlighting(highlightFields)
.size(1)
.query(
// NB this is a "must" rather than a "should" so we only return pages containing highlights
must(query).filter(documentFilter)
),
// The last page to contain highlights PRIOR to the FIRST page in the viewport
search(indexName)
.sortByFieldDesc(PagesFields.page)
.highlighting(highlightFields)
.size(1)
.query(
must(query).filter(
documentFilter,
rangeQuery(PagesFields.page).lt(firstPageInViewport)
)
),
// The first page to contain highlights AFTER the LAST page in the viewport
search(indexName)
.sortByFieldAsc(PagesFields.page)
.highlighting(highlightFields)
.size(1)
.query(
must(query).filter(
documentFilter,
rangeQuery(PagesFields.page).gt(lastPageInViewport)
)
),
// The last page to contain highlights
search(indexName)
.sortByFieldDesc(PagesFields.page)
.highlighting(highlightFields)
.size(1)
.query(
must(query).filter(documentFilter)
)
)
}.flatMap { response =>
val results = response.items.collect { case MultisearchResponseItem(_, _, Right(result)) => result }
val errors = response.items.collect { case MultisearchResponseItem(_, status, Left(err)) =>
ElasticSearchQueryFailure(new IllegalStateException(err.toString), status, None)
}
if(errors.nonEmpty) {
Attempt.Left(MultipleFailures(errors.toList))
} else {
val pointers = results.flatMap(_.hits.hits).map(_.to[Page]).sortBy(_.page)
if(pointers.isEmpty) {
Attempt.Right(List.empty)
} else {
val min = pointers.head
val max = pointers.last
val maybePrior = pointers.reverse.find(_.page < firstPageInViewport)
val maybeAfter = pointers.find(_.page > lastPageInViewport)
// The first and last are used if you need to wrap around, e.g. you're on highlight 0 and you press "previous"
// you should be taken to the last highlight in the entire document
val prior = maybePrior.getOrElse(max)
val after = maybeAfter.getOrElse(min)
Attempt.Right(distinctByPageNumber(List(after, prior)))
}
}
}
}
}