(ns metabase-enterprise.stale (:require [malli.experimental.time] [metabase.embed.settings :as embed.settings] [metabase.models.setting :as setting :refer [defsetting]] [metabase.util.honey-sql-2 :as h2x] [metabase.util.i18n :refer [deferred-tru]] [metabase.util.malli :as mu] [toucan2.core :as t2])) | |
(set! *warn-on-reflection* true) | |
(def ^:private FindStaleContentArgs
[:map
[:collection-ids [:set {:doc "The set of collection IDs to search for stale content."} [:maybe :int]]]
[:cutoff-date [:time/local-date {:doc "The cutoff date for stale content."}]]
[:limit [:maybe {:doc "The limit for pagination."} :int]]
[:offset [:maybe {:doc "The offset for pagination."} :int]]
[:sort-column [:enum {:doc "The column to sort by."} :name :last_used_at]]
[:sort-direction [:enum {:doc "The direction to sort by."} :asc :desc]]]) | |
Find stale content of a given model type. | (defmulti ^:private find-stale-query
{:arglists '([model args])}
(fn [model _args] model)) |
(defmethod find-stale-query :model/Card
[_model args]
{:select [:report_card.id
[(h2x/literal "Card") :model]
[:report_card.name :name]
:last_used_at]
:from :report_card
:left-join [:moderation_review [:and
[:= :moderation_review.moderated_item_id :report_card.id]
[:= :moderation_review.moderated_item_type (h2x/literal "card")]
[:= :moderation_review.most_recent true]
[:= :moderation_review.status (h2x/literal "verified")]]
:pulse_card [:= :pulse_card.card_id :report_card.id]
:pulse [:and
[:= :pulse_card.pulse_id :pulse.id]
[:= :pulse.archived false]]
:sandboxes [:= :sandboxes.card_id :report_card.id]
:collection [:= :collection.id :report_card.collection_id]]
:where [:and
[:= :sandboxes.id nil]
[:= :pulse.id nil]
[:= :moderation_review.id nil]
[:= :report_card.archived false]
[:<= :report_card.last_used_at (-> args :cutoff-date)]
;; find things only in regular collections, not the `instance-analytics` collection.
[:= :collection.type nil]
(when (embed.settings/some-embedding-enabled?)
[:= :report_card.enable_embedding false])
(when (setting/get :enable-public-sharing)
[:= :report_card.public_uuid nil])
[:or
(when (contains? (:collection-ids args) nil)
[:is :report_card.collection_id nil])
[:in :report_card.collection_id (-> args :collection-ids)]]]}) | |
(defmethod find-stale-query :model/Dashboard
[_model args]
{:select [:report_dashboard.id
[(h2x/literal "Dashboard") :model]
[:report_dashboard.name :name]
[:last_viewed_at :last_used_at]]
:from :report_dashboard
:left-join [:pulse [:and
[:= :pulse.archived false]
[:= :pulse.dashboard_id :report_dashboard.id]]
:collection [:= :collection.id :report_dashboard.collection_id]
:moderation_review [:and
[:= :moderation_review.moderated_item_id :report_dashboard.id]
[:= :moderation_review.moderated_item_type (h2x/literal "dashboard")]
[:= :moderation_review.most_recent true]
[:= :moderation_review.status (h2x/literal "verified")]]]
:where [:and
[:= :pulse.id nil]
[:= :moderation_review.id nil]
[:= :report_dashboard.archived false]
[:<= :report_dashboard.last_viewed_at (-> args :cutoff-date)]
;; find things only in regular collections, not the `instance-analytics` collection.
[:= :collection.type nil]
(when (embed.settings/some-embedding-enabled?)
[:= :report_dashboard.enable_embedding false])
(when (setting/get :enable-public-sharing)
[:= :report_dashboard.public_uuid nil])
[:or
(when (contains? (:collection-ids args) nil)
[:is :report_dashboard.collection_id nil])
[:in :report_dashboard.collection_id (-> args :collection-ids)]]]}) | |
(defn- sort-column [column]
(case column
:name :%lower.name
:last_used_at :last_used_at)) | |
(defn- queries [args]
(for [model [:model/Card :model/Dashboard]]
(find-stale-query model args))) | |
(mu/defn ^:private rows-query [{:keys [limit offset] :as args} :- FindStaleContentArgs]
(cond-> {:select [:id :model]
:from [[{:union-all (queries args)} :dummy_alias]]
:order-by [[(sort-column (:sort-column args))
(:sort-direction args)]]}
(some? limit) ;; limit
(assoc :limit limit)
(some? offset) ;; offset
(assoc :offset offset))) | |
(mu/defn ^:private total-query [args :- FindStaleContentArgs]
{:select [[:%count.* :count]]
:from [[{:union-all (queries args)} :dummy_alias]]}) | |
(mu/defn find-candidates :- [:map
[:rows [:sequential [:map
[:id pos-int?]
[:model keyword?]]]]
[:total :int]]
"Find stale content in the given collections.
Arguments are defined by [[FindStaleContentArgs]]:
- `collection-ids`: the set of collection IDs to look for stale content in. Non-recursive, the exact set you pass in
will be searched
- `cutoff-date`: if something was last accessed before this date, it is 'stale'
- `limit` / `offset`: to support pagination
- `sort-column`: one of `:name` or `:last_used_at` (column to sort on)
- `sort-direction`: `:asc` or `:desc`
Returns a map containing two keys,
- `:rows` (a collection of maps containing an `:id` and `:model` field, like `{:id 1 :model :model/Card}`), and
- `:total` (the total count of stale elements that could be found if you iterated through all pages)
"
[{:keys [collection-ids] :as args} :- FindStaleContentArgs]
(when (contains? collection-ids :root) (throw (ex-info "not implemented." {:collection-ids collection-ids})))
{:rows (into []
(comp
(map #(select-keys % [:id :model]))
(map (fn [v] (update v :model #(keyword "model" %)))))
(t2/query (rows-query args)))
:total (:count (t2/query-one (total-query args)))}) | |
(defsetting dismissed-collection-cleanup-banner (deferred-tru "Was the collection cleanup banner dismissed?") :user-local :only :visibility :authenticated :type :boolean :default false :export? false) | |