mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-22 02:18:26 +00:00
Correctly count tag categories
If multiple tags of the same category are applied to the same item, just summing tag counts will produce the wrong results as now items are counted multiple times.
This commit is contained in:
@ -71,6 +71,9 @@ object OCollective {
|
|||||||
type TagCount = docspell.store.queries.TagCount
|
type TagCount = docspell.store.queries.TagCount
|
||||||
val TagCount = docspell.store.queries.TagCount
|
val TagCount = docspell.store.queries.TagCount
|
||||||
|
|
||||||
|
type CategoryCount = docspell.store.queries.CategoryCount
|
||||||
|
val CategoryCount = docspell.store.queries.CategoryCount
|
||||||
|
|
||||||
type InsightData = QCollective.InsightData
|
type InsightData = QCollective.InsightData
|
||||||
val insightData = QCollective.InsightData
|
val insightData = QCollective.InsightData
|
||||||
|
|
||||||
|
@ -4242,6 +4242,7 @@ components:
|
|||||||
required:
|
required:
|
||||||
- count
|
- count
|
||||||
- tagCloud
|
- tagCloud
|
||||||
|
- tagCategoryCloud
|
||||||
- fieldStats
|
- fieldStats
|
||||||
- folderStats
|
- folderStats
|
||||||
properties:
|
properties:
|
||||||
@ -4250,6 +4251,8 @@ components:
|
|||||||
format: int32
|
format: int32
|
||||||
tagCloud:
|
tagCloud:
|
||||||
$ref: "#/components/schemas/TagCloud"
|
$ref: "#/components/schemas/TagCloud"
|
||||||
|
tagCategoryCloud:
|
||||||
|
$ref: "#/components/schemas/NameCloud"
|
||||||
fieldStats:
|
fieldStats:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
@ -4354,7 +4357,7 @@ components:
|
|||||||
$ref: "#/components/schemas/TagCount"
|
$ref: "#/components/schemas/TagCount"
|
||||||
TagCount:
|
TagCount:
|
||||||
description: |
|
description: |
|
||||||
Generic structure for counting something.
|
Structure for counting tags.
|
||||||
required:
|
required:
|
||||||
- tag
|
- tag
|
||||||
- count
|
- count
|
||||||
@ -4364,6 +4367,30 @@ components:
|
|||||||
count:
|
count:
|
||||||
type: integer
|
type: integer
|
||||||
format: int32
|
format: int32
|
||||||
|
|
||||||
|
NameCloud:
|
||||||
|
description: |
|
||||||
|
A set of counters.
|
||||||
|
required:
|
||||||
|
- items
|
||||||
|
properties:
|
||||||
|
items:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/NameCount"
|
||||||
|
NameCount:
|
||||||
|
description: |
|
||||||
|
Generic structure for counting something.
|
||||||
|
required:
|
||||||
|
- name
|
||||||
|
- count
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
count:
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
|
|
||||||
AttachmentMeta:
|
AttachmentMeta:
|
||||||
description: |
|
description: |
|
||||||
Extracted meta data of an attachment.
|
Extracted meta data of an attachment.
|
||||||
|
@ -31,6 +31,7 @@ trait Conversions {
|
|||||||
SearchStats(
|
SearchStats(
|
||||||
sum.count,
|
sum.count,
|
||||||
mkTagCloud(sum.tags),
|
mkTagCloud(sum.tags),
|
||||||
|
mkTagCategoryCloud(sum.cats),
|
||||||
sum.fields.map(mkFieldStats),
|
sum.fields.map(mkFieldStats),
|
||||||
sum.folders.map(mkFolderStats)
|
sum.folders.map(mkFolderStats)
|
||||||
)
|
)
|
||||||
@ -63,6 +64,9 @@ trait Conversions {
|
|||||||
def mkTagCloud(tags: List[OCollective.TagCount]) =
|
def mkTagCloud(tags: List[OCollective.TagCount]) =
|
||||||
TagCloud(tags.map(tc => TagCount(mkTag(tc.tag), tc.count)))
|
TagCloud(tags.map(tc => TagCount(mkTag(tc.tag), tc.count)))
|
||||||
|
|
||||||
|
def mkTagCategoryCloud(tags: List[OCollective.CategoryCount]) =
|
||||||
|
NameCloud(tags.map(tc => NameCount(tc.category, tc.count)))
|
||||||
|
|
||||||
// attachment meta
|
// attachment meta
|
||||||
def mkAttachmentMeta(rm: RAttachmentMeta): AttachmentMeta =
|
def mkAttachmentMeta(rm: RAttachmentMeta): AttachmentMeta =
|
||||||
AttachmentMeta(
|
AttachmentMeta(
|
||||||
|
@ -9,11 +9,11 @@ object DBFunction {
|
|||||||
val countAll: DBFunction = CountAll
|
val countAll: DBFunction = CountAll
|
||||||
|
|
||||||
def countAs[A](column: Column[A]): DBFunction =
|
def countAs[A](column: Column[A]): DBFunction =
|
||||||
Count(column)
|
Count(column, false)
|
||||||
|
|
||||||
case object CountAll extends DBFunction
|
case object CountAll extends DBFunction
|
||||||
|
|
||||||
case class Count(column: Column[_]) extends DBFunction
|
case class Count(column: Column[_], distinct: Boolean) extends DBFunction
|
||||||
|
|
||||||
case class Max(expr: SelectExpr) extends DBFunction
|
case class Max(expr: SelectExpr) extends DBFunction
|
||||||
|
|
||||||
|
@ -63,7 +63,10 @@ trait DSL extends DoobieMeta {
|
|||||||
FromExpr.From(sel, alias)
|
FromExpr.From(sel, alias)
|
||||||
|
|
||||||
def count(c: Column[_]): DBFunction =
|
def count(c: Column[_]): DBFunction =
|
||||||
DBFunction.Count(c)
|
DBFunction.Count(c, false)
|
||||||
|
|
||||||
|
def countDistinct(c: Column[_]): DBFunction =
|
||||||
|
DBFunction.Count(c, true)
|
||||||
|
|
||||||
def countAll: DBFunction =
|
def countAll: DBFunction =
|
||||||
DBFunction.CountAll
|
DBFunction.CountAll
|
||||||
|
@ -13,8 +13,9 @@ object DBFunctionBuilder extends CommonBuilder {
|
|||||||
case DBFunction.CountAll =>
|
case DBFunction.CountAll =>
|
||||||
sql"COUNT(*)"
|
sql"COUNT(*)"
|
||||||
|
|
||||||
case DBFunction.Count(col) =>
|
case DBFunction.Count(col, distinct) =>
|
||||||
sql"COUNT(" ++ column(col) ++ fr")"
|
if (distinct) sql"COUNT(DISTINCT " ++ column(col) ++ fr")"
|
||||||
|
else sql"COUNT(" ++ column(col) ++ fr")"
|
||||||
|
|
||||||
case DBFunction.Max(expr) =>
|
case DBFunction.Max(expr) =>
|
||||||
sql"MAX(" ++ SelectExprBuilder.build(expr) ++ fr")"
|
sql"MAX(" ++ SelectExprBuilder.build(expr) ++ fr")"
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
package docspell.store.queries
|
||||||
|
|
||||||
|
final case class CategoryCount(category: String, count: Int)
|
@ -190,9 +190,38 @@ object QItem {
|
|||||||
for {
|
for {
|
||||||
count <- searchCountSummary(today)(q)
|
count <- searchCountSummary(today)(q)
|
||||||
tags <- searchTagSummary(today)(q)
|
tags <- searchTagSummary(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, fields, folders)
|
} yield SearchSummary(count, tags, cats, fields, folders)
|
||||||
|
|
||||||
|
def searchTagCategorySummary(
|
||||||
|
today: LocalDate
|
||||||
|
)(q: Query): ConnectionIO[List[CategoryCount]] = {
|
||||||
|
val tagFrom =
|
||||||
|
from(ti)
|
||||||
|
.innerJoin(tag, tag.tid === ti.tagId)
|
||||||
|
.innerJoin(i, i.id === ti.itemId)
|
||||||
|
|
||||||
|
val tagCloud =
|
||||||
|
findItemsBase(q.fix, today, 0).unwrap
|
||||||
|
.withSelect(select(tag.category).append(countDistinct(i.id).as("num")))
|
||||||
|
.changeFrom(_.prepend(tagFrom))
|
||||||
|
.changeWhere(c => c && queryCondition(today, q.fix.account.collective, q.cond))
|
||||||
|
.groupBy(tag.category)
|
||||||
|
.build
|
||||||
|
.query[CategoryCount]
|
||||||
|
.to[List]
|
||||||
|
|
||||||
|
// the previous query starts from tags, so items with tag-count=0
|
||||||
|
// are not included they are fetched separately
|
||||||
|
for {
|
||||||
|
existing <- tagCloud
|
||||||
|
allCats <- RTag.listCategories(q.fix.account.collective)
|
||||||
|
other = allCats.diff(existing.map(_.category))
|
||||||
|
} yield existing ++ other.map(CategoryCount(_, 0))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
def searchTagSummary(today: LocalDate)(q: Query): ConnectionIO[List[TagCount]] = {
|
def searchTagSummary(today: LocalDate)(q: Query): ConnectionIO[List[TagCount]] = {
|
||||||
val tagFrom =
|
val tagFrom =
|
||||||
|
@ -3,6 +3,7 @@ package docspell.store.queries
|
|||||||
case class SearchSummary(
|
case class SearchSummary(
|
||||||
count: Int,
|
count: Int,
|
||||||
tags: List[TagCount],
|
tags: List[TagCount],
|
||||||
|
cats: List[CategoryCount],
|
||||||
fields: List[FieldStats],
|
fields: List[FieldStats],
|
||||||
folders: List[FolderCount]
|
folders: List[FolderCount]
|
||||||
)
|
)
|
||||||
|
@ -2,4 +2,4 @@ package docspell.store.queries
|
|||||||
|
|
||||||
import docspell.store.records.RTag
|
import docspell.store.records.RTag
|
||||||
|
|
||||||
case class TagCount(tag: RTag, count: Int)
|
final case class TagCount(tag: RTag, count: Int)
|
||||||
|
Reference in New Issue
Block a user