From 647181fcd6bcf94943641bc2db7b7053bb885774 Mon Sep 17 00:00:00 2001 From: Ngoc Khuat <qn.khuat@gmail.com> Date: Fri, 16 Jun 2023 12:38:45 +0700 Subject: [PATCH] =?UTF-8?q?Rest=20of=20models=20to=20toucan2=20?= =?UTF-8?q?=F0=9F=8E=89=20(#31385)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * User to toucan2 * Pulse to toucan2 * PulseCard to toucan 2 * PulseChannel and PulseChannelRecipient to toucan2 * Query, QueryCache, QueryExecution to toucan2 * Metric, Segment, MetricImportantField to toucan2 * ParameterCard to toucan2 * DataMigrations and FakeCard to toucan2 * NativeQuerySnippet to toucan2 --- .../models/entity_id_test.clj | 23 +++-- src/metabase/db/data_migrations.clj | 13 ++- src/metabase/lib/metadata/jvm.clj | 4 +- src/metabase/models/interface.clj | 5 + src/metabase/models/metric.clj | 32 ++++--- .../models/metric_important_field.clj | 18 ++-- src/metabase/models/native_query_snippet.clj | 36 ++++---- src/metabase/models/parameter_card.clj | 38 ++++---- src/metabase/models/pulse.clj | 38 ++++---- src/metabase/models/pulse_card.clj | 16 ++-- src/metabase/models/pulse_channel.clj | 47 ++++++---- .../models/pulse_channel_recipient.clj | 17 +++- src/metabase/models/query.clj | 21 +++-- src/metabase/models/query_cache.clj | 18 ++-- src/metabase/models/query_execution.clj | 32 +++++-- src/metabase/models/segment.clj | 33 ++++--- src/metabase/models/user.clj | 92 +++++++++++-------- test/metabase/models/revision_test.clj | 56 +++++------ test/metabase/models/user_test.clj | 12 +-- test/metabase/test/util.clj | 20 ++-- 20 files changed, 328 insertions(+), 243 deletions(-) diff --git a/enterprise/backend/test/metabase_enterprise/models/entity_id_test.clj b/enterprise/backend/test/metabase_enterprise/models/entity_id_test.clj index 6c0cae6fa03..7c23b26ccd4 100644 --- a/enterprise/backend/test/metabase_enterprise/models/entity_id_test.clj +++ b/enterprise/backend/test/metabase_enterprise/models/entity_id_test.clj @@ -33,7 +33,7 @@ - not exported in serialization; or - exported as a child of something else (eg. timeline_event under timeline) so they don't need a generated entity_id." - #{:metabase.db.data-migrations/DataMigrations + #{:model/DataMigrations :model/HTTPAction :model/ImplicitAction :model/QueryAction @@ -46,32 +46,31 @@ :metabase.models.collection.root/RootCollection :metabase.models.collection-permission-graph-revision/CollectionPermissionGraphRevision :model/DashboardCardSeries - :metabase.models.field-values/FieldValues :model/LoginHistory :model/FieldValues - :metabase.models.metric-important-field/MetricImportantField + :model/MetricImportantField :model/ModelIndex :model/ModelIndexValue :model/ModerationReview - :metabase.models.parameter-card/ParameterCard + :model/ParameterCard :metabase.models.permissions/Permissions :metabase.models.permissions-group/PermissionsGroup :metabase.models.permissions-group-membership/PermissionsGroupMembership :metabase.models.permissions-revision/PermissionsRevision :model/PersistedInfo - :metabase.models.pulse-card/PulseCard - :metabase.models.pulse-channel/PulseChannel - :metabase.models.pulse-channel-recipient/PulseChannelRecipient - :metabase.models.query/Query - :metabase.models.query-cache/QueryCache - :metabase.models.query-execution/QueryExecution + :model/PulseCard + :model/PulseChannel + :model/PulseChannelRecipient + :model/Query + :model/QueryCache + :model/QueryExecution :model/Revision - :metabase.models.revision-test/FakedCard + :model/FakedCard :model/Secret :model/Session :model/TaskHistory :model/TimelineEvent - :metabase.models.user/User + :model/User :model/ViewLog :metabase-enterprise.sandbox.models.group-table-access-policy/GroupTableAccessPolicy}) diff --git a/src/metabase/db/data_migrations.clj b/src/metabase/db/data_migrations.clj index 7744de6d08c..ca853b241c9 100644 --- a/src/metabase/db/data_migrations.clj +++ b/src/metabase/db/data_migrations.clj @@ -17,14 +17,19 @@ [metabase.models.setting :as setting :refer [Setting]] [metabase.util :as u] [metabase.util.log :as log] - [toucan.models :as models] + [methodical.core :as methodical] [toucan2.core :as t2])) (set! *warn-on-reflection* true) ;;; # Migration Helpers +(def DataMigrations + "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." + :model/DataMigrations) -(models/defmodel ^:deprecated DataMigrations :data_migrations) +(methodical/defmethod t2/table-name :model/DataMigrations [_model] :data_migrations) +(derive :model/DataMigrations :metabase/model) (defn- ^:deprecated run-migration-if-needed! "Run migration defined by `migration-var` if needed. `ran-migrations` is a set of migrations names that have already @@ -45,7 +50,7 @@ (if catch? (log/warn (format "Data migration %s failed: %s" migration-name (.getMessage e))) (throw e)))) - (t2/insert! DataMigrations + (t2/insert! :model/DataMigrations :id migration-name :timestamp :%now)))) @@ -62,7 +67,7 @@ "Run all data migrations defined by `defmigration`." [] (log/info "Running all necessary data migrations, this may take a minute.") - (let [ran-migrations (t2/select-pks-set DataMigrations)] + (let [ran-migrations (t2/select-pks-set :model/DataMigrations)] (doseq [migration @data-migrations] (run-migration-if-needed! ran-migrations migration))) (log/info "Finished running data migrations.")) diff --git a/src/metabase/lib/metadata/jvm.clj b/src/metabase/lib/metadata/jvm.clj index 2435a5df2c5..28ad6b985a0 100644 --- a/src/metabase/lib/metadata/jvm.clj +++ b/src/metabase/lib/metadata/jvm.clj @@ -15,8 +15,8 @@ :metadata/table :model/Table :metadata/field :model/Field :metadata/card :model/Card - :metadata/metric :metabase.models.metric/Metric - :metadata/segment :metabase.models.segment/Segment)) + :metadata/metric :model/Metric + :metadata/segment :model/Segment)) (defn- instance->metadata [instance metadata-type] (some-> instance diff --git a/src/metabase/models/interface.clj b/src/metabase/models/interface.clj index 75d8f83705b..71ac57d770a 100644 --- a/src/metabase/models/interface.clj +++ b/src/metabase/models/interface.clj @@ -446,6 +446,11 @@ {:in validate-cron-string :out identity}) +(def transform-metric-segment-definition + "Transform for inner queries like those in Metric definitions." + {:in (comp json-in normalize-metric-segment-definition) + :out (comp (catch-normalization-exceptions normalize-metric-segment-definition) json-out-with-keywordization)}) + ;; --- predefined hooks (t2/define-before-insert :hook/timestamped? diff --git a/src/metabase/models/metric.clj b/src/metabase/models/metric.clj index b5b6f6b07af..76561a23ec7 100644 --- a/src/metabase/models/metric.clj +++ b/src/metabase/models/metric.clj @@ -20,22 +20,33 @@ [metabase.util.log :as log] [metabase.util.malli :as mu] [methodical.core :as methodical] - [toucan.models :as models] [toucan2.core :as t2] [toucan2.tools.hydrate :as t2.hydrate])) -(models/defmodel Metric :metric) +(def Metric + "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." + :model/Metric) -(doto Metric +(methodical/defmethod t2/table-name :model/Metric [_model] :metric) + +(doto :model/Metric + (derive :metabase/model) + (derive :hook/timestamped?) + (derive :hook/entity-id) (derive ::mi/read-policy.full-perms-for-perms-set) (derive ::mi/write-policy.superuser) (derive ::mi/create-policy.superuser)) -(defn- pre-update [{:keys [creator_id id], :as updates}] - (u/prog1 updates +(t2/deftransforms :model/Metric + {:definition mi/transform-metric-segment-definition}) + +(t2/define-before-update :model/Metric + [{:keys [creator_id id], :as metric}] + (u/prog1 (t2/changes metric) ;; throw an Exception if someone tries to update creator_id - (when (contains? updates :creator_id) - (when (not= creator_id (t2/select-one-fn :creator_id Metric :id id)) + (when (contains? <> :creator_id) + (when (not= (:creator_id <>) (t2/select-one-fn :creator_id Metric :id id)) (throw (UnsupportedOperationException. (tru "You cannot update the creator_id of a Metric."))))))) (defmethod mi/perms-objects-set Metric @@ -44,13 +55,6 @@ (t2/select-one ['Table :db_id :schema :id] :id (u/the-id (:table_id metric))))] (mi/perms-objects-set table read-or-write))) -(mi/define-methods - Metric - {:types (constantly {:definition :metric-segment-definition}) - :properties (constantly {::mi/timestamped? true - ::mi/entity-id true}) - :pre-update pre-update}) - (mu/defn ^:private definition-description :- [:maybe ::lib.schema.common/non-blank-string] "Calculate a nice description of a Metric's definition." [metadata-provider :- lib.metadata/MetadataProvider diff --git a/src/metabase/models/metric_important_field.clj b/src/metabase/models/metric_important_field.clj index bf88ba2ce75..630e8b5d268 100644 --- a/src/metabase/models/metric_important_field.clj +++ b/src/metabase/models/metric_important_field.clj @@ -2,14 +2,20 @@ "Intersection table for `Metric` and `Field`; this is used to keep track of the top 0-3 important fields for a metric as shown in the Getting Started guide." (:require [metabase.models.interface :as mi] - [toucan.models :as models])) + [methodical.core :as methodical] + [toucan2.core :as t2])) -(models/defmodel MetricImportantField :metric_important_field) +(def MetricImportantField + "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." + :model/MetricImportantField) -(doto MetricImportantField +(methodical/defmethod t2/table-name :model/MetricImportantField [_model] :metric_important_field) + +(doto :model/MetricImportantField + (derive :metabase/model) (derive ::mi/read-policy.always-allow) (derive ::mi/write-policy.superuser)) -(mi/define-methods - MetricImportantField - {:types (constantly {:definition :json})}) +(t2/deftransforms :model/MetricImportantField + {:definition mi/transform-json}) diff --git a/src/metabase/models/native_query_snippet.clj b/src/metabase/models/native_query_snippet.clj index 4577c9c0a91..793dbb19d7a 100644 --- a/src/metabase/models/native_query_snippet.clj +++ b/src/metabase/models/native_query_snippet.clj @@ -7,36 +7,40 @@ [metabase.util :as u] [metabase.util.i18n :refer [deferred-tru tru]] [metabase.util.schema :as su] + [methodical.core :as methodical] [schema.core :as s] - [toucan.models :as models] [toucan2.core :as t2])) ;;; ----------------------------------------------- Entity & Lifecycle ----------------------------------------------- -(models/defmodel NativeQuerySnippet :native_query_snippet) +(def NativeQuerySnippet + "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." + :model/NativeQuerySnippet) -(defmethod collection/allowed-namespaces NativeQuerySnippet +(methodical/defmethod t2/table-name :model/NativeQuerySnippet [_model] :native_query_snippet) + +(doto :model/NativeQuerySnippet + (derive :metabase/model) + (derive :hook/timestamped?) + (derive :hook/entity-id)) + +(defmethod collection/allowed-namespaces :model/NativeQuerySnippet [_] #{:snippets}) -(defn- pre-insert [snippet] +(t2/define-before-insert :model/NativeQuerySnippet [snippet] (u/prog1 snippet (collection/check-collection-namespace NativeQuerySnippet (:collection_id snippet)))) -(defn- pre-update [{:keys [creator_id id], :as updates}] - (u/prog1 updates +(t2/define-before-update :model/NativeQuerySnippet + [{:keys [creator_id id], :as snippet}] + (u/prog1 (t2/changes snippet) ;; throw an Exception if someone tries to update creator_id - (when (contains? updates :creator_id) - (when (not= creator_id (t2/select-one-fn :creator_id NativeQuerySnippet :id id)) + (when (contains? <> :creator_id) + (when (not= (:creator_id <>) (t2/select-one-fn :creator_id NativeQuerySnippet :id id)) (throw (UnsupportedOperationException. (tru "You cannot update the creator_id of a NativeQuerySnippet."))))) - (collection/check-collection-namespace NativeQuerySnippet (:collection_id updates)))) - -(mi/define-methods - NativeQuerySnippet - {:properties (constantly {::mi/timestamped? true - ::mi/entity-id true}) - :pre-insert pre-insert - :pre-update pre-update}) + (collection/check-collection-namespace NativeQuerySnippet (:collection_id snippet)))) (defmethod serdes/hash-fields NativeQuerySnippet [_snippet] diff --git a/src/metabase/models/parameter_card.clj b/src/metabase/models/parameter_card.clj index 147ed89ed6c..4601c827a18 100644 --- a/src/metabase/models/parameter_card.clj +++ b/src/metabase/models/parameter_card.clj @@ -5,10 +5,24 @@ [metabase.util.i18n :refer [tru]] [metabase.util.malli :as mu] [metabase.util.malli.schema :as ms] - [toucan.models :as models] + [methodical.core :as methodical] [toucan2.core :as t2])) -(models/defmodel ParameterCard :parameter_card) +;;; ----------------------------------------------- Entity & Lifecycle ----------------------------------------------- +(def ParameterCard + "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." + :model/ParameterCard) + +(methodical/defmethod t2/table-name :model/ParameterCard [_model] :parameter_card) + +(doto :model/ParameterCard + (derive :metabase/model) + (derive :hook/timestamped?) + (derive :hook/entity-id)) + +(t2/deftransforms :model/ParameterCard + {:parameterized_object_type mi/transform-keyword}) (defonce ^{:doc "Set of valid parameterized_object_type for a ParameterCard"} valid-parameterized-object-type #{"dashboard" "card"}) @@ -19,26 +33,16 @@ (throw (ex-info (tru "invalid parameterized_object_type") {:allowed-types valid-parameterized-object-type})))) -;;; ----------------------------------------------- Entity & Lifecycle ----------------------------------------------- - -(defn- pre-insert +(t2/define-before-insert :model/ParameterCard [pc] (u/prog1 pc (validate-parameterized-object-type pc))) -(defn- pre-update +(t2/define-before-update :model/ParameterCard [pc] - (u/prog1 pc - (when (:parameterized_object_type pc) - (validate-parameterized-object-type pc)))) - -(mi/define-methods - ParameterCard - {:properties (constantly {::mi/timestamped? true - ::mi/entity-id true}) - :types (constantly {:parameterized_object_type :keyword}) - :pre-insert pre-insert - :pre-update pre-update}) + (u/prog1 (t2/changes pc) + (when (:parameterized_object_type <>) + (validate-parameterized-object-type <>)))) (defn delete-all-for-parameterized-object! "Delete all ParameterCard for a give Parameterized Object and NOT listed in the optional diff --git a/src/metabase/models/pulse.clj b/src/metabase/models/pulse.clj index ee76bc2af22..b44a14c8600 100644 --- a/src/metabase/models/pulse.clj +++ b/src/metabase/models/pulse.clj @@ -32,22 +32,36 @@ [metabase.util :as u] [metabase.util.i18n :refer [deferred-tru tru]] [metabase.util.schema :as su] + [methodical.core :as methodical] [schema.core :as s] - [toucan.models :as models] [toucan2.core :as t2])) ;;; ----------------------------------------------- Entity & Lifecycle ----------------------------------------------- -(models/defmodel Pulse :pulse) +(def Pulse + "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." + :model/Pulse) -(derive Pulse ::mi/read-policy.full-perms-for-perms-set) +(methodical/defmethod t2/table-name :model/Pulse [_model] :pulse) +(methodical/defmethod t2/model-for-automagic-hydration [:default :pulse] [_original-model _k] :model/Pulse) + +(doto :model/Pulse + (derive :metabase/model) + (derive :hook/timestamped?) + (derive :hook/entity-id) + (derive ::mi/read-policy.full-perms-for-perms-set)) + +(t2/deftransforms :model/Pulse + {:parameters mi/transform-json}) (defn- assert-valid-parameters [{:keys [parameters]}] (when (s/check (s/maybe [{:id su/NonBlankString, s/Keyword s/Any}]) parameters) (throw (ex-info (tru ":parameters must be a sequence of maps with String :id keys") {:parameters parameters})))) -(defn- pre-insert [notification] +(t2/define-before-insert :model/Pulse + [notification] (let [defaults {:parameters []} dashboard-id (:dashboard_id notification) collection-id (if dashboard-id @@ -66,8 +80,9 @@ only be done when the associated dashboard is being moved to a new collection." false) -(defn- pre-update [notification] - (let [{:keys [collection_id dashboard_id]} (t2/select-one [Pulse :collection_id :dashboard_id] :id (u/the-id notification))] +(t2/define-before-update :model/Pulse + [notification] + (let [{:keys [collection_id dashboard_id]} (t2/original notification)] (when (and dashboard_id (contains? notification :collection_id) (not= (:collection_id notification) collection_id) @@ -77,7 +92,7 @@ (contains? notification :dashboard_id) (not= (:dashboard_id notification) dashboard_id)) (throw (ex-info (tru "dashboard ID of a dashboard subscription cannot be modified") notification)))) - (u/prog1 notification + (u/prog1 (t2/changes notification) (assert-valid-parameters notification) (collection/check-collection-namespace Pulse (:collection_id notification)))) @@ -143,15 +158,6 @@ (and (mi/current-user-has-full-permissions? :read notification) (current-user-is-creator? notification))))) -(mi/define-methods - Pulse - {:hydration-keys (constantly [:pulse]) - :properties (constantly {::mi/timestamped? true - ::mi/entity-id true}) - :pre-insert pre-insert - :pre-update pre-update - :types (constantly {:parameters :json})}) - (defmethod serdes/hash-fields Pulse [_pulse] [:name (serdes/hydrated-hash :collection) :created_at]) diff --git a/src/metabase/models/pulse_card.clj b/src/metabase/models/pulse_card.clj index f92e854bc18..a3c37ce765a 100644 --- a/src/metabase/models/pulse_card.clj +++ b/src/metabase/models/pulse_card.clj @@ -1,18 +1,22 @@ (ns metabase.models.pulse-card (:require - [metabase.models.interface :as mi] [metabase.models.serialization :as serdes] [metabase.util :as u] [metabase.util.schema :as su] + [methodical.core :as methodical] [schema.core :as s] - [toucan.models :as models] [toucan2.core :as t2])) -(models/defmodel PulseCard :pulse_card) +(def PulseCard + "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." + :model/PulseCard) -(mi/define-methods - PulseCard - {:properties (constantly {::mi/entity-id true})}) +(methodical/defmethod t2/table-name :model/PulseCard [_model] :pulse_card) + +(doto :model/PulseCard + (derive :metabase/model) + (derive :hook/entity-id)) (defmethod serdes/hash-fields PulseCard [_pulse-card] diff --git a/src/metabase/models/pulse_channel.clj b/src/metabase/models/pulse_channel.clj index d0b9096af3e..59bd8d40ce4 100644 --- a/src/metabase/models/pulse_channel.clj +++ b/src/metabase/models/pulse_channel.clj @@ -12,7 +12,6 @@ [metabase.util.i18n :refer [tru]] [methodical.core :as methodical] [schema.core :as s] - [toucan.models :as models] [toucan2.core :as t2])) ;; ## Static Definitions @@ -113,12 +112,27 @@ ;; ## Entity -(models/defmodel PulseChannel :pulse_channel) +(def PulseChannel + "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." + :model/PulseChannel) -(doto PulseChannel +(methodical/defmethod t2/table-name :model/PulseChannel [_model] :pulse_channel) +(methodical/defmethod t2/model-for-automagic-hydration [:default :pulse_channel] [_original-model _k] :model/PulseChannel) + +(doto :model/PulseChannel + (derive :metabase/model) + (derive :hook/timestamped?) + (derive :hook/entity-id) (derive ::mi/read-policy.always-allow) (derive ::mi/write-policy.superuser)) +(t2/deftransforms :model/PulseChannel + {:details mi/transform-json + :channel_type mi/transform-keyword + :schedule_type mi/transform-keyword + :schedule_frame mi/transform-keyword}) + (mi/define-simple-hydration-method recipients :recipients "Return the `PulseChannelRecipients` associated with this `pulse-channel`." @@ -141,14 +155,14 @@ in [[update-notification-channels!]] which creates/deletes/updates several channels sequentially." true) -(defn- pre-delete - "This function is called by [[metabase.models.pulse-channel/pre-delete]] when the `PulseChannel` is about to be - deleted. Archives `Pulse` if the channel being deleted is its last channel." +(t2/define-before-delete :model/PulseChannel [{pulse-id :pulse_id, pulse-channel-id :id}] + ;; This function is called by [[metabase.models.pulse-channel/pre-delete]] when the `PulseChannel` is about to be + ;; deleted. Archives `Pulse` if the channel being deleted is its last channel." (when *archive-parent-pulse-when-last-channel-is-deleted* (let [other-channels-count (t2/count PulseChannel :pulse_id pulse-id, :id [:not= pulse-channel-id])] (when (zero? other-channels-count) - (t2/update! :metabase.models.pulse/Pulse pulse-id {:archived true}))))) + (t2/update! :model/Pulse pulse-id {:archived true}))))) ;; we want to load this at the top level so the Setting the namespace defines gets loaded (def ^:private ^{:arglists '([email-addresses])} validate-email-domains* @@ -193,18 +207,13 @@ (throw (ex-info (tru "Wrong email address for User {0}." id) {:status-code 403}))))))))) -(mi/define-methods - PulseChannel - {:hydration-keys (constantly [:pulse_channel]) - :types (constantly {:details :json - :channel_type :keyword - :schedule_type :keyword - :schedule_frame :keyword}) - :properties (constantly {::mi/timestamped? true - ::mi/entity-id true}) - :pre-delete pre-delete - :pre-insert validate-email-domains - :pre-update validate-email-domains}) +(t2/define-before-insert :model/PulseChannel + [pulse-channel] + (validate-email-domains pulse-channel)) + +(t2/define-before-update :model/PulseChannel + [pulse-channel] + (validate-email-domains (mi/pre-update-changes pulse-channel))) (defmethod serdes/hash-fields PulseChannel [_pulse-channel] diff --git a/src/metabase/models/pulse_channel_recipient.clj b/src/metabase/models/pulse_channel_recipient.clj index 77c339f654e..b3493e49593 100644 --- a/src/metabase/models/pulse_channel_recipient.clj +++ b/src/metabase/models/pulse_channel_recipient.clj @@ -1,14 +1,21 @@ (ns metabase.models.pulse-channel-recipient (:require - [toucan.models :as models] + [methodical.core :as methodical] [toucan2.core :as t2])) -(models/defmodel PulseChannelRecipient :pulse_channel_recipient) +(def PulseChannelRecipient + "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." + :model/PulseChannelRecipient) + +(methodical/defmethod t2/table-name :model/PulseChannelRecipient [_model] :pulse_channel_recipient) + +(derive :model/PulseChannelRecipient :metabase/model) ;;; Deletes `PulseChannel` if the recipient being deleted is its last recipient. (This only applies ;;; to PulseChannels with User subscriptions; Slack PulseChannels and ones with email address subscriptions are not ;;; automatically deleted. -(t2/define-before-delete PulseChannelRecipient +(t2/define-before-delete :model/PulseChannelRecipient [{channel-id :pulse_channel_id, pulse-channel-recipient-id :id}] (let [other-recipients-count (t2/count PulseChannelRecipient :pulse_channel_id channel-id @@ -16,7 +23,7 @@ last-recipient? (zero? other-recipients-count)] (when last-recipient? ;; make sure this channel doesn't have any email-address (non-User) recipients. - (let [details (t2/select-one-fn :details :metabase.models.pulse-channel/PulseChannel :id channel-id) + (let [details (t2/select-one-fn :details :model/PulseChannel :id channel-id) has-email-addresses? (seq (:emails details))] (when-not has-email-addresses? - (t2/delete! :metabase.models.pulse-channel/PulseChannel :id channel-id)))))) + (t2/delete! :model/PulseChannel :id channel-id)))))) diff --git a/src/metabase/models/query.clj b/src/metabase/models/query.clj index 3e07bf17297..c3950983a7f 100644 --- a/src/metabase/models/query.clj +++ b/src/metabase/models/query.clj @@ -7,17 +7,24 @@ [metabase.mbql.normalize :as mbql.normalize] [metabase.models.interface :as mi] [metabase.util.honey-sql-2 :as h2x] - [toucan.models :as models] - [toucan2.core :as t2])) + [methodical.core :as methodical] + [toucan2.core :as t2] + [toucan2.model :as t2.model])) (set! *warn-on-reflection* true) -(models/defmodel Query :query) +(def Query + "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." + :model/Query) -(mi/define-methods - Query - {:types (constantly {:query :json}) - :primary-key (constantly :query_hash)}) +(methodical/defmethod t2/table-name :model/Query [_model] :query) +(methodical/defmethod t2.model/primary-keys :model/Query [_model] [:query_hash]) + +(t2/deftransforms :model/Query + {:query mi/transform-json}) + +(derive :model/Query :metabase/model) ;;; Helper Fns diff --git a/src/metabase/models/query_cache.clj b/src/metabase/models/query_cache.clj index 80f0961d4d7..d544d52d70d 100644 --- a/src/metabase/models/query_cache.clj +++ b/src/metabase/models/query_cache.clj @@ -1,17 +1,17 @@ (ns metabase.models.query-cache "A model used to cache query results in the database." (:require - [metabase.models.interface :as mi] [methodical.core :as methodical] - [toucan.models :as models] [toucan2.core :as t2])) -(models/defmodel QueryCache :query_cache) +(def QueryCache + "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." + :model/QueryCache) -(methodical/defmethod t2/primary-keys QueryCache - [_model] - [:query_hash]) +(methodical/defmethod t2/table-name :model/QueryCache [_model] :query_cache) +(methodical/defmethod t2/primary-keys QueryCache [_model] [:query_hash]) -(mi/define-methods - QueryCache - {:properties (constantly {::mi/updated-at-timestamped? true})}) +(doto :model/QueryCache + (derive :metabase/model) + (derive :hook/updated-at-timestamped?)) diff --git a/src/metabase/models/query_execution.clj b/src/metabase/models/query_execution.clj index f90785ce667..a75f3c03b1d 100644 --- a/src/metabase/models/query_execution.clj +++ b/src/metabase/models/query_execution.clj @@ -6,25 +6,37 @@ [metabase.models.interface :as mi] [metabase.util :as u] [metabase.util.i18n :refer [tru]] + [methodical.core :as methodical] [schema.core :as s] - [toucan.models :as models])) + [toucan2.core :as t2])) -(models/defmodel QueryExecution :query_execution) +(def QueryExecution + "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." + :model/QueryExecution) + +(methodical/defmethod t2/table-name :model/QueryExecution [_model] :query_execution) + +(derive :model/QueryExecution :metabase/model) + +(t2/deftransforms :model/QueryExecution + {:json_query mi/transform-json + :status mi/transform-keyword + :context mi/transform-keyword}) (def ^:private ^{:arglists '([context])} validate-context (s/validator mbql.s/Context)) -(defn- pre-insert [{context :context, :as query-execution}] +(t2/define-before-insert :model/QueryExecution + [{context :context, :as query-execution}] (u/prog1 query-execution (validate-context context))) -(defn- post-select [{:keys [result_rows] :as query-execution}] +(t2/define-after-select :model/QueryExecution + [{:keys [result_rows] :as query-execution}] ;; sadly we have 2 ways to reference the row count :( (assoc query-execution :row_count (or result_rows 0))) -(mi/define-methods - QueryExecution - {:types (constantly {:json_query :json, :status :keyword, :context :keyword}) - :pre-insert pre-insert - :pre-update (fn [& _] (throw (Exception. (tru "You cannot update a QueryExecution!")))) - :post-select post-select}) +(t2/define-before-update :model/QueryExecution + [_query-execution] + (throw (Exception. (tru "You cannot update a QueryExecution!")))) diff --git a/src/metabase/models/segment.clj b/src/metabase/models/segment.clj index e96d3b2052e..97d6807ea49 100644 --- a/src/metabase/models/segment.clj +++ b/src/metabase/models/segment.clj @@ -19,22 +19,33 @@ [metabase.util.log :as log] [metabase.util.malli :as mu] [methodical.core :as methodical] - [toucan.models :as models] [toucan2.core :as t2] [toucan2.tools.hydrate :as t2.hydrate])) -(models/defmodel Segment :segment) +(def Segment + "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." + :model/Segment) -(doto 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-metric-segment-definition}) + +(doto :model/Segment + (derive :metabase/model) + (derive :hook/timestamped?) + (derive :hook/entity-id) (derive ::mi/read-policy.full-perms-for-perms-set) (derive ::mi/write-policy.superuser) (derive ::mi/create-policy.superuser)) -(defn- pre-update [{:keys [creator_id id], :as updates}] - (u/prog1 updates +(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? updates :creator_id) - (when (not= creator_id (t2/select-one-fn :creator_id Segment :id 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 @@ -43,14 +54,6 @@ (t2/select-one ['Table :db_id :schema :id] :id (u/the-id (:table_id segment))))] (mi/perms-objects-set table read-or-write))) -(mi/define-methods - Segment - {:types (constantly {:definition :metric-segment-definition}) - :properties (constantly {::mi/timestamped? true - ::mi/entity-id true}) - :hydration-keys (constantly [:segment]) - :pre-update pre-update}) - (mu/defn ^:private definition-description :- [:maybe ::lib.schema.common/non-blank-string] "Calculate a nice description of a Segment's definition." [metadata-provider :- lib.metadata/MetadataProvider diff --git a/src/metabase/models/user.clj b/src/metabase/models/user.clj index d5c1eaae5eb..3df58aefe03 100644 --- a/src/metabase/models/user.clj +++ b/src/metabase/models/user.clj @@ -21,9 +21,10 @@ [metabase.util.log :as log] [metabase.util.password :as u.password] [metabase.util.schema :as su] + [methodical.core :as methodical] [schema.core :as schema] - [toucan.models :as models] - [toucan2.core :as t2]) + [toucan2.core :as t2] + [toucan2.tools.default-fields :as t2.default-fields]) (:import (java.util UUID))) @@ -31,7 +32,24 @@ ;;; ----------------------------------------------- Entity & Lifecycle ----------------------------------------------- -(models/defmodel User :core_user) +(def User + "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." + :model/User) + +(methodical/defmethod t2/table-name :model/User [_model] :core_user) +(methodical/defmethod t2/model-for-automagic-hydration [:default :author] [_original-model _k] :model/User) +(methodical/defmethod t2/model-for-automagic-hydration [:default :creator] [_original-model _k] :model/User) +(methodical/defmethod t2/model-for-automagic-hydration [:default :user] [_original-model _k] :model/User) + +(doto :model/User + (derive :metabase/model) + (derive :hook/updated-at-timestamped?)) + +(t2/deftransforms :model/User + {:login_attributes mi/transform-json-no-keywordization + :settings mi/transform-encrypted-json + :sso_source mi/transform-keyword}) (def ^:private insert-default-values {:date_joined :%now @@ -50,7 +68,8 @@ {:password_salt salt :password (u.password/hash-bcrypt (str salt password))}))) -(defn- pre-insert [{:keys [email password reset_token locale], :as user}] +(t2/define-before-insert :model/User + [{:keys [email password reset_token locale], :as user}] ;; these assertions aren't meant to be user-facing, the API endpoints should be validation these as well. (assert (u/email? email)) (assert ((every-pred string? (complement str/blank?)) password)) @@ -69,7 +88,8 @@ (when locale {:locale (i18n/normalized-locale-string locale)}))) -(defn- post-insert [{user-id :id, superuser? :is_superuser, :as user}] +(t2/define-after-insert :model/User + [{user-id :id, superuser? :is_superuser, :as user}] (u/prog1 user ;; add the newly created user to the magic perms groups (binding [perms-group-membership/*allow-changing-all-users-group-members* true] @@ -83,12 +103,16 @@ :user_id user-id :group_id (:id (perms-group/admin)))))) -(defn- pre-update - [{reset-token :reset_token, superuser? :is_superuser, active? :is_active, :keys [email id locale], :as user}] +(t2/define-before-update :model/User + [{:keys [id] :as user}] ;; when `:is_superuser` is toggled add or remove the user from the 'Admin' group as appropriate - (let [in-admin-group? (t2/exists? PermissionsGroupMembership - :group_id (:id (perms-group/admin)) - :user_id id)] + (let [{reset-token :reset_token + superuser? :is_superuser + active? :is_active + :keys [email locale]} (t2/changes user) + in-admin-group? (t2/exists? PermissionsGroupMembership + :group_id (:id (perms-group/admin)) + :user_id id)] ;; Do not let the last admin archive themselves (when (and in-admin-group? (false? active?)) @@ -105,22 +129,22 @@ (and (not superuser?) in-admin-group?) (t2/delete! (t2/table-name PermissionsGroupMembership) - :group_id (u/the-id (perms-group/admin)) - :user_id id)))) - ;; make sure email and locale are valid if set - (when email - (assert (u/email? email))) - (when locale - (assert (i18n/available-locale? locale) (tru "Invalid locale: {0}" (pr-str locale)))) - ;; delete all subscriptions to pulses/alerts/etc. if the User is getting archived (`:is_active` status changes) - (when (false? active?) - (t2/delete! 'PulseChannelRecipient :user_id id)) - ;; If we're setting the reset_token then encrypt it before it goes into the DB - (cond-> user - true (merge (hashed-password-values user)) - reset-token (update :reset_token u.password/hash-bcrypt) - locale (update :locale i18n/normalized-locale-string) - email (update :email u/lower-case-en))) + :group_id (u/the-id (perms-group/admin)) + :user_id id))) + ;; make sure email and locale are valid if set + (when email + (assert (u/email? email))) + (when locale + (assert (i18n/available-locale? locale) (tru "Invalid locale: {0}" (pr-str locale)))) + ;; delete all subscriptions to pulses/alerts/etc. if the User is getting archived (`:is_active` status changes) + (when (false? active?) + (t2/delete! 'PulseChannelRecipient :user_id id)) + ;; If we're setting the reset_token then encrypt it before it goes into the DB + (cond-> user + true (merge (hashed-password-values (t2/changes user))) + reset-token (update :reset_token u.password/hash-bcrypt) + locale (update :locale i18n/normalized-locale-string) + email (update :email u/lower-case-en)))) (defn add-common-name "Add a `:common_name` key to `user` by combining their first and last names, or using their email if names are `nil`." @@ -131,7 +155,8 @@ (cond-> user common-name (assoc :common_name common-name)))) -(defn- post-select [user] +(t2/define-after-select :model/User + [user] (add-common-name user)) (def ^:private default-user-columns @@ -153,18 +178,7 @@ "Sequence of columns Group Managers can see when fetching a list of Users.." (into non-admin-or-self-visible-columns [:is_superuser :last_login])) -(mi/define-methods - User - {:default-fields (constantly default-user-columns) - :hydration-keys (constantly [:author :creator :user]) - :properties (constantly {::mi/updated-at-timestamped? true}) - :pre-insert pre-insert - :post-insert post-insert - :pre-update pre-update - :post-select post-select - :types (constantly {:login_attributes :json-no-keywordization - :settings :encrypted-json - :sso_source :keyword})}) +(t2.default-fields/define-default-fields :model/User default-user-columns) (defmethod serdes/hash-fields User [_user] diff --git a/test/metabase/models/revision_test.clj b/test/metabase/models/revision_test.clj index 1750f55ed20..0ad4ee201b5 100644 --- a/test/metabase/models/revision_test.clj +++ b/test/metabase/models/revision_test.clj @@ -7,6 +7,7 @@ [metabase.models.revision.diff :refer [build-sentence]] [metabase.test :as mt] [metabase.util.i18n :refer [deferred-tru]] + [methodical.core :as methodical] [toucan.models :as models] [toucan2.core :as t2] [toucan2.tools.with-temp :as t2.with-temp])) @@ -14,7 +15,8 @@ (def ^:private reverted-to (atom nil)) -(models/defmodel ^:private FakedCard :report_card) +(methodical/defmethod t2/table-name :model/FakedCard [_model] :report_card) +(derive :model/FakedCard :metabase/model) (use-fixtures :each (fn [thunk] (with-redefs [metabase.models.revision.diff/model-str->i18n-str (fn [model-str] @@ -27,26 +29,26 @@ "FakeCard" "FakeCard"))] (thunk)))) -(defmethod revision/serialize-instance FakedCard +(defmethod revision/serialize-instance :model/FakedCard [_model _id obj] (into {} (assoc obj :serialized true))) -(defmethod revision/revert-to-revision! FakedCard +(defmethod revision/revert-to-revision! :model/FakedCard [_model _id _user-id serialized-instance] (reset! reverted-to (dissoc serialized-instance :serialized))) -(defmethod revision/diff-map FakedCard +(defmethod revision/diff-map :model/FakedCard [_model o1 o2] {:o1 (when o1 (into {} o1)), :o2 (when o2 (into {} o2))}) -(defmethod revision/diff-strings FakedCard +(defmethod revision/diff-strings :model/FakedCard [_model o1 o2] (when o1 [(str "BEFORE=" (into {} o1) ",AFTER=" (into {} o2))])) (defn- push-fake-revision! [card-id & {:keys [message] :as object}] (revision/push-revision! - :entity FakedCard + :entity :model/FakedCard :id card-id :user-id (mt/user->id :rasta) :object (dissoc object :message) @@ -102,7 +104,7 @@ (testing "Test that a newly created Card doesn't have any revisions" (t2.with-temp/with-temp [Card {card-id :id}] (is (= [] - (revision/revisions FakedCard card-id)))))) + (revision/revisions :model/FakedCard card-id)))))) (deftest add-revision-test (testing "Test that we can add a revision" @@ -112,11 +114,11 @@ Revision {:model "FakedCard" :user_id (mt/user->id :rasta) - :object (mi/instance FakedCard {:name "Tips Created by Day", :serialized true}) + :object (mi/instance :model/FakedCard {:name "Tips Created by Day", :serialized true}) :is_reversion false :is_creation false :message "yay!"})] - (for [revision (revision/revisions FakedCard card-id)] + (for [revision (revision/revisions :model/FakedCard card-id)] (dissoc revision :timestamp :id :model_id))))))) (deftest sorting-test @@ -128,7 +130,7 @@ Revision {:model "FakedCard" :user_id (mt/user->id :rasta) - :object (mi/instance FakedCard {:name "Spots Created by Day", :serialized true}) + :object (mi/instance :model/FakedCard {:name "Spots Created by Day", :serialized true}) :is_reversion false :is_creation false :message nil}) @@ -136,11 +138,11 @@ Revision {:model "FakedCard" :user_id (mt/user->id :rasta) - :object (mi/instance FakedCard {:name "Tips Created by Day", :serialized true}) + :object (mi/instance :model/FakedCard {:name "Tips Created by Day", :serialized true}) :is_reversion false :is_creation false :message nil})] - (->> (revision/revisions FakedCard card-id) + (->> (revision/revisions :model/FakedCard card-id) (map #(dissoc % :timestamp :id :model_id)))))))) (deftest delete-old-revisions-test @@ -150,7 +152,7 @@ (dorun (doseq [i (range (inc revision/max-revisions))] (push-fake-revision! card-id, :name (format "Tips Created by Day %d" i)))) (is (= revision/max-revisions - (count (revision/revisions FakedCard card-id))))))) + (count (revision/revisions :model/FakedCard card-id))))))) (deftest do-not-record-if-object-is-not-changed-test (testing "Check that we don't record a revision if the object hasn't changed" @@ -159,15 +161,15 @@ (push-fake-revision! card-id, :name (format "Tips Created by Day %s" x)))] (testing "first revision should be recorded" (new-revision 1) - (is (= 1 (count (revision/revisions FakedCard card-id))))) + (is (= 1 (count (revision/revisions :model/FakedCard card-id))))) (testing "repeatedly push reivisions with thesame object shouldn't create new revision" (dorun (repeatedly 5 #(new-revision 1))) - (is (= 1 (count (revision/revisions FakedCard card-id))))) + (is (= 1 (count (revision/revisions :model/FakedCard card-id))))) (testing "push a revision with different object should create new revision" (new-revision 2) - (is (= 2 (count (revision/revisions FakedCard card-id))))))))) + (is (= 2 (count (revision/revisions :model/FakedCard card-id))))))))) ;;; # REVISIONS+DETAILS @@ -184,19 +186,19 @@ :o2 {:name "Modified Name", :serialized true}} :has_multiple_changes false :description "BEFORE={:name \"Initial Name\", :serialized true},AFTER={:name \"Modified Name\", :serialized true}."} - (let [revisions (revision/revisions FakedCard card-id)] + (let [revisions (revision/revisions :model/FakedCard card-id)] (assert (= 2 (count revisions))) - (-> (revision/add-revision-details FakedCard (first revisions) (last revisions)) + (-> (revision/add-revision-details :model/FakedCard (first revisions) (last revisions)) (dissoc :timestamp :id :model_id) mt/derecordize)))))) (testing "test that we return a description even when there is no change between revision" (is (= "created a revision with no change." - (str (:description (revision/add-revision-details FakedCard {:name "Apple"} {:name "Apple"})))))) + (str (:description (revision/add-revision-details :model/FakedCard {:name "Apple"} {:name "Apple"})))))) (testing "that we return a descrtiopn when there is no previous revision" (is (= "modified this." - (str (:description (revision/add-revision-details FakedCard {:name "Apple"} nil))))))) + (str (:description (revision/add-revision-details :model/FakedCard {:name "Apple"} nil))))))) (deftest revisions+details-test (testing "Check that revisions+details pulls in user info and adds description" @@ -212,7 +214,7 @@ :o2 {:name "Tips Created by Day", :serialized true}} :has_multiple_changes false :description "modified this."})] - (->> (revision/revisions+details FakedCard card-id) + (->> (revision/revisions+details :model/FakedCard card-id) (map #(dissoc % :timestamp :id :model_id)) (map #(update % :description str)))))))) @@ -242,7 +244,7 @@ :o2 {:name "Tips Created by Day", :serialized true}} :has_multiple_changes false :description "modified this."})] - (->> (revision/revisions+details FakedCard card-id) + (->> (revision/revisions+details :model/FakedCard card-id) (map #(dissoc % :timestamp :id :model_id)) (map #(update % :description str)))))))) @@ -252,8 +254,8 @@ (testing "Check that revert defers to revert-to-revision!" (t2.with-temp/with-temp [Card {card-id :id}] (push-fake-revision! card-id, :name "Tips Created by Day") - (let [[{revision-id :id}] (revision/revisions FakedCard card-id)] - (revision/revert! :entity FakedCard, :id card-id, :user-id (mt/user->id :rasta), :revision-id revision-id) + (let [[{revision-id :id}] (revision/revisions :model/FakedCard card-id)] + (revision/revert! :entity :model/FakedCard, :id card-id, :user-id (mt/user->id :rasta), :revision-id revision-id) (is (= {:name "Tips Created by Day"} @reverted-to)))))) @@ -274,8 +276,8 @@ (t2.with-temp/with-temp [Card {card-id :id}] (push-fake-revision! card-id, :name "Tips Created by Day") (push-fake-revision! card-id, :name "Spots Created by Day") - (let [[_ {old-revision-id :id}] (revision/revisions FakedCard card-id)] - (revision/revert! :entity FakedCard, :id card-id, :user-id (mt/user->id :rasta), :revision-id old-revision-id) + (let [[_ {old-revision-id :id}] (revision/revisions :model/FakedCard card-id)] + (revision/revert! :entity :model/FakedCard, :id card-id, :user-id (mt/user->id :rasta), :revision-id old-revision-id) (is (partial= [(mi/instance Revision @@ -301,7 +303,7 @@ :is_reversion false :is_creation false :message nil})] - (->> (revision/revisions FakedCard card-id) + (->> (revision/revisions :model/FakedCard card-id) (map #(dissoc % :timestamp :id :model_id))))))))) (deftest generic-models-revision-title+description-test diff --git a/test/metabase/models/user_test.clj b/test/metabase/models/user_test.clj index 97f110ad7c3..0e3df12ac57 100644 --- a/test/metabase/models/user_test.clj +++ b/test/metabase/models/user_test.clj @@ -414,7 +414,7 @@ (t2/select-one-fn :reset_token User :id user-id))))) (testing "should clear out all existing Sessions" - (mt/with-temp* [User [{user-id :id}]] + (t2.with-temp/with-temp [User {user-id :id} {}] (dotimes [_ 2] (t2/insert! Session {:id (str (java.util.UUID/randomUUID)), :user_id user-id})) (letfn [(session-count [] (t2/count Session :user_id user-id))] @@ -463,16 +463,16 @@ (deftest delete-pulse-subscriptions-when-archived-test (testing "Delete a User's Pulse/Alert/Dashboard Subscription subscriptions when they get archived" - (mt/with-temp* [User [{user-id :id}] - Pulse [{pulse-id :id}] - PulseChannel [{pulse-channel-id :id} {:pulse_id pulse-id}] - PulseChannelRecipient [_ {:pulse_channel_id pulse-channel-id, :user_id user-id}]] + (t2.with-temp/with-temp [User {user-id :id} {} + Pulse {pulse-id :id} {} + PulseChannel {pulse-channel-id :id} {:pulse_id pulse-id} + PulseChannelRecipient _ {:pulse_channel_id pulse-channel-id, :user_id user-id}] (letfn [(subscription-exists? [] (t2/exists? PulseChannelRecipient :pulse_channel_id pulse-channel-id, :user_id user-id))] (testing "Sanity check: subscription should exist" (is (subscription-exists?))) (testing "user is updated but not archived: don't delete the subscription" - (is (pos? (t2/update! User user-id {:is_active true}))) + (is (pos? (t2/update! User user-id {:is_active true :first_name "New name"}))) (is (subscription-exists?))) (testing "archive the user" (is (pos? (t2/update! User user-id {:is_active false})))) diff --git a/test/metabase/test/util.clj b/test/metabase/test/util.clj index c019eda8c48..e13aa72b9a6 100644 --- a/test/metabase/test/util.clj +++ b/test/metabase/test/util.clj @@ -21,17 +21,11 @@ Field FieldValues LoginHistory - Metric - NativeQuerySnippet Permissions PermissionsGroup PermissionsGroupMembership PersistedInfo - Pulse - PulseCard - PulseChannel Revision - Segment Setting Table TaskHistory @@ -158,14 +152,14 @@ :device_description "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML like Gecko) Chrome/89.0.4389.86 Safari/537.36" :ip_address "0:0:0:0:0:0:0:1"}) - Metric + :model/Metric (fn [_] {:creator_id (rasta-id) :definition {} :description "Lookin' for a blueberry" :name "Toucans in the rainforest" :table_id (data/id :checkins)}) - NativeQuerySnippet + :model/NativeQuerySnippet (fn [_] {:creator_id (user-id :crowberto) :name (tu.random/random-name) :content "1 = 1"}) @@ -187,16 +181,16 @@ PermissionsGroup (fn [_] {:name (tu.random/random-name)}) - Pulse + :model/Pulse (fn [_] {:creator_id (rasta-id) :name (tu.random/random-name)}) - PulseCard + :model/PulseCard (fn [_] {:position 0 :include_csv false :include_xls false}) - PulseChannel + :model/PulseChannel (constantly {:channel_type :email :details {} :schedule_type :daily @@ -207,7 +201,7 @@ :is_creation false :is_reversion false}) - Segment + :model/Segment (fn [_] {:creator_id (rasta-id) :definition {} :description "Lookin' for a blueberry" @@ -247,7 +241,7 @@ :time_matters true :creator_id (rasta-id)}) - User + :model/User (fn [_] {:first_name (tu.random/random-name) :last_name (tu.random/random-name) :email (tu.random/random-email) -- GitLab