From 5be9510069c05d9d7d5d0f540a4d750258d19dff Mon Sep 17 00:00:00 2001
From: Chris Truter <crisptrutski@users.noreply.github.com>
Date: Tue, 19 Nov 2024 18:05:46 +0200
Subject: [PATCH] For new search, default to excluding other people's
 collections (#50146)

---
 src/metabase/api/search.clj    |  6 ++++--
 src/metabase/search/config.clj |  3 ++-
 src/metabase/search/filter.clj | 24 ++++++++++++++++++++----
 src/metabase/search/impl.clj   | 33 +++++++++++++++++++++------------
 4 files changed, 47 insertions(+), 19 deletions(-)

diff --git a/src/metabase/api/search.clj b/src/metabase/api/search.clj
index c907b9a6bef..695c64d80e4 100644
--- a/src/metabase/api/search.clj
+++ b/src/metabase/api/search.clj
@@ -122,14 +122,15 @@
   - The `verified` filter supports models and cards.
 
   A search query that has both filters applied will only return models and cards."
-  [q archived created_at created_by table_db_id models last_edited_at last_edited_by
+  [q context archived created_at created_by table_db_id models last_edited_at last_edited_by
    filter_items_in_personal_collection model_ancestors search_engine search_native_query
    verified ids calculate_available_models]
   {q                                   [:maybe ms/NonBlankString]
+   context                             [:maybe :keyword]
    archived                            [:maybe :boolean]
    table_db_id                         [:maybe ms/PositiveInt]
    models                              [:maybe (ms/QueryVectorOf search/SearchableModel)]
-   filter_items_in_personal_collection [:maybe [:enum "only" "exclude"]]
+   filter_items_in_personal_collection [:maybe [:enum "all" "only" "only-mine" "exclude" "exclude-others"]]
    created_at                          [:maybe ms/NonBlankString]
    created_by                          [:maybe (ms/QueryVectorOf ms/PositiveInt)]
    last_edited_at                      [:maybe ms/NonBlankString]
@@ -147,6 +148,7 @@
     (search/search
      (search/search-context
       {:archived                            archived
+       :context                             context
        :created-at                          created_at
        :created-by                          (set created_by)
        :current-user-id                     api/*current-user-id*
diff --git a/src/metabase/search/config.clj b/src/metabase/search/config.clj
index 6a697c9c4c0..50c19a42270 100644
--- a/src/metabase/search/config.clj
+++ b/src/metabase/search/config.clj
@@ -185,6 +185,7 @@
    [:current-user-id    pos-int?]
    [:is-superuser?      :boolean]
    ;; TODO only optional and maybe for tests, clean that up!
+   [:context               {:optional true} [:maybe :keyword]]
    [:is-impersonated-user? {:optional true} [:maybe :boolean]]
    [:is-sandboxed-user?    {:optional true} [:maybe :boolean]]
    [:current-user-perms [:set perms.u/PathSchema]]
@@ -199,7 +200,7 @@
    ;;
    [:created-at                          {:optional true} ms/NonBlankString]
    [:created-by                          {:optional true} [:set {:min 1} ms/PositiveInt]]
-   [:filter-items-in-personal-collection {:optional true} [:enum "only" "exclude"]]
+   [:filter-items-in-personal-collection {:optional true} [:enum "all" "only" "only-mine" "exclude" "exclude-others"]]
    [:last-edited-at                      {:optional true} ms/NonBlankString]
    [:last-edited-by                      {:optional true} [:set {:min 1} ms/PositiveInt]]
    [:limit-int                           {:optional true} ms/Int]
diff --git a/src/metabase/search/filter.clj b/src/metabase/search/filter.clj
index cc9c731cad1..882f7e848f5 100644
--- a/src/metabase/search/filter.clj
+++ b/src/metabase/search/filter.clj
@@ -81,10 +81,26 @@
 (defn personal-collections-where-clause
   "Build a clause limiting the entries to those (not) within or within personal collections, if relevant.
   WARNING: this method queries the appdb, and its approach will get very slow when there are many users!"
-  [{filter-type :filter-items-in-personal-collection} collection-id-col]
-  (when filter-type
-    (let [parent-ids     (t2/select-pks-vec :model/Collection :personal_owner_id [:not= nil])
-          child-patterns (for [id parent-ids] (format "/%d/%%" id))]
+  [{filter-type :filter-items-in-personal-collection :keys [current-user-id] :as search-ctx} collection-id-col]
+  (case (or filter-type "all")
+    "all" nil
+
+    "only-mine"
+    [:or
+     [:= :collection.personal_owner_id current-user-id]
+     (into [:or]
+           (let [your-collection-ids (t2/select-pks-vec :model/Collection :personal_owner_id [:= current-user-id])
+                 child-patterns      (for [id your-collection-ids] (format "/%d/%%" id))]
+             (for [p child-patterns] [:like :collection.location p])))]
+
+    "exclude-others"
+    (let [with-filter #(personal-collections-where-clause
+                        (assoc search-ctx :filter-items-in-personal-collection %)
+                        collection-id-col)]
+      [:or (with-filter "only-mine") (with-filter "exclude")])
+
+    (let [personal-ids   (t2/select-pks-vec :model/Collection :personal_owner_id [:not= nil])
+          child-patterns (for [id personal-ids] (format "/%d/%%" id))]
       (case filter-type
         "only"
         `[:or
diff --git a/src/metabase/search/impl.clj b/src/metabase/search/impl.clj
index e79dbe252a1..80b0474d998 100644
--- a/src/metabase/search/impl.clj
+++ b/src/metabase/search/impl.clj
@@ -251,6 +251,7 @@
 (mr/def ::search-context.input
   [:map {:closed true}
    [:search-string                                        [:maybe ms/NonBlankString]]
+   [:context                             {:optional true} [:maybe :keyword]]
    [:models                                               [:maybe [:set SearchableModel]]]
    [:current-user-id                                      pos-int?]
    [:is-impersonated-user?               {:optional true} :boolean]
@@ -260,7 +261,7 @@
    [:archived                            {:optional true} [:maybe :boolean]]
    [:created-at                          {:optional true} [:maybe ms/NonBlankString]]
    [:created-by                          {:optional true} [:maybe [:set ms/PositiveInt]]]
-   [:filter-items-in-personal-collection {:optional true} [:maybe [:enum "only" "exclude"]]]
+   [:filter-items-in-personal-collection {:optional true} [:maybe [:enum "all" "only" "only-mine" "exclude" "exclude-others"]]]
    [:last-edited-at                      {:optional true} [:maybe ms/NonBlankString]]
    [:last-edited-by                      {:optional true} [:maybe [:set ms/PositiveInt]]]
    [:limit                               {:optional true} [:maybe ms/Int]]
@@ -276,6 +277,7 @@
 (mu/defn search-context :- SearchContext
   "Create a new search context that you can pass to other functions like [[search]]."
   [{:keys [archived
+           context
            calculate-available-models?
            created-at
            created-by
@@ -304,17 +306,24 @@
      [:content-verification :official-collections]
      (deferred-tru "Content Management or Official Collections")))
   (let [models (if (string? models) [models] models)
-        ctx    (cond-> {:archived?                   (boolean archived)
-                        :calculate-available-models? (boolean calculate-available-models?)
-                        :current-user-id             current-user-id
-                        :current-user-perms          current-user-perms
-                        :is-impersonated-user?       is-impersonated-user?
-                        :is-sandboxed-user?          is-sandboxed-user?
-                        :is-superuser?               is-superuser?
-                        :models                      models
-                        :model-ancestors?            (boolean model-ancestors?)
-                        :search-engine               (parse-engine search-engine)
-                        :search-string               search-string}
+        engine (parse-engine search-engine)
+        ctx    (cond-> {:archived?                           (boolean archived)
+                        :context                             (or context :unknown)
+                        :calculate-available-models?         (boolean calculate-available-models?)
+                        :current-user-id                     current-user-id
+                        :current-user-perms                  current-user-perms
+                        :filter-items-in-personal-collection (or filter-items-in-personal-collection
+                                                                 (if (and (not= engine :search.engine/in-place)
+                                                                          (#{:search-app :command-palette} context))
+                                                                   "exclude-others"
+                                                                   "all"))
+                        :is-impersonated-user?               is-impersonated-user?
+                        :is-sandboxed-user?                  is-sandboxed-user?
+                        :is-superuser?                       is-superuser?
+                        :models                              models
+                        :model-ancestors?                    (boolean model-ancestors?)
+                        :search-engine                       engine
+                        :search-string                       search-string}
                  (some? created-at)                          (assoc :created-at created-at)
                  (seq created-by)                            (assoc :created-by created-by)
                  (some? filter-items-in-personal-collection) (assoc :filter-items-in-personal-collection filter-items-in-personal-collection)
-- 
GitLab