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.api.public-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]]) | |