diff --git a/src/metabase/api/card.clj b/src/metabase/api/card.clj index be1e49794b1241bb5b18056f2104a5e201467952..559e23477d580b6d133c9727d45a813284285322 100644 --- a/src/metabase/api/card.clj +++ b/src/metabase/api/card.clj @@ -31,8 +31,7 @@ [metabase.models.interface :as mi] [metabase.models.moderation-review :as moderation-review] [metabase.models.params :as params] - [metabase.models.params.card-values :as params.card-values] - [metabase.models.params.static-values :as params.static-values] + [metabase.models.params.custom-values :as custom-values] [metabase.models.persisted-info :as persisted-info] [metabase.models.pulse :as pulse] [metabase.models.query :as query] @@ -944,15 +943,7 @@ saved later when it is ready." (when-not param (throw (ex-info (tru "Card does not have a parameter with the ID {0}" (pr-str param-key)) {:status-code 400}))) - (case source-type - "static-list" (params.static-values/param->values param query) - "card" (do - (api/read-check Card (get-in param [:values_source_config :card_id])) - (params.card-values/param->values param query)) - nil (mapping->field-values card param query) - (throw (ex-info (tru "Invalid values-source-type: {0}" (pr-str source-type)) - {:values-source-type source-type - :status-code 400})))))) + (custom-values/parameter->values param query (fn [] (mapping->field-values card param query)))))) (api/defendpoint GET "/:card-id/params/:param-key/values" "Fetch possible values of the parameter whose ID is `:param-key`. @@ -962,8 +953,7 @@ saved later when it is ready." [card-id param-key] {card-id ms/IntGreaterThanZero param-key ms/NonBlankString} - (let [card (api/read-check Card card-id)] - (param-values card param-key))) + (param-values (api/read-check Card card-id) param-key)) (api/defendpoint GET "/:card-id/params/:param-key/search/:query" "Fetch possible values of the parameter whose ID is `:param-key` that contain `:query`. @@ -976,7 +966,6 @@ saved later when it is ready." {card-id ms/IntGreaterThanZero param-key ms/NonBlankString query ms/NonBlankString} - (let [card (api/read-check Card card-id)] - (param-values card param-key query))) + (param-values (api/read-check Card card-id) param-key query)) (api/define-routes) diff --git a/src/metabase/api/dashboard.clj b/src/metabase/api/dashboard.clj index a321fdf02bac4dfa7f893501596ed4153e93a4d5..9235071d5512a470d0e1dae1413ac6e8dcf9753c 100644 --- a/src/metabase/api/dashboard.clj +++ b/src/metabase/api/dashboard.clj @@ -21,9 +21,8 @@ [metabase.models.field :refer [Field]] [metabase.models.interface :as mi] [metabase.models.params :as params] - [metabase.models.params.card-values :as params.card-values] [metabase.models.params.chain-filter :as chain-filter] - [metabase.models.params.static-values :as params.static-values] + [metabase.models.params.custom-values :as custom-values] [metabase.models.query :as query :refer [Query]] [metabase.models.query.permissions :as query-perms] [metabase.models.revision :as revision] @@ -770,12 +769,7 @@ (throw (ex-info (tru "Dashboard does not have a parameter with the ID {0}" (pr-str param-key)) {:resolved-params (keys (:resolved-params dashboard)) :status-code 400}))) - (case (:values_source_type param) - "static-list" (params.static-values/param->values param query) - "card" (do - (api/read-check Card (get-in param [:values_source_config :card_id])) - (params.card-values/param->values param query)) - nil (chain-filter dashboard param-key constraint-param-key->value query))))) + (custom-values/parameter->values param query (fn [] (chain-filter dashboard param-key constraint-param-key->value query)))))) #_{:clj-kondo/ignore [:deprecated-var]} (api/defendpoint-schema GET "/:id/params/:param-key/values" diff --git a/src/metabase/api/dataset.clj b/src/metabase/api/dataset.clj index 0a53da464e89bfdf292f0102dd7588977f562657..451076c9535e7e651f3152a0dbd8bca69daded1a 100644 --- a/src/metabase/api/dataset.clj +++ b/src/metabase/api/dataset.clj @@ -11,8 +11,7 @@ [metabase.mbql.schema :as mbql.s] [metabase.models.card :refer [Card]] [metabase.models.database :as database :refer [Database]] - [metabase.models.params.card-values :as params.card-values] - [metabase.models.params.static-values :as params.static-values] + [metabase.models.params.custom-values :as custom-values] [metabase.models.persisted-info :as persisted-info] [metabase.models.query :as query] [metabase.models.table :refer [Table]] @@ -179,6 +178,9 @@ (defn- parameter-field-values [field-ids query] + (when-not (seq field-ids) + (throw (ex-info (tru "Missing field-ids for parameter") + {:status-code 400}))) (-> (reduce (fn [resp id] (let [{values :values more? :has_more_values} (api.field/field-id->values id query)] (-> resp @@ -194,25 +196,16 @@ "Fetch parameter values. Parameter should be a full parameter, field-ids is an optional vector of field ids, only consulted if `:values_source_type` is nil. Query is an optional string return matching field values not all." [parameter field-ids query] - (case (:values_source_type parameter) - "static-list" (params.static-values/param->values parameter query) - "card" (params.card-values/param->values parameter query) - nil (if (seq field-ids) - (parameter-field-values field-ids query) - (throw (ex-info (tru "Missing field-ids for parameter") - {:status-code 400 - :parameter parameter}))) - (throw (ex-info (tru "Invalid parameter source {0}" (:values_source_type parameter)) - {:status-code 400 - :parameter parameter})))) + (custom-values/parameter->values + parameter query + (fn [] (parameter-field-values field-ids query)))) (api/defendpoint POST "/parameter/values" "Return parameter values for cards or dashboards that are being edited." [:as {{:keys [parameter field_ids]} :body}] {parameter ms/Parameter field_ids [:maybe [:sequential ms/IntGreaterThanZero]]} - (let [nil-query nil] - (parameter-values parameter field_ids nil-query))) + (parameter-values parameter field_ids nil)) (api/defendpoint POST "/parameter/search/:query" "Return parameter values for cards or dashboards that are being edited. Expects a query string at `?query=foo`." diff --git a/src/metabase/models/params.clj b/src/metabase/models/params.clj index 5404be959ece326e39adad5a64e3aa303c49a903..70078e9f19c24fcbcb244a0350e251a16d6f6f66 100644 --- a/src/metabase/models/params.clj +++ b/src/metabase/models/params.clj @@ -1,5 +1,15 @@ (ns metabase.models.params - "Utility functions for dealing with parameters for Dashboards and Cards." + "Utility functions for dealing with parameters for Dashboards and Cards. + + Parameter are objects that exists on Dashboard/Card. In FE terms, we call it \"Widget\". + The values of a parameter is provided so the Widget can show a list of options to the user. + + + There are 3 mains ways to provide values to a parameter: + - chain-filter: see [metabase.models.params.chain-filter] + - field-values: see [metabase.models.params.field-values] + - custom-values: see [metabase.models.params.custom-values] + " (:require [clojure.set :as set] [medley.core :as m] diff --git a/src/metabase/models/params/card_values.clj b/src/metabase/models/params/card_values.clj deleted file mode 100644 index f39a17e0edd52217c9cdbcb10a9799357b84e526..0000000000000000000000000000000000000000 --- a/src/metabase/models/params/card_values.clj +++ /dev/null @@ -1,108 +0,0 @@ -(ns metabase.models.params.card-values - "Code related to getting values for a parameter where its source values is a card." - (:require - [metabase.mbql.normalize :as mbql.normalize] - [metabase.models :refer [Card]] - [metabase.query-processor :as qp] - [metabase.util :as u] - [metabase.util.i18n :refer [tru]] - [metabase.util.malli :as mu] - [metabase.util.malli.schema :as ms] - [toucan.db :as db])) - -(def ^:dynamic *max-rows* - "Maximum number of rows returned when running a card. - It's 1000 because it matches with the limit for chain-filter. - Maybe we should lower it for the sake of displaying a parameter dropdown." - 1000) - -(def field-options-for-identification - "Set of FieldOptions that only mattered for identification purposes." ;; base-type is required for field that use name instead of id - #{:source-field :join-alias :base-type}) - -(defn- field-normalizer - [field] - (let [[type id-or-name options ] (mbql.normalize/normalize-tokens field)] - [type id-or-name (select-keys options field-options-for-identification)])) - -(defn- field->field-info - [field result-metadata] - (let [[_ttype id-or-name options :as field] (field-normalizer field)] - (or - ;; try match field_ref first - (first (filter (fn [field-info] - (= field - (-> field-info - :field_ref - field-normalizer))) - result-metadata)) - ;; if not match name and base type for aggregation or field with string id - (first (filter (fn [field-info] - (and (= (:name field-info) - id-or-name) - (= (:base-type options) - (:base-type field-info)))) - result-metadata))))) - -(defn- values-from-card-query - [card value-field query] - (let [value-base-type (:base_type (field->field-info value-field (:result_metadata card)))] - {:database (:database_id card) - :type :query - :query (merge - {:source-table (format "card__%d" (:id card)) - :breakout [value-field] - :limit *max-rows*} - {:filter [:and - [(if-not (isa? value-base-type :type/Text) - :not-null - :not-empty) - value-field] - (when query - (if-not (isa? value-base-type :type/Text) - [:= value-field query] - [:contains [:lower value-field] (u/lower-case-en query)]))]}) - :middleware {:disable-remaps? true}})) - -(mu/defn values-from-card - "Get distinct values of a field from a card. - - (values-from-card 1 [:field \"name\" nil] \"red\") - ;; will execute a mbql that looks like - ;; {:source-table (format \"card__%d\" card-id) - ;; :fields [value-field] - ;; :breakout [value-field] - ;; :filter [:contains [:lower value-field] \"red\"] - ;; :limit *max-rows*} - => - {:values [\"Red Medicine\"] - :has_more_values false} - " - ([card value-field] - (values-from-card card value-field nil)) - - ([card :- (ms/InstanceOf Card) - value-field :- ms/Field - query :- [:any]] - (let [mbql-query (values-from-card-query card value-field query) - result (qp/process-query mbql-query) - values (map first (get-in result [:data :rows]))] - {:values values - ;; if the row_count returned = the limit we specified, then it's probably has more than that - :has_more_values (= (:row_count result) - (get-in mbql-query [:query :limit]))}))) - -(defn param->values - "Given a param and query returns the values." - [{config :values_source_config :as _param} query] - (let [card-id (:card_id config) - card (db/select-one Card :id card-id)] - (when-not card - (throw (ex-info (tru "Source card not found") - {:card-id card-id - :status-code 400}))) - (when (:archived card) - (throw (ex-info (tru "Source card is archived") - {:card-id card-id - :status-code 400}))) - (values-from-card card (:value_field config) query))) diff --git a/src/metabase/models/params/custom_values.clj b/src/metabase/models/params/custom_values.clj new file mode 100644 index 0000000000000000000000000000000000000000..ccd3c6470bc3f9d5f69af66e22a0fca25c8fea9f --- /dev/null +++ b/src/metabase/models/params/custom_values.clj @@ -0,0 +1,161 @@ +(ns metabase.models.params.custom-values + "Custom values for Parameters. + + A parameter with custom values will need to define a source: + - static-list: the values is pre-defined and stored inside parameter's config + - card: the values is a column from a saved question + " + (:require + [clojure.string :as str] + [metabase.mbql.normalize :as mbql.normalize] + [metabase.models.card :refer [Card]] + [metabase.models.interface :as mi] + [metabase.query-processor :as qp] + [metabase.search.util :as search] + [metabase.util :as u] + [metabase.util.i18n :refer [tru]] + [metabase.util.malli :as mu] + [metabase.util.malli.schema :as ms] + [toucan.db :as db])) + +;;; ------------------------------------------------- source=static-list -------------------------------------------------- +(defn- query-matches + "Filter the values according to the `search-term`. + + Values could have 2 shapes + - [value1, value2] + - [[value1, label1], [value2, label2]] - we search using label in this case" + [query values] + (let [normalized-query (search/normalize query)] + (filter #(str/includes? (search/normalize (if (string? %) + % + ;; search by label + (second %))) + normalized-query) values))) + +(defn- static-list-values + [{values-source-options :values_source_config :as _param} query] + (when-let [values (:values values-source-options)] + {:values (if query + (query-matches query values) + values) + :has_more_values false})) + +;;; ---------------------------------------------------- source=card ------------------------------------------------------ + +(def ^:dynamic *max-rows* + "Maximum number of rows returned when running a card. + It's 1000 because it matches with the limit for chain-filter. + Maybe we should lower it for the sake of displaying a parameter dropdown." + 1000) + +(def field-options-for-identification + "Set of FieldOptions that only mattered for identification purposes." ;; base-type is required for field that use name instead of id + #{:source-field :join-alias :base-type}) + +(defn- field-normalizer + [field] + (let [[type id-or-name options ] (mbql.normalize/normalize-tokens field)] + [type id-or-name (select-keys options field-options-for-identification)])) + +(defn- field->field-info + [field result-metadata] + (let [[_ttype id-or-name options :as field] (field-normalizer field)] + (or + ;; try match field_ref first + (first (filter (fn [field-info] + (= field + (-> field-info + :field_ref + field-normalizer))) + result-metadata)) + ;; if not match name and base type for aggregation or field with string id + (first (filter (fn [field-info] + (and (= (:name field-info) + id-or-name) + (= (:base-type options) + (:base_type field-info)))) + result-metadata))))) + +(defn- values-from-card-query + [card value-field query] + (let [value-base-type (:base_type (field->field-info value-field (:result_metadata card)))] + {:database (:database_id card) + :type :query + :query (merge + {:source-table (format "card__%d" (:id card)) + :breakout [value-field] + :limit *max-rows*} + {:filter [:and + [(if (isa? value-base-type :type/Text) + :not-empty + :not-null) + value-field] + (when query + (if-not (isa? value-base-type :type/Text) + [:= value-field query] + [:contains [:lower value-field] (u/lower-case-en query)]))]}) + :middleware {:disable-remaps? true}})) + +(mu/defn values-from-card + "Get distinct values of a field from a card. + + (values-from-card 1 [:field \"name\" nil] \"red\") + ;; will execute a mbql that looks like + ;; {:source-table (format \"card__%d\" card-id) + ;; :fields [value-field] + ;; :breakout [value-field] + ;; :filter [:contains [:lower value-field] \"red\"] + ;; :limit *max-rows*} + => + {:values [\"Red Medicine\"] + :has_more_values false} + " + ([card value-field] + (values-from-card card value-field nil)) + + ([card :- (ms/InstanceOf Card) + value-field :- ms/Field + query :- [:any]] + (let [mbql-query (values-from-card-query card value-field query) + result (qp/process-query mbql-query) + values (map first (get-in result [:data :rows]))] + {:values values + ;; if the row_count returned = the limit we specified, then it's probably has more than that + :has_more_values (= (:row_count result) + (get-in mbql-query [:query :limit]))}))) + +(defn card-values + "Given a param and query returns the values." + [{config :values_source_config :as _param} query] + (let [card-id (:card_id config) + card (db/select-one Card :id card-id)] + (values-from-card card (:value_field config) query))) + +(defn- can-get-card-values? + [card value-field] + (boolean + (and (not (:archived card)) + (some? (field->field-info value-field (:result_metadata card)))))) + +;;; --------------------------------------------- Putting it together ---------------------------------------------- + +(defn parameter->values + "Given a parameter with a custom-values source, return the values. + + `default-case-fn` is a 0-arity function that returns values list when: + - :values_source_type = card but the card is archived or the card no longer contains the value-field. + - :values_source_type = nil." + [parameter query default-case-fn] + (case (:values_source_type parameter) + "static-list" (static-list-values parameter query) + "card" (let [card (db/select-one Card :id (get-in parameter [:values_source_config :card_id]))] + (when-not (mi/can-read? card) + (throw (ex-info "You don't have permissions to do that." {:status-code 403}))) + (if (can-get-card-values? card (get-in parameter [:values_source_config :value_field])) + (card-values parameter query) + (default-case-fn))) + nil (default-case-fn) + (throw (ex-info (tru "Invalid parameter source {0}" (:values_source_type parameter)) + {:status-code 400 + :parameter parameter})))) diff --git a/src/metabase/models/params/static_values.clj b/src/metabase/models/params/static_values.clj deleted file mode 100644 index a172868ae2fcd1c6052e094f8d7e3d5fea751848..0000000000000000000000000000000000000000 --- a/src/metabase/models/params/static_values.clj +++ /dev/null @@ -1,28 +0,0 @@ -(ns metabase.models.params.static-values - "Code related to getting values for a parameter where its source values is a card." - (:require - [clojure.string :as str] - [metabase.search.util :as search])) - -(defn- query-matches - "Filter the values according to the `search-term`. - - Values could have 2 shapes - - [value1, value2] - - [[value1, label1], [value2, label2]] - we search using label in this case" - [query values] - (let [normalized-query (search/normalize query)] - (filter #(str/includes? (search/normalize (if (string? %) - % - ;; search by label - (second %))) - normalized-query) values))) - -(defn param->values - "Given a param return the values" - [{values-source-options :values_source_config :as _param} query] - (when-let [values (:values values-source-options)] - {:values (if query - (query-matches query values) - values) - :has_more_values false})) diff --git a/test/metabase/api/card_test.clj b/test/metabase/api/card_test.clj index 38252d9a10c68635e1438cbcecaa10348398538d..1867c31cfee2134fb105f0e85dff5297f7276bea 100644 --- a/test/metabase/api/card_test.clj +++ b/test/metabase/api/card_test.clj @@ -2396,6 +2396,32 @@ :has_more_values false} (mt/user-http-request :rasta :get 200 (param-values-url card (:card param-keys) "red"))))))) + (testing "fallback to field-values" + (with-redefs [api.card/mapping->field-values (constantly "field-values")] + (testing "if value-field not found in source card" + (mt/with-temp* [Card [{source-card-id :id}] + Card [card + {:parameters [{:id "abc" + :type "category" + :name "CATEGORY" + :values_source_type "card" + :values_source_config {:card_id source-card-id + :value_field (mt/$ids $venues.name)}}]}]] + (let [url (param-values-url card "abc")] + (is (= "field-values" (mt/user-http-request :rasta :get 200 url)))))) + + (testing "if card is archived" + (mt/with-temp* [Card [{source-card-id :id} {:archived true}] + Card [card + {:parameters [{:id "abc" + :type "category" + :name "CATEGORY" + :values_source_type "card" + :values_source_config {:card_id source-card-id + :value_field (mt/$ids $venues.name)}}]}]] + (let [url (param-values-url card "abc")] + (is (= "field-values" (mt/user-http-request :rasta :get 200 url)))))))) + (testing "users must have permissions to read the collection that source card is in" (mt/with-non-admin-groups-no-root-collection-perms (mt/with-temp* diff --git a/test/metabase/api/dashboard_test.clj b/test/metabase/api/dashboard_test.clj index 46bdcdba278ca90f555c05d8f08b5ef9c05da938..7925f441114c2639f2f0c28485f65e87b6225c22 100644 --- a/test/metabase/api/dashboard_test.clj +++ b/test/metabase/api/dashboard_test.clj @@ -2149,6 +2149,32 @@ :has_more_values false} (mt/user-http-request :rasta :get 200 url))))))) + (testing "fallback to chain-filter" + (with-redefs [api.dashboard/chain-filter (constantly "chain-filter")] + (testing "if value-field not found in source card" + (mt/with-temp* [Card [{card-id :id}] + Dashboard [dashboard + {:parameters [{:id "abc" + :type "category" + :name "CATEGORY" + :values_source_type "card" + :values_source_config {:card_id card-id + :value_field (mt/$ids $venues.name)}}]}]] + (let-url [url (chain-filter-values-url dashboard "abc")] + (is (= "chain-filter" (mt/user-http-request :rasta :get 200 url)))))) + + (testing "if card is archived" + (mt/with-temp* [Card [{card-id :id} {:archived true}] + Dashboard [dashboard + {:parameters [{:id "abc" + :type "category" + :name "CATEGORY" + :values_source_type "card" + :values_source_config {:card_id card-id + :value_field (mt/$ids $venues.name)}}]}]] + (let-url [url (chain-filter-values-url dashboard "abc")] + (is (= "chain-filter" (mt/user-http-request :rasta :get 200 url)))))))) + (testing "users must have permissions to read the collection that source card is in" (mt/with-non-admin-groups-no-root-collection-perms (mt/with-temp* @@ -2159,14 +2185,14 @@ :table_id (mt/id :venues) :dataset_query (mt/mbql-query venues {:limit 5})}] Collection [coll2 {:name "Dashboard collections"}] - Dashboard [{dashboard-id :id} - {:collection_id (:id coll2) - :parameters [{:id "abc" - :type "category" - :name "CATEGORY" - :values_source_type "card" - :values_source_config {:card_id source-card-id - :value_field (mt/$ids $venues.name)}}]}]] + Dashboard [{dashboard-id :id} + {:collection_id (:id coll2) + :parameters [{:id "abc" + :type "category" + :name "CATEGORY" + :values_source_type "card" + :values_source_config {:card_id source-card-id + :value_field (mt/$ids $venues.name)}}]}]] (testing "Fail because user doesn't have read permissions to coll1" (is (=? "You don't have permissions to do that." (mt/user-http-request :rasta :get 403 (chain-filter-values-url dashboard-id "abc")))) diff --git a/test/metabase/api/dataset_test.clj b/test/metabase/api/dataset_test.clj index 71b652120761f2871f167ca91c2b244576d995a1..3061d00b7fe6a22eff3eb201c7375a12fa3fcaa3 100644 --- a/test/metabase/api/dataset_test.clj +++ b/test/metabase/api/dataset_test.clj @@ -9,6 +9,7 @@ [clojure.string :as str] [clojure.test :refer :all] [medley.core :as m] + [metabase.api.dataset :as api.dataset] [metabase.api.pivots :as api.pivots] [metabase.driver :as driver] [metabase.http-client :as client] @@ -444,6 +445,7 @@ {:parameter parameter}) :values set)] (is (= #{"Gizmo" "Widget" "Gadget"} values))))))) + (testing "nil value (current behavior of field values)" (let [parameter {:values_query_type "list", :values_source_type nil, @@ -480,4 +482,28 @@ :field_ids [(mt/id :people :source) (mt/id :people :source)]}) :values)] - (is (= [["Twitter"] ["Organic"] ["Affiliate"] ["Google"] ["Facebook"]] values)))))))) + (is (= [["Twitter"] ["Organic"] ["Affiliate"] ["Google"] ["Facebook"]] values)))))) + + (testing "fallback to field-values" + (with-redefs [api.dataset/parameter-field-values (constantly "field-values")] + (testing "if value-field not found in source card" + (mt/with-temp Card [{source-card-id :id}] + (is (= "field-values" + (mt/user-http-request :rasta :post 200 "dataset/parameter/values" + {:parameter {:values_source_type "card" + :values_source_config {:card_id source-card-id + :value_field (mt/$ids $people.source)} + :type :string/=, + :name "Text" + :id "abc"}}))))) + + (testing "if value-field not found in source card" + (mt/with-temp Card [{source-card-id :id} {:archived true}] + (is (= "field-values" + (mt/user-http-request :rasta :post 200 "dataset/parameter/values" + {:parameter {:values_source_type "card" + :values_source_config {:card_id source-card-id + :value_field (mt/$ids $people.source)} + :type :string/=, + :name "Text" + :id "abc"}}))))))))) diff --git a/test/metabase/models/params/card_values_test.clj b/test/metabase/models/params/custom_values_test.clj similarity index 68% rename from test/metabase/models/params/card_values_test.clj rename to test/metabase/models/params/custom_values_test.clj index ff0c1954236eca6721f979dbfe8d9e9ee0781d3e..833888676e8314330c3e4069b147be410db8ec02 100644 --- a/test/metabase/models/params/card_values_test.clj +++ b/test/metabase/models/params/custom_values_test.clj @@ -1,15 +1,17 @@ -(ns metabase.models.params.card-values-test +(ns metabase.models.params.custom-values-test (:require [clojure.test :refer :all] - [metabase.models :refer [Card]] - [metabase.models.params.card-values :as params.card-values] + [metabase.models :refer [Card Collection]] + [metabase.models.params.custom-values :as custom-values] [metabase.test :as mt] [toucan.db :as db])) +;;; --------------------------------------------- source=card ---------------------------------------------- + (deftest with-mbql-card-test (doseq [dataset? [true false]] (testing (format "source card is a %s" (if dataset? "model" "question")) - (binding [params.card-values/*max-rows* 3] + (binding [custom-values/*max-rows* 3] (testing "with simple mbql" (mt/with-temp* [Card [{card-id :id} (merge (mt/card-with-source-metadata-for-query (mt/mbql-query venues)) @@ -19,14 +21,14 @@ (testing "get values" (is (=? {:has_more_values true, :values ["20th Century Cafe" "25°" "33 Taps"]} - (params.card-values/values-from-card + (custom-values/values-from-card (db/select-one Card :id card-id) (mt/$ids $venues.name))))) (testing "case in-sensitve search test" (is (=? {:has_more_values false :values ["Liguria Bakery" "Noe Valley Bakery"]} - (params.card-values/values-from-card + (custom-values/values-from-card (db/select-one Card :id card-id) (mt/$ids $venues.name) "bakery")))))) @@ -44,21 +46,21 @@ (testing "get values from breakout columns" (is (=? {:has_more_values true, :values ["American" "Artisan" "Asian"]} - (params.card-values/values-from-card + (custom-values/values-from-card (db/select-one Card :id card-id) (mt/$ids $categories.name))))) (testing "get values from aggregation column" (is (=? {:has_more_values true, :values [1 2 3]} - (params.card-values/values-from-card + (custom-values/values-from-card (db/select-one Card :id card-id) [:field "sum" {:base-type :type/Float}])))) (testing "can search on aggregation column" (is (=? {:has_more_values false, :values [2]} - (params.card-values/values-from-card + (custom-values/values-from-card (db/select-one Card :id card-id) [:field "sum" {:base-type :type/Float}] 2)))) @@ -66,7 +68,7 @@ (testing "doing case in-sensitve search on breakout columns" (is (=? {:has_more_values false :values ["Bakery"]} - (params.card-values/values-from-card + (custom-values/values-from-card (db/select-one Card :id card-id) [:field (mt/id :categories :name) {:source-field (mt/id :venues :category_id)}] "bakery")))))) @@ -84,7 +86,7 @@ (testing "get values returns the value, not remapped values" (is (=? {:has_more_values true, :values [2 3 4]} - (params.card-values/values-from-card + (custom-values/values-from-card (db/select-one Card :id card-id) (mt/$ids $venues.category_id))))) @@ -92,7 +94,7 @@ (testing "search with the value, not remapped values" (is (=? {:has_more_values false, :values [2]} - (params.card-values/values-from-card + (custom-values/values-from-card (db/select-one Card :id card-id) (mt/$ids $venues.category_id) 2))))))))))) @@ -100,7 +102,7 @@ (deftest with-native-card-test (doseq [dataset? [true false]] (testing (format "source card is a %s with native question" (if dataset? "model" "question")) - (binding [params.card-values/*max-rows* 3] + (binding [custom-values/*max-rows* 3] (mt/with-temp* [Card [{card-id :id} (merge (mt/card-with-source-metadata-for-query (mt/native-query {:query "select * from venues where lower(name) like '%red%'"})) @@ -110,7 +112,7 @@ (testing "get values from breakout columns" (is (=? {:has_more_values false, :values ["Fred 62" "Red Medicine"]} - (params.card-values/values-from-card + (custom-values/values-from-card (db/select-one Card :id card-id) [:field "NAME" {:base-type :type/Text}])))) @@ -118,7 +120,7 @@ (testing "doing case in-sensitve search on breakout columns" (is (=? {:has_more_values false :values ["Red Medicine"]} - (params.card-values/values-from-card + (custom-values/values-from-card (db/select-one Card :id card-id) [:field "NAME" {:base-type :type/Text}] "medicine"))))))))) @@ -133,7 +135,7 @@ (testing "get values from breakout columns" (is (=? {:has_more_values false, :values ["Affiliate" "Facebook" "Google" "Organic" "Twitter"]} - (params.card-values/values-from-card + (custom-values/values-from-card (db/select-one Card :id card-id) [:field "SOURCE" {:base-type :type/Text}])))) @@ -141,7 +143,7 @@ (testing "doing case in-sensitve search on breakout columns" (is (=? {:has_more_values false :values ["Facebook" "Google"]} - (params.card-values/values-from-card + (custom-values/values-from-card (db/select-one Card :id card-id) [:field "SOURCE" {:base-type :type/Text}] "oo")))))) @@ -153,7 +155,7 @@ (testing "get values from breakout columns" (is (=? {:has_more_values false, :values ["Affiliate" "Facebook" "Google" "Organic" "Twitter"]} - (params.card-values/values-from-card + (custom-values/values-from-card (db/select-one Card :id card-id) (mt/$ids $people.source))))) @@ -161,36 +163,59 @@ (testing "doing case in-sensitve search on breakout columns" (is (=? {:has_more_values false :values ["Facebook" "Google"]} - (params.card-values/values-from-card + (custom-values/values-from-card (db/select-one Card :id card-id) (mt/$ids $people.source) "oo"))))))))) (deftest errors-test - (testing "error if source card does not exist" - (is (thrown-with-msg? - clojure.lang.ExceptionInfo - #"Source card not found" - (params.card-values/param->values - {:name "Card as source" - :slug "card" - :id "_CARD_" - :type "category" - :values_source_type "card" - :values_source_config {:card_id (inc (db/count Card)) - :value_field (mt/$ids $venues.name)}} - nil)))) - (testing "error if source card is archived" - (mt/with-temp Card [card {:archived true}] - (is (thrown-with-msg? - clojure.lang.ExceptionInfo - #"Source card is archived" - (params.card-values/param->values - {:name "Card as source" - :slug "card" - :id "_CARD_" - :type "category" - :values_source_type "card" - :values_source_config {:card_id (:id card) - :value_field (mt/$ids $venues.name)}} - nil)))))) + (testing "error if doesn't have permissions" + (mt/with-current-user (mt/user->id :rasta) + (mt/with-non-admin-groups-no-root-collection-perms + (mt/with-temp* + [Collection [coll] + Card [card {:collection_id (:id coll)}]] + (is (thrown-with-msg? + clojure.lang.ExceptionInfo + #"You don't have permissions to do that." + (custom-values/parameter->values + {:name "Card as source" + :slug "card" + :id "_CARD_" + :type "category" + :values_source_type "card" + :values_source_config {:card_id (:id card) + :value_field (mt/$ids $venues.name)}} + nil + (fn [] (throw (ex-info "Shouldn't call this function" {})))))))))) + + ;; bind to an admin to bypass the permissions check + (mt/with-current-user (mt/user->id :crowberto) + (testing "call to default-case-fn if " + (testing "souce card is archived" + (mt/with-temp Card [card {:archived true}] + (is (= :archived + (custom-values/parameter->values + {:name "Card as source" + :slug "card" + :id "_CARD_" + :type "category" + :values_source_type "card" + :values_source_config {:card_id (:id card) + :value_field (mt/$ids $venues.name)}} + nil + (constantly :archived)))))) + + (testing "value-field not found in card's result_metadata" + (mt/with-temp Card [card {}] + (is (= :field-not-found + (custom-values/parameter->values + {:name "Card as source" + :slug "card" + :id "_CARD_" + :type "category" + :values_source_type "card" + :values_source_config {:card_id (:id card) + :value_field [:field 0 nil]}} + nil + (constantly :field-not-found))))))))) diff --git a/test/metabase/models/permissions_test.clj b/test/metabase/models/permissions_test.clj index b3d7fbd26bf09d7d64f032b523f46e18240b44d2..cce5ddaef0aed29cdb587b76421a1010899e8abd 100644 --- a/test/metabase/models/permissions_test.clj +++ b/test/metabase/models/permissions_test.clj @@ -753,7 +753,7 @@ (testing "Partial permission graphs with no changes to the existing graph do not error when run repeatedly (#25221)" (mt/with-temp PermissionsGroup [group] ;; Bind *current-user* so that permission revisions are written, which was the source of the original error - (mt/with-current-user 1 + (mt/with-current-user (mt/user->id :rasta) (is (nil? (perms/update-data-perms-graph! {:groups {(u/the-id group) {(mt/id) {:data {:native :none :schemas :none}}}} :revision (:revision (perms/data-perms-graph))}))) (is (nil? (perms/update-data-perms-graph! {:groups {(u/the-id group) {(mt/id) {:data {:native :none :schemas :none}}}} @@ -925,8 +925,8 @@ w w w w w w w; w w; w w - w w w w; - ) ) ) ) ) ) + w w w w)))))); + (deftest data-permissions-v2-migration-move-test (testing "move admin"