A Segment is a saved MBQL 'macro', expanding to a | (ns metabase.models.segment (:require [clojure.set :as set] [medley.core :as m] [metabase.api.common :as api] [metabase.legacy-mbql.util :as mbql.u] [metabase.lib.core :as lib] [metabase.lib.metadata.jvm :as lib.metadata.jvm] [metabase.lib.metadata.protocols :as lib.metadata.protocols] [metabase.lib.query :as lib.query] [metabase.lib.schema.common :as lib.schema.common] [metabase.lib.schema.id :as lib.schema.id] [metabase.lib.schema.metadata :as lib.schema.metadata] [metabase.models.audit-log :as audit-log] [metabase.models.data-permissions :as data-perms] [metabase.models.database :as database] [metabase.models.interface :as mi] [metabase.models.revision :as revision] [metabase.models.serialization :as serdes] [metabase.search.core :as search] [metabase.util :as u] [metabase.util.i18n :refer [tru]] [metabase.util.log :as log] [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])) |
Used to be the toucan1 model name defined using [[toucan.models/defmodel]], not it's a reference to the toucan2 model name. We'll keep this till we replace all these symbols in our codebase. | (def Segment :model/Segment) |
(methodical/defmethod t2/table-name :model/Segment [_model] :segment) (methodical/defmethod t2/model-for-automagic-hydration [:default :segment] [_original-model _k] :model/Segment) | |
(t2/deftransforms :model/Segment {:definition mi/transform-legacy-metric-segment-definition}) | |
(doto :model/Segment (derive :metabase/model) (derive :hook/timestamped?) (derive :hook/entity-id) (derive ::mi/write-policy.superuser) (derive ::mi/create-policy.superuser)) | |
(defmethod mi/can-read? :model/Segment ([instance] (let [table (:table (t2/hydrate instance :table))] (data-perms/user-has-permission-for-table? api/*current-user-id* :perms/manage-table-metadata :yes (:db_id table) (u/the-id table)))) ([model pk] (mi/can-read? (t2/select-one model pk)))) | |
(t2/define-before-update :model/Segment [{:keys [creator_id id], :as segment}] (u/prog1 (t2/changes segment) ;; throw an Exception if someone tries to update creator_id (when (contains? <> :creator_id) (when (not= (:creator_id <>) (t2/select-one-fn :creator_id Segment :id id)) (throw (UnsupportedOperationException. (tru "You cannot update the creator_id of a Segment."))))))) | |
(defmethod mi/perms-objects-set Segment [segment read-or-write] (let [table (or (:table segment) (t2/select-one ['Table :db_id :schema :id] :id (u/the-id (:table_id segment))))] (mi/perms-objects-set table read-or-write))) | |
(mu/defn- definition-description :- [:maybe ::lib.schema.common/non-blank-string] "Calculate a nice description of a Segment's definition." [metadata-provider :- ::lib.schema.metadata/metadata-provider {table-id :table_id, :keys [definition], :as _segment} :- (ms/InstanceOf :model/Segment)] (when (seq definition) (try (let [definition (merge {:source-table table-id} definition) database-id (u/the-id (lib.metadata.protocols/database metadata-provider)) query (lib.query/query-from-legacy-inner-query metadata-provider database-id definition)] (lib/describe-top-level-key query :filters)) (catch Throwable e (log/errorf e "Error calculating Segment description: %s" (ex-message e)) nil)))) | |
(mu/defn- warmed-metadata-provider :- ::lib.schema.metadata/metadata-provider [database-id :- ::lib.schema.id/database segments :- [:maybe [:sequential (ms/InstanceOf :model/Segment)]]] (let [metadata-provider (doto (lib.metadata.jvm/application-database-metadata-provider database-id) (lib.metadata.protocols/store-metadatas! (map #(lib.metadata.jvm/instance->metadata % :metadata/segment) segments))) field-ids (mbql.u/referenced-field-ids (map :definition segments)) fields (lib.metadata.protocols/metadatas metadata-provider :metadata/column field-ids) table-ids (into #{} cat [(map :table-id fields) (map :table_id segments)])] ;; this is done for side effects (lib.metadata.protocols/warm-cache metadata-provider :metadata/table table-ids) metadata-provider)) | |
(mu/defn- segments->table-id->warmed-metadata-provider :- fn? [segments :- [:maybe [:sequential (ms/InstanceOf :model/Segment)]]] (let [table-id->db-id (when-let [table-ids (not-empty (into #{} (map :table_id segments)))] (t2/select-pk->fn :db_id :model/Table :id [:in table-ids])) db-id->metadata-provider (memoize (mu/fn db-id->warmed-metadata-provider :- ::lib.schema.metadata/metadata-provider [database-id :- ::lib.schema.id/database] (let [segments-for-db (filter (fn [segment] (= (table-id->db-id (:table_id segment)) database-id)) segments)] (warmed-metadata-provider database-id segments-for-db))))] (mu/fn table-id->warmed-metadata-provider :- ::lib.schema.metadata/metadata-provider [table-id :- ::lib.schema.id/table] (-> table-id table-id->db-id db-id->metadata-provider)))) | |
(methodical/defmethod t2.hydrate/batched-hydrate [Segment :definition_description] [_model _key segments] (let [table-id->warmed-metadata-provider (segments->table-id->warmed-metadata-provider segments)] (for [segment segments :let [metadata-provider (table-id->warmed-metadata-provider (:table_id segment))]] (assoc segment :definition_description (definition-description metadata-provider segment))))) | |
--------------------------------------------------- Revisions ---------------------------------------------------- | |
(defmethod revision/serialize-instance Segment [_model _id instance] (dissoc instance :created_at :updated_at)) | |
(defmethod revision/diff-map Segment [model segment1 segment2] (if-not segment1 ;; this is the first version of the segment (m/map-vals (fn [v] {:after v}) (select-keys segment2 [:name :description :definition])) ;; do our diff logic (let [base-diff ((get-method revision/diff-map :default) model (select-keys segment1 [:name :description :definition]) (select-keys segment2 [:name :description :definition]))] (cond-> (merge-with merge (m/map-vals (fn [v] {:after v}) (:after base-diff)) (m/map-vals (fn [v] {:before v}) (:before base-diff))) (or (get-in base-diff [:after :definition]) (get-in base-diff [:before :definition])) (assoc :definition {:before (get segment1 :definition) :after (get segment2 :definition)}))))) | |
------------------------------------------------ Serialization --------------------------------------------------- | |
(defmethod serdes/hash-fields Segment [_segment] [:name (serdes/hydrated-hash :table) :created_at]) | |
(defmethod serdes/dependencies "Segment" [{:keys [definition table_id]}] (set/union #{(serdes/table->path table_id)} (serdes/mbql-deps definition))) | |
(defmethod serdes/storage-path "Segment" [segment _ctx] (let [{:keys [id label]} (-> segment serdes/path last)] (-> segment :table_id serdes/table->path serdes/storage-path-prefixes (concat ["segments" (serdes/storage-leaf-file-name id label)])))) | |
(defmethod serdes/make-spec "Segment" [_model-name _opts] {:copy [:name :points_of_interest :archived :caveats :description :entity_id :show_in_getting_started] :skip [] :transform {:created_at (serdes/date) :table_id (serdes/fk :model/Table) :creator_id (serdes/fk :model/User) :definition {:export serdes/export-mbql :import serdes/import-mbql}}}) | |
---------------------------------------------- Audit Log Table ---------------------------------------------------- | |
(defmethod audit-log/model-details :model/Segment [metric _event-type] (let [table-id (:table_id metric) db-id (database/table-id->database-id table-id)] (assoc (select-keys metric [:name :description :revision_message]) :table_id table-id :database_id db-id))) | |
------------------------------------------------- Search ---------------------------------------------------------- | |
(search/define-spec "segment" {:model :model/Segment :attrs {:archived true :collection-id false :creator-id false :database-id :table.db_id ;; Matching legacy behavior, where this cannot be filtered on. ;:created-at true :updated-at true} :search-terms [:name :description] :render-terms {:table-id :table_id :table_description :table.description :table_name :table.name :table_schema :table.schema} :joins {:table [:model/Table [:= :table.id :this.table_id]]}}) | |