Extend search stats to fully populate search menu

Refs: #856
This commit is contained in:
eikek 2021-10-05 13:50:31 +02:00
parent e961a5ac10
commit 813797756c
8 changed files with 167 additions and 8 deletions

View File

@ -5305,6 +5305,10 @@ components:
- tagCategoryCloud - tagCategoryCloud
- fieldStats - fieldStats
- folderStats - folderStats
- corrOrgStats
- corrPersStats
- concPersStats
- concEquipStats
properties: properties:
count: count:
type: integer type: integer
@ -5321,6 +5325,23 @@ components:
type: array type: array
items: items:
$ref: "#/components/schemas/FolderStats" $ref: "#/components/schemas/FolderStats"
corrOrgStats:
type: array
items:
$ref: "#/components/schemas/IdRefStats"
corrPersStats:
type: array
items:
$ref: "#/components/schemas/IdRefStats"
concPersStats:
type: array
items:
$ref: "#/components/schemas/IdRefStats"
concEquipStats:
type: array
items:
$ref: "#/components/schemas/IdRefStats"
ItemInsights: ItemInsights:
description: | description: |
Information about the items in docspell. Information about the items in docspell.
@ -5454,6 +5475,19 @@ components:
type: integer type: integer
format: int32 format: int32
IdRefStats:
description: |
Counting some objects that have an id and a name.
required:
- ref
- count
properties:
ref:
$ref: "#/components/schemas/IdName"
count:
type: integer
format: int32
AttachmentMeta: AttachmentMeta:
description: | description: |
Extracted meta data of an attachment. Extracted meta data of an attachment.

View File

@ -7,11 +7,9 @@
package docspell.restserver.conv package docspell.restserver.conv
import java.time.{LocalDate, ZoneId} import java.time.{LocalDate, ZoneId}
import cats.effect.{Async, Sync} import cats.effect.{Async, Sync}
import cats.implicits._ import cats.implicits._
import fs2.Stream import fs2.Stream
import docspell.backend.ops.OCollective.{InsightData, PassChangeResult} import docspell.backend.ops.OCollective.{InsightData, PassChangeResult}
import docspell.backend.ops.OCustomFields.SetValueResult import docspell.backend.ops.OCustomFields.SetValueResult
import docspell.backend.ops.OJob.JobCancelResult import docspell.backend.ops.OJob.JobCancelResult
@ -22,10 +20,9 @@ import docspell.common.syntax.all._
import docspell.ftsclient.FtsResult import docspell.ftsclient.FtsResult
import docspell.restapi.model._ import docspell.restapi.model._
import docspell.restserver.conv.Conversions._ import docspell.restserver.conv.Conversions._
import docspell.store.queries.{AttachmentLight => QAttachmentLight} import docspell.store.queries.{AttachmentLight => QAttachmentLight, IdRefCount}
import docspell.store.records._ import docspell.store.records._
import docspell.store.{AddResult, UpdateResult} import docspell.store.{AddResult, UpdateResult}
import org.http4s.headers.`Content-Type` import org.http4s.headers.`Content-Type`
import org.http4s.multipart.Multipart import org.http4s.multipart.Multipart
import org.log4s.Logger import org.log4s.Logger
@ -38,9 +35,16 @@ trait Conversions {
mkTagCloud(sum.tags), mkTagCloud(sum.tags),
mkTagCategoryCloud(sum.cats), mkTagCategoryCloud(sum.cats),
sum.fields.map(mkFieldStats), sum.fields.map(mkFieldStats),
sum.folders.map(mkFolderStats) sum.folders.map(mkFolderStats),
sum.corrOrgs.map(mkIdRefStats),
sum.corrPers.map(mkIdRefStats),
sum.concPers.map(mkIdRefStats),
sum.concEquip.map(mkIdRefStats)
) )
def mkIdRefStats(s: IdRefCount): IdRefStats =
IdRefStats(mkIdName(s.ref), s.count)
def mkFolderStats(fs: docspell.store.queries.FolderCount): FolderStats = def mkFolderStats(fs: docspell.store.queries.FolderCount): FolderStats =
FolderStats(fs.id, fs.name, mkIdName(fs.owner), fs.count) FolderStats(fs.id, fs.name, mkIdName(fs.owner), fs.count)

View File

@ -0,0 +1,5 @@
package docspell.store.queries
import docspell.common._
final case class IdRefCount(ref: IdRef, count: Int) {}

View File

@ -192,7 +192,21 @@ object QItem {
cats <- searchTagCategorySummary(today)(q) cats <- searchTagCategorySummary(today)(q)
fields <- searchFieldSummary(today)(q) fields <- searchFieldSummary(today)(q)
folders <- searchFolderSummary(today)(q) folders <- searchFolderSummary(today)(q)
} yield SearchSummary(count, tags, cats, fields, folders) orgs <- searchCorrOrgSummary(today)(q)
corrPers <- searchCorrPersonSummary(today)(q)
concPers <- searchConcPersonSummary(today)(q)
concEquip <- searchConcEquipSummary(today)(q)
} yield SearchSummary(
count,
tags,
cats,
fields,
folders,
orgs,
corrPers,
concPers,
concEquip
)
def searchTagCategorySummary( def searchTagCategorySummary(
today: LocalDate today: LocalDate
@ -251,6 +265,40 @@ object QItem {
.query[Int] .query[Int]
.unique .unique
def searchCorrOrgSummary(today: LocalDate)(q: Query): ConnectionIO[List[IdRefCount]] =
searchIdRefSummary(org.oid, org.name, i.corrOrg, today)(q)
def searchCorrPersonSummary(today: LocalDate)(
q: Query
): ConnectionIO[List[IdRefCount]] =
searchIdRefSummary(pers0.pid, pers0.name, i.corrPerson, today)(q)
def searchConcPersonSummary(today: LocalDate)(
q: Query
): ConnectionIO[List[IdRefCount]] =
searchIdRefSummary(pers1.pid, pers1.name, i.concPerson, today)(q)
def searchConcEquipSummary(today: LocalDate)(
q: Query
): ConnectionIO[List[IdRefCount]] =
searchIdRefSummary(equip.eid, equip.name, i.concEquipment, today)(q)
private def searchIdRefSummary(
idCol: Column[Ident],
nameCol: Column[String],
fkCol: Column[Ident],
today: LocalDate
)(q: Query): ConnectionIO[List[IdRefCount]] =
findItemsBase(q.fix, today, 0).unwrap
.withSelect(select(idCol, nameCol).append(count(idCol).as("num")))
.changeWhere(c =>
c && fkCol.isNotNull && queryCondition(today, q.fix.account.collective, q.cond)
)
.groupBy(idCol, nameCol)
.build
.query[IdRefCount]
.to[List]
def searchFolderSummary(today: LocalDate)(q: Query): ConnectionIO[List[FolderCount]] = { def searchFolderSummary(today: LocalDate)(q: Query): ConnectionIO[List[FolderCount]] = {
val fu = RUser.as("fu") val fu = RUser.as("fu")
findItemsBase(q.fix, today, 0).unwrap findItemsBase(q.fix, today, 0).unwrap

View File

@ -11,7 +11,11 @@ case class SearchSummary(
tags: List[TagCount], tags: List[TagCount],
cats: List[CategoryCount], cats: List[CategoryCount],
fields: List[FieldStats], fields: List[FieldStats],
folders: List[FolderCount] folders: List[FolderCount],
corrOrgs: List[IdRefCount],
corrPers: List[IdRefCount],
concPers: List[IdRefCount],
concEquip: List[IdRefCount]
) { ) {
def onlyExisting: SearchSummary = def onlyExisting: SearchSummary =
@ -20,6 +24,10 @@ case class SearchSummary(
tags.filter(_.count > 0), tags.filter(_.count > 0),
cats.filter(_.count > 0), cats.filter(_.count > 0),
fields.filter(_.count > 0), fields.filter(_.count > 0),
folders.filter(_.count > 0) folders.filter(_.count > 0),
corrOrgs = corrOrgs.filter(_.count > 0),
corrPers = corrPers.filter(_.count > 0),
concPers = concPers.filter(_.count > 0),
concEquip = concEquip.filter(_.count > 0)
) )
} }

View File

@ -16,6 +16,7 @@ module Comp.CustomFieldMultiInput exposing
, isEmpty , isEmpty
, nonEmpty , nonEmpty
, reset , reset
, setOptions
, setValues , setValues
, update , update
, updateSearch , updateSearch
@ -125,6 +126,11 @@ setValues values =
SetValues values SetValues values
setOptions : List CustomField -> Msg
setOptions fields =
CustomFieldResp (Ok (CustomFieldList fields))
reset : Model -> Model reset : Model -> Model
reset model = reset model =
let let

View File

@ -60,6 +60,7 @@ import Http
import Messages.Comp.SearchMenu exposing (Texts) import Messages.Comp.SearchMenu exposing (Texts)
import Set exposing (Set) import Set exposing (Set)
import Styles as S import Styles as S
import Util.CustomField
import Util.Html exposing (KeyCode(..)) import Util.Html exposing (KeyCode(..))
import Util.ItemDragDrop as DD import Util.ItemDragDrop as DD
import Util.Maybe import Util.Maybe
@ -564,6 +565,42 @@ updateDrop ddm flags settings msg model =
selectModel = selectModel =
Comp.TagSelect.modifyCount model.tagSelectModel tagCount catCount Comp.TagSelect.modifyCount model.tagSelectModel tagCount catCount
orgOpts =
Comp.Dropdown.update (Comp.Dropdown.SetOptions (List.map .ref stats.corrOrgStats))
model.orgModel
|> Tuple.first
corrPersOpts =
Comp.Dropdown.update (Comp.Dropdown.SetOptions (List.map .ref stats.corrPersStats))
model.corrPersonModel
|> Tuple.first
concPersOpts =
Comp.Dropdown.update (Comp.Dropdown.SetOptions (List.map .ref stats.concPersStats))
model.concPersonModel
|> Tuple.first
concEquipOpts =
let
mkEquip ref =
Equipment ref.id ref.name 0 Nothing ""
in
Comp.Dropdown.update
(Comp.Dropdown.SetOptions
(List.map (.ref >> mkEquip) stats.concEquipStats)
)
model.concEquipmentModel
|> Tuple.first
fields =
Util.CustomField.statsToFields stats
fieldOpts =
Comp.CustomFieldMultiInput.update flags
(Comp.CustomFieldMultiInput.setOptions fields)
model.customFieldModel
|> .model
model_ = model_ =
{ model { model
| tagSelectModel = selectModel | tagSelectModel = selectModel
@ -571,6 +608,11 @@ updateDrop ddm flags settings msg model =
Comp.FolderSelect.modify model.selectedFolder Comp.FolderSelect.modify model.selectedFolder
model.folderList model.folderList
stats.folderStats stats.folderStats
, orgModel = orgOpts
, corrPersonModel = corrPersOpts
, concPersonModel = concPersOpts
, concEquipmentModel = concEquipOpts
, customFieldModel = fieldOpts
} }
in in
{ model = model_ { model = model_

View File

@ -10,9 +10,12 @@ module Util.CustomField exposing
, nameOrLabel , nameOrLabel
, renderValue , renderValue
, renderValue2 , renderValue2
, statsToFields
) )
import Api.Model.CustomField exposing (CustomField)
import Api.Model.ItemFieldValue exposing (ItemFieldValue) import Api.Model.ItemFieldValue exposing (ItemFieldValue)
import Api.Model.SearchStats exposing (SearchStats)
import Data.CustomFieldType import Data.CustomFieldType
import Data.Icons as Icons import Data.Icons as Icons
import Html exposing (..) import Html exposing (..)
@ -20,6 +23,15 @@ import Html.Attributes exposing (..)
import Html.Events exposing (onClick) import Html.Events exposing (onClick)
statsToFields : SearchStats -> List CustomField
statsToFields stats =
let
mkField fs =
CustomField fs.id fs.name fs.label fs.ftype fs.count 0
in
List.map mkField stats.fieldStats
{-| This is how the server wants the value to a bool custom field {-| This is how the server wants the value to a bool custom field
-} -}
boolValue : Bool -> String boolValue : Bool -> String