Allow client to load items in batches

This commit is contained in:
Eike Kettner 2020-06-04 21:14:49 +02:00
parent 062618bf86
commit e5b90eff34
6 changed files with 62 additions and 15 deletions

View File

@ -12,6 +12,7 @@ import OItem.{
AttachmentArchiveData,
AttachmentData,
AttachmentSourceData,
Batch,
ItemData,
ListItem,
Query
@ -24,7 +25,7 @@ trait OItem[F[_]] {
def findItem(id: Ident, collective: Ident): F[Option[ItemData]]
def findItems(q: Query, maxResults: Int): F[Vector[ListItem]]
def findItems(q: Query, batch: Batch): F[Vector[ListItem]]
def findAttachment(id: Ident, collective: Ident): F[Option[AttachmentData[F]]]
@ -84,6 +85,9 @@ object OItem {
type Query = QItem.Query
val Query = QItem.Query
type Batch = QItem.Batch
val Batch = QItem.Batch
type ListItem = QItem.ListItem
val ListItem = QItem.ListItem
@ -138,8 +142,11 @@ object OItem {
.transact(QItem.findItem(id))
.map(opt => opt.flatMap(_.filterCollective(collective)))
def findItems(q: Query, maxResults: Int): F[Vector[ListItem]] =
store.transact(QItem.findItems(q).take(maxResults.toLong)).compile.toVector
def findItems(q: Query, batch: Batch): F[Vector[ListItem]] =
store
.transact(QItem.findItems(q, batch).take(batch.limit.toLong))
.compile
.toVector
def findAttachment(id: Ident, collective: Ident): F[Option[AttachmentData[F]]] =
store

View File

@ -8,6 +8,7 @@ import emil.markdown._
import emil.javamail.syntax._
import docspell.common._
import docspell.backend.ops.OItem.Batch
import docspell.store.records._
import docspell.store.queries.QItem
import docspell.joex.scheduler.{Context, Task}
@ -15,7 +16,7 @@ import cats.data.OptionT
import docspell.joex.mail.EmilHeader
object NotifyDueItemsTask {
val maxItems: Long = 7
val maxItems: Int = 7
type Args = NotifyDueItemsArgs
def apply[F[_]: Sync](cfg: MailSendConfig, emil: Emil[F]): Task[F, Args, Unit] =
@ -78,7 +79,11 @@ object NotifyDueItemsTask {
dueDateTo = Some(now + Duration.days(ctx.args.remindDays.toLong)),
orderAsc = Some(_.dueDate)
)
res <- ctx.store.transact(QItem.findItems(q).take(maxItems)).compile.toVector
res <-
ctx.store
.transact(QItem.findItems(q, Batch.limit(maxItems)).take(maxItems.toLong))
.compile
.toVector
} yield res
def makeMail[F[_]: Sync](

View File

@ -3121,6 +3121,8 @@ components:
- tagsInclude
- tagsExclude
- inbox
- offset
- limit
properties:
tagsInclude:
type: array
@ -3134,6 +3136,16 @@ components:
format: ident
inbox:
type: boolean
offset:
type: integer
format: int32
limit:
type: integer
format: int32
description: |
The maximum number of results to return. Note that this
limit is a soft limit, there is some hard limit on the
server, too.
direction:
type: string
format: direction

View File

@ -4,6 +4,7 @@ import cats.effect._
import cats.implicits._
import docspell.backend.BackendApp
import docspell.backend.auth.AuthToken
import docspell.backend.ops.OItem.Batch
import docspell.common.{Ident, ItemState}
import org.http4s.HttpRoutes
import org.http4s.dsl.Http4sDsl
@ -27,9 +28,12 @@ object ItemRoutes {
mask <- req.as[ItemSearch]
_ <- logger.ftrace(s"Got search mask: $mask")
query = Conversions.mkQuery(mask, user.account.collective)
_ <- logger.ftrace(s"Running query: $query")
items <- backend.item.findItems(query, 100)
resp <- Ok(Conversions.mkItemList(items))
_ <- logger.ftrace(s"Running query: $query")
items <- backend.item.findItems(
query,
Batch(mask.offset, mask.limit).restrictLimitTo(500)
)
resp <- Ok(Conversions.mkItemList(items))
} yield resp
case GET -> Root / Ident(id) =>

View File

@ -187,7 +187,22 @@ object QItem {
)
}
def findItems(q: Query): Stream[ConnectionIO, ListItem] = {
case class Batch(offset: Int, limit: Int) {
def restrictLimitTo(n: Int): Batch =
Batch(offset, math.min(n, limit))
}
object Batch {
val all: Batch = Batch(0, Int.MaxValue)
def page(n: Int, size: Int): Batch =
Batch(n * size, size)
def limit(c: Int): Batch =
Batch(0, c)
}
def findItems(q: Query, batch: Batch): Stream[ConnectionIO, ListItem] = {
val IC = RItem.Columns
val AC = RAttachment.Columns
val PC = RPerson.Columns
@ -202,7 +217,7 @@ object QItem {
IC.id.prefix("i").f,
IC.name.prefix("i").f,
IC.state.prefix("i").f,
coalesce(IC.itemDate.prefix("i").f, IC.created.prefix("i").f),
coalesce(IC.itemDate.prefix("i").f, IC.created.prefix("i").f) ++ fr"i_date",
IC.dueDate.prefix("i").f,
IC.source.prefix("i").f,
IC.incoming.prefix("i").f,
@ -310,11 +325,12 @@ object QItem {
case Some(co) =>
orderBy(coalesce(co(IC).prefix("i").f, IC.created.prefix("i").f) ++ fr"ASC")
case None =>
orderBy(
coalesce(IC.itemDate.prefix("i").f, IC.created.prefix("i").f) ++ fr"DESC"
)
orderBy(fr"i_date DESC")
}
val frag = query ++ fr"WHERE" ++ cond ++ order
val frag =
query ++ fr"WHERE" ++ cond ++ order ++ (if (batch == Batch.all) Fragment.empty
else
fr"OFFSET ${batch.offset} LIMIT ${batch.limit}")
logger.trace(s"List items: $frag")
frag.query[ListItem].stream
}

View File

@ -77,8 +77,11 @@ update key flags msg model =
doSearch : Flags -> Model -> ( Model, Cmd Msg )
doSearch flags model =
let
mask =
smask =
Comp.SearchMenu.getItemSearch model.searchMenuModel
mask =
{ smask | limit = 100 }
in
( { model | searchInProgress = True, viewMode = Listing }
, Api.itemSearch flags mask ItemSearchResp