From 3fccc3df39273a1437429d34148e344f51c3b8b4 Mon Sep 17 00:00:00 2001
From: Eike Kettner <eike.kettner@posteo.de>
Date: Mon, 11 Jan 2021 12:06:20 +0100
Subject: [PATCH] Return all tags in search stats result

Before only tags with a count > 0 were included. Now those that have
not attached to any item are returned as well.
---
 .../scala/docspell/store/queries/QItem.scala  | 24 ++++++++++++-------
 .../scala/docspell/store/records/RTag.scala   | 13 ++++++++++
 2 files changed, 29 insertions(+), 8 deletions(-)

diff --git a/modules/store/src/main/scala/docspell/store/queries/QItem.scala b/modules/store/src/main/scala/docspell/store/queries/QItem.scala
index b4ca2472..3ce1af55 100644
--- a/modules/store/src/main/scala/docspell/store/queries/QItem.scala
+++ b/modules/store/src/main/scala/docspell/store/queries/QItem.scala
@@ -250,14 +250,22 @@ object QItem {
         .innerJoin(tag, tag.tid === ti.tagId)
         .innerJoin(i, i.id === ti.itemId)
 
-    findItemsBase(q, 0).unwrap
-      .withSelect(select(tag.all).append(count(i.id).as("num")))
-      .changeFrom(_.prepend(tagFrom))
-      .changeWhere(c => c && queryCondition(q))
-      .groupBy(tag.tid)
-      .build
-      .query[TagCount]
-      .to[List]
+    val tagCloud =
+      findItemsBase(q, 0).unwrap
+        .withSelect(select(tag.all).append(count(i.id).as("num")))
+        .changeFrom(_.prepend(tagFrom))
+        .changeWhere(c => c && queryCondition(q))
+        .groupBy(tag.tid)
+        .build
+        .query[TagCount]
+        .to[List]
+
+    // the previous query starts from tags, so items with tag-count=0
+    // are not included they are fetched separately
+    for {
+      existing <- tagCloud
+      other    <- RTag.findOthers(q.account.collective, existing.map(_.tag.tagId))
+    } yield existing ++ other.map(TagCount(_, 0))
   }
 
   def searchCountSummary(q: Query): ConnectionIO[Int] =
diff --git a/modules/store/src/main/scala/docspell/store/records/RTag.scala b/modules/store/src/main/scala/docspell/store/records/RTag.scala
index 9884a04d..27a30031 100644
--- a/modules/store/src/main/scala/docspell/store/records/RTag.scala
+++ b/modules/store/src/main/scala/docspell/store/records/RTag.scala
@@ -135,6 +135,19 @@ object RTag {
     }
   }
 
+  def findOthers(coll: Ident, excludeTags: List[Ident]): ConnectionIO[List[RTag]] = {
+    val excl =
+      NonEmptyList
+        .fromList(excludeTags)
+        .map(nel => T.tid.notIn(nel))
+
+    Select(
+      select(T.all),
+      from(T),
+      T.cid === coll &&? excl
+    ).orderBy(T.name.asc).build.query[RTag].to[List]
+  }
+
   def delete(tagId: Ident, coll: Ident): ConnectionIO[Int] =
     DML.delete(T, T.tid === tagId && T.cid === coll)
 }