Use no-op fts-client if disabled + push this flag to the webui

This commit is contained in:
Eike Kettner 2020-06-21 21:06:08 +02:00
parent 330fdcdd5b
commit cfe5aa8894
10 changed files with 88 additions and 18 deletions

View File

@ -60,7 +60,8 @@ object OFulltext {
} yield () } yield ()
def findItems(q: Query, ftsQ: String, batch: Batch): F[Vector[ListItem]] = def findItems(q: Query, ftsQ: String, batch: Batch): F[Vector[ListItem]] =
findItemsFts(q, ftsQ, batch, itemSearch.findItems) findItemsFts(q, ftsQ, batch.first, itemSearch.findItems)
.drop(batch.offset.toLong)
.take(batch.limit.toLong) .take(batch.limit.toLong)
.compile .compile
.toVector .toVector
@ -70,28 +71,33 @@ object OFulltext {
ftsQ: String, ftsQ: String,
batch: Batch batch: Batch
): F[Vector[ListItemWithTags]] = ): F[Vector[ListItemWithTags]] =
findItemsFts(q, ftsQ, batch, itemSearch.findItemsWithTags) findItemsFts(q, ftsQ, batch.first, itemSearch.findItemsWithTags)
.drop(batch.offset.toLong)
.take(batch.limit.toLong) .take(batch.limit.toLong)
.compile .compile
.toVector .toVector
private def findItemsFts[A]( private def findItemsFts[A: ItemId](
q: Query, q: Query,
ftsQ: String, ftsQ: String,
batch: Batch, batch: Batch,
search: (Query, Batch) => F[Vector[A]] search: (Query, Batch) => F[Vector[A]]
): Stream[F, A] = { ): Stream[F, A] = {
val fq = FtsQuery(ftsQ, q.collective, Nil, batch.limit, batch.offset)
val sqlResult = search(q, batch)
val fq = FtsQuery(ftsQ, q.collective, Set.empty, batch.limit, batch.offset)
val qres = val qres =
for { for {
items <- items <- sqlResult
ids = items.map(a => ItemId[A].itemId(a))
ftsQ = fq.copy(items = ids.toSet)
ftsR <-
fts fts
.search(fq) .search(ftsQ)
.map(_.results.map(_.itemId)) .map(_.results.map(_.itemId))
.map(_.toSet) .map(_.toSet)
sq = q.copy(itemIds = Some(items)) res = items.filter(a => ftsR.contains(ItemId[A].itemId(a)))
res <- search(sq, batch)
} yield res } yield res
Stream.eval(qres).flatMap { v => Stream.eval(qres).flatMap { v =>
@ -100,6 +106,23 @@ object OFulltext {
else results ++ findItemsFts(q, ftsQ, batch.next, search) else results ++ findItemsFts(q, ftsQ, batch.next, search)
} }
} }
}) })
trait ItemId[A] {
def itemId(a: A): Ident
}
object ItemId {
def apply[A](implicit ev: ItemId[A]): ItemId[A] = ev
def from[A](f: A => Ident): ItemId[A] =
new ItemId[A] {
def itemId(a: A) = f(a)
}
implicit val listItemId: ItemId[ListItem] =
ItemId.from(_.id)
implicit val listItemWithTagsId: ItemId[ListItemWithTags] =
ItemId.from(_.item.id)
}
} }

View File

@ -1,6 +1,9 @@
package docspell.ftsclient package docspell.ftsclient
import fs2.Stream import fs2.Stream
import cats.implicits._
import cats.effect._
import org.log4s.getLogger
import docspell.common._ import docspell.common._
/** The fts client is the interface for docspell to a fulltext search /** The fts client is the interface for docspell to a fulltext search
@ -90,3 +93,29 @@ trait FtsClient[F[_]] {
def clear(logger: Logger[F], collective: Ident): F[Unit] def clear(logger: Logger[F], collective: Ident): F[Unit]
} }
object FtsClient {
def none[F[_]: Sync] =
new FtsClient[F] {
private[this] val logger = Logger.log4s[F](getLogger)
def initialize: F[Unit] =
logger.info("Full-text search is disabled!")
def search(q: FtsQuery): F[FtsResult] =
logger.warn("Full-text search is disabled!") *> FtsResult.empty.pure[F]
def updateIndex(logger: Logger[F], data: Stream[F, TextData]): F[Unit] =
logger.warn("Full-text search is disabled!")
def indexData(logger: Logger[F], data: Stream[F, TextData]): F[Unit] =
logger.warn("Full-text search is disabled!")
def clearAll(logger: Logger[F]): F[Unit] =
logger.warn("Full-text search is disabled!")
def clear(logger: Logger[F], collective: Ident): F[Unit] =
logger.warn("Full-text search is disabled!")
}
}

View File

@ -13,7 +13,7 @@ import docspell.common._
final case class FtsQuery( final case class FtsQuery(
q: String, q: String,
collective: Ident, collective: Ident,
items: List[Ident], items: Set[Ident],
limit: Int, limit: Int,
offset: Int offset: Int
) { ) {

View File

@ -14,6 +14,9 @@ final case class FtsResult(
object FtsResult { object FtsResult {
val empty =
FtsResult(Duration.millis(0), 0, 0.0, Map.empty, Nil)
sealed trait MatchData sealed trait MatchData
case class AttachmentData(attachId: Ident) extends MatchData case class AttachmentData(attachId: Ident) extends MatchData
case object ItemData extends MatchData case object ItemData extends MatchData

View File

@ -39,7 +39,7 @@ object QueryData {
val items = fq.items.map(_.id).mkString(" ") val items = fq.items.map(_.id).mkString(" ")
val collQ = s"""${Field.collectiveId.name}:"${fq.collective.id}"""" val collQ = s"""${Field.collectiveId.name}:"${fq.collective.id}""""
val filterQ = fq.items match { val filterQ = fq.items match {
case Nil => case s if s.isEmpty =>
collQ collQ
case _ => case _ =>
(collQ :: List(s"""${Field.itemId.name}:($items)""")).mkString(" AND ") (collQ :: List(s"""${Field.itemId.name}:($items)""")).mkString(" AND ")

View File

@ -3,6 +3,10 @@ package docspell.joex
import cats.implicits._ import cats.implicits._
import cats.effect._ import cats.effect._
import emil.javamail._ import emil.javamail._
import fs2.concurrent.SignallingRef
import scala.concurrent.ExecutionContext
import org.http4s.client.Client
import org.http4s.client.blaze.BlazeClientBuilder
import docspell.common._ import docspell.common._
import docspell.backend.ops._ import docspell.backend.ops._
import docspell.joex.hk._ import docspell.joex.hk._
@ -15,10 +19,8 @@ import docspell.joexapi.client.JoexClient
import docspell.store.Store import docspell.store.Store
import docspell.store.queue._ import docspell.store.queue._
import docspell.store.records.RJobLog import docspell.store.records.RJobLog
import docspell.ftsclient.FtsClient
import docspell.ftssolr.SolrFtsClient import docspell.ftssolr.SolrFtsClient
import fs2.concurrent.SignallingRef
import scala.concurrent.ExecutionContext
import org.http4s.client.blaze.BlazeClientBuilder
final class JoexAppImpl[F[_]: ConcurrentEffect: ContextShift: Timer]( final class JoexAppImpl[F[_]: ConcurrentEffect: ContextShift: Timer](
cfg: Config, cfg: Config,
@ -78,7 +80,7 @@ object JoexAppImpl {
nodeOps <- ONode(store) nodeOps <- ONode(store)
joex <- OJoex(client, store) joex <- OJoex(client, store)
upload <- OUpload(store, queue, cfg.files, joex) upload <- OUpload(store, queue, cfg.files, joex)
fts <- SolrFtsClient(cfg.fullTextSearch.solr, httpClient) fts <- createFtsClient(cfg)(httpClient)
javaEmil = javaEmil =
JavaMailEmil(blocker, Settings.defaultSettings.copy(debug = cfg.mailDebug)) JavaMailEmil(blocker, Settings.defaultSettings.copy(debug = cfg.mailDebug))
sch <- SchedulerBuilder(cfg.scheduler, blocker, store) sch <- SchedulerBuilder(cfg.scheduler, blocker, store)
@ -137,4 +139,10 @@ object JoexAppImpl {
app = new JoexAppImpl(cfg, nodeOps, store, queue, pstore, termSignal, sch, psch) app = new JoexAppImpl(cfg, nodeOps, store, queue, pstore, termSignal, sch, psch)
appR <- Resource.make(app.init.map(_ => app))(_.shutdown) appR <- Resource.make(app.init.map(_ => app))(_.shutdown)
} yield appR } yield appR
private def createFtsClient[F[_]: ConcurrentEffect: ContextShift](
cfg: Config
)(client: Client[F]): Resource[F, FtsClient[F]] =
if (cfg.fullTextSearch.enabled) SolrFtsClient(cfg.fullTextSearch.solr, client)
else Resource.pure[F, FtsClient[F]](FtsClient.none[F])
} }

View File

@ -39,5 +39,6 @@ object RestAppImpl {
private def createFtsClient[F[_]: ConcurrentEffect: ContextShift]( private def createFtsClient[F[_]: ConcurrentEffect: ContextShift](
cfg: Config cfg: Config
)(client: Client[F]): Resource[F, FtsClient[F]] = )(client: Client[F]): Resource[F, FtsClient[F]] =
SolrFtsClient(cfg.fullTextSearch.solr, client) if (cfg.fullTextSearch.enabled) SolrFtsClient(cfg.fullTextSearch.solr, client)
else Resource.pure[F, FtsClient[F]](FtsClient.none[F])
} }

View File

@ -13,7 +13,8 @@ case class Flags(
baseUrl: LenientUri, baseUrl: LenientUri,
signupMode: SignupConfig.Mode, signupMode: SignupConfig.Mode,
docspellAssetPath: String, docspellAssetPath: String,
integrationEnabled: Boolean integrationEnabled: Boolean,
fullTextSearchEnabled: Boolean
) )
object Flags { object Flags {
@ -23,7 +24,8 @@ object Flags {
cfg.baseUrl, cfg.baseUrl,
cfg.backend.signup.mode, cfg.backend.signup.mode,
s"/app/assets/docspell-webapp/${BuildInfo.version}", s"/app/assets/docspell-webapp/${BuildInfo.version}",
cfg.integrationEndpoint.enabled cfg.integrationEndpoint.enabled,
cfg.fullTextSearch.enabled
) )
implicit val jsonEncoder: Encoder[Flags] = implicit val jsonEncoder: Encoder[Flags] =

View File

@ -199,6 +199,9 @@ object QItem {
def next: Batch = def next: Batch =
Batch(offset + limit, limit) Batch(offset + limit, limit)
def first: Batch =
Batch(0, limit)
} }
object Batch { object Batch {

View File

@ -15,6 +15,7 @@ type alias Config =
, signupMode : String , signupMode : String
, docspellAssetPath : String , docspellAssetPath : String
, integrationEnabled : Bool , integrationEnabled : Bool
, fullTextSearchEnabled : Bool
} }