diff --git a/src/metabase/models/card.clj b/src/metabase/models/card.clj index 2abbcd774c001914733468081dab7bce11266132..7e2e1de81a47bf7bd42c93d5ed6fcf1ce9abd60d 100644 --- a/src/metabase/models/card.clj +++ b/src/metabase/models/card.clj @@ -17,6 +17,7 @@ [permissions :as perms] [revision :as revision]] [metabase.query-processor.middleware.permissions :as qp-perms] + [metabase.query-processor.util :as qputil] [toucan [db :as db] [models :as models]])) @@ -43,36 +44,58 @@ ;;; ------------------------------------------------------------ Permissions Checking ------------------------------------------------------------ -(defn- permissions-path-set:mbql [{database-id :database, :as query}] - {:pre [(integer? database-id) (map? (:query query))]} - (try (let [{{:keys [source-table join-tables]} :query} (qp/expand query)] - (set (for [table (cons source-table join-tables)] - (perms/object-path database-id - (:schema table) - (or (:id table) (:table-id table)))))) +(defn- native-permissions-path + "Return the `:read` (for running) or `:write` (for saving) native permissions path for DATABASE-OR-ID." + [read-or-write database-or-id] + ((case read-or-write + :read perms/native-read-path + :write perms/native-readwrite-path) (u/get-id database-or-id))) + +(defn- query->source-and-join-tables + "Return a sequence of all Tables (as TableInstance maps) referenced by QUERY." + [{:keys [source-table join-tables native], :as query}] + (cond + ;; if we come across a native query just put a placeholder (`::native`) there so we know we need to add native permissions to the complete set below. + native [::native] + ;; for root MBQL queries just return source-table + join-tables + :else (cons source-table join-tables))) + +(defn- tables->permissions-path-set + "Given a sequence of TABLES referenced by a query, return a set of required permissions." + [read-or-write database-or-id tables] + (set (for [table tables] + (if (= ::native table) + ;; Any `::native` placeholders from above mean we need READ-OR-WRITE native permissions for this DATABASE + (native-permissions-path read-or-write database-or-id) + ;; anything else (i.e., a normal table) just gets normal table permissions + (perms/object-path (u/get-id database-or-id) + (:schema table) + (or (:id table) (:table-id table))))))) + +(defn- mbql-permissions-path-set + "Return the set of required permissions needed to run QUERY." + [read-or-write query] + {:pre [(map? query) (map? (:query query))]} + (try (let [{:keys [query database]} (qp/expand query)] + (tables->permissions-path-set read-or-write database (query->source-and-join-tables query))) ;; if for some reason we can't expand the Card (i.e. it's an invalid legacy card) ;; just return a set of permissions that means no one will ever get to see it (catch Throwable e (log/warn "Error getting permissions for card:" (.getMessage e) "\n" (u/pprint-to-str (u/filtered-stacktrace e))) - #{"/db/0/"}))) ; DB 0 will never exist - -(defn- permissions-path-set:native [read-or-write {database-id :database}] - #{((case read-or-write - :read perms/native-read-path - :write perms/native-readwrite-path) database-id)}) + #{"/db/0/"}))) ; DB 0 will never exist ;; it takes a lot of DB calls and function calls to expand/resolve a query, and since they're pure functions we can save ourselves some a lot of DB calls ;; by caching the results. Cache the permissions reqquired to run a given query dictionary for up to 6 hours -(defn- query-perms-set* [{query-type :type, :as query} read-or-write] +(defn- query-perms-set* [{query-type :type, database :database, :as query} read-or-write] (cond (= query {}) #{} - (= (keyword query-type) :native) (permissions-path-set:native read-or-write query) - (= (keyword query-type) :query) (permissions-path-set:mbql query) + (= (keyword query-type) :native) #{(native-permissions-path read-or-write database)} + (= (keyword query-type) :query) (mbql-permissions-path-set read-or-write query) :else (throw (Exception. (str "Invalid query type: " query-type))))) (def ^{:arglists '([query read-or-write])} query-perms-set "Return a set of required permissions for *running* QUERY (if READ-OR-WRITE is `:read`) or *saving* it (if READ-OR-WRITE is `:write`)." - (memoize/ttl query-perms-set* :ttl/threshold (* 6 60 60 1000))) + (memoize/ttl query-perms-set* :ttl/threshold (* 6 60 60 1000))) ; memoize for 6 hours (defn- perms-objects-set @@ -111,17 +134,21 @@ ;;; ------------------------------------------------------------ Lifecycle ------------------------------------------------------------ - -(defn- populate-query-fields [card] - (let [{query :query, database-id :database, query-type :type} (:dataset_query card) - table-id (or (:source_table query) ; legacy (MBQL '95) - (:source-table query)) - defaults {:database_id database-id - :table_id table-id - :query_type (keyword query-type)}] - (if query-type - (merge defaults card) - card))) +(defn- query->database-and-table-ids + "Return a map with `:database-id` and source `:table-id` that should be saved for a Card." + [outer-query] + (let [database (qputil/get-normalized outer-query :database) + source-table (qputil/get-in-normalized outer-query [:query :source-table])] + (when source-table + {:database-id (u/get-id database), :table-id (u/get-id source-table)}))) + +(defn- populate-query-fields [{{query-type :type, :as outer-query} :dataset_query, :as card}] + (merge (when query-type + (let [{:keys [database-id table-id]} (query->database-and-table-ids outer-query)] + {:database_id database-id + :table_id table-id + :query_type (keyword query-type)})) + card)) (defn- pre-insert [{:keys [dataset_query], :as card}] ;; TODO - make sure if `collection_id` is specified that we have write permissions for tha tcollection diff --git a/src/metabase/models/user.clj b/src/metabase/models/user.clj index 2c5b6dfa36f5c3e0b6d059a036920132798cdac4..6e147cda3c8ff6fdf27e327e2ffe2afe81c3ac74 100644 --- a/src/metabase/models/user.clj +++ b/src/metabase/models/user.clj @@ -1,6 +1,7 @@ (ns metabase.models.user (:require [cemerick.friend.credentials :as creds] [clojure.string :as s] + [clojure.tools.logging :as log] [metabase [public-settings :as public-settings] [util :as u]] @@ -42,12 +43,12 @@ (u/prog1 user ;; add the newly created user to the magic perms groups (binding [perm-membership/*allow-changing-all-users-group-members* true] - #_(log/info (format "Adding user %d to All Users permissions group..." user-id)) + (log/info (format "Adding user %d to All Users permissions group..." user-id)) (db/insert! PermissionsGroupMembership :user_id user-id :group_id (:id (group/all-users)))) (when superuser? - #_(log/info (format "Adding user %d to Admin permissions group..." user-id)) + (log/info (format "Adding user %d to Admin permissions group..." user-id)) (db/insert! PermissionsGroupMembership :user_id user-id :group_id (:id (group/admin)))))) diff --git a/src/metabase/query_processor.clj b/src/metabase/query_processor.clj index 47802aa602a73ea89a705b6f90517857d5d52439..2e079b455c015ba3a1b4508f01f18113bd6b8176 100644 --- a/src/metabase/query_processor.clj +++ b/src/metabase/query_processor.clj @@ -61,34 +61,55 @@ ;; PRE-PROCESSING fns are applied from bottom to top, and POST-PROCESSING from top to bottom; ;; the easiest way to wrap your head around this is picturing a the query as a ball being thrown in the air ;; (up through the preprocessing fns, back down through the post-processing ones) +(defn- qp-pipeline + "Construct a new Query Processor pipeline with F as the final 'piviotal' function. e.g.: + + All PRE-PROCESSING (query) --> F --> All POST-PROCESSING (result) + + Or another way of looking at it is + + (post-process (f (pre-process query))) + + Normally F is something that runs the query, like the `execute-query` function above, but this can be swapped out when we want to do things like + process a query without actually running it." + [f] + ;; ▼▼▼ POST-PROCESSING ▼▼▼ happens from TOP-TO-BOTTOM, e.g. the results of `f` are (eventually) passed to `limit` + (-> f + dev/guard-multiple-calls + mbql-to-native/mbql->native ; ▲▲▲ NATIVE-ONLY POINT ▲▲▲ Query converted from MBQL to native here; all functions *above* will only see the native query + annotate-and-sort/annotate-and-sort + perms/check-query-permissions + log-query/log-expanded-query + dev/check-results-format + limit/limit + cumulative-ags/handle-cumulative-aggregations + implicit-clauses/add-implicit-clauses + format-rows/format-rows + expand-resolve/expand-resolve ; ▲▲▲ QUERY EXPANSION POINT ▲▲▲ All functions *above* will see EXPANDED query during PRE-PROCESSING + row-count-and-status/add-row-count-and-status ; ▼▼▼ RESULTS WRAPPING POINT ▼▼▼ All functions *below* will see results WRAPPED in `:data` during POST-PROCESSING + parameters/substitute-parameters + expand-macros/expand-macros + driver-specific/process-query-in-context ; (drivers can inject custom middleware if they implement IDriver's `process-query-in-context`) + add-settings/add-settings + resolve-driver/resolve-driver ; ▲▲▲ DRIVER RESOLUTION POINT ▲▲▲ All functions *above* will have access to the driver during PRE- *and* POST-PROCESSING + log-query/log-initial-query + cache/maybe-return-cached-results + catch-exceptions/catch-exceptions)) +;; ▲▲▲ PRE-PROCESSING ▲▲▲ happens from BOTTOM-TO-TOP, e.g. the results of `expand-macros` are (eventually) passed to `expand-resolve` + +(defn query->native + "Return the native form for QUERY (e.g. for a MBQL query on Postgres this would return a map containing the compiled SQL form)." + {:style/indent 0} + [query] + (-> ((qp-pipeline identity) query) + (get-in [:data :native_form]))) + (defn process-query "A pipeline of various QP functions (including middleware) that are used to process MB queries." {:style/indent 0} [query] - ;; ▼▼▼ POST-PROCESSING ▼▼▼ happens from TOP-TO-BOTTOM, e.g. the results of `run-query` are (eventually) passed to `limit` - ((-> execute-query - dev/guard-multiple-calls - mbql-to-native/mbql->native ; ▲▲▲ NATIVE-ONLY POINT ▲▲▲ Query converted from MBQL to native here; all functions *above* will only see the native query - annotate-and-sort/annotate-and-sort - perms/check-query-permissions - log-query/log-expanded-query - dev/check-results-format - limit/limit - cumulative-ags/handle-cumulative-aggregations - implicit-clauses/add-implicit-clauses - format-rows/format-rows - expand-resolve/expand-resolve ; ▲▲▲ QUERY EXPANSION POINT ▲▲▲ All functions *above* will see EXPANDED query during PRE-PROCESSING - row-count-and-status/add-row-count-and-status ; ▼▼▼ RESULTS WRAPPING POINT ▼▼▼ All functions *below* will see results WRAPPED in `:data` during POST-PROCESSING - parameters/substitute-parameters - expand-macros/expand-macros - driver-specific/process-query-in-context ; (drivers can inject custom middleware if they implement IDriver's `process-query-in-context`) - add-settings/add-settings - resolve-driver/resolve-driver ; ▲▲▲ DRIVER RESOLUTION POINT ▲▲▲ All functions *above* will have access to the driver during PRE- *and* POST-PROCESSING - log-query/log-initial-query - cache/maybe-return-cached-results - catch-exceptions/catch-exceptions) - query)) -;; ▲▲▲ PRE-PROCESSING ▲▲▲ happens from BOTTOM-TO-TOP, e.g. the results of `expand-macros` are (eventually) passed to `expand-resolve` + ((qp-pipeline execute-query) query)) + (def ^{:arglists '([query])} expand diff --git a/test/metabase/api/database_test.clj b/test/metabase/api/database_test.clj index c22b310d80a83f373b29d41024574b32b20a2103..0262b362f2346f4be35014560c2f73c07961168d 100644 --- a/test/metabase/api/database_test.clj +++ b/test/metabase/api/database_test.clj @@ -8,7 +8,7 @@ [field :refer [Field]] [table :refer [Table]]] [metabase.test - [data :refer :all] + [data :as data :refer :all] [util :as tu :refer [match-$]]] [metabase.test.data [datasets :as datasets] @@ -49,24 +49,28 @@ (u/ignore-exceptions (first @~result)) ; in case @result# barfs we don't want the test to succeed (Exception == Exception for expectations) (second @~result))))) +(def ^:private default-db-details + {:engine "h2" + :name "test-data" + :is_sample false + :is_full_sync true + :description nil + :caveats nil + :points_of_interest nil}) + (defn- db-details + "Return default column values for a database (either the test database, via `(db)`, or optionally passed in)." ([] (db-details (db))) ([db] - (match-$ db - {:created_at $ - :engine "h2" - :id $ - :details $ - :updated_at $ - :name "test-data" - :is_sample false - :is_full_sync true - :description nil - :caveats nil - :points_of_interest nil - :features (mapv name (driver/features (driver/engine->driver (:engine db))))}))) + (merge default-db-details + (match-$ db + {:created_at $ + :id $ + :details $ + :updated_at $ + :features (mapv name (driver/features (driver/engine->driver (:engine db))))})))) ;; # DB LIFECYCLE ENDPOINTS @@ -85,19 +89,16 @@ ;; ## POST /api/database ;; Check that we can create a Database (expect-with-temp-db-created-via-api [db {:is_full_sync false}] - (match-$ db - {:created_at $ - :engine :postgres - :id $ - :details {:host "localhost", :port 5432, :dbname "fakedb", :user "cam", :ssl true} - :updated_at $ - :name $ - :is_sample false - :is_full_sync false - :description nil - :caveats nil - :points_of_interest nil - :features (driver/features (driver/engine->driver :postgres))}) + (merge default-db-details + (match-$ db + {:created_at $ + :engine :postgres + :is_full_sync false + :id $ + :details {:host "localhost", :port 5432, :dbname "fakedb", :user "cam", :ssl true} + :updated_at $ + :name $ + :features (driver/features (driver/engine->driver :postgres))})) (Database (:id db))) @@ -122,26 +123,38 @@ (dissoc (into {} (db/select-one [Database :name :engine :details :is_full_sync], :id db-id)) :features))) +:description nil + :entity_type nil + :caveats nil + :points_of_interest nil + :visibility_type nil +(def ^:private default-table-details + {:description nil + :entity_name nil + :entity_type nil + :caveats nil + :points_of_interest nil + :visibility_type nil + :active true + :show_in_getting_started false}) (defn- table-details [table] - (match-$ table - {:description $ - :entity_type $ - :caveats nil - :points_of_interest nil - :visibility_type $ - :schema $ - :name $ - :display_name $ - :rows $ - :updated_at $ - :entity_name $ - :active $ - :id $ - :db_id $ - :show_in_getting_started false - :raw_table_id $ - :created_at $})) + (merge default-table-details + (match-$ table + {:description $ + :entity_type $ + :visibility_type $ + :schema $ + :name $ + :display_name $ + :rows $ + :updated_at $ + :entity_name $ + :active $ + :id $ + :db_id $ + :raw_table_id $ + :created_at $}))) ;; TODO - this is a test code smell, each test should clean up after itself and this step shouldn't be neccessary. One day we should be able to remove this! @@ -163,32 +176,24 @@ (expect-with-temp-db-created-via-api [{db-id :id}] (set (filter identity (conj (for [engine datasets/all-valid-engines] (datasets/when-testing-engine engine - (match-$ (get-or-create-test-data-db! (driver/engine->driver engine)) - {:created_at $ - :engine (name $engine) - :id $ - :updated_at $ - :name "test-data" - :native_permissions "write" - :is_sample false - :is_full_sync true - :description nil - :caveats nil - :points_of_interest nil - :features (map name (driver/features (driver/engine->driver engine)))}))) - (match-$ (Database db-id) - {:created_at $ - :engine "postgres" - :id $ - :updated_at $ - :name $ - :native_permissions "write" - :is_sample false - :is_full_sync true - :description nil - :caveats nil - :points_of_interest nil - :features (map name (driver/features (driver/engine->driver :postgres)))})))) + (merge default-db-details + (match-$ (get-or-create-test-data-db! (driver/engine->driver engine)) + {:created_at $ + :engine (name $engine) + :id $ + :updated_at $ + :name "test-data" + :native_permissions "write" + :features (map name (driver/features (driver/engine->driver engine)))})))) + (merge default-db-details + (match-$ (Database db-id) + {:created_at $ + :engine "postgres" + :id $ + :updated_at $ + :name $ + :native_permissions "write" + :features (map name (driver/features (driver/engine->driver :postgres)))}))))) (do (delete-randomly-created-databases! :skip [db-id]) (set ((user->client :rasta) :get 200 "database")))) @@ -197,121 +202,98 @@ ;; GET /api/databases (include tables) (expect-with-temp-db-created-via-api [{db-id :id}] - (set (cons (match-$ (Database db-id) - {:created_at $ - :engine "postgres" - :id $ - :updated_at $ - :name $ - :native_permissions "write" - :is_sample false - :is_full_sync true - :description nil - :caveats nil - :points_of_interest nil - :tables [] - :features (map name (driver/features (driver/engine->driver :postgres)))}) + (set (cons (merge default-db-details + (match-$ (Database db-id) + {:created_at $ + :engine "postgres" + :id $ + :updated_at $ + :name $ + :native_permissions "write" + :tables [] + :features (map name (driver/features (driver/engine->driver :postgres)))})) (filter identity (for [engine datasets/all-valid-engines] (datasets/when-testing-engine engine (let [database (get-or-create-test-data-db! (driver/engine->driver engine))] - (match-$ database - {:created_at $ - :engine (name $engine) - :id $ - :updated_at $ - :name "test-data" - :native_permissions "write" - :is_sample false - :is_full_sync true - :description nil - :caveats nil - :points_of_interest nil - :tables (sort-by :name (for [table (db/select Table, :db_id (:id database))] - (table-details table))) - :features (map name (driver/features (driver/engine->driver engine)))}))))))) + (merge default-db-details + (match-$ database + {:created_at $ + :engine (name $engine) + :id $ + :updated_at $ + :name "test-data" + :native_permissions "write" + :tables (sort-by :name (for [table (db/select Table, :db_id (:id database))] + (table-details table))) + :features (map name (driver/features (driver/engine->driver engine)))})))))))) (do (delete-randomly-created-databases! :skip [db-id]) (set ((user->client :rasta) :get 200 "database" :include_tables true)))) +(def ^:private default-field-details + {:description nil + :caveats nil + :points_of_interest nil + :active true + :position 0 + :target nil + :preview_display true + :parent_id nil}) + ;; ## GET /api/meta/table/:id/query_metadata ;; TODO - add in example with Field :values (expect - (match-$ (db) - {:created_at $ - :engine "h2" - :id $ - :updated_at $ - :name "test-data" - :is_sample false - :is_full_sync true - :description nil - :caveats nil - :points_of_interest nil - :features (mapv name (driver/features (driver/engine->driver :h2))) - :tables [(match-$ (Table (id :categories)) - {:description nil - :entity_type nil - :caveats nil - :points_of_interest nil - :visibility_type nil - :schema "PUBLIC" - :name "CATEGORIES" - :display_name "Categories" - :fields [(match-$ (hydrate/hydrate (Field (id :categories :id)) :values) - {:description nil - :table_id (id :categories) - :caveats nil - :points_of_interest nil - :special_type "type/PK" - :name "ID" - :display_name "ID" - :updated_at $ - :active true - :id $ - :raw_column_id $ - :position 0 - :target nil - :preview_display true - :created_at $ - :last_analyzed $ - :base_type "type/BigInteger" - :visibility_type "normal" - :fk_target_field_id $ - :parent_id nil - :values $}) - (match-$ (hydrate/hydrate (Field (id :categories :name)) :values) - {:description nil - :table_id (id :categories) - :caveats nil - :points_of_interest nil - :special_type "type/Name" - :name "NAME" - :display_name "Name" - :updated_at $ - :active true - :id $ - :raw_column_id $ - :position 0 - :target nil - :preview_display true - :created_at $ - :last_analyzed $ - :base_type "type/Text" - :visibility_type "normal" - :fk_target_field_id $ - :parent_id nil - :values $})] - :segments [] - :metrics [] - :rows 75 - :updated_at $ - :entity_name nil - :active true - :id (id :categories) - :raw_table_id $ - :db_id (id) - :show_in_getting_started false - :created_at $})]}) + (merge default-db-details + (match-$ (db) + {:created_at $ + :engine "h2" + :id $ + :updated_at $ + :name "test-data" + :features (mapv name (driver/features (driver/engine->driver :h2))) + :tables [(merge default-table-details + (match-$ (Table (id :categories)) + {:schema "PUBLIC" + :name "CATEGORIES" + :display_name "Categories" + :fields [(merge default-field-details + (match-$ (hydrate/hydrate (Field (id :categories :id)) :values) + {:table_id (id :categories) + :special_type "type/PK" + :name "ID" + :display_name "ID" + :updated_at $ + :id $ + :raw_column_id $ + :created_at $ + :last_analyzed $ + :base_type "type/BigInteger" + :visibility_type "normal" + :fk_target_field_id $ + :values $})) + (merge default-field-details + (match-$ (hydrate/hydrate (Field (id :categories :name)) :values) + {:table_id (id :categories) + :special_type "type/Name" + :name "NAME" + :display_name "Name" + :updated_at $ + :id $ + :raw_column_id $ + :created_at $ + :last_analyzed $ + :base_type "type/Text" + :visibility_type "normal" + :fk_target_field_id $ + :values $}))] + :segments [] + :metrics [] + :rows 75 + :updated_at $ + :id (id :categories) + :raw_table_id $ + :db_id (id) + :created_at $}))]})) (let [resp ((user->client :rasta) :get 200 (format "database/%d/metadata" (id)))] (assoc resp :tables (filter #(= "CATEGORIES" (:name %)) (:tables resp))))) diff --git a/test/metabase/events/activity_feed_test.clj b/test/metabase/events/activity_feed_test.clj index e7e41c27d6e713f3c8b5cccf4f00bc119cfa76ad..9f243b06e06031e809a01949631846f3f3cb669f 100644 --- a/test/metabase/events/activity_feed_test.clj +++ b/test/metabase/events/activity_feed_test.clj @@ -26,83 +26,84 @@ ;; `:card-create` event -(tt/expect-with-temp [Card [card {:name "My Cool Card"}]] +(expect {:topic :card-create :user_id (user->id :rasta) :model "card" - :model_id (:id card) :database_id nil :table_id nil :details {:name "My Cool Card", :description nil}} - (with-temp-activities - (process-activity-event! {:topic :card-create, :item card}) - (db/select-one [Activity :topic :user_id :model :model_id :database_id :table_id :details] - :topic "card-create" - :model_id (:id card)))) + (tt/with-temp Card [card {:name "My Cool Card"}] + (with-temp-activities + (process-activity-event! {:topic :card-create, :item card}) + (db/select-one [Activity :topic :user_id :model :database_id :table_id :details] + :topic "card-create" + :model_id (:id card))))) + ;; `:card-update` event -(tt/expect-with-temp [Card [card {:name "My Cool Card"}]] +(expect {:topic :card-update :user_id (user->id :rasta) :model "card" - :model_id (:id card) :database_id nil :table_id nil :details {:name "My Cool Card", :description nil}} - (with-temp-activities - (process-activity-event! {:topic :card-update, :item card}) - (db/select-one [Activity :topic :user_id :model :model_id :database_id :table_id :details] - :topic "card-update" - :model_id (:id card)))) + (tt/with-temp Card [card {:name "My Cool Card"}] + (with-temp-activities + (process-activity-event! {:topic :card-update, :item card}) + (db/select-one [Activity :topic :user_id :model :database_id :table_id :details] + :topic "card-update" + :model_id (:id card))))) ;; `:card-delete` event -(tt/expect-with-temp [Card [card {:name "My Cool Card"}]] +(expect {:topic :card-delete :user_id (user->id :rasta) :model "card" - :model_id (:id card) :database_id nil :table_id nil :details {:name "My Cool Card", :description nil}} - (with-temp-activities - (process-activity-event! {:topic :card-delete, :item card}) - (db/select-one [Activity :topic :user_id :model :model_id :database_id :table_id :details] - :topic "card-delete" - :model_id (:id card)))) + (tt/with-temp Card [card {:name "My Cool Card"}] + (with-temp-activities + (process-activity-event! {:topic :card-delete, :item card}) + (db/select-one [Activity :topic :user_id :model :database_id :table_id :details] + :topic "card-delete" + :model_id (:id card))))) ;; `:dashboard-create` event -(tt/expect-with-temp [Dashboard [dashboard {:name "My Cool Dashboard"}]] +(expect {:topic :dashboard-create :user_id (user->id :rasta) :model "dashboard" - :model_id (:id dashboard) :database_id nil :table_id nil :details {:name "My Cool Dashboard", :description nil}} - (with-temp-activities - (process-activity-event! {:topic :dashboard-create, :item dashboard}) - (db/select-one [Activity :topic :user_id :model :model_id :database_id :table_id :details] - :topic "dashboard-create" - :model_id (:id dashboard)))) + (tt/with-temp Dashboard [dashboard {:name "My Cool Dashboard"}] + (with-temp-activities + (process-activity-event! {:topic :dashboard-create, :item dashboard}) + (db/select-one [Activity :topic :user_id :model :database_id :table_id :details] + :topic "dashboard-create" + :model_id (:id dashboard))))) ;; `:dashboard-delete` event -(tt/expect-with-temp [Dashboard [dashboard {:name "My Cool Dashboard"}]] +(expect {:topic :dashboard-delete :user_id (user->id :rasta) :model "dashboard" - :model_id (:id dashboard) :database_id nil :table_id nil :details {:name "My Cool Dashboard", :description nil}} - (with-temp-activities - (process-activity-event! {:topic :dashboard-delete, :item dashboard}) - (db/select-one [Activity :topic :user_id :model :model_id :database_id :table_id :details] - :topic "dashboard-delete" - :model_id (:id dashboard)))) + (tt/with-temp Dashboard [dashboard {:name "My Cool Dashboard"}] + (with-temp-activities + (process-activity-event! {:topic :dashboard-delete, :item dashboard}) + (db/select-one [Activity :topic :user_id :model :database_id :table_id :details] + :topic "dashboard-delete" + :model_id (:id dashboard))))) ;; `:dashboard-add-cards` event diff --git a/test/metabase/query_processor_test/middleware/limit_test.clj b/test/metabase/query_processor/middleware/limit_test.clj similarity index 96% rename from test/metabase/query_processor_test/middleware/limit_test.clj rename to test/metabase/query_processor/middleware/limit_test.clj index dcfe3586e1a1e40c7bf3f634484d15d61d85ed85..a17c4c583c2806077f19584648252dfb3a62ba7d 100644 --- a/test/metabase/query_processor_test/middleware/limit_test.clj +++ b/test/metabase/query_processor/middleware/limit_test.clj @@ -1,4 +1,4 @@ -(ns metabase.query-processor-test.middleware.limit-test +(ns metabase.query-processor.middleware.limit-test "Tests for the `:limit` clause and `:max-results` constraints." (:require [expectations :refer :all] [metabase.query-processor.interface :as i] diff --git a/test/metabase/test/data/bigquery.clj b/test/metabase/test/data/bigquery.clj index cc4b74df7fba36992e6eae701a6b8d874def6ed0..6c9a831e48f7433675b12b30aacd4acfb90cc038 100644 --- a/test/metabase/test/data/bigquery.clj +++ b/test/metabase/test/data/bigquery.clj @@ -147,7 +147,7 @@ (for [[i row] (m/indexed rows)] (assoc (zipmap field-names (for [v row] (u/prog1 (if (instance? java.util.Date v) - (DateTime. v) ; convert to Google version of DateTime, otherwise it doesn't work (!) + (DateTime. ^java.util.Date v) ; convert to Google version of DateTime, otherwise it doesn't work (!) v) (assert (not (nil? <>)))))) ; make sure v is non-nil :id (inc i)))))