From 122fb292de8116a0c0ec9c83519c5994463874a7 Mon Sep 17 00:00:00 2001 From: Case Nelson <case@metabase.com> Date: Tue, 30 Aug 2022 09:34:56 -0600 Subject: [PATCH] [Apps] Hydrate action_id on is_write Card (#25075) * [Apps] Hydrate action_id on is_write Card It'll be helpful for the frontend if we send back the created action_id when saving an `is_write` Card. Also update the `api/card` tests to ensure that a QueryAction (and by fk constraints the Action) is created/deleted as expected when changing `is_write`. * Add extra arg to more result-fns --- src/metabase/api/card.clj | 6 +++-- src/metabase/models/action.clj | 13 ++++++++++ test/metabase/api/card_test.clj | 43 +++++++++++++++++++++------------ 3 files changed, 45 insertions(+), 17 deletions(-) diff --git a/src/metabase/api/card.clj b/src/metabase/api/card.clj index f7a60afab6c..43f27409dd3 100644 --- a/src/metabase/api/card.clj +++ b/src/metabase/api/card.clj @@ -163,7 +163,7 @@ (cond-> ;; card api/*is-superuser?* (hydrate [:emitters [:action :card]]) (:dataset raw-card) (hydrate :persisted) - (:is_write raw-card) (hydrate :card/emitter-usages)) + (:is_write raw-card) (hydrate :card/emitter-usages :card/action-id)) api/read-check (last-edit/with-last-edit-info :card))] (u/prog1 card @@ -361,6 +361,8 @@ saved later when it is ready." :average_query_time :last_query_start :collection [:moderation_reviews :moderator_details]) + (cond-> ;; card + (:is_write card) (hydrate :card/action-id)) (assoc :last-edit-info (last-edit/edit-information-for-user @api/*current-user*))) (when timed-out? (schedule-metadata-saving result-metadata-chan <>))))) @@ -596,7 +598,7 @@ saved later when it is ready." :collection [:moderation_reviews :moderator_details]) (cond-> ;; card (:dataset card) (hydrate :persisted) - (:is_write card) (hydrate :card/emitter-usages)) + (:is_write card) (hydrate :card/emitter-usages :card/action-id)) (assoc :last-edit-info (last-edit/edit-information-for-user @api/*current-user*))))) (api/defendpoint PUT "/:id" diff --git a/src/metabase/models/action.clj b/src/metabase/models/action.clj index 96e0e3a2005..84a59413932 100644 --- a/src/metabase/models/action.clj +++ b/src/metabase/models/action.clj @@ -138,3 +138,16 @@ (for [{emitter-id :id, :as emitter} emitters] (some-> emitter (assoc :action (get actions-by-id (get action-id-by-emitter-id emitter-id)))))) emitters)) + +(defn cards-by-action-id + "Hydrates action_id from Card for is_write cards" + {:batched-hydrate :card/action-id} + [cards] + (if-let [card-id->action-id (not-empty (db/select-field->field + :card_id :action_id + 'QueryAction + :card_id [:in (map :id cards)]))] + + (for [card cards] + (m/assoc-some card :action_id (get card-id->action-id (:id card)))) + cards)) diff --git a/test/metabase/api/card_test.clj b/test/metabase/api/card_test.clj index 696fe70a6f1..6b2bf7f02fd 100644 --- a/test/metabase/api/card_test.clj +++ b/test/metabase/api/card_test.clj @@ -2260,7 +2260,7 @@ (defmacro ^:private with-actions-enabled {:style/indent 0} [& body] `(do-with-actions-enabled (fn [] ~@body))) -(defn- test-update-is-write-card [{:keys [user query status-code before-fn result-fn] +(defn- test-update-is-write-card [{:keys [user query status-code before-fn result-fn get-fn] :or {user :crowberto query (mt/native-query {:query "UPDATE whatever SET whatever = {{whatever}};"})}}] (testing "PUT /api/card/:id" @@ -2274,7 +2274,7 @@ (when before-fn (before-fn (db/select-one Card :id card-id))) (let [result (mt/user-http-request user :put status-code (str "card/" card-id) {:is_write new-value})] - (result-fn result)) + (result-fn result new-value)) (let [fail? (>= status-code 400) expected-value (if fail? initial-value @@ -2283,8 +2283,11 @@ (is (= expected-value (db/select-one-field :is_write Card :id card-id)))) (testing "GET /api/card/:id value" - (is (partial= {:is_write expected-value} - (mt/user-http-request :crowberto :get 200 (str "card/" card-id))))) + (let [get-result (mt/user-http-request :crowberto :get 200 (str "card/" card-id))] + (is (partial= {:is_write expected-value} + get-result)) + (when get-fn + (get-fn get-result expected-value)))) (when fail? (testing "\nNo-op update should be allowed." (is (some? (mt/user-http-request user :put 200 (str "card/" card-id) {:is_write initial-value}))))))))))) @@ -2297,7 +2300,7 @@ (let [result (mt/user-http-request user :post status-code "card" (merge (mt/with-temp-defaults Card) {:is_write true :dataset_query query}))] - (result-fn result) + (result-fn result true) (when (map? result) (when-let [card-id (:id result)] (let [fail? (>= status-code 400)] @@ -2313,7 +2316,7 @@ (doseq [f [test-update-is-write-card test-create-is-write-card]] (f {:status-code 400 - :result-fn (fn [result] + :result-fn (fn [result _] (is (= {:errors {:is_write "Cannot mark Saved Question as 'is_write': Actions are not enabled."}} result)))}))))) @@ -2326,7 +2329,7 @@ (doseq [f [test-update-is-write-card test-create-is-write-card]] (f {:status-code 400 - :result-fn (fn [result] + :result-fn (fn [result _] (is (schema= {:errors {:is_write #"Cannot mark Saved Question as 'is_write': Actions are not enabled for Database [\d,]+\."}} result)))}))))) @@ -2342,7 +2345,7 @@ (doseq [f [test-update-is-write-card test-create-is-write-card]] (f {:status-code 400 - :result-fn (fn [result] + :result-fn (fn [result _] (is (schema= {:errors {:is_write #"Cannot mark Saved Question as 'is_write': Actions are not enabled for Database [\d,]+\."}} result)))}))))) @@ -2352,7 +2355,7 @@ {:before-fn (fn [{card-id :id}] (db/update! Card card-id :dataset true)) :status-code 400 - :result-fn (fn [result] + :result-fn (fn [result _] (is (= {:errors {:is_write "Cannot mark Saved Question as 'is_write': Saved Question is a Dataset."}} result)))}))) @@ -2362,7 +2365,7 @@ test-create-is-write-card]] (f {:status-code 403 :user :rasta - :result-fn (fn [result] + :result-fn (fn [result _] (is (= "You don't have permissions to do that." result)))})))) @@ -2372,7 +2375,7 @@ test-create-is-write-card]] (f {:status-code 400 :query (mt/mbql-query venues) - :result-fn (fn [result] + :result-fn (fn [result _] (is (schema= {:errors {:is_write #"Cannot mark Saved Question as 'is_write': Query must be a native query."}} result)))})))) @@ -2380,11 +2383,21 @@ (with-actions-enabled (doseq [f [test-update-is-write-card test-create-is-write-card]] - ;; TODO -- Setting `is_write` also needs to create the `Action` and `QueryAction`. Unsetting should delete those - ;; rows. Add tests for these once that code is in place. (f {:status-code 200 - :result-fn (fn [result] - (is (map? result)))})))) + :get-fn (fn [result is-write] + (if is-write + (is (contains? result :action_id)) + (is (not (contains? result :action_id))))) + :result-fn (fn [result is-write] + (if is-write + (do + (is (contains? result :action_id)) + (is (some? (db/select-one 'QueryAction :card_id (:id result))))) + (do + (is (not (contains? result :action_id))) + (is (nil? (db/select-one 'QueryAction :card_id (:id result)))))) + (is (map? result)))})))) + (defn- do-with-persistence-setup [f] ;; mt/with-temp-scheduler actually just reuses the current scheduler. The scheduler factory caches by name set in ;; the resources/quartz.properties file and we reuse that scheduler -- GitLab