Skip to content

Commit

Permalink
Merge pull request #1574 from eikek/fix/search-limit-cap
Browse files Browse the repository at this point in the history
Include limit-capped flag with search response
  • Loading branch information
mergify[bot] authored May 26, 2022
2 parents f8baf44 + 50edf13 commit e56e313
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 35 deletions.
22 changes: 22 additions & 0 deletions modules/restapi/src/main/resources/docspell-openapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8591,11 +8591,33 @@ components:
A list of item details.
required:
- groups
- limit
- offset
- limitCapped
properties:
groups:
type: array
items:
$ref: "#/components/schemas/ItemLightGroup"
limit:
type: integer
format: int32
description: |
Returns the `limit` value as used for this search. This
can deviate from the requested limit, if it exceeds the
server defined maximum. See `limitCapped`.
offset:
type: integer
format: int32
description: |
The `offset` value used for this search.
limitCapped:
type: boolean
description: |
The server defines a maximum `limit` value. If the
requested `limit` exceeds the server defined one, this
flag is set to true. The limit used for the query is
returned with this response.
ItemLightGroup:
description: |
A group of items.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import docspell.ftsclient.FtsResult
import docspell.restapi.model._
import docspell.restserver.conv.Conversions._
import docspell.restserver.http4s.ContentDisposition
import docspell.store.qb.Batch
import docspell.store.queries.{AttachmentLight => QAttachmentLight, IdRefCount}
import docspell.store.records._
import docspell.store.{AddResult, UpdateResult}
Expand Down Expand Up @@ -171,57 +172,93 @@ trait Conversions {
def mkCustomValue(v: CustomFieldValue): OItemSearch.CustomValue =
OItemSearch.CustomValue(v.field, v.value)

def mkItemList(v: Vector[OItemSearch.ListItem]): ItemLightList = {
def mkItemList(
v: Vector[OItemSearch.ListItem],
batch: Batch,
capped: Boolean
): ItemLightList = {
val groups = v.groupBy(item => item.date.toUtcDate.toString.substring(0, 7))

def mkGroup(g: (String, Vector[OItemSearch.ListItem])): ItemLightGroup =
ItemLightGroup(g._1, g._2.map(mkItemLight).toList)

val gs =
groups.map(mkGroup).toList.sortWith((g1, g2) => g1.name.compareTo(g2.name) >= 0)
ItemLightList(gs)
ItemLightList(gs, batch.limit, batch.offset, capped)
}

def mkItemListFts(v: Vector[OFulltext.FtsItem]): ItemLightList = {
def mkItemListFts(
v: Vector[OFulltext.FtsItem],
batch: Batch,
capped: Boolean
): ItemLightList = {
val groups = v.groupBy(item => item.item.date.toUtcDate.toString.substring(0, 7))

def mkGroup(g: (String, Vector[OFulltext.FtsItem])): ItemLightGroup =
ItemLightGroup(g._1, g._2.map(mkItemLight).toList)

val gs =
groups.map(mkGroup).toList.sortWith((g1, g2) => g1.name.compareTo(g2.name) >= 0)
ItemLightList(gs)
ItemLightList(gs, batch.limit, batch.offset, capped)
}

def mkItemListWithTags(v: Vector[OItemSearch.ListItemWithTags]): ItemLightList = {
def mkItemListWithTags(
v: Vector[OItemSearch.ListItemWithTags],
batch: Batch,
capped: Boolean
): ItemLightList = {
val groups = v.groupBy(ti => ti.item.date.toUtcDate.toString.substring(0, 7))

def mkGroup(g: (String, Vector[OItemSearch.ListItemWithTags])): ItemLightGroup =
ItemLightGroup(g._1, g._2.map(mkItemLightWithTags).toList)

val gs =
groups.map(mkGroup).toList.sortWith((g1, g2) => g1.name.compareTo(g2.name) >= 0)
ItemLightList(gs)
ItemLightList(gs, batch.limit, batch.offset, capped)
}

def mkItemListWithTagsFts(v: Vector[OFulltext.FtsItemWithTags]): ItemLightList = {
def mkItemListWithTagsFts(
v: Vector[OFulltext.FtsItemWithTags],
batch: Batch,
capped: Boolean
): ItemLightList = {
val groups = v.groupBy(ti => ti.item.item.date.toUtcDate.toString.substring(0, 7))

def mkGroup(g: (String, Vector[OFulltext.FtsItemWithTags])): ItemLightGroup =
ItemLightGroup(g._1, g._2.map(mkItemLightWithTags).toList)

val gs =
groups.map(mkGroup).toList.sortWith((g1, g2) => g1.name.compareTo(g2.name) >= 0)
ItemLightList(gs)
ItemLightList(gs, batch.limit, batch.offset, capped)
}

def mkItemListWithTagsFtsPlain(v: Vector[OFulltext.FtsItemWithTags]): ItemLightList =
if (v.isEmpty) ItemLightList(Nil)
else ItemLightList(List(ItemLightGroup("Results", v.map(mkItemLightWithTags).toList)))
def mkItemListWithTagsFtsPlain(
v: Vector[OFulltext.FtsItemWithTags],
batch: Batch,
capped: Boolean
): ItemLightList =
if (v.isEmpty) ItemLightList(Nil, batch.limit, batch.offset, capped)
else
ItemLightList(
List(ItemLightGroup("Results", v.map(mkItemLightWithTags).toList)),
batch.limit,
batch.offset,
capped
)

def mkItemListFtsPlain(v: Vector[OFulltext.FtsItem]): ItemLightList =
if (v.isEmpty) ItemLightList(Nil)
else ItemLightList(List(ItemLightGroup("Results", v.map(mkItemLight).toList)))
def mkItemListFtsPlain(
v: Vector[OFulltext.FtsItem],
batch: Batch,
capped: Boolean
): ItemLightList =
if (v.isEmpty) ItemLightList(Nil, batch.limit, batch.offset, capped)
else
ItemLightList(
List(ItemLightGroup("Results", v.map(mkItemLight).toList)),
batch.limit,
batch.offset,
capped
)

def mkItemLight(i: OItemSearch.ListItem): ItemLight =
ItemLight(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ object ItemRoutes {
) :? QP.WithDetails(detailFlag) :? QP.SearchKind(searchMode) =>
val batch = Batch(offset.getOrElse(0), limit.getOrElse(cfg.maxItemPageSize))
.restrictLimitTo(cfg.maxItemPageSize)
val limitCapped = limit.exists(_ > cfg.maxItemPageSize)
val itemQuery = ItemQueryString(q)
val settings = OSimpleSearch.Settings(
batch,
Expand All @@ -59,7 +60,7 @@ object ItemRoutes {
searchMode.getOrElse(SearchMode.Normal)
)
val fixQuery = Query.Fix(user.account, None, None)
searchItems(backend, dsl)(settings, fixQuery, itemQuery)
searchItems(backend, dsl)(settings, fixQuery, itemQuery, limitCapped)

case GET -> Root / "searchStats" :? QP.Query(q) :? QP.SearchKind(searchMode) =>
val itemQuery = ItemQueryString(q)
Expand All @@ -79,6 +80,7 @@ object ItemRoutes {
).restrictLimitTo(
cfg.maxItemPageSize
)
limitCapped = userQuery.limit.exists(_ > cfg.maxItemPageSize)
itemQuery = ItemQueryString(userQuery.query)
settings = OSimpleSearch.Settings(
batch,
Expand All @@ -88,7 +90,7 @@ object ItemRoutes {
searchMode = userQuery.searchMode.getOrElse(SearchMode.Normal)
)
fixQuery = Query.Fix(user.account, None, None)
resp <- searchItems(backend, dsl)(settings, fixQuery, itemQuery)
resp <- searchItems(backend, dsl)(settings, fixQuery, itemQuery, limitCapped)
} yield resp

case req @ POST -> Root / "searchStats" =>
Expand All @@ -106,19 +108,20 @@ object ItemRoutes {
case req @ POST -> Root / "searchIndex" =>
for {
mask <- req.as[ItemQuery]
limitCapped = mask.limit.exists(_ > cfg.maxItemPageSize)
resp <- mask.query match {
case q if q.length > 1 =>
val ftsIn = OFulltext.FtsInput(q)
val batch = Batch(
mask.offset.getOrElse(0),
mask.limit.getOrElse(cfg.maxItemPageSize)
).restrictLimitTo(cfg.maxItemPageSize)
for {
items <- backend.fulltext.findIndexOnly(cfg.maxNoteLength)(
ftsIn,
user.account,
Batch(
mask.offset.getOrElse(0),
mask.limit.getOrElse(cfg.maxItemPageSize)
).restrictLimitTo(cfg.maxItemPageSize)
items <- backend.fulltext
.findIndexOnly(cfg.maxNoteLength)(ftsIn, user.account, batch)
ok <- Ok(
Conversions.mkItemListWithTagsFtsPlain(items, batch, limitCapped)
)
ok <- Ok(Conversions.mkItemListWithTagsFtsPlain(items))
} yield ok

case _ =>
Expand Down Expand Up @@ -429,17 +432,20 @@ object ItemRoutes {
)(
settings: OSimpleSearch.Settings,
fixQuery: Query.Fix,
itemQuery: ItemQueryString
itemQuery: ItemQueryString,
limitCapped: Boolean
): F[Response[F]] = {
import dsl._

def convertFts(res: OSimpleSearch.Items.FtsItems): ItemLightList =
if (res.indexOnly) Conversions.mkItemListFtsPlain(res.items)
else Conversions.mkItemListFts(res.items)
if (res.indexOnly)
Conversions.mkItemListFtsPlain(res.items, settings.batch, limitCapped)
else Conversions.mkItemListFts(res.items, settings.batch, limitCapped)

def convertFtsFull(res: OSimpleSearch.Items.FtsItemsFull): ItemLightList =
if (res.indexOnly) Conversions.mkItemListWithTagsFtsPlain(res.items)
else Conversions.mkItemListWithTagsFts(res.items)
if (res.indexOnly)
Conversions.mkItemListWithTagsFtsPlain(res.items, settings.batch, limitCapped)
else Conversions.mkItemListWithTagsFts(res.items, settings.batch, limitCapped)

backend.simpleSearch
.searchByString(settings)(fixQuery, itemQuery)
Expand All @@ -449,8 +455,8 @@ object ItemRoutes {
items.fold(
convertFts,
convertFtsFull,
Conversions.mkItemList,
Conversions.mkItemListWithTags
els => Conversions.mkItemList(els, settings.batch, limitCapped),
els => Conversions.mkItemListWithTags(els, settings.batch, limitCapped)
)
)
case StringSearchResult.FulltextMismatch(TooMany) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ object ShareSearchRoutes {
).restrictLimitTo(
cfg.maxItemPageSize
)
limitCapped = userQuery.limit.exists(_ > cfg.maxItemPageSize)
itemQuery = ItemQueryString(userQuery.query)
settings = OSimpleSearch.Settings(
batch,
Expand All @@ -62,7 +63,12 @@ object ShareSearchRoutes {
account = share.account
fixQuery = Query.Fix(account, Some(share.query.expr), None)
_ <- logger.debug(s"Searching in share ${share.id.id}: ${userQuery.query}")
resp <- ItemRoutes.searchItems(backend, dsl)(settings, fixQuery, itemQuery)
resp <- ItemRoutes.searchItems(backend, dsl)(
settings,
fixQuery,
itemQuery,
limitCapped
)
} yield resp
}
.getOrElseF(NotFound())
Expand Down
6 changes: 3 additions & 3 deletions modules/webapp/src/main/elm/Data/Items.elm
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,10 @@ concat l0 l1 =
suff =
List.drop 1 l1.groups
in
ItemLightList (prev ++ (ng :: suff))
ItemLightList (prev ++ (ng :: suff)) 0 0 False

else
ItemLightList (l0.groups ++ l1.groups)
ItemLightList (l0.groups ++ l1.groups) 0 0 False


first : ItemLightList -> Maybe ItemLight
Expand Down Expand Up @@ -121,7 +121,7 @@ replaceIn origin replacements =
|> ItemLightGroup g.name
in
List.map replaceGroup origin.groups
|> ItemLightList
|> (\els -> ItemLightList els origin.limit origin.offset origin.limitCapped)



Expand Down

0 comments on commit e56e313

Please sign in to comment.