There are three things called 'type' in play when we talk about parameters and template tags. Two are used when the parameters are specified/declared, in a [[TemplateTag]] or in a Dashboard parameter:
One type is used in the [[Parameter]] list (
Note that some types that makes sense as widget types (e.g. | (ns metabase.lib.schema.parameter (:require [metabase.lib.schema.common :as lib.schema.common] [metabase.util.malli.registry :as mr])) |
Some clauses, like | (defn- variadic-opts-first
[[tag & args :as clause] options]
(if (<= (count args) 2)
(cond-> clause
options (conj options))
(into [tag (or options {})] args))) |
Map of parameter-type -> info. Info is a map with the following keys: `:type`The general type of this parameter. `:operator`Signifies this is one of the new 'operator' parameter types added in 0.39.0 or so. These parameters can only be used
for [[TemplateTag:FieldFilter]]s or for Dashboard parameters mapped to MBQL queries. The value of this key is the
arity for the parameter, either `:allowed-for`[[Parameter]]s with this `:options-fn`Optional, specifies a function | (def types
{;; the basic raw-value types. These can be used with [[TemplateTag:RawValue]] template tags as well as
;; [[TemplateTag:FieldFilter]] template tags.
:number {:type :numeric, :allowed-for #{:number :number/= :id :category :location/zip_code}}
:text {:type :string, :allowed-for #{:text :string/= :id :category
:location/city :location/state :location/zip_code :location/country}}
:date {:type :date, :allowed-for #{:date :date/single :date/all-options :id :category}}
;; I don't think `:boolean` is actually used on the FE at all.
:boolean {:type :boolean, :allowed-for #{:boolean :id :category}}
;; as far as I can tell this is basically just an alias for `:date`... I'm not sure what the difference is TBH
:date/single {:type :date, :allowed-for #{:date :date/single :date/all-options :id :category}}
;; everything else can't be used with raw value template tags -- they can only be used with Dashboard parameters
;; for MBQL queries or Field filters in native queries
;; `:id` and `:category` conceptually aren't types in a "the parameter value is of this type" sense, but they are
;; widget types. They have something to do with telling the frontend to show FieldValues list/search widgets or
;; something like that.
;;
;; Apparently the frontend might still pass in parameters with these types, in which case we're supposed to infer
;; the actual type of the parameter based on the Field we're filtering on. Or something like that. Parameters with
;; these types are only allowed if the widget type matches exactly, but you can also pass in something like a
;; `:number/=` for a parameter with widget type `:category`.
;;
;; TODO FIXME -- actually, it turns out the the FE client passes parameter type `:category` for parameters in
;; public Cards. Who knows why! For now, we'll continue allowing it. But we should fix it soon. See
;; [[metabase.public-sharing.api-test/execute-public-card-with-parameters-test]]
:id {:allowed-for #{:id}}
:category {:allowed-for #{:category #_FIXME :number :text :date :boolean}}
;; Like `:id` and `:category`, the `:location/*` types are primarily widget types. They don't really have a meaning
;; as a parameter type, so in an ideal world they wouldn't be allowed; however it seems like the FE still passed
;; these in as parameter type on occasion anyway. In this case the backend is just supposed to infer the actual
;; type -- which should be `:text` and, in the case of ZIP code, possibly `:number`.
;;
;; As with `:id` and `:category`, it would be preferable to just pass in a parameter with type `:text` or `:number`
;; for these widget types, but for compatibility we'll allow them to continue to be used as parameter types for the
;; time being. We'll only allow that if the widget type matches exactly, however.
:location/city {:allowed-for #{:location/city}}
:location/state {:allowed-for #{:location/state}}
:location/zip_code {:allowed-for #{:location/zip_code}}
:location/country {:allowed-for #{:location/country}}
;; date range types -- these match a range of dates
:date/range {:type :date, :allowed-for #{:date/range :date/all-options}}
:date/month-year {:type :date, :allowed-for #{:date/month-year :date/all-options}}
:date/quarter-year {:type :date, :allowed-for #{:date/quarter-year :date/all-options}}
:date/relative {:type :date, :allowed-for #{:date/relative :date/all-options}}
;; Like `:id` and `:category` above, `:date/all-options` is primarily a widget type. It means that we should allow
;; any date option above.
:date/all-options {:type :date, :allowed-for #{:date/all-options}}
;; `:temporal-unit` is a specialized type of parameter, and specialized widget. In MBQL queries, it maps only to
;; breakout columns which have temporal bucketing set, and overrides the unit from the query.
;; The value for this type of parameter is one of the temporal units from [[metabase.lib.schema.temporal-bucketing]].
;; TODO: Document how this works for native queries.
:temporal-unit {:allowed-for #{:temporal-unit}}
;; "operator" parameter types.
:number/!= {:type :numeric, :operator :variadic, :allowed-for #{:number/!=}}
:number/<= {:type :numeric, :operator :unary, :allowed-for #{:number/<=}}
:number/= {:type :numeric, :operator :variadic, :allowed-for #{:number/= :number :id :category
:location/zip_code}}
:number/>= {:type :numeric, :operator :unary, :allowed-for #{:number/>=}}
:number/between {:type :numeric, :operator :binary, :allowed-for #{:number/between}}
:string/!= {:type :string, :operator :variadic, :allowed-for #{:string/!=}}
:string/= {:type :string, :operator :variadic, :allowed-for #{:string/= :text :id :category
:location/city :location/state
:location/zip_code :location/country}}
:string/contains {:type :string, :operator :variadic, :options-fn variadic-opts-first, :allowed-for #{:string/contains}}
:string/does-not-contain {:type :string, :operator :variadic, :options-fn variadic-opts-first, :allowed-for #{:string/does-not-contain}}
:string/ends-with {:type :string, :operator :variadic, :options-fn variadic-opts-first, :allowed-for #{:string/ends-with}}
:string/starts-with {:type :string, :operator :variadic, :options-fn variadic-opts-first, :allowed-for #{:string/starts-with}}}) |
(mr/def ::type
(into [:enum {:error/message "valid parameter type"
:decode/normalize lib.schema.common/normalize-keyword}]
(keys types))) | |
(mr/def ::widget-type
(into [:enum
{:error/message "valid template tag widget type"
:decode/normalize lib.schema.common/normalize-keyword}
:none]
(keys types))) | |
the next few clauses are used for parameter examples: {:target [:dimension [:template-tag "my_tag"]]} {:target [:dimension [:template-tag {:id "mytagid"}]]} {:target [:variable [:template-tag "another_tag"]]} {:target [:variable [:template-tag {:id "anothertagid"}]]} {:target [:dimension [:field 100 nil]]} {:target [:field 100 nil]} I'm not 100% clear on which situations we'll get which version. But I think the following is generally true:
One more thing to note: apparently | |
These are all legacy-style MBQL clauses FOR NOW, obviously at some point in the future we need to
update [[metabase.lib.convert]] to convert | |
(mr/def ::legacy-field-ref [:ref :metabase.legacy-mbql.schema/field]) | |
(mr/def ::legacy-expression-ref [:ref :metabase.legacy-mbql.schema/expression]) | |
(mr/def ::dimension.target
[:multi {:dispatch lib.schema.common/mbql-clause-tag
:error/fn (fn [{:keys [value]} _]
(str "Invalid :dimension target: must be either a :field or a :template-tag, got: "
(pr-str value)))}
[:field [:ref ::legacy-field-ref]]
[:expression [:ref ::legacy-expression-ref]]
[:template-tag [:ref ::template-tag]]]) | |
(mr/def ::DimensionOptions
[:map
{:error/message "dimension options"}
[:stage-number {:optional true} :int]]) | |
(mr/def ::dimension
[:catn
[:tag [:= {:decode/normalize lib.schema.common/normalize-keyword} :dimension]]
[:target ::dimension.target]
[:options [:? [:maybe ::DimensionOptions]]]]) | |
this is the reference like [:template-tag | (mr/def ::template-tag
[:tuple
#_tag [:= {:decode/normalize lib.schema.common/normalize-keyword} :template-tag]
#_tag-name [:multi {:dispatch map?}
[true [:map
[:id ::lib.schema.common/non-blank-string]]]
[false ::lib.schema.common/non-blank-string]]]) |
(mr/def ::variable
[:tuple
#_tag [:= {:decode/normalize lib.schema.common/normalize-keyword} :variable]
#_target [:ref ::template-tag]]) | |
(mr/def ::target
[:multi {:dispatch lib.schema.common/mbql-clause-tag
:error/fn (fn [{:keys [value]} _]
(str "Invalid parameter :target, must be either :field, :dimension, or :variable; got: "
(pr-str value)))}
[:field [:ref ::legacy-field-ref]]
[:dimension [:ref ::dimension]]
[:variable [:ref ::variable]]]) | |
(mr/def ::parameter
[:map
[:type [:ref ::type]]
;; TODO -- these definitely SHOULD NOT be optional but a ton of tests aren't passing them in like they should be.
;; At some point we need to go fix those tests and then make these keys required
[:id {:optional true} ::lib.schema.common/non-blank-string]
[:target {:optional true} [:ref ::target]]
;; not specified if the param has no value. TODO - make this stricter; type of `:value` should be validated based
;; on the [[ParameterType]]
[:value {:optional true} :any]
;; the name of the parameter we're trying to set -- this is actually required now I think, or at least needs to get
;; merged in appropriately
[:name {:optional true} ::lib.schema.common/non-blank-string]
;; The following are not used by the code in this namespace but may or may not be specified depending on what the
;; code that constructs the query params is doing. We can go ahead and ignore these when present.
[:slug {:optional true} ::lib.schema.common/non-blank-string]
[:default {:optional true} :any]
[:required {:optional true} :any]]) | |
(mr/def ::parameters [:sequential [:ref ::parameter]]) | |