From 9f94b7a60305e3ac699d77853d0316acc051b909 Mon Sep 17 00:00:00 2001
From: Ngoc Khuat <qn.khuat@gmail.com>
Date: Thu, 19 Jan 2023 19:17:18 +0700
Subject: [PATCH] Custom list for cards (#27403)

---
 .clj-kondo/config.edn                         |   1 +
 .../serialization/v2/extract_test.clj         | 166 +++++++++++++---
 .../serialization/v2/load_test.clj            |  96 +++++++++-
 src/metabase/api/card.clj                     |  68 +++++++
 src/metabase/api/dashboard.clj                |  38 +---
 src/metabase/api/embed.clj                    |  73 +++++++-
 src/metabase/api/field.clj                    |   2 +-
 src/metabase/api/public.clj                   |  17 ++
 src/metabase/models/card.clj                  |  47 +++--
 src/metabase/models/dashboard.clj             |  37 ++--
 src/metabase/models/parameter_card.clj        |  35 ++--
 src/metabase/models/params.clj                |  10 +-
 src/metabase/models/params/card_values.clj    |  11 +-
 src/metabase/models/params/static_values.clj  |  28 +++
 src/metabase/models/serialization/base.clj    |  65 +++----
 src/metabase/models/serialization/util.clj    |  27 ++-
 test/metabase/api/card_test.clj               | 146 +++++++++++++++
 test/metabase/api/dashboard_test.clj          | 101 +++-------
 test/metabase/api/embed_test.clj              |  52 +++++
 test/metabase/api/public_test.clj             | 177 +++++++++++++-----
 test/metabase/models/card_test.clj            |  82 +++++++-
 test/metabase/models/dashboard_test.clj       |  31 +++
 22 files changed, 1027 insertions(+), 283 deletions(-)
 create mode 100644 src/metabase/models/params/static_values.clj

diff --git a/.clj-kondo/config.edn b/.clj-kondo/config.edn
index 9bc9fb6452e..70ae863e0e2 100644
--- a/.clj-kondo/config.edn
+++ b/.clj-kondo/config.edn
@@ -451,6 +451,7 @@
    metabase.api.common/defendpoint-async-schema                                                                              hooks.metabase.api.common/defendpoint
    metabase.api.common/defendpoint-schema                                                                                    hooks.metabase.api.common/defendpoint
    metabase.api.dashboard-test/with-chain-filter-fixtures                                                                    hooks.common/let-one-with-optional-value
+   metabase.api.card-test/with-card-param-values-fixtures                                                                    hooks.common/let-one-with-optional-value
    metabase.api.embed-test/do-response-formats                                                                               hooks.common/with-two-bindings
    metabase.api.embed-test/with-chain-filter-fixtures                                                                        hooks.common/let-one-with-optional-value
    metabase.api.embed-test/with-temp-card                                                                                    hooks.common/let-one-with-optional-value
diff --git a/enterprise/backend/test/metabase_enterprise/serialization/v2/extract_test.clj b/enterprise/backend/test/metabase_enterprise/serialization/v2/extract_test.clj
index 69201fd9468..6b8519526b8 100644
--- a/enterprise/backend/test/metabase_enterprise/serialization/v2/extract_test.clj
+++ b/enterprise/backend/test/metabase_enterprise/serialization/v2/extract_test.clj
@@ -184,6 +184,18 @@
                                                                :collection_id dave-coll-id
                                                                :creator_id    mark-id
                                                                :parameters    []}]
+                       Dashboard  [{param-dash-id :id
+                                    param-dash    :entity_id} {:name          "Dave's Dash with parameters"
+                                                               :collection_id dave-coll-id
+                                                               :creator_id    mark-id
+                                                               :parameters    [{:id                   "abc"
+                                                                                :type                 "category"
+                                                                                :name                 "CATEGORY"
+                                                                                :values_source_type   "card"
+                                                                                ;; card_id is in a different collection with dashboard's collection
+                                                                                :values_source_config {:card_id     c1-id
+                                                                                                       :value_field [:field field-id nil]}}]}]
+
                        DashboardCard [_                       {:card_id      c1-id
                                                                :dashboard_id dash-id
                                                                :parameter_mappings
@@ -378,37 +390,79 @@
                      [{:model "Collection" :id dave-coll-eid}]}
                    (set (serdes.base/serdes-dependencies ser)))))))
 
-      (testing "collection filtering based on :user option"
-        (testing "only unowned collections are returned with no user"
-          (is (= ["Some Collection"]
-                 (->> (serdes.base/extract-all "Collection" {:collection-set #{coll-id}})
-                      (into [])
-                      (map :name)))))
-        (testing "unowned collections and the personal one with a user"
-          (is (= #{coll-eid mark-coll-eid}
-                 (->> {:collection-set (extract/collection-set-for-user mark-id)}
-                      (serdes.base/extract-all "Collection")
-                      (by-model "Collection"))))
-          (is (= #{coll-eid dave-coll-eid}
-                 (->> {:collection-set (extract/collection-set-for-user dave-id)}
-                      (serdes.base/extract-all "Collection")
-                      (by-model "Collection"))))))
-
-      (testing "dashboards are filtered based on :user"
-        (testing "dashboards in unowned collections are always returned"
-          (is (= #{dash-eid}
-                 (->> {:collection-set #{coll-id}}
-                      (serdes.base/extract-all "Dashboard")
-                      (by-model "Dashboard"))))
-          (is (= #{dash-eid}
-                 (->> {:collection-set (extract/collection-set-for-user mark-id)}
-                      (serdes.base/extract-all "Dashboard")
-                      (by-model "Dashboard")))))
-        (testing "dashboards in personal collections are returned for the :user"
-          (is (= #{dash-eid other-dash}
-                 (->> {:collection-set (extract/collection-set-for-user dave-id)}
-                      (serdes.base/extract-all "Dashboard")
-                      (by-model "Dashboard")))))))))
+     (testing "Dashboards with parameters where the source is a card"
+       (let [ser (serdes.base/extract-one "Dashboard" {} (db/select-one 'Dashboard :id param-dash-id))]
+         (is (schema= {:parameters
+                        (s/eq [{:id                   "abc"
+                                :name                 "CATEGORY"
+                                :type                 :category
+                                :values_query_type    "list"
+                                :values_source_config {:card_id     c1-eid
+                                                       :value_field [:field
+                                                                     ["My Database" nil "Schemaless Table" "Some Field"]
+                                                                     nil]},
+                                :values_source_type "card"}])
+                        s/Keyword s/Any}
+               ser))
+         (is (= #{[{:model "Collection" :id dave-coll-eid}]
+                  [{:model "Card"       :id c1-eid}]
+                  [{:model "Database", :id "My Database"}
+                   {:model "Table",    :id "Schemaless Table"}
+                   {:model "Field",    :id "Some Field"}]}
+                (set (serdes.base/serdes-dependencies ser))))))
+
+     (testing "Cards with parameters where the source is a card"
+       (let [ser (serdes.base/extract-one "Dashboard" {} (db/select-one 'Dashboard :id param-dash-id))]
+         (is (schema= {:parameters
+                       (s/eq [{:id                   "abc"
+                               :name                 "CATEGORY"
+                               :type                 :category
+                                :values_query_type    "list"
+                               :values_source_config {:card_id     c1-eid
+                                                      :value_field [:field
+                                                                    ["My Database" nil "Schemaless Table" "Some Field"]
+                                                                    nil]},
+                               :values_source_type "card"}])
+                       s/Keyword s/Any}
+                      ser))
+         (is (= #{[{:model "Collection" :id dave-coll-eid}]
+                  [{:model "Card"       :id c1-eid}]
+                  [{:model "Database", :id "My Database"}
+                   {:model "Table",    :id "Schemaless Table"}
+                   {:model "Field",    :id "Some Field"}]}
+                (set (serdes.base/serdes-dependencies ser))))))
+
+     (testing "collection filtering based on :user option"
+       (testing "only unowned collections are returned with no user"
+         (is (= ["Some Collection"]
+                (->> (serdes.base/extract-all "Collection" {:collection-set #{coll-id}})
+                     (into [])
+                     (map :name)))))
+       (testing "unowned collections and the personal one with a user"
+         (is (= #{coll-eid mark-coll-eid}
+                (->> {:collection-set (extract/collection-set-for-user mark-id)}
+                     (serdes.base/extract-all "Collection")
+                     (by-model "Collection"))))
+         (is (= #{coll-eid dave-coll-eid}
+                (->> {:collection-set (extract/collection-set-for-user dave-id)}
+                     (serdes.base/extract-all "Collection")
+                     (by-model "Collection"))))))
+
+     (testing "dashboards are filtered based on :user"
+       (testing "dashboards in unowned collections are always returned"
+         (is (= #{dash-eid}
+                (->> {:collection-set #{coll-id}}
+                     (serdes.base/extract-all "Dashboard")
+                     (by-model "Dashboard"))))
+         (is (= #{dash-eid}
+                (->> {:collection-set (extract/collection-set-for-user mark-id)}
+                     (serdes.base/extract-all "Dashboard")
+                     (by-model "Dashboard")))))
+       (testing "dashboards in personal collections are returned for the :user"
+         (is (= #{dash-eid other-dash param-dash}
+                (->> {:collection-set (extract/collection-set-for-user dave-id)}
+                     (serdes.base/extract-all "Dashboard")
+                     (by-model "Dashboard")))))))))
 
 (deftest dimensions-test
   (ts/with-empty-h2-app-db
@@ -894,6 +948,56 @@
                      [{:model "Dashboard" :id dash-eid}]}
                    (set (serdes.base/serdes-dependencies ser))))))))))
 
+(deftest cards-test
+ (ts/with-empty-h2-app-db
+   (ts/with-temp-dpc
+     [User       [{mark-id :id}              {:first_name "Mark"
+                                              :last_name  "Knopfler"
+                                              :email      "mark@direstrai.ts"}]
+      Database   [{db-id    :id}             {:name "My Database"}]
+      Table      [{table-id :id}             {:name "Schemaless Table" :db_id db-id}]
+      Field      [{field-id :id}             {:name "A Field" :table_id table-id}]
+      Collection [{coll-id-1  :id}           {:name "1st collection"}]
+      Collection [{coll-id-2  :id
+                   coll-eid-2 :entity_id}    {:name "2nd collection"}]
+
+      Card       [{card-id-1  :id
+                   card-eid-1 :entity_id}    {:name          "Source question"
+                                              :database_id   db-id
+                                              :table_id      table-id
+                                              :collection_id coll-id-1
+                                              :creator_id    mark-id}]
+      Card       [{card-id-2  :id}           {:name          "Card 2"
+                                              :database_id   db-id
+                                              :table_id      table-id
+                                              :collection_id coll-id-2
+                                              :creator_id    mark-id
+                                              :parameters    [{:id                   "abc"
+                                                               :type                 "category"
+                                                               :name                 "CATEGORY"
+                                                               :values_source_type   "card"
+                                                               ;; card_id is in a different collection with dashboard's collection
+                                                               :values_source_config {:card_id     card-id-1
+                                                                                      :value_field [:field field-id nil]}}]}]]
+     (testing "Cards with parameter's source is another question"
+       (let [ser (serdes.base/extract-one "Card" {} (db/select-one Card :id card-id-2))]
+         (is (= [{:id                   "abc",
+                  :type                 :category,
+                  :name                 "CATEGORY",
+                  :values_source_type   "card",
+                  :values_source_config {:card_id card-eid-1,
+                                         :value_field [:field ["My Database" nil "Schemaless Table" "A Field"] nil]}}]
+                (:parameters ser)))
+         (is (= #{[{:model "Database"   :id "My Database"}]
+                  [{:model "Collection" :id coll-eid-2}]
+                  [{:model "Database"   :id "My Database"}
+                   {:model "Table"      :id "Schemaless Table"}]
+                  [{:model "Card"       :id card-eid-1}]
+                  [{:model "Database"   :id "My Database"}
+                   {:model "Table"      :id "Schemaless Table"}
+                   {:model "Field"      :id "A Field"}]}
+                (set (serdes.base/serdes-dependencies ser)))))))))
+
 (deftest selective-serialization-basic-test
   (ts/with-empty-h2-app-db
     (ts/with-temp-dpc [User       [{mark-id :id}              {:first_name "Mark"
diff --git a/enterprise/backend/test/metabase_enterprise/serialization/v2/load_test.clj b/enterprise/backend/test/metabase_enterprise/serialization/v2/load_test.clj
index 2acdf2b49f3..6fb5c4fa993 100644
--- a/enterprise/backend/test/metabase_enterprise/serialization/v2/load_test.clj
+++ b/enterprise/backend/test/metabase_enterprise/serialization/v2/load_test.clj
@@ -201,7 +201,6 @@
           table2d    (atom nil)
           field2d    (atom nil)]
 
-
       (ts/with-source-and-dest-dbs
         (testing "serializing the original database, table, field and card"
           (ts/with-source-db
@@ -940,3 +939,98 @@
                        :template-tags
                        (get "snippet: things")
                        :snippet-id)))))))))
+
+(deftest card-has-parameter-with-source-card-test
+  (let [db        (atom nil)
+        table     (atom nil)
+        field     (atom nil)
+        card      (atom nil)
+        card2     (atom nil)
+        extracted (atom nil)]
+    (testing "Card that has parameter with source card must be deserialization"
+      (ts/with-empty-h2-app-db
+        (reset! db    (ts/create! Database :name "my-db"))
+        (reset! table (ts/create! Table :name "CUSTOMERS" :db_id (:id @db)))
+        (reset! field (ts/create! Field :name "NAME" :table_id (:id @table)))
+        (reset! card  (ts/create! Card :name "source card"))
+        (reset! card2 (ts/create! Card
+                                   :name "the card"
+                                   :parameters [{:id                   "abc"
+                                                 :type                 "category"
+                                                 :name                 "CATEGORY"
+                                                 :values_source_type   "card"
+                                                 ;; card_id is in a different collection with dashboard's collection
+                                                 :values_source_config {:card_id     (:id @card)
+                                                                        :value_field [:field (:id @field) nil]}}]))
+
+        (testing "on extraction"
+          (reset! extracted (serdes.base/extract-one "Card" {} @card2))
+          (is (= [{:id                   "abc",
+                   :name                 "CATEGORY",
+                   :type                 :category,
+                   :values_source_config {:card_id     (:entity_id @card),
+                                          :value_field [:field
+                                                        ["my-db" nil "CUSTOMERS" "NAME"]
+                                                        nil]},
+                   :values_source_type "card"}]
+                 (:parameters @extracted))))
+
+        (testing "when loading"
+          (let [new-eid   (u/generate-nano-id)
+                ingestion (ingestion-in-memory [(assoc @extracted :entity_id new-eid)])]
+            (is (some? (serdes.load/load-metabase ingestion)))
+            (is (= {:card_id     (:id @card),
+                    :value_field [:field (:id @field) nil]}
+                   (-> (db/select-one-field :parameters Card :entity_id new-eid)
+                       first
+                       :values_source_config)))))))))
+
+(deftest dashboard-has-parameter-with-source-card-test
+  (let [db        (atom nil)
+        table     (atom nil)
+        field     (atom nil)
+        card      (atom nil)
+        dash      (atom nil)
+        coll      (atom nil)
+        extracted (atom nil)]
+    (testing "Card that has parameter with source card must be deserialization"
+      (ts/with-empty-h2-app-db
+        (reset! db    (ts/create! Database :name "my-db"))
+        (reset! coll  (ts/create! Collection :name "pop! minis"))
+        (reset! table (ts/create! Table :name "CUSTOMERS" :db_id (:id @db)))
+        (reset! field (ts/create! Field :name "NAME" :table_id (:id @table)))
+        (reset! card  (ts/create! Card :name "source card"))
+        (reset! dash  (ts/create! Dashboard
+                                  :name "A dashboard"
+                                  :collection_id (:id @coll)
+                                  :parameters [{:id                   "abc"
+                                                 :type                 "category"
+                                                 :name                 "CATEGORY"
+                                                 :values_source_type   "card"
+                                                 ;; card_id is in a different collection with dashboard's collection
+                                                 :values_source_config {:card_id     (:id @card)
+                                                                        :value_field [:field (:id @field) nil]}}]))
+
+
+        (testing "on extraction"
+          (reset! extracted (serdes.base/extract-one "Dashboard" {} @dash))
+          (is (= [{:id                   "abc",
+                   :name                 "CATEGORY",
+                   :type                 :category,
+                   :values_query_type    "list"
+                   :values_source_config {:card_id     (:entity_id @card),
+                                          :value_field [:field
+                                                        ["my-db" nil "CUSTOMERS" "NAME"]
+                                                        nil]},
+                   :values_source_type "card"}]
+                 (:parameters @extracted))))
+
+        (testing "when loading"
+          (let [new-eid   (u/generate-nano-id)
+                ingestion (ingestion-in-memory [(assoc @extracted :entity_id new-eid)])]
+            (is (some? (serdes.load/load-metabase ingestion)))
+            (is (= {:card_id     (:id @card),
+                    :value_field [:field (:id @field) nil]}
+                   (-> (db/select-one-field :parameters Dashboard :entity_id new-eid)
+                       first
+                       :values_source_config)))))))))
diff --git a/src/metabase/api/card.clj b/src/metabase/api/card.clj
index cd6612560cd..2dcde4dd8ca 100644
--- a/src/metabase/api/card.clj
+++ b/src/metabase/api/card.clj
@@ -4,6 +4,7 @@
    [cheshire.core :as json]
    [clojure.core.async :as a]
    [clojure.data :as data]
+   [clojure.string :as str]
    [clojure.tools.logging :as log]
    [clojure.walk :as walk]
    [compojure.core :refer [DELETE GET POST PUT]]
@@ -11,17 +12,20 @@
    [metabase.api.common :as api]
    [metabase.api.common.validation :as validation]
    [metabase.api.dataset :as api.dataset]
+   [metabase.api.field :as api.field]
    [metabase.api.timeline :as api.timeline]
    [metabase.db.query :as mdb.query]
    [metabase.driver :as driver]
    [metabase.email.messages :as messages]
    [metabase.events :as events]
    [metabase.mbql.normalize :as mbql.normalize]
+   [metabase.mbql.util :as mbql.u]
    [metabase.models
     :refer [Card
             CardBookmark
             Collection
             Database
+            Field
             PersistedInfo
             Pulse
             Table
@@ -29,6 +33,9 @@
    [metabase.models.collection :as collection]
    [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.persisted-info :as persisted-info]
    [metabase.models.pulse :as pulse]
    [metabase.models.query :as query]
@@ -908,4 +915,65 @@ saved later when it is ready."
       (persisted-info/mark-for-pruning! {:id (:id persisted-info)} "off")
       api/generic-204-no-content)))
 
+(defn mapping->field-values
+  "Get param values for the \"old style\" parameters. This mimic's the api/dashboard version except we don't have
+  chain-filter issues or dashcards to worry about."
+  [card param query]
+  (when-let [field-clause (params/param-target->field-clause (:target param) card)]
+    (when-let [field-id (mbql.u/match-one field-clause [:field (id :guard integer?) _] id)]
+      (if (str/blank? query)
+        (api.field/check-perms-and-return-field-values field-id)
+        (let [field (api/check-404 (db/select-one Field :id field-id))]
+          ;; matching the output of the other params. [["Foo" "Foo"] ["Bar" "Bar"]] -> [["Foo"] ["Bar"]]. This shape
+          ;; is what the return-field-values returns above
+          {:values (map (comp vector first) (api.field/search-values field field query))
+           ;; assume there are more
+           :has_more_values true
+           :field_id field-id})))))
+
+(s/defn param-values
+  "Fetch values for a parameter.
+
+  The source of values could be:
+  - static-list: user defined values list
+  - card: values is result of running a card"
+  ([card param-key]
+   (param-values card param-key nil))
+
+  ([card      :- su/Map
+    param-key :- su/NonBlankString
+    query     :- (s/maybe su/NonBlankString)]
+   (let [param       (get (m/index-by :id (:parameters card)) param-key)
+         source-type (:values_source_type param)]
+     (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"        (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}))))))
+
+(api/defendpoint GET "/:card-id/params/:param-key/values"
+  "Fetch possible values of the parameter whose ID is `:param-key`.
+
+    ;; fetch values for Card 1 parameter 'abc' that are possible
+    GET /api/card/1/params/abc/values"
+  [card-id param-key]
+  (let [card (api/read-check Card card-id)]
+    (param-values card 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`.
+
+    ;; fetch values for Card 1 parameter 'abc' that contain 'Orange';
+     GET /api/card/1/params/abc/search/Orange
+
+  Currently limited to first 1000 results."
+  [card-id param-key query]
+  (let [card (api/read-check Card card-id)]
+    (param-values card param-key query)))
+
 (api/define-routes)
diff --git a/src/metabase/api/dashboard.clj b/src/metabase/api/dashboard.clj
index 258622a0e73..5e46b92d475 100644
--- a/src/metabase/api/dashboard.clj
+++ b/src/metabase/api/dashboard.clj
@@ -3,7 +3,6 @@
   (:require
    [cheshire.core :as json]
    [clojure.set :as set]
-   [clojure.string :as str]
    [clojure.tools.logging :as log]
    [compojure.core :refer [DELETE GET POST PUT]]
    [medley.core :as m]
@@ -27,6 +26,7 @@
    [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.query :as query :refer [Query]]
    [metabase.models.query.permissions :as query-perms]
    [metabase.models.revision :as revision]
@@ -38,7 +38,6 @@
    [metabase.query-processor.pivot :as qp.pivot]
    [metabase.query-processor.util :as qp.util]
    [metabase.related :as related]
-   [metabase.search.util :as search]
    [metabase.util :as u]
    [metabase.util.i18n :refer [tru]]
    [metabase.util.schema :as su]
@@ -439,7 +438,7 @@
   api/generic-204-no-content)
 
 (defn- param-target->field-id [target query]
-  (when-let [field-clause (params/param-target->field-clause target {:card {:dataset_query query}})]
+  (when-let [field-clause (params/param-target->field-clause target {:dataset_query query})]
     (mbql.u/match-one field-clause [:field (id :guard integer?) _] id)))
 
 ;; TODO -- should we only check *new* or *modified* mappings?
@@ -685,7 +684,8 @@
 (s/defn ^:private mappings->field-ids :- (s/maybe #{su/IntGreaterThanZero})
   [parameter-mappings :- (s/maybe (s/cond-pre #{dashboard-card/ParamMapping} [dashboard-card/ParamMapping]))]
   (set (for [param parameter-mappings
-             :let  [field-clause (params/param-target->field-clause (:target param) (:dashcard param))]
+             :let  [field-clause (params/param-target->field-clause (:target param)
+                                                                    (-> param :dashcard :card))]
              :when field-clause
              :let  [field-id (mbql.u/match-one field-clause [:field (id :guard integer?) _] id)]
              :when field-id]
@@ -751,32 +751,6 @@
            (api/throw-403 e)
            (throw e)))))))
 
-(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-parameter-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}))
-
-(defn- card-parameter-values
-  [{config :values_source_config :as _param} query]
-  (params.card-values/values-from-card (:card_id config) (:value_field config) query))
-
 (s/defn param-values
   "Fetch values for a parameter.
 
@@ -798,8 +772,8 @@
                        {:resolved-params (keys (:resolved-params dashboard))
                         :status-code     400})))
      (case (:values_source_type param)
-       "static-list" (static-parameter-values param query)
-       "card"        (card-parameter-values param query)
+       "static-list" (params.static-values/param->values param query)
+       "card"        (params.card-values/param->values param query)
        nil           (chain-filter dashboard param-key constraint-param-key->value query)))))
 
 #_{:clj-kondo/ignore [:deprecated-var]}
diff --git a/src/metabase/api/embed.clj b/src/metabase/api/embed.clj
index a0df238431c..a263c76aee7 100644
--- a/src/metabase/api/embed.clj
+++ b/src/metabase/api/embed.clj
@@ -20,6 +20,7 @@
    [clojure.tools.logging :as log]
    [compojure.core :refer [GET]]
    [medley.core :as m]
+   [metabase.api.card :as api.card]
    [metabase.api.common :as api]
    [metabase.api.common.validation :as validation]
    [metabase.api.dashboard :as api.dashboard]
@@ -552,7 +553,48 @@
     (into {} (for [[slug value] merged-slug->value]
                [(get slug->id (name slug)) value]))))
 
-(defn- param-values [token searched-param-id prefix id-query-params]
+(defn card-param-values
+  "Search for card parameter values. Does security checks to ensure the parameter is on the card and then gets param
+  values according to [[api.card/param-values]]."
+  [{:keys [unsigned-token card param-key search-prefix]}]
+  (let [slug-token-params   (embed/get-in-unsigned-token-or-throw unsigned-token [:params])
+        id->slug            (into {} (map (juxt :id :slug) (:parameters card)))
+        slug->id            (into {} (map (juxt :slug :id) (:parameters card)))
+        searched-param-slug (get id->slug param-key)
+        embedding-params    (:embedding_params card)]
+    (try
+      (when-not (= (get embedding-params (keyword searched-param-slug)) "enabled")
+        (throw (ex-info (tru "Cannot search for values: {0} is not an enabled parameter."
+                             (pr-str searched-param-slug))
+                        {:status-code 400})))
+      (when (get slug-token-params (keyword searched-param-slug))
+        (throw (ex-info (tru "You can''t specify a value for {0} if it's already set in the JWT." (pr-str searched-param-slug))
+                        {:status-code 400})))
+      (try
+        (binding [api/*current-user-permissions-set* (atom #{"/"})]
+          (api.card/param-values card param-key search-prefix))
+        (catch Throwable e
+          (throw (ex-info (.getMessage e)
+                          {:card-id       (u/the-id card)
+                           :param-key     param-key
+                           :search-prefix search-prefix}
+                          e))))
+      (catch Throwable e
+        (let [e (ex-info (.getMessage e)
+                         {:card-id (u/the-id card)
+                          :card-params (:parametres card)
+                          :allowed-param-slugs embedding-params
+                          :slug->id            slug->id
+                          :id->slug            id->slug
+                          :param-id            param-key
+                          :param-slug          searched-param-slug
+                          :token-params        slug-token-params}
+                         e)]
+          (log/errorf e "embedded card-param-values error\n%s"
+                      (u/pprint-to-str (u/all-ex-data e)))
+          (throw e))))))
+
+(defn- dashboard-param-values [token searched-param-id prefix id-query-params]
   (let [unsigned-token                       (embed/unsign token)
         dashboard-id                         (embed/get-in-unsigned-token-or-throw unsigned-token [:resource :dashboard])
         _                                    (check-embedding-enabled-for-dashboard dashboard-id)
@@ -597,13 +639,38 @@
 (api/defendpoint-schema GET "/dashboard/:token/params/:param-key/values"
   "Embedded version of chain filter values endpoint."
   [token param-key :as {:keys [query-params]}]
-  (param-values token param-key nil query-params))
+  (dashboard-param-values token param-key nil query-params))
 
 #_{:clj-kondo/ignore [:deprecated-var]}
 (api/defendpoint-schema GET "/dashboard/:token/params/:param-key/search/:prefix"
   "Embedded version of chain filter search endpoint."
   [token param-key prefix :as {:keys [query-params]}]
-  (param-values token param-key prefix query-params))
+  (dashboard-param-values token param-key prefix query-params))
+
+#_{:clj-kondo/ignore [:deprecated-var]}
+(api/defendpoint-schema GET "/card/:token/params/:param-key/values"
+  "Embedded version of api.card filter values endpoint."
+  [token param-key]
+  (let [unsigned (embed/unsign token)
+        card-id  (embed/get-in-unsigned-token-or-throw unsigned [:resource :question])
+        card     (db/select-one Card :id card-id)]
+    (check-embedding-enabled-for-card card-id)
+    (card-param-values {:unsigned-token unsigned
+                        :card           card
+                        :param-key      param-key})))
+
+#_{:clj-kondo/ignore [:deprecated-var]}
+(api/defendpoint-schema GET "/card/:token/params/:param-key/search/:prefix"
+  "Embedded version of chain filter search endpoint."
+  [token param-key prefix]
+  (let [unsigned (embed/unsign token)
+        card-id  (embed/get-in-unsigned-token-or-throw unsigned [:resource :question])
+        card     (db/select-one Card :id card-id)]
+    (check-embedding-enabled-for-card card-id)
+    (card-param-values {:unsigned-token unsigned
+                        :card           card
+                        :param-key      param-key
+                        :search-prefix  prefix})))
 
 #_{:clj-kondo/ignore [:deprecated-var]}
 (api/defendpoint-schema ^:streaming GET "/pivot/card/:token/query"
diff --git a/src/metabase/api/field.clj b/src/metabase/api/field.clj
index d41e539ecfb..539972343f2 100644
--- a/src/metabase/api/field.clj
+++ b/src/metabase/api/field.clj
@@ -219,7 +219,7 @@
      :has_more_values has_more_values}
     (params.field-values/get-or-create-field-values-for-current-user! (api/check-404 field))))
 
-(defn- check-perms-and-return-field-values
+(defn check-perms-and-return-field-values
   "Impl for `GET /api/field/:id/values` endpoint; check whether current user has read perms for Field with `id`, and, if
   so, return its values."
   [field-id]
diff --git a/src/metabase/api/public.clj b/src/metabase/api/public.clj
index 34b02aa71c9..82207edf420 100644
--- a/src/metabase/api/public.clj
+++ b/src/metabase/api/public.clj
@@ -6,6 +6,7 @@
    [compojure.core :refer [GET]]
    [medley.core :as m]
    [metabase.actions.execution :as actions.execution]
+   [metabase.api.card :as api.card]
    [metabase.api.common :as api]
    [metabase.api.common.validation :as validation]
    [metabase.api.dashboard :as api.dashboard]
@@ -470,6 +471,22 @@
 
 ;;; ------------------------------------------------ Param Values -------------------------------------------------
 
+#_{:clj-kondo/ignore [:deprecated-var]}
+(api/defendpoint-schema GET "/card/:uuid/params/:param-key/values"
+  "Fetch values for a parameter on a public card."
+  [uuid param-key]
+  (validation/check-public-sharing-enabled)
+  (let [card (db/select-one Card :public_uuid uuid, :archived false)]
+    (api.card/param-values card param-key)))
+
+#_{:clj-kondo/ignore [:deprecated-var]}
+(api/defendpoint-schema GET "/card/:uuid/params/:param-key/search/:query"
+  "Fetch values for a parameter on a public card containing `query`."
+  [uuid param-key query]
+  (validation/check-public-sharing-enabled)
+  (let [card (db/select-one Card :public_uuid uuid, :archived false)]
+    (api.card/param-values card param-key query)))
+
 #_{:clj-kondo/ignore [:deprecated-var]}
 (api/defendpoint-schema GET "/dashboard/:uuid/params/:param-key/values"
   "Fetch filter values for dashboard parameter `param-key`."
diff --git a/src/metabase/models/card.clj b/src/metabase/models/card.clj
index 301d77c2959..e8803ee60da 100644
--- a/src/metabase/models/card.clj
+++ b/src/metabase/models/card.clj
@@ -11,6 +11,7 @@
    [metabase.models.collection :as collection]
    [metabase.models.field-values :as field-values]
    [metabase.models.interface :as mi]
+   [metabase.models.parameter-card :as parameter-card :refer [ParameterCard]]
    [metabase.models.params :as params]
    [metabase.models.permissions :as perms]
    [metabase.models.query :as query]
@@ -46,7 +47,7 @@
   "Return the number of dashboard/card filters and other widgets that use this card to populate their available
   values (via ParameterCards)"
   [{:keys [id]}]
-  (db/count 'ParameterCard, :card_id id))
+  (db/count ParameterCard, :card_id id))
 
 (mi/define-simple-hydration-method average-query-time
   :average_query_time
@@ -223,15 +224,15 @@
   (let [defaults {:parameters         []
                   :parameter_mappings []}
         card     (merge defaults card)]
-   (u/prog1 card
-     ;; make sure this Card doesn't have circular source query references
-     (check-for-circular-source-query-references card)
-     (check-field-filter-fields-are-from-correct-database card)
-     ;; TODO: add a check to see if all id in :parameter_mappings are in :parameters
-     (assert-valid-model card)
-     (params/assert-valid-parameters card)
-     (params/assert-valid-parameter-mappings card)
-     (collection/check-collection-namespace Card (:collection_id card)))))
+    (u/prog1 card
+      ;; make sure this Card doesn't have circular source query references
+      (check-for-circular-source-query-references card)
+      (check-field-filter-fields-are-from-correct-database card)
+      ;; TODO: add a check to see if all id in :parameter_mappings are in :parameters
+      (assert-valid-model card)
+      (params/assert-valid-parameters card)
+      (params/assert-valid-parameter-mappings card)
+      (collection/check-collection-namespace Card (:collection_id card)))))
 
 (defn- post-insert [card]
   ;; if this Card has any native template tag parameters we need to update FieldValues for any Fields that are
@@ -239,7 +240,8 @@
   (u/prog1 card
     (when-let [field-ids (seq (params/card->template-tag-field-ids card))]
       (log/info "Card references Fields in params:" field-ids)
-      (field-values/update-field-values-for-on-demand-dbs! field-ids))))
+      (field-values/update-field-values-for-on-demand-dbs! field-ids))
+    (parameter-card/upsert-or-delete-from-parameters! "card" (:id card) (:parameters card))))
 
 (defonce
   ^{:doc "Atom containing a function used to check additional sandboxing constraints for Metabase Enterprise Edition.
@@ -291,12 +293,17 @@
       (collection/check-collection-namespace Card (:collection_id changes))
       (params/assert-valid-parameters changes)
       (params/assert-valid-parameter-mappings changes)
+      (parameter-card/upsert-or-delete-from-parameters! "card" id (:parameters changes))
       ;; additional checks (Enterprise Edition only)
       (@pre-update-check-sandbox-constraints changes)
       (assert-valid-model (merge old-card-info changes)))))
 
 ;; Cards don't normally get deleted (they get archived instead) so this mostly affects tests
 (defn- pre-delete [{:keys [id]}]
+  ;; delete any ParameterCard that the parameters on this card linked to
+  (parameter-card/delete-all-for-parameterized-object! "card" id)
+  ;; delete any ParameterCard linked to this card
+  (db/delete! ParameterCard :card_id id)
   (db/delete! 'ModerationReview :moderated_item_type "card", :moderated_item_id id)
   (db/delete! 'Revision :model "Card", :model_id id))
 
@@ -377,6 +384,7 @@
         (update :creator_id             serdes.util/export-user)
         (update :made_public_by_id      serdes.util/export-user)
         (update :dataset_query          serdes.util/export-mbql)
+        (update :parameters             serdes.util/export-parameters)
         (update :parameter_mappings     serdes.util/export-parameter-mappings)
         (update :visualization_settings serdes.util/export-visualization-settings)
         (update :result_metadata        export-result-metadata))
@@ -393,14 +401,17 @@
       (update :made_public_by_id      serdes.util/import-user)
       (update :collection_id          serdes.util/import-fk 'Collection)
       (update :dataset_query          serdes.util/import-mbql)
+      (update :parameters             serdes.util/import-parameters)
       (update :parameter_mappings     serdes.util/import-parameter-mappings)
       (update :visualization_settings serdes.util/import-visualization-settings)
       (update :result_metadata        import-result-metadata)))
 
 (defmethod serdes.base/serdes-dependencies "Card"
-  [{:keys [collection_id database_id dataset_query parameter_mappings result_metadata table_id visualization_settings]}]
+  [{:keys [collection_id database_id dataset_query parameters parameter_mappings
+           result_metadata table_id visualization_settings]}]
   (->> (map serdes.util/mbql-deps parameter_mappings)
        (reduce set/union)
+       (set/union (serdes.util/parameters-deps parameters))
        (set/union #{[{:model "Database" :id database_id}]})
        ; table_id and collection_id are nullable.
        (set/union (when table_id #{(serdes.util/table->path table_id)}))
@@ -411,10 +422,11 @@
        vec))
 
 (defmethod serdes.base/serdes-descendants "Card" [_model-name id]
-  (let [card          (db/select-one Card :id id)
-        source-table  (some->  card :dataset_query :query :source-table)
-        template-tags (some->> card :dataset_query :native :template-tags vals (keep :card-id))
-        snippets      (some->> card :dataset_query :native :template-tags vals (keep :snippet-id))]
+  (let [card               (db/select-one Card :id id)
+        source-table       (some->  card :dataset_query :query :source-table)
+        template-tags      (some->> card :dataset_query :native :template-tags vals (keep :card-id))
+        parameters-card-id (some->> card :parameters (keep (comp :card_id :values_source_config)))
+        snippets           (some->> card :dataset_query :native :template-tags vals (keep :snippet-id))]
     (set/union
       (when (and (string? source-table)
                  (str/starts-with? source-table "card__"))
@@ -422,6 +434,9 @@
       (when (seq template-tags)
         (set (for [card-id template-tags]
                ["Card" card-id])))
+      (when (seq parameters-card-id)
+        (set (for [card-id parameters-card-id]
+               ["Card" card-id])))
       (when (seq snippets)
         (set (for [snippet-id snippets]
                ["NativeQuerySnippet" snippet-id]))))))
diff --git a/src/metabase/models/dashboard.clj b/src/metabase/models/dashboard.clj
index e40f0a0e49f..43c27e87419 100644
--- a/src/metabase/models/dashboard.clj
+++ b/src/metabase/models/dashboard.clj
@@ -91,12 +91,12 @@
 (defn- post-insert
   [dashboard]
   (u/prog1 dashboard
-    (parameter-card/upsert-or-delete-for-dashboard! dashboard)))
+    (parameter-card/upsert-or-delete-from-parameters! "dashboard" (:id dashboard) (:parameters dashboard))))
 
 (defn- pre-update [dashboard]
   (u/prog1 dashboard
     (params/assert-valid-parameters dashboard)
-    (parameter-card/upsert-or-delete-for-dashboard! dashboard)
+    (parameter-card/upsert-or-delete-from-parameters! "dashboard" (:id dashboard) (:parameters dashboard))
     (collection/check-collection-namespace Dashboard (:collection_id dashboard))))
 
 (defn- update-dashboard-subscription-pulses!
@@ -456,6 +456,7 @@
                (hydrate dash :ordered_cards))]
     (-> (serdes.base/extract-one-basics "Dashboard" dash)
         (update :ordered_cards     #(mapv extract-dashcard %))
+        (update :parameters        serdes.util/export-parameters)
         (update :collection_id     serdes.util/export-fk Collection)
         (update :creator_id        serdes.util/export-user)
         (update :made_public_by_id serdes.util/export-user))))
@@ -466,6 +467,7 @@
       serdes.base/load-xform-basics
       ;; Deliberately not doing anything to :ordered_cards - they get handled by load-insert! and load-update! below.
       (update :collection_id     serdes.util/import-fk Collection)
+      (update :parameters        serdes.util/import-parameters)
       (update :creator_id        serdes.util/import-user)
       (update :made_public_by_id serdes.util/import-user)))
 
@@ -490,19 +492,26 @@
        set))
 
 (defmethod serdes.base/serdes-dependencies "Dashboard"
-  [{:keys [collection_id ordered_cards]}]
-  (->> ordered_cards
-       (map serdes-deps-dashcard)
-       (reduce set/union #{[{:model "Collection" :id collection_id}]})))
+  [{:keys [collection_id ordered_cards parameters]}]
+  (->> (map serdes-deps-dashcard ordered_cards)
+       (reduce set/union)
+       (set/union #{[{:model "Collection" :id collection_id}]})
+       (set/union (serdes.util/parameters-deps parameters))))
 
 (defmethod serdes.base/serdes-descendants "Dashboard" [_model-name id]
-  ;; DashboardCards are inlined into Dashboards, but we need to capture what those those DashboardCards rely on
-  ;; here. So their cards, both direct and mentioned in their parameters.
-  (set (for [{:keys [card_id parameter_mappings]} (db/select ['DashboardCard :card_id :parameter_mappings]
-                                                             :dashboard_id id)
-             ;; Capture all card_ids in the parameters, plus this dashcard's card_id if non-nil.
-             card-id  (cond-> (set (keep :card_id parameter_mappings))
-                        card_id (conj card_id))]
-         ["Card" card-id])))
+  (let [dashcards (db/select ['DashboardCard :card_id :parameter_mappings]
+                             :dashboard_id id)
+        dashboard (db/select-one Dashboard :id id)]
+    (set/union
+      ;; DashboardCards are inlined into Dashboards, but we need to capture what those those DashboardCards rely on
+      ;; here. So their cards, both direct and mentioned in their parameters.
+      (set (for [{:keys [card_id parameter_mappings]} dashcards
+                 ;; Capture all card_ids in the parameters, plus this dashcard's card_id if non-nil.
+                 card-id (cond-> (set (keep :card_id parameter_mappings))
+                           card_id (conj card_id))]
+             ["Card" card-id]))
+      ;; parameter with values_source_type = "card" will depend on a card
+      (set (for [card-id (some->> dashboard :parameters (keep (comp :card_id :values_source_config)))]
+             ["Card" card-id])))))
 
 (serdes.base/register-ingestion-path! "Dashboard" (serdes.base/ingestion-matcher-collected "collections" "Dashboard"))
diff --git a/src/metabase/models/parameter_card.clj b/src/metabase/models/parameter_card.clj
index 166a297b518..54b65ddedb0 100644
--- a/src/metabase/models/parameter_card.clj
+++ b/src/metabase/models/parameter_card.clj
@@ -3,19 +3,21 @@
    [metabase.models.interface :as mi]
    [metabase.util :as u]
    [metabase.util.i18n :refer [tru]]
+   [metabase.util.malli :as mu]
+   [metabase.util.malli.schema :as ms]
    [toucan.db :as db]
    [toucan.models :as models]))
 
 (models/defmodel ParameterCard :parameter_card)
 
 (defonce ^{:doc "Set of valid parameterized_object_type for a ParameterCard"}
-  valid-parameterized_object_type #{"dashboard"})
+  valid-parameterized-object-type #{"dashboard" "card"})
 
 (defn- validate-parameterized-object-type
   [{:keys [parameterized_object_type] :as _parameter-card}]
-  (when-not (valid-parameterized_object_type parameterized_object_type)
+  (when-not (valid-parameterized-object-type parameterized_object_type)
     (throw (ex-info (tru "invalid parameterized_object_type")
-                    {:allowed-types valid-parameterized_object_type}))))
+                    {:allowed-types valid-parameterized-object-type}))))
 
 ;;; ----------------------------------------------- Entity & Lifecycle -----------------------------------------------
 
@@ -45,27 +47,30 @@
   ([parameterized-object-type parameterized-object-id parameter-ids-still-in-use]
    (db/delete! ParameterCard
                :parameterized_object_type parameterized-object-type
-               :parameterized_object_id  parameterized-object-id
+               :parameterized_object_id parameterized-object-id
                (if (empty? parameter-ids-still-in-use)
-                {}
-                {:where [:not-in :parameter_id parameter-ids-still-in-use]}))))
+                 {}
+                 {:where [:not-in :parameter_id parameter-ids-still-in-use]}))))
 
-(defn- upsert-for-dashboard!
-  [dashboard-id parameters]
+(defn- upsert-from-parameters!
+  [parameterized-object-type parameterized-object-id parameters]
   (doseq [{:keys [values_source_config id]} parameters]
     (let [card-id    (:card_id values_source_config)
-          conditions {:parameterized_object_id   dashboard-id
-                      :parameterized_object_type "dashboard"
+          conditions {:parameterized_object_id   parameterized-object-id
+                      :parameterized_object_type parameterized-object-type
                       :parameter_id              id}]
       (or (db/update-where! ParameterCard conditions :card_id card-id)
           (db/insert! ParameterCard (merge conditions {:card_id card-id}))))))
 
-(defn upsert-or-delete-for-dashboard!
-  "Create, update, or delete appropriate ParameterCards for each parameter in the dashboard"
-  [{dashboard-id :id parameters :parameters}]
+(mu/defn upsert-or-delete-from-parameters!
+  "From a parameters list on card or dashboard, create, update,
+  or delete appropriate ParameterCards for each parameter in the dashboard"
+  [parameterized-object-type :- ms/NonBlankString
+   parameterized-object-id   :- ms/IntGreaterThanZero
+   parameters                :- [:maybe [:sequential ms/Parameter]]]
   (let [upsertable?           (fn [{:keys [values_source_type values_source_config id]}]
                                 (and values_source_type id (:card_id values_source_config)
                                      (= values_source_type "card")))
         upsertable-parameters (filter upsertable? parameters)]
-    (upsert-for-dashboard! dashboard-id upsertable-parameters)
-    (delete-all-for-parameterized-object! "dashboard" dashboard-id (map :id upsertable-parameters))))
+    (upsert-from-parameters! parameterized-object-type parameterized-object-id upsertable-parameters)
+    (delete-all-for-parameterized-object! parameterized-object-type parameterized-object-id (map :id upsertable-parameters))))
diff --git a/src/metabase/models/params.clj b/src/metabase/models/params.clj
index 09b07f30b53..bb6df696bba 100644
--- a/src/metabase/models/params.clj
+++ b/src/metabase/models/params.clj
@@ -61,20 +61,20 @@
   "Fetch the `:field` clause from `dashcard` referenced by `template-tag`.
 
     (template-tag->field-form [:template-tag :company] some-dashcard) ; -> [:field 100 nil]"
-  [[_ tag] dashcard]
-  (get-in dashcard [:card :dataset_query :native :template-tags (u/qualified-name tag) :dimension]))
+  [[_ tag] card]
+  (get-in card [:dataset_query :native :template-tags (u/qualified-name tag) :dimension]))
 
 (s/defn param-target->field-clause :- (s/maybe mbql.s/field)
   "Parse a Card parameter `target` form, which looks something like `[:dimension [:field-id 100]]`, and return the Field
   ID it references (if any)."
-  [target dashcard]
+  [target card]
   (let [target (mbql.normalize/normalize-tokens target :ignore-path)]
     (when (mbql.u/is-clause? :dimension target)
       (let [[_ dimension] target]
         (try
           (unwrap-field-clause
            (if (mbql.u/is-clause? :template-tag dimension)
-             (template-tag->field-form dimension dashcard)
+             (template-tag->field-form dimension card)
              dimension))
           (catch Throwable e
             (log/error e (tru "Could not find matching Field ID for target:") target)))))))
@@ -182,7 +182,7 @@
   [dashboard]
   (when-let [fields (seq (for [dashcard (:ordered_cards dashboard)
                                param    (:parameter_mappings dashcard)
-                               :let     [field-clause (param-target->field-clause (:target param) dashcard)]
+                               :let     [field-clause (param-target->field-clause (:target param) (:card dashcard))]
                                :when    field-clause]
                            field-clause))]
     (set fields)))
diff --git a/src/metabase/models/params/card_values.clj b/src/metabase/models/params/card_values.clj
index ba2b51a0f84..de0321e0183 100644
--- a/src/metabase/models/params/card_values.clj
+++ b/src/metabase/models/params/card_values.clj
@@ -10,9 +10,9 @@
 
 (def ^:dynamic *max-rows*
   "Maximum number of rows returned when running a card.
-  It's 2000 because this is the limit we use when viewing a question.
-  Maybe we should lower it for the sake of display a parameter dropdown."
-  2000)
+  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)
 
 (defn- values-from-card-query
   [card-id value-field query]
@@ -52,3 +52,8 @@
      {:values          (map first (get-in result [:data :rows]))
       ;; if the row_count returned = the limit we specified, then it's probably has more than that
       :has_more_values (= (:row_count result) query-limit)})))
+
+(defn param->values
+  "Given a param and query returns the values."
+  [{config :values_source_config :as _param} query]
+  (values-from-card (:card_id config) (:value_field config) query))
diff --git a/src/metabase/models/params/static_values.clj b/src/metabase/models/params/static_values.clj
new file mode 100644
index 00000000000..a172868ae2f
--- /dev/null
+++ b/src/metabase/models/params/static_values.clj
@@ -0,0 +1,28 @@
+(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/src/metabase/models/serialization/base.clj b/src/metabase/models/serialization/base.clj
index 61a7032c960..486f2cce981 100644
--- a/src/metabase/models/serialization/base.clj
+++ b/src/metabase/models/serialization/base.clj
@@ -264,6 +264,39 @@
 (defmethod extract-one :default [model-name _opts entity]
   (extract-one-basics model-name entity))
 
+(defmulti serdes-descendants
+  "Captures the notion that eg. a dashboard \"contains\" its cards.
+  Returns a set, possibly empty or nil, of `[model-name database-id]` pairs for all entities that this entity contains
+  or requires to be executed.
+  Dispatches on the model name.
+
+  For example:
+  - a `Collection` contains 0 or more other `Collection`s plus many `Card`s and `Dashboard`s;
+  - a `Dashboard` contains its `DashboardCard`s;
+  - each `DashboardCard` contains its `Card`;
+  - a `Card` might stand alone, or it might require `NativeQuerySnippet`s or other `Card`s as inputs; and
+  - a `NativeQuerySnippet` similarly might derive from others;
+
+  A transitive closure over [[serdes-descendants]] should thus give a complete \"subtree\", such as a complete
+  `Collection` and all its contents.
+
+  A typical implementation will run a query or two to collect eg. all `DashboardCard`s that are part of this
+  `Dashboard`, and return them as pairs like `[\"DashboardCard\" 17]`.
+
+  What about [[serdes-dependencies]]?
+  Despite the similar-sounding names, this differs crucially from [[serdes-dependencies]]. [[serdes-descendants]] finds
+  all entities that are \"part\" of the given entity.
+
+  [[serdes-dependencies]] finds all entities that need to be loaded into appdb before this one can be, generally because
+  this has a foreign key to them. The arrow \"points the other way\": [[serdes-dependencies]] points *up* -- from a
+  `Dashboard` to its containing `Collection`, `Collection` to its parent, from a `DashboardCard` to its `Dashboard` and
+  `Card`. [[serdes-descendants]] points *down* to contents, children, and components."
+  {:arglists '([model-name db-id])}
+  (fn [model-name _] model-name))
+
+(defmethod serdes-descendants :default [_ _]
+  nil)
+
 ;;; +----------------------------------------------------------------------------------------------------------------+
 ;;; |                                         Deserialization Process                                                |
 ;;; +----------------------------------------------------------------------------------------------------------------+
@@ -446,38 +479,6 @@
         (load-insert! model adjusted)
         (load-update! model adjusted maybe-local)))))
 
-(defmulti serdes-descendants
-  "Captures the notion that eg. a dashboard \"contains\" its cards.
-  Returns a set, possibly empty or nil, of `[model-name database-id]` pairs for all entities that this entity contains
-  or requires to be executed.
-  Dispatches on the model name.
-
-  For example:
-  - a `Collection` contains 0 or more other `Collection`s plus many `Card`s and `Dashboard`s;
-  - a `Dashboard` contains its `DashboardCard`s;
-  - each `DashboardCard` contains its `Card`;
-  - a `Card` might stand alone, or it might require `NativeQuerySnippet`s or other `Card`s as inputs; and
-  - a `NativeQuerySnippet` similarly might derive from others;
-
-  A transitive closure over [[serdes-descendants]] should thus give a complete \"subtree\", such as a complete
-  `Collection` and all its contents.
-
-  A typical implementation will run a query or two to collect eg. all `DashboardCard`s that are part of this
-  `Dashboard`, and return them as pairs like `[\"DashboardCard\" 17]`.
-
-  What about [[serdes-dependencies]]?
-  Despite the similar-sounding names, this differs crucially from [[serdes-dependencies]]. [[serdes-descendants]] finds
-  all entities that are \"part\" of the given entity.
-
-  [[serdes-dependencies]] finds all entities that need to be loaded into appdb before this one can be, generally because
-  this has a foreign key to them. The arrow \"points the other way\": [[serdes-dependencies]] points *up* -- from a
-  `Dashboard` to its containing `Collection`, `Collection` to its parent, from a `DashboardCard` to its `Dashboard` and
-  `Card`. [[serdes-descendants]] points *down* to contents, children, and components."
-  {:arglists '([model-name db-id])}
-  (fn [model-name _] model-name))
-
-(defmethod serdes-descendants :default [_ _]
-  nil)
 
 (defn entity-id?
   "Checks if the given string is a 21-character NanoID. Useful for telling entity IDs apart from identity hashes."
diff --git a/src/metabase/models/serialization/util.clj b/src/metabase/models/serialization/util.clj
index 6a5ebe5bc8b..aa69adc0417 100644
--- a/src/metabase/models/serialization/util.clj
+++ b/src/metabase/models/serialization/util.clj
@@ -254,7 +254,6 @@
                                  :database :card_id :card-id :source-table :breakout :aggregation :filter :segment
                                  ::mb.viz/param-mapping-source :snippet-id))))))
 
-;(ids->fully-qualified-names {:aggregation [[:sum [:field 277405 nil]]]})
 
 (defn export-mbql
   "Given an MBQL expression, convert it to an EDN structure and turn the non-portable Database, Table and Field IDs
@@ -405,6 +404,32 @@
        (map mbql-fully-qualified-names->ids)
        (map #(m/update-existing % :card_id import-fk 'Card))))
 
+(defn export-parameters
+  "Given the :parameter field of a `Card` or `Dashboard`, as a vector of maps, converts
+  it to a portable form with the CardIds/FieldIds replaced with `[db schema table field]` references."
+  [parameters]
+  (map ids->fully-qualified-names parameters))
+
+(defn import-parameters
+  "Given the :parameter field as exported by serialization convert its field references
+  (`[db schema table field]`) back into raw IDs."
+  [parameters]
+  (for [param parameters]
+    (-> param
+        mbql-fully-qualified-names->ids
+        (m/update-existing-in [:values_source_config :card_id] import-fk 'Card))))
+
+(defn parameters-deps
+  "Given the :parameters (possibly nil) for an entity, return any embedded serdes-deps as a set.
+  Always returns an empty set even if the input is nil."
+  [parameters]
+  (reduce set/union #{}
+          (for [parameter parameters
+                :when (= "card" (:values_source_type parameter))
+                :let  [config (:values_source_config parameter)]]
+            (set/union #{[{:model "Card" :id (:card_id config)}]}
+                       (mbql-deps-vector (:value_field config))))))
+
 (defn- export-visualizations [entity]
   (mbql.u/replace
     entity
diff --git a/test/metabase/api/card_test.clj b/test/metabase/api/card_test.clj
index e9067d7da57..29cfbce4eac 100644
--- a/test/metabase/api/card_test.clj
+++ b/test/metabase/api/card_test.clj
@@ -2,6 +2,7 @@
   "Tests for /api/card endpoints."
   (:require
    [cheshire.core :as json]
+   [clojure.set :as set]
    [clojure.string :as str]
    [clojure.test :refer :all]
    [clojure.tools.macro :as tools.macro]
@@ -2300,3 +2301,148 @@
           (is (not (contains? (task.persist-refresh/job-info-for-individual-refresh)
                               (u/the-id parchived)))
               "Scheduled refresh of archived model"))))))
+
+(defn- param-values-url
+  ([card-or-id param-key]
+   (param-values-url card-or-id param-key nil))
+  ([card-or-id param-key query]
+   (if query
+     (format "card/%d/params/%s/search/%s" (u/the-id card-or-id) (name param-key) query)
+     (format "card/%d/params/%s/values" (u/the-id card-or-id) (name param-key)))))
+
+(defn do-with-card-param-values-fixtures
+  "Impl of `with-card-param-values-fixtures` macro."
+  ([f]
+   (do-with-card-param-values-fixtures nil f))
+
+  ([card-values f]
+   (mt/with-temp*
+     [Card [source-card {:database_id   (mt/id)
+                         :table_id      (mt/id :venues)
+                         :dataset_query (mt/mbql-query venues {:limit 5})}]
+      Card [field-filter-card  {:dataset_query
+                                {:database (mt/id)
+                                 :type     :native
+                                 :native   {:query         "SELECT COUNT(*) FROM VENUES WHERE {{NAME}}"
+                                            :template-tags {"NAME" {:id           "name_param_id"
+                                                                    :name         "NAME"
+                                                                    :display_name "Name"
+                                                                    :type         :dimension
+                                                                    :dimension    [:field (mt/id :venues :name) nil]
+                                                                    :required     true}}}}
+                                :name       "native card with field filter"
+                                :parameters [{:id     "name_param_id",
+                                              :type   :string/=,
+                                              :target [:dimension [:template-tag "NAME"]],
+                                              :name   "Name",
+                                              :slug   "NAME"}]}]
+      Card [card        (merge
+                         {:database_id   (mt/id)
+                          :dataset_query (mt/mbql-query venues)
+                          :parameters    [{:name                 "Static Category",
+                                           :slug                 "static_category"
+                                           :id                   "_STATIC_CATEGORY_",
+                                           :type                 "category",
+                                           :values_source_type   "static-list"
+                                           :values_source_config {:values ["African" "American" "Asian"]}}
+                                          {:name                 "Static Category label",
+                                           :slug                 "static_category_label"
+                                           :id                   "_STATIC_CATEGORY_LABEL_",
+                                           :type                 "category",
+                                           :values_source_type   "static-list"
+                                           :values_source_config {:values [["African" "Af"] ["American" "Am"] ["Asian" "As"]]}}
+                                          {:name                 "Card as source"
+                                           :slug                 "card"
+                                           :id                   "_CARD_"
+                                           :type                 "category"
+                                           :values_source_type   "card"
+                                           :values_source_config {:card_id     (:id source-card)
+                                                                  :value_field (mt/$ids $venues.name)}}]
+                          :table_id      (mt/id :venues)}
+                         card-values)]]
+     (f {:source-card       source-card
+         :card              card
+         :field-filter-card field-filter-card
+         :param-keys        {:static-list       "_STATIC_CATEGORY_"
+                             :static-list-label "_STATIC_CATEGORY_LABEL_"
+                             :card              "_CARD_"
+                             :field-values      "name_param_id"}}))))
+
+(defmacro with-card-param-values-fixtures
+  "Execute `body` with all needed setup to tests param values on card."
+  [[binding card-values] & body]
+  `(do-with-card-param-values-fixtures ~card-values (fn [~binding] ~@body)))
+
+(deftest paramters-using-old-style-field-values
+  (with-card-param-values-fixtures [{:keys [param-keys field-filter-card]}]
+    (testing "GET /api/card/:card-id/params/:param-key/values for field-filter based params"
+      (testing "without search query"
+        (let [response (mt/user-http-request :rasta :get 200
+                                             (param-values-url field-filter-card (:field-values param-keys)))]
+          (is (false? (:has_more_values response)))
+          (is (set/subset? #{["20th Century Cafe"] ["33 Taps"]}
+                           (-> response :values set)))))
+      (testing "with search query"
+        (let [response (mt/user-http-request :rasta :get 200
+                                             (param-values-url field-filter-card
+                                                               (:field-values param-keys)
+                                                               "bar"))]
+          (is (set/subset? #{["Barney's Beanery"] ["bigmista's barbecue"]}
+                           (-> response :values set)))
+          (is (not ((into #{} (mapcat identity) (:values response)) "The Virgil"))))))))
+
+(deftest parameters-with-source-is-card-test
+  (with-card-param-values-fixtures [{:keys [card param-keys]}]
+    (testing "GET /api/card/:card-id/params/:param-key/values"
+      (is (=? {:values          ["Red Medicine"
+                                 "Stout Burgers & Beers"
+                                 "The Apple Pan"
+                                 "Wurstküche"
+                                 "Brite Spot Family Restaurant"]
+               :has_more_values false}
+              (mt/user-http-request :rasta :get 200 (param-values-url card (:card param-keys))))))
+
+    (testing "GET /api/card/:card-id/params/:param-key/search/:query"
+      (is (= {:values          ["Red Medicine"]
+              :has_more_values false}
+             (mt/user-http-request :rasta :get 200 (param-values-url card (:card param-keys) "red")))))))
+
+(deftest parameters-with-source-is-static-list-test
+  (with-card-param-values-fixtures [{:keys [card param-keys]}]
+    (testing "we could get the values"
+      (is (= {:has_more_values false,
+              :values          ["African" "American" "Asian"]}
+             (mt/user-http-request :rasta :get 200
+                                   (param-values-url card (:static-list param-keys)))))
+
+      (is (= {:has_more_values false,
+              :values          [["African" "Af"] ["American" "Am"] ["Asian" "As"]]}
+             (mt/user-http-request :rasta :get 200
+                                   (param-values-url card (:static-list-label param-keys))))))
+
+    (testing "we could search the values"
+      (is (= {:has_more_values false,
+              :values          ["African"]}
+             (mt/user-http-request :rasta :get 200
+                                   (param-values-url card (:static-list param-keys) "af"))))
+
+      (is (= {:has_more_values false,
+              :values          [["African" "Af"]]}
+             (mt/user-http-request :rasta :get 200
+                                   (param-values-url card (:static-list-label param-keys) "af")))))
+
+    (testing "we could edit the values list"
+      (let [card (mt/user-http-request :rasta :put 200 (str "card/" (:id card))
+                                       {:parameters [{:name                  "Static Category",
+                                                      :slug                  "static_category"
+                                                      :id                    "_STATIC_CATEGORY_",
+                                                      :type                  "category",
+                                                      :values_source_type    "static-list"
+                                                      :values_source_config {"values" ["BBQ" "Bakery" "Bar"]}}]})]
+        (is (= [{:name                  "Static Category",
+                 :slug                  "static_category"
+                 :id                    "_STATIC_CATEGORY_",
+                 :type                  "category",
+                 :values_source_type    "static-list"
+                 :values_source_config {:values ["BBQ" "Bakery" "Bar"]}}]
+               (:parameters card)))))))
diff --git a/test/metabase/api/dashboard_test.clj b/test/metabase/api/dashboard_test.clj
index 0e855a0cc1d..1103992fe84 100644
--- a/test/metabase/api/dashboard_test.clj
+++ b/test/metabase/api/dashboard_test.clj
@@ -1694,7 +1694,11 @@
    (do-with-chain-filter-fixtures nil f))
 
   ([dashboard-values f]
-   (mt/with-temp* [Dashboard     [dashboard (merge {:parameters [{:name "Category Name"
+   (mt/with-temp* [Card          [{source-card-id         :id}
+                                  (merge (mt/card-with-source-metadata-for-query (mt/mbql-query categories {:limit 5}))
+                                         {:database_id     (mt/id)
+                                          :table_id        (mt/id :venues)})]
+                   Dashboard     [dashboard (merge {:parameters [{:name "Category Name"
                                                                   :slug "category_name"
                                                                   :id   "_CATEGORY_NAME_"
                                                                   :type "category"}
@@ -1721,7 +1725,13 @@
                                                                   :id                    "_STATIC_CATEGORY_LABEL_",
                                                                   :type                  "category",
                                                                   :values_source_type    "static-list"
-                                                                  :values_source_config {:values [["African" "Af"] ["American" "Am"] ["Asian" "As"]]}}]}
+                                                                  :values_source_config {:values [["African" "Af"] ["American" "Am"] ["Asian" "As"]]}}
+                                                                 {:id                   "_CARD_"
+                                                                  :type                 "category"
+                                                                  :name                 "CATEGORY"
+                                                                  :values_source_type   "card"
+                                                                  :values_source_config {:card_id     source-card-id
+                                                                                         :value_field (mt/$ids $venues.name)}}]}
                                                    dashboard-values)]
                    Card          [card {:database_id   (mt/id)
                                         :table_id      (mt/id :venues)
@@ -1757,7 +1767,8 @@
                       :price                 "_PRICE_"
                       :id                    "_ID_"
                       :static-category       "_STATIC_CATEGORY_"
-                      :static-category-label "_STATIC_CATEGORY_LABEL_"}}))))
+                      :static-category-label "_STATIC_CATEGORY_LABEL_"
+                      :card                  "_CARD_"}}))))
 
 (defmacro with-chain-filter-fixtures [[binding dashboard-values] & body]
   `(do-with-chain-filter-fixtures ~dashboard-values (fn [~binding] ~@body)))
@@ -2047,82 +2058,20 @@
                     :has_more_values false}
                    (mt/user-http-request :rasta :get 200 url)))))))))
 
-(defn- card-fields-from-table-metadata
-  [card-id]
-  (:fields (mt/user-http-request :rasta :get 200 (format "/table/card__%d/query_metadata" card-id))))
-
 (deftest parameter-values-from-card-test
   ;; TODO add permissions tests
-  (testing "getting values"
-    (mt/with-temp*
-      [Card      [{card-id         :id}
-                  (merge (mt/card-with-source-metadata-for-query (mt/mbql-query venues {:limit 5}))
-                         {:database_id     (mt/id)
-                          :table_id        (mt/id :venues)})]
-       Dashboard [{dashboard-id :id}
-                  {:parameters [{:id                   "abc"
-                                 :type                 "category"
-                                 :name                 "CATEGORY"
-                                 :values_source_type   "card"
-                                 :values_source_config {:card_id     card-id
-                                                        :value_field (mt/$ids $venues.name)}}]}]]
-
-      (testing "It uses the results of the card's query execution"
-        (let-url [url (chain-filter-values-url dashboard-id "abc")]
-          (is (= {:values          ["Red Medicine"
-                                    "Stout Burgers & Beers"
-                                    "The Apple Pan"
-                                    "Wurstküche"
-                                    "Brite Spot Family Restaurant"]
-                  :has_more_values false}
-                 (mt/user-http-request :rasta :get 200 url)))))
+  (with-chain-filter-fixtures [{:keys [dashboard param-keys]}]
+    (testing "It uses the results of the card's query execution"
+      (let-url [url (chain-filter-values-url dashboard (:card param-keys))]
+        (is (= {:values          ["African" "American" "Artisan" "Asian" "BBQ"]
+                :has_more_values false}
+               (mt/user-http-request :rasta :get 200 url)))))
 
-      (testing "it only returns search matches"
-        (let-url [url (chain-filter-search-url dashboard-id "abc" "red")]
-          (is (= {:values          ["Red Medicine"]
-                  :has_more_values false}
-                 (mt/user-http-request :rasta :get 200 url))))))
-
-    (testing "field selection should compatible with field-id from /api/table/:card__id/query_metadata"
-      ;; FE use the id returned by /api/table/:card__id/query_metadata
-      ;; for the `values_source_config.value_field`, so we need to test to make sure
-      ;; the id is a valid field that we could use to retrieve values.
-      (mt/with-temp*
-        ;; card with agggregation and binning columns
-        [Card [{mbql-card-id :id}
-               (merge (mt/card-with-source-metadata-for-query
-                        (mt/mbql-query venues {:limit 5
-                                               :aggregation [:count]
-                                               :breakout [[:field %latitude {:binning {:strategy :num-bins :num-bins 10}}]]}))
-                      {:name        "MBQL question"
-                       :database_id (mt/id)
-                       :table_id    (mt/id :venues)})]
-         Card [{native-card-id :id}
-               (merge (mt/card-with-source-metadata-for-query
-                        (mt/native-query {:query "select name from venues;"}))
-                      {:name        "Native question"
-                       :database_id (mt/id)
-                       :table_id    (mt/id :venues)})]]
-
-        (let [mbql-card-fields   (card-fields-from-table-metadata mbql-card-id)
-              native-card-fields (card-fields-from-table-metadata native-card-id)
-              fields->parameter  (fn [fields card-id]
-                                   (for [{:keys [id field_ref name]} fields]
-                                     {:id                   (format "id_%s" name)
-                                      :type                 "category"
-                                      :name                 name
-                                      :values_source_type   "card"
-                                      :values_source_config {:card_id     card-id
-                                                             :value_field (if (number? id)
-                                                                            field_ref
-                                                                            id)}}))
-              parameters         (concat
-                                   (fields->parameter mbql-card-fields mbql-card-id)
-                                   (fields->parameter native-card-fields native-card-id))]
-          (mt/with-temp Dashboard [{dash-id :id} {:parameters parameters}]
-            (doseq [param parameters]
-              (let-url [url (chain-filter-values-url dash-id (:id param))]
-                (is (some? (mt/user-http-request :rasta :get 200 url)))))))))))
+    (testing "it only returns search matches"
+      (let-url [url (chain-filter-search-url dashboard (:card param-keys) "af")]
+        (is (= {:values          ["African"]
+                :has_more_values false}
+               (mt/user-http-request :rasta :get 200 url)))))))
 
 (deftest valid-filter-fields-test
   (testing "GET /api/dashboard/params/valid-filter-fields"
diff --git a/test/metabase/api/embed_test.clj b/test/metabase/api/embed_test.clj
index 1cdf1078008..ab6aa2004bf 100644
--- a/test/metabase/api/embed_test.clj
+++ b/test/metabase/api/embed_test.clj
@@ -5,10 +5,12 @@
    [buddy.sign.util :as buddy-util]
    [clj-time.core :as time]
    [clojure.data.csv :as csv]
+   [clojure.set :as set]
    [clojure.string :as str]
    [clojure.test :refer :all]
    [crypto.random :as crypto-random]
    [dk.ative.docjure.spreadsheet :as spreadsheet]
+   [metabase.api.card-test :as api.card-test]
    [metabase.api.dashboard-test :as api.dashboard-test]
    [metabase.api.embed :as api.embed]
    [metabase.api.pivots :as api.pivots]
@@ -644,6 +646,56 @@
            (db/update! Card (u/the-id card) :enable_embedding false)
            (client/client :get 400 (field-values-url card (mt/id :venues :name)))))))
 
+(deftest card-param-values
+  (letfn [(search [card param-key prefix]
+            (client/client :get 200 (format "embed/card/%s/params/%s/search/%s"
+                                            (card-token card) param-key prefix)))
+          (dropdown [card param-key]
+            (client/client :get 200 (format "embed/card/%s/params/%s/values"
+                                            (card-token card) param-key)))]
+    (mt/with-temporary-setting-values [enable-embedding true]
+      (with-new-secret-key
+        (api.card-test/with-card-param-values-fixtures [{:keys [card field-filter-card param-keys]}]
+          (db/update! Card (:id field-filter-card)
+                      {:enable_embedding true
+                       :embedding_params (zipmap (map :slug (:parameters field-filter-card))
+                                                 (repeat "enabled"))})
+          (db/update! Card (:id card)
+                      {:enable_embedding true
+                       :embedding_params (zipmap (map :slug (:parameters card))
+                                                 (repeat "enabled"))})
+          (testing "field filter based param"
+            (let [response (dropdown field-filter-card (:field-values param-keys))]
+              (is (false? (:has_more_values response)))
+              (is (set/subset? #{["20th Century Cafe"] ["33 Taps"]}
+                               (-> response :values set))))
+            (let [response (search field-filter-card (:field-values param-keys) "bar")]
+              (is (set/subset? #{["Barney's Beanery"] ["bigmista's barbecue"]}
+                               (-> response :values set)))
+              (is (not ((into #{} (mapcat identity) (:values response)) "The Virgil")))))
+          (testing "static based param"
+            (let [response (dropdown card (:static-list param-keys))]
+              (is (= {:has_more_values false,
+                      :values          ["African" "American" "Asian"]}
+                     response)))
+            (let [response (search card (:static-list param-keys) "af")]
+              (is (= {:has_more_values false,
+                      :values          ["African"]}
+                     response))))
+          (testing "card based param"
+            (let [response (dropdown card (:card param-keys))]
+              (is (= {:values          ["Red Medicine"
+                                        "Stout Burgers & Beers"
+                                        "The Apple Pan"
+                                        "Wurstküche"
+                                        "Brite Spot Family Restaurant"]
+                      :has_more_values false}
+                     response)))
+            (let [response (search card (:card param-keys) "red")]
+              (is (= {:has_more_values false,
+                      :values          ["Red Medicine"]}
+                     response)))))))))
+
 ;;; ----------------------------- GET /api/embed/dashboard/:token/field/:field/values nil -----------------------------
 
 (defn- do-with-embedding-enabled-and-temp-dashcard-referencing {:style/indent 2} [table-kw field-kw f]
diff --git a/test/metabase/api/public_test.clj b/test/metabase/api/public_test.clj
index 049108b040d..3665e3cbaac 100644
--- a/test/metabase/api/public_test.clj
+++ b/test/metabase/api/public_test.clj
@@ -6,6 +6,7 @@
    [clojure.test :refer :all]
    [dk.ative.docjure.spreadsheet :as spreadsheet]
    [metabase.actions.test-util :as actions.test-util]
+   [metabase.api.card-test :as api.card-test]
    [metabase.api.dashboard-test :as api.dashboard-test]
    [metabase.api.pivots :as api.pivots]
    [metabase.api.public :as api.public]
@@ -1009,65 +1010,137 @@
 
 ;;; --------------------------------------------- Param values endpoints ---------------------------------------------
 
-(defn- chain-filter-values-url [uuid param-key]
-  (format "public/dashboard/%s/params/%s/values" uuid param-key))
+(defn- param-values-url
+  ([card-or-dashboard uuid param-key]
+   (param-values-url card-or-dashboard uuid param-key nil))
+  ([card-or-dashboard uuid param-key query]
+   (str "public/"
+        (name card-or-dashboard)
+        "/" uuid
+        "/params/" param-key
+        (if query
+          (str "/search/" query)
+          "/values"))))
+
+(deftest param-values-test
+  (mt/with-temporary-setting-values [enable-public-sharing true]
+    (testing "with dashboard"
+      (api.dashboard-test/with-chain-filter-fixtures [{:keys [dashboard param-keys]}]
+        (let [uuid (str (UUID/randomUUID))]
+          (is (= true
+                 (db/update! Dashboard (u/the-id dashboard) :public_uuid uuid)))
+          (testing "GET /api/public/dashboard/:uuid/params/:param-key/values"
+            (testing "parameter with source is a static list"
+              (is (= {:values          ["African" "American" "Asian"]
+                      :has_more_values false}
+                     (client/client :get 200 (param-values-url :dashboard uuid (:static-category param-keys))))))
 
-(defn- chain-filter-search-url [uuid param-key query]
-  (format "public/dashboard/%s/params/%s/search/%s" uuid param-key query))
+            (testing "parameter with source is card"
+              (is (= {:values          ["African" "American" "Artisan" "Asian" "BBQ"]
+                      :has_more_values false}
+                     (client/client :get 200 (param-values-url :dashboard uuid (:card param-keys))))))
 
-(deftest chain-filter-test
-  (mt/with-temporary-setting-values [enable-public-sharing true]
-    (api.dashboard-test/with-chain-filter-fixtures [{:keys [dashboard param-keys]}]
-      (let [uuid (str (UUID/randomUUID))]
-        (is (= true
-               (db/update! Dashboard (u/the-id dashboard) :public_uuid uuid)))
-        (testing "GET /api/public/dashboard/:uuid/params/:param-key/values"
-          (is (= {:values          [2 3 4 5 6]
-                  :has_more_values false}
-                 (->> (client/client :get 200 (chain-filter-values-url uuid (:category-id param-keys)))
-                      (chain-filter-test/take-n-values 5)))))
-        (testing "GET /api/public/dashboard/:uuid/params/:param-key/search/:query"
-          (is (= {:values          ["Fast Food" "Food Truck" "Seafood"]
-                  :has_more_values false}
-                 (->> (client/client :get 200 (chain-filter-search-url uuid (:category-name param-keys) "food"))
-                      (chain-filter-test/take-n-values 3)))))))))
-
-(deftest chain-filter-ignore-current-user-permissions-test
-  (testing "Should not fail if request is authenticated but current user does not have data permissions"
-    (mt/with-temp-copy-of-db
-      (perms/revoke-data-perms! (perms-group/all-users) (mt/db))
-      (mt/with-temporary-setting-values [enable-public-sharing true]
-        (api.dashboard-test/with-chain-filter-fixtures [{:keys [dashboard param-keys]}]
-          (let [uuid (str (UUID/randomUUID))]
-            (is (= true
-                   (db/update! Dashboard (u/the-id dashboard) :public_uuid uuid)))
-            (testing "GET /api/public/dashboard/:uuid/params/:param-key/values"
+            (testing "parameter with source is chain filter"
               (is (= {:values          [2 3 4 5 6]
                       :has_more_values false}
-                     (->> (mt/user-http-request :rasta :get 200 (chain-filter-values-url uuid (:category-id param-keys)))
-                          (chain-filter-test/take-n-values 5)))))
-            (testing "GET /api/public/dashboard/:uuid/params/:param-key/search/:prefix"
+                     (->> (client/client :get 200 (param-values-url :dashboard uuid (:category-id param-keys)))
+                          (chain-filter-test/take-n-values 5))))))
+
+          (testing "GET /api/public/dashboard/:uuid/params/:param-key/search/:query"
+            (testing "parameter with source is a static list"
+              (is (= {:values          ["African"]
+                      :has_more_values false}
+                     (client/client :get 200 (param-values-url :dashboard uuid (:static-category param-keys) "af")))))
+
+            (testing "parameter with source is card"
+              (is (= {:values          ["African"]
+                      :has_more_values false}
+                     (client/client :get 200 (param-values-url :dashboard uuid (:card param-keys) "af")))))
+
+            (testing "parameter with source is a chain filter"
               (is (= {:values          ["Fast Food" "Food Truck" "Seafood"]
                       :has_more_values false}
-                     (->> (mt/user-http-request :rasta :get 200 (chain-filter-search-url uuid (:category-name param-keys) "food"))
-                          (chain-filter-test/take-n-values 3)))))))))))
+                     (->> (client/client :get 200 (param-values-url :dashboard uuid (:category-name param-keys) "food"))
+                          (chain-filter-test/take-n-values 3)))))))))
+
+    (testing "with card"
+      (api.card-test/with-card-param-values-fixtures [{:keys [card param-keys]}]
+        (let [uuid (str (UUID/randomUUID))]
+          (is (= true
+                 (db/update! Card (u/the-id card) :public_uuid uuid)))
+          (testing "GET /api/public/card/:uuid/params/:param-key/values"
+            (testing "parameter with source is a static list"
+              (is (= {:values          ["African" "American" "Asian"]
+                      :has_more_values false}
+                     (client/client :get 200 (param-values-url :card uuid (:static-list param-keys))))))
+
+            (testing "parameter with source is a card"
+              (is (= {:values          ["Red Medicine"
+                                        "Stout Burgers & Beers"
+                                        "The Apple Pan"
+                                        "Wurstküche"
+                                        "Brite Spot Family Restaurant"]
+                      :has_more_values false}
+                     (client/client :get 200 (param-values-url :card uuid (:card param-keys)))))))
 
-(deftest params-with-static-list-test
-  (mt/with-temporary-setting-values [enable-public-sharing true]
-    (api.dashboard-test/with-chain-filter-fixtures [{:keys [dashboard param-keys]}]
-      (let [uuid (str (UUID/randomUUID))]
-        (is (= true
-               (db/update! Dashboard (u/the-id dashboard) :public_uuid uuid)))
-        (testing "GET /api/public/dashboard/:uuid/params/:param-key/values"
-          (is (= {:values          ["African" "American" "Asian"]
-                  :has_more_values false}
-                 (->> (client/client :get 200 (chain-filter-values-url uuid (:static-category param-keys)))
-                      (chain-filter-test/take-n-values 5)))))
-        (testing "GET /api/public/dashboard/:uuid/params/:param-key/search/:query"
-          (is (= {:values          [["African" "Af"]]
-                  :has_more_values false}
-                 (->> (client/client :get 200 (chain-filter-search-url uuid (:static-category-label param-keys) "af"))
-                      (chain-filter-test/take-n-values 3)))))))))
+          (testing "GET /api/public/card/:uuid/params/:param-key/search/:query"
+            (testing "parameter with source is a static list"
+              (is (= {:values          ["African"]
+                      :has_more_values false}
+                     (client/client :get 200 (param-values-url :card uuid (:static-list param-keys) "af")))))
+
+            (testing "parameter with source is a card"
+              (is (= {:values          ["Red Medicine"]
+                      :has_more_values false}
+                     (client/client :get 200 (param-values-url :card uuid (:card param-keys) "red")))))))))))
+
+(deftest param-values-ignore-current-user-permissions-test
+  (testing "Should not fail if request is authenticated but current user does not have data permissions"
+    (mt/with-temp-copy-of-db
+      (perms/revoke-data-perms! (perms-group/all-users) (mt/db))
+      (mt/with-temporary-setting-values [enable-public-sharing true]
+        (testing "with dashboard"
+          (api.dashboard-test/with-chain-filter-fixtures [{:keys [dashboard param-keys]}]
+            (let [uuid (str (UUID/randomUUID))]
+              (is (= true
+                     (db/update! Dashboard (u/the-id dashboard) :public_uuid uuid)))
+              (testing "GET /api/public/dashboard/:uuid/params/:param-key/values"
+                (is (= {:values          [2 3 4 5 6]
+                        :has_more_values false}
+                       (->> (mt/user-http-request :rasta :get 200 (param-values-url :dashboard uuid (:category-id param-keys)))
+                            (chain-filter-test/take-n-values 5)))))
+              (testing "GET /api/public/dashboard/:uuid/params/:param-key/search/:prefix"
+                (is (= {:values          ["Fast Food" "Food Truck" "Seafood"]
+                        :has_more_values false}
+                       (->> (mt/user-http-request :rasta :get 200 (param-values-url :dashboard uuid (:category-name param-keys) "food"))
+                            (chain-filter-test/take-n-values 3))))))))
+
+        (testing "with card"
+          (api.card-test/with-card-param-values-fixtures [{:keys [card param-keys]}]
+            (let [uuid (str (UUID/randomUUID))]
+             (is (= true
+                    (db/update! Card (u/the-id card) :public_uuid uuid)))
+             (testing "GET /api/public/card/:uuid/params/:param-key/values"
+               (is (= {:values          ["African" "American" "Asian"]
+                       :has_more_values false}
+                      (client/client :get 200 (param-values-url :card uuid (:static-list param-keys)))))
+
+               (is (= {:values          ["Red Medicine"
+                                         "Stout Burgers & Beers"
+                                         "The Apple Pan"
+                                         "Wurstküche"
+                                         "Brite Spot Family Restaurant"]
+                       :has_more_values false}
+                      (client/client :get 200 (param-values-url :card uuid (:card param-keys))))))
+
+             (testing "GET /api/public/card/:uuid/params/:param-key/search/:query"
+               (is (= {:values          ["African"]
+                       :has_more_values false}
+                      (client/client :get 200 (param-values-url :card uuid (:static-list param-keys) "af"))))
+
+               (is (= {:values          ["Red Medicine"]
+                       :has_more_values false}
+                      (client/client :get 200 (param-values-url :card uuid (:card param-keys) "red"))))))))))))
 
 ;;; --------------------------------------------- Pivot tables ---------------------------------------------
 
diff --git a/test/metabase/models/card_test.clj b/test/metabase/models/card_test.clj
index 7dcd52cb98d..23c9ad070d9 100644
--- a/test/metabase/models/card_test.clj
+++ b/test/metabase/models/card_test.clj
@@ -2,7 +2,8 @@
   (:require
    [cheshire.core :as json]
    [clojure.test :refer :all]
-   [metabase.models :refer [Card Collection Dashboard DashboardCard]]
+   [metabase.models
+    :refer [Card Collection Dashboard DashboardCard NativeQuerySnippet]]
    [metabase.models.card :as card]
    [metabase.models.serialization.base :as serdes.base]
    [metabase.models.serialization.hash :as serdes.hash]
@@ -357,6 +358,63 @@
                (serdes.hash/raw-hash ["the card" (serdes.hash/identity-hash coll) now])
                (serdes.hash/identity-hash card)))))))
 
+(deftest parameter-card-test
+  (let [default-params {:name       "Category Name"
+                        :slug       "category_name"
+                        :id         "_CATEGORY_NAME_"
+                        :type       "category"}]
+    (testing "parameter with source is card create ParameterCard"
+      (tt/with-temp* [Card  [{source-card-id-1 :id}]
+                      Card  [{source-card-id-2 :id}]
+                      Card  [{card-id :id}
+                             {:parameters [(merge default-params
+                                                  {:values_source_type    "card"
+                                                   :values_source_config {:card_id source-card-id-1}})]}]]
+        (is (=? [{:card_id                   source-card-id-1
+                  :parameterized_object_type :card
+                  :parameterized_object_id   card-id
+                  :parameter_id              "_CATEGORY_NAME_"}]
+                (db/select 'ParameterCard :parameterized_object_type "card" :parameterized_object_id card-id)))
+
+        (testing "update values_source_config.card_id will update ParameterCard"
+          (db/update! Card card-id {:parameters [(merge default-params
+                                                        {:values_source_type    "card"
+                                                         :values_source_config {:card_id source-card-id-2}})]})
+          (is (=? [{:card_id                   source-card-id-2
+                    :parameterized_object_type :card
+                    :parameterized_object_id   card-id
+                    :parameter_id              "_CATEGORY_NAME_"}]
+                  (db/select 'ParameterCard :parameterized_object_type "card" :parameterized_object_id card-id))))
+
+        (testing "delete the card will delete ParameterCard"
+          (db/delete! Card :id card-id)
+          (is (= []
+                 (db/select 'ParameterCard :parameterized_object_type "card" :parameterized_object_id card-id))))))
+
+    (testing "Delete a card will delete any ParameterCard that linked to it"
+      (tt/with-temp* [Card  [{source-card-id :id}]
+                      Card  [{card-id-1 :id}
+                             {:parameters [(merge default-params
+                                                  {:values_source_type    "card"
+                                                   :values_source_config {:card_id source-card-id}})]}]
+                      Card  [{card-id-2 :id}
+                             {:parameters [(merge default-params
+                                                  {:values_source_type    "card"
+                                                   :values_source_config {:card_id source-card-id}})]}]]
+        ;; makes sure we have ParameterCard to start with
+        (is (=? [{:card_id                   source-card-id
+                  :parameterized_object_type :card
+                  :parameterized_object_id   card-id-1
+                  :parameter_id              "_CATEGORY_NAME_"}
+                 {:card_id                   source-card-id
+                  :parameterized_object_type :card
+                  :parameterized_object_id   card-id-2
+                  :parameter_id              "_CATEGORY_NAME_"}]
+                (db/select 'ParameterCard :card_id source-card-id)))
+        (db/delete! Card :id source-card-id)
+        (is (= []
+               (db/select 'ParameterCard :card_id source-card-id)))))))
+
 (deftest serdes-descendants-test
   (testing "regular cards don't depend on anything"
     (mt/with-temp* [Card [card {:name "some card"}]]
@@ -367,6 +425,28 @@
                     Card [card2 {:name "derived card"
                                  :dataset_query {:query {:source-table (str "card__" (:id card1))}}}]]
       (is (empty? (serdes.base/serdes-descendants "Card" (:id card1))))
+      (is (= #{["Card" (:id card1)]}
+             (serdes.base/serdes-descendants "Card" (:id card2))))))
+
+  (testing "cards that has a native template tag"
+    (mt/with-temp* [NativeQuerySnippet [snippet {:name "category" :content "category = 'Gizmo'"}]
+                    Card               [card {:name          "Business Card"
+                                              :dataset_query {:native
+                                                              {:template-tags {:snippet {:name         "snippet"
+                                                                                         :type         :snippet
+                                                                                         :snippet-name "snippet"
+                                                                                         :snippet-id   (:id snippet)}}
+                                                               :query "select * from products where {{snippet}}"}}}]]
+      (is (= #{["NativeQuerySnippet" (:id snippet)]}
+             (serdes.base/serdes-descendants "Card" (:id card))))))
+
+  (testing "cards which have parameter's source is another card"
+    (mt/with-temp* [Card [card1 {:name "base card"}]
+                    Card [card2 {:name       "derived card"
+                                 :parameters [{:id                   "valid-id"
+                                               :type                 "id"
+                                               :values_source_type   "card"
+                                               :values_source_config {:card_id (:id card1)}}]}]]
       (is (= #{["Card" (:id card1)]}
              (serdes.base/serdes-descendants "Card" (:id card2)))))))
 
diff --git a/test/metabase/models/dashboard_test.clj b/test/metabase/models/dashboard_test.clj
index c73b5eeac8b..62f34844955 100644
--- a/test/metabase/models/dashboard_test.clj
+++ b/test/metabase/models/dashboard_test.clj
@@ -11,11 +11,13 @@
     :refer [DashboardCard]]
    [metabase.models.dashboard-card-series :refer [DashboardCardSeries]]
    [metabase.models.database :refer [Database]]
+   [metabase.models.field :refer [Field]]
    [metabase.models.interface :as mi]
    [metabase.models.permissions :as perms]
    [metabase.models.pulse :refer [Pulse]]
    [metabase.models.pulse-card :refer [PulseCard]]
    [metabase.models.revision :as revision]
+   [metabase.models.serialization.base :as serdes.base]
    [metabase.models.serialization.hash :as serdes.hash]
    [metabase.models.table :refer [Table]]
    [metabase.models.user :as user]
@@ -408,3 +410,32 @@
         (is (= "8cbf93b7"
                (serdes.hash/raw-hash ["my dashboard" (serdes.hash/identity-hash c1) now])
                (serdes.hash/identity-hash dash)))))))
+
+(deftest serdes-descendants-test
+  (testing "dashboard which have parameter's source is another card"
+    (mt/with-temp* [Field     [field     {:name "A field"}]
+                    Card      [card      {:name "A card"}]
+                    Dashboard [dashboard {:name       "A dashboard"
+                                          :parameters [{:id "abc"
+                                                        :type "category"
+                                                        :values_source_type "card"
+                                                        :values_source_config {:card_id     (:id card)
+                                                                               :value_field [:field (:id field) nil]}}]}]]
+      (is (= #{["Card" (:id card)]}
+             (serdes.base/serdes-descendants "Dashboard" (:id dashboard))))))
+
+  (testing "dashboard in which its dashcards has parameter_mappings to a card"
+    (mt/with-temp* [Card          [card1     {:name "Card attached to dashcard"}]
+                    Card          [card2     {:name "Card attached to parameters"}]
+                    Dashboard     [dashboard {:parameters [{:name "Category Name"
+                                                            :slug "category_name"
+                                                            :id   "_CATEGORY_NAME_"
+                                                            :type "category"}]}]
+                    DashboardCard [_         {:card_id            (:id card1)
+                                              :dashboard_id       (:id dashboard)
+                                              :parameter_mappings [{:parameter_id "_CATEGORY_NAME_"
+                                                                    :card_id      (:id card2)
+                                                                    :target       [:dimension (mt/$ids $categories.name)]}]}]]
+      (is (= #{["Card" (:id card1)]
+               ["Card" (:id card2)]}
+             (serdes.base/serdes-descendants "Dashboard" (:id dashboard)))))))
-- 
GitLab