mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-07 15:45:59 +00:00
Add summary for fulltext searches
This commit is contained in:
parent
8d7b3c7d74
commit
6346bf6a34
@ -445,7 +445,8 @@ val joex = project
|
|||||||
buildInfoPackage := "docspell.joex",
|
buildInfoPackage := "docspell.joex",
|
||||||
reStart / javaOptions ++= Seq(
|
reStart / javaOptions ++= Seq(
|
||||||
s"-Dconfig.file=${(LocalRootProject / baseDirectory).value / "local" / "dev.conf"}"
|
s"-Dconfig.file=${(LocalRootProject / baseDirectory).value / "local" / "dev.conf"}"
|
||||||
)
|
),
|
||||||
|
Revolver.enableDebugging(port = 5051, suspend = false)
|
||||||
)
|
)
|
||||||
.dependsOn(store, backend, extract, convert, analysis, joexapi, restapi, ftssolr)
|
.dependsOn(store, backend, extract, convert, analysis, joexapi, restapi, ftssolr)
|
||||||
|
|
||||||
@ -492,7 +493,8 @@ val restserver = project
|
|||||||
),
|
),
|
||||||
reStart / javaOptions ++= Seq(
|
reStart / javaOptions ++= Seq(
|
||||||
s"-Dconfig.file=${(LocalRootProject / baseDirectory).value / "local" / "dev.conf"}"
|
s"-Dconfig.file=${(LocalRootProject / baseDirectory).value / "local" / "dev.conf"}"
|
||||||
)
|
),
|
||||||
|
Revolver.enableDebugging(port = 5050, suspend = false)
|
||||||
)
|
)
|
||||||
.dependsOn(restapi, joexapi, backend, webapp, ftssolr)
|
.dependsOn(restapi, joexapi, backend, webapp, ftssolr)
|
||||||
|
|
||||||
|
@ -7,12 +7,15 @@ import fs2.Stream
|
|||||||
import docspell.backend.JobFactory
|
import docspell.backend.JobFactory
|
||||||
import docspell.backend.ops.OItemSearch._
|
import docspell.backend.ops.OItemSearch._
|
||||||
import docspell.common._
|
import docspell.common._
|
||||||
|
import docspell.common.syntax.all._
|
||||||
import docspell.ftsclient._
|
import docspell.ftsclient._
|
||||||
import docspell.store.queries.{QFolder, QItem, SelectedItem}
|
import docspell.store.queries.{QFolder, QItem, SelectedItem}
|
||||||
import docspell.store.queue.JobQueue
|
import docspell.store.queue.JobQueue
|
||||||
import docspell.store.records.RJob
|
import docspell.store.records.RJob
|
||||||
import docspell.store.{Store, qb}
|
import docspell.store.{Store, qb}
|
||||||
|
|
||||||
|
import org.log4s.getLogger
|
||||||
|
|
||||||
trait OFulltext[F[_]] {
|
trait OFulltext[F[_]] {
|
||||||
|
|
||||||
def findItems(maxNoteLen: Int)(
|
def findItems(maxNoteLen: Int)(
|
||||||
@ -34,6 +37,9 @@ trait OFulltext[F[_]] {
|
|||||||
batch: qb.Batch
|
batch: qb.Batch
|
||||||
): F[Vector[OFulltext.FtsItemWithTags]]
|
): F[Vector[OFulltext.FtsItemWithTags]]
|
||||||
|
|
||||||
|
def findIndexOnlySummary(account: AccountId, fts: OFulltext.FtsInput): F[SearchSummary]
|
||||||
|
def findItemsSummary(q: Query, fts: OFulltext.FtsInput): F[SearchSummary]
|
||||||
|
|
||||||
/** Clears the full-text index completely and launches a task that
|
/** Clears the full-text index completely and launches a task that
|
||||||
* indexes all data.
|
* indexes all data.
|
||||||
*/
|
*/
|
||||||
@ -46,6 +52,7 @@ trait OFulltext[F[_]] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
object OFulltext {
|
object OFulltext {
|
||||||
|
private[this] val logger = getLogger
|
||||||
|
|
||||||
case class FtsInput(
|
case class FtsInput(
|
||||||
query: String,
|
query: String,
|
||||||
@ -77,12 +84,14 @@ object OFulltext {
|
|||||||
Resource.pure[F, OFulltext[F]](new OFulltext[F] {
|
Resource.pure[F, OFulltext[F]](new OFulltext[F] {
|
||||||
def reindexAll: F[Unit] =
|
def reindexAll: F[Unit] =
|
||||||
for {
|
for {
|
||||||
|
_ <- logger.finfo(s"Re-index all.")
|
||||||
job <- JobFactory.reIndexAll[F]
|
job <- JobFactory.reIndexAll[F]
|
||||||
_ <- queue.insertIfNew(job) *> joex.notifyAllNodes
|
_ <- queue.insertIfNew(job) *> joex.notifyAllNodes
|
||||||
} yield ()
|
} yield ()
|
||||||
|
|
||||||
def reindexCollective(account: AccountId): F[Unit] =
|
def reindexCollective(account: AccountId): F[Unit] =
|
||||||
for {
|
for {
|
||||||
|
_ <- logger.fdebug(s"Re-index collective: $account")
|
||||||
exist <- store.transact(
|
exist <- store.transact(
|
||||||
RJob.findNonFinalByTracker(DocspellSystem.migrationTaskTracker)
|
RJob.findNonFinalByTracker(DocspellSystem.migrationTaskTracker)
|
||||||
)
|
)
|
||||||
@ -107,6 +116,7 @@ object OFulltext {
|
|||||||
FtsQuery.HighlightSetting(ftsQ.highlightPre, ftsQ.highlightPost)
|
FtsQuery.HighlightSetting(ftsQ.highlightPre, ftsQ.highlightPost)
|
||||||
)
|
)
|
||||||
for {
|
for {
|
||||||
|
_ <- logger.ftrace(s"Find index only: ${ftsQ.query}/${batch}")
|
||||||
folders <- store.transact(QFolder.getMemberFolders(account))
|
folders <- store.transact(QFolder.getMemberFolders(account))
|
||||||
ftsR <- fts.search(fq.withFolders(folders))
|
ftsR <- fts.search(fq.withFolders(folders))
|
||||||
ftsItems = ftsR.results.groupBy(_.itemId)
|
ftsItems = ftsR.results.groupBy(_.itemId)
|
||||||
@ -133,6 +143,32 @@ object OFulltext {
|
|||||||
} yield res
|
} yield res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def findIndexOnlySummary(
|
||||||
|
account: AccountId,
|
||||||
|
ftsQ: OFulltext.FtsInput
|
||||||
|
): F[SearchSummary] = {
|
||||||
|
val fq = FtsQuery(
|
||||||
|
ftsQ.query,
|
||||||
|
account.collective,
|
||||||
|
Set.empty,
|
||||||
|
Set.empty,
|
||||||
|
500,
|
||||||
|
0,
|
||||||
|
FtsQuery.HighlightSetting.default
|
||||||
|
)
|
||||||
|
|
||||||
|
for {
|
||||||
|
folder <- store.transact(QFolder.getMemberFolders(account))
|
||||||
|
itemIds <- fts
|
||||||
|
.searchAll(fq.withFolders(folder))
|
||||||
|
.flatMap(r => Stream.emits(r.results.map(_.itemId)))
|
||||||
|
.compile
|
||||||
|
.to(Set)
|
||||||
|
q = Query.empty(account).copy(itemIds = itemIds.some)
|
||||||
|
res <- store.transact(QItem.searchStats(q))
|
||||||
|
} yield res
|
||||||
|
}
|
||||||
|
|
||||||
def findItems(
|
def findItems(
|
||||||
maxNoteLen: Int
|
maxNoteLen: Int
|
||||||
)(q: Query, ftsQ: FtsInput, batch: qb.Batch): F[Vector[FtsItem]] =
|
)(q: Query, ftsQ: FtsInput, batch: qb.Batch): F[Vector[FtsItem]] =
|
||||||
@ -167,6 +203,27 @@ object OFulltext {
|
|||||||
.compile
|
.compile
|
||||||
.toVector
|
.toVector
|
||||||
|
|
||||||
|
def findItemsSummary(q: Query, ftsQ: OFulltext.FtsInput): F[SearchSummary] =
|
||||||
|
for {
|
||||||
|
search <- itemSearch.findItems(0)(q, Batch.all)
|
||||||
|
fq = FtsQuery(
|
||||||
|
ftsQ.query,
|
||||||
|
q.account.collective,
|
||||||
|
search.map(_.id).toSet,
|
||||||
|
Set.empty,
|
||||||
|
500,
|
||||||
|
0,
|
||||||
|
FtsQuery.HighlightSetting.default
|
||||||
|
)
|
||||||
|
items <- fts
|
||||||
|
.searchAll(fq)
|
||||||
|
.flatMap(r => Stream.emits(r.results.map(_.itemId)))
|
||||||
|
.compile
|
||||||
|
.to(Set)
|
||||||
|
qnext = q.copy(itemIds = items.some)
|
||||||
|
res <- store.transact(QItem.searchStats(qnext))
|
||||||
|
} yield res
|
||||||
|
|
||||||
// Helper
|
// Helper
|
||||||
|
|
||||||
private def findItemsFts[A: ItemId, B](
|
private def findItemsFts[A: ItemId, B](
|
||||||
|
@ -147,8 +147,19 @@ object ItemRoutes {
|
|||||||
for {
|
for {
|
||||||
mask <- req.as[ItemSearch]
|
mask <- req.as[ItemSearch]
|
||||||
query = Conversions.mkQuery(mask, user.account)
|
query = Conversions.mkQuery(mask, user.account)
|
||||||
stats <- backend.itemSearch.findItemsSummary(query)
|
stats <- mask match {
|
||||||
resp <- Ok(Conversions.mkSearchStats(stats))
|
case SearchFulltextOnly(ftq) if cfg.fullTextSearch.enabled =>
|
||||||
|
logger.finfo(s"Make index only summary: $ftq") *>
|
||||||
|
backend.fulltext.findIndexOnlySummary(
|
||||||
|
user.account,
|
||||||
|
OFulltext.FtsInput(ftq.query)
|
||||||
|
)
|
||||||
|
case SearchWithFulltext(fq) if cfg.fullTextSearch.enabled =>
|
||||||
|
backend.fulltext.findItemsSummary(query, OFulltext.FtsInput(fq))
|
||||||
|
case _ =>
|
||||||
|
backend.itemSearch.findItemsSummary(query)
|
||||||
|
}
|
||||||
|
resp <- Ok(Conversions.mkSearchStats(stats))
|
||||||
} yield resp
|
} yield resp
|
||||||
|
|
||||||
case GET -> Root / Ident(id) =>
|
case GET -> Root / Ident(id) =>
|
||||||
|
@ -201,27 +201,37 @@ viewStats _ model =
|
|||||||
fields =
|
fields =
|
||||||
List.filter isNumField stats.fieldStats
|
List.filter isNumField stats.fieldStats
|
||||||
in
|
in
|
||||||
if List.isEmpty fields then
|
[ div [ class "ui container" ]
|
||||||
[]
|
[ div [ class "ui middle aligned grid" ]
|
||||||
|
[ div [ class "three wide center aligned column" ]
|
||||||
else
|
[ div [ class "ui small statistic" ]
|
||||||
[ div [ class "ui container" ]
|
[ div [ class "value" ]
|
||||||
[ table [ class "ui very basic tiny six column table" ]
|
[ String.fromInt stats.count |> text
|
||||||
[ thead []
|
]
|
||||||
[ tr [ class "center aligned" ]
|
, div [ class "label" ]
|
||||||
[ th [] []
|
[ text "Results"
|
||||||
, th [] [ text "Count" ]
|
|
||||||
, th [] [ text "Sum" ]
|
|
||||||
, th [] [ text "Avg" ]
|
|
||||||
, th [] [ text "Min" ]
|
|
||||||
, th [] [ text "Max" ]
|
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
, tbody []
|
]
|
||||||
(List.map statValues fields)
|
, div [ class "thirteen wide column" ]
|
||||||
|
[ table [ class "ui very basic tiny six column table" ]
|
||||||
|
[ thead []
|
||||||
|
[ tr [ class "center aligned" ]
|
||||||
|
[ th [] []
|
||||||
|
, th [] [ text "Count" ]
|
||||||
|
, th [] [ text "Sum" ]
|
||||||
|
, th [] [ text "Avg" ]
|
||||||
|
, th [] [ text "Min" ]
|
||||||
|
, th [] [ text "Max" ]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
, tbody []
|
||||||
|
(List.map statValues fields)
|
||||||
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
viewLeftMenu : Flags -> UiSettings -> Model -> List (Html Msg)
|
viewLeftMenu : Flags -> UiSettings -> Model -> List (Html Msg)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user