(ns metabase.models.dashboard-tab (:require [medley.core :as m] [metabase.models.dashboard-card :as dashboard-card] [metabase.models.interface :as mi] [metabase.models.serialization :as serdes] [metabase.util :as u] [metabase.util.malli :as mu] [metabase.util.malli.schema :as ms] [methodical.core :as methodical] [toucan2.core :as t2] [toucan2.tools.hydrate :as t2.hydrate])) | |
(methodical/defmethod t2/table-name :model/DashboardTab [_model] :dashboard_tab) | |
(doto :model/DashboardTab (derive :metabase/model) (derive ::mi/read-policy.full-perms-for-perms-set) (derive ::mi/write-policy.full-perms-for-perms-set) (derive :hook/timestamped?) (derive :hook/entity-id)) | |
(methodical/defmethod t2/model-for-automagic-hydration [:metabase.models.dashboard-card/DashboardCard :dashboard_tab] [_original-model _k] :model/DashboardTab) | |
(methodical/defmethod t2.hydrate/fk-keys-for-automagic-hydration [:metabase.models.dashboard-card/DashboardCard :dashboard_tab :default] [_original-model _dest-key _hydrating-model] [:dashboard_tab_id]) | |
(methodical/defmethod t2.hydrate/batched-hydrate [:default :tab-cards]
"Given a list of tabs, return a seq of ordered tabs, in which each tabs contain a seq of ordered cards."
[_model _k tabs]
(assert (= 1 (count (set (map :dashboard_id tabs)))), "All tabs must belong to the same dashboard")
(let [dashboard-id (:dashboard_id (first tabs))
tab-ids (map :id tabs)
dashcards (t2/select :model/DashboardCard :dashboard_id dashboard-id :dashboard_tab_id [:in tab-ids])
tab-id->dashcards (-> (group-by :dashboard_tab_id dashcards)
(update-vals #(sort dashboard-card/dashcard-comparator %)))
tabs (sort-by :position tabs)]
(for [{:keys [id] :as tab} tabs]
(assoc tab :cards (get tab-id->dashcards id []))))) | |
(defmethod mi/perms-objects-set :model/DashboardTab
[dashtab read-or-write]
(let [dashboard (or (:dashboard dashtab)
(t2/select-one :model/Dashboard :id (:dashboard_id dashtab)))]
(mi/perms-objects-set dashboard read-or-write))) | |
----------------------------------------------- SERIALIZATION ---------------------------------------------------- | (defmethod serdes/hash-fields :model/DashboardTab
[_dashboard-tab]
[:name
(comp serdes/identity-hash
#(t2/select-one :model/Dashboard :id %)
:dashboard_id)
:position
:created_at]) |
(defmethod serdes/generate-path "DashboardTab" [_ dashcard] [(serdes/infer-self-path "Dashboard" (t2/select-one :model/Dashboard :id (:dashboard_id dashcard))) (serdes/infer-self-path "DashboardTab" dashcard)]) | |
(defmethod serdes/make-spec "DashboardTab" [_model-name _opts]
{:copy [:entity_id :name :position]
:skip []
:transform {:created_at (serdes/date)
:dashboard_id (serdes/parent-ref)}}) | |
-------------------------------------------------- CRUD fns ------------------------------------------------------ | |
(mu/defn create-tabs! :- [:map-of neg-int? pos-int?]
"Create the new tabs and returned a mapping from temporary tab ID to the new tab ID."
[dashboard-id :- ms/PositiveInt
new-tabs :- [:sequential [:map [:id neg-int?]]]]
(let [new-tab-ids (t2/insert-returning-pks! :model/DashboardTab (->> new-tabs
(map #(dissoc % :id))
(map #(assoc % :dashboard_id dashboard-id))))]
(zipmap (map :id new-tabs) new-tab-ids))) | |
(mu/defn update-tabs! :- nil?
"Updates tabs of a dashboard if changed."
[current-tabs :- [:sequential [:map [:id ms/PositiveInt]]]
new-tabs :- [:sequential [:map [:id ms/PositiveInt]]]]
(let [update-ks [:name :position]
id->current-tab (m/index-by :id current-tabs)
to-update-tabs (filter
;; filter out tabs that haven't changed
(fn [new-tab]
(let [current-tab (get id->current-tab (:id new-tab))]
(not= (select-keys current-tab update-ks)
(select-keys new-tab update-ks))))
new-tabs)]
(doseq [tab to-update-tabs]
(t2/update! :model/DashboardTab (:id tab) (select-keys tab update-ks)))
nil)) | |
(mu/defn delete-tabs! :- nil?
"Delete tabs of a Dashboard"
[tab-ids :- [:sequential {:min 1} ms/PositiveInt]]
(when (seq tab-ids)
(t2/delete! :model/DashboardTab :id [:in tab-ids]))
nil) | |
Given current tabs and new tabs, do the necessary create/update/delete to apply new tab changes.
Returns:
- | (defn do-update-tabs!
[dashboard-id current-tabs new-tabs]
(let [{:keys [to-create
to-update
to-delete]} (u/row-diff current-tabs new-tabs)
to-delete-ids (map :id to-delete)
_ (when-let [to-delete-ids (seq to-delete-ids)]
(delete-tabs! to-delete-ids))
old->new-tab-id (when (seq to-create)
(let [new-tab-ids (t2/insert-returning-pks! :model/DashboardTab
(->> to-create
(map #(dissoc % :id))
(map #(assoc % :dashboard_id dashboard-id))))]
(zipmap (map :id to-create) new-tab-ids)))]
(when (seq to-update)
(update-tabs! current-tabs to-update))
{:old->new-tab-id old->new-tab-id
:created-tab-ids (vals old->new-tab-id)
:deleted-tab-ids to-delete-ids
:total-num-tabs (reduce + (map count [to-create to-update]))})) |