Adds a filter clause with simple operators like Entry points:
Requirements:
Query transformation:
Question transformation:
There is a separate function | (ns metabase.lib.drill-thru.quick-filter (:require [medley.core :as m] [metabase.lib.drill-thru.column-filter :as lib.drill-thru.column-filter] [metabase.lib.drill-thru.common :as lib.drill-thru.common] [metabase.lib.filter :as lib.filter] [metabase.lib.options :as lib.options] [metabase.lib.ref :as lib.ref] [metabase.lib.schema :as lib.schema] [metabase.lib.schema.common :as lib.schema.common] [metabase.lib.schema.drill-thru :as lib.schema.drill-thru] [metabase.lib.schema.expression :as lib.schema.expression] [metabase.lib.schema.metadata :as lib.schema.metadata] [metabase.lib.temporal-bucket :as lib.temporal-bucket] [metabase.lib.types.isa :as lib.types.isa] [metabase.lib.underlying :as lib.underlying] [metabase.util.malli :as mu])) |
(defn- operator [op & args] (lib.options/ensure-uuid (into [op {}] args))) | |
(mu/defn- operators-for :- [:sequential ::lib.schema.drill-thru/drill-thru.quick-filter.operator] [column :- ::lib.schema.metadata/column value] (let [field-ref (cond-> (lib.ref/ref column) (:temporal-unit column) (lib.temporal-bucket/with-temporal-bucket (:temporal-unit column)))] (cond (lib.types.isa/structured? column) [] (= value :null) (for [[op label] (if (or (lib.types.isa/string? column) (lib.types.isa/string-like? column)) [[:is-empty "="] [:not-empty "≠"]] [[:is-null "="] [:not-null "≠"]])] {:name label :filter (operator op field-ref)}) (or (lib.types.isa/numeric? column) (lib.types.isa/temporal? column)) (for [[op label] [[:< "<"] [:> ">"] [:= "="] [:!= "≠"]] :when (or (not (#{:< :>} op)) (lib.schema.expression/comparable-expressions? field-ref value))] {:name label :filter (operator op field-ref value)}) (and (lib.types.isa/string? column) (or (lib.types.isa/comment? column) (lib.types.isa/description? column))) (for [[op label] [[:contains "contains"] [:does-not-contain "does-not-contain"]]] {:name label :filter (operator op field-ref value)}) :else (for [[op label] [[:= "="] [:!= "≠"]]] {:name label :filter (operator op field-ref value)})))) | |
(mu/defn quick-filter-drill :- [:maybe ::lib.schema.drill-thru/drill-thru.quick-filter] "Filter the current query based on the value clicked. The options vary depending on the type of the field: - `:is-null` and `:not-null` for a `NULL` value; - `:=` and `:!=` for everything else; - plus `:<` and `:>` for numeric and date columns. Note that this returns a single `::drill-thru` value with 1 or more `:operators`; these are rendered as a set of small buttons in a single row of the drop-down." [query :- ::lib.schema/query stage-number :- :int {:keys [column column-ref dimensions value], :as _context} :- ::lib.schema.drill-thru/context] (when (and (lib.drill-thru.common/mbql-stage? query stage-number) column (some? value) ; Deliberately allows value :null, only a missing value should fail this test. ;; If this is an aggregation, there must be breakouts (dimensions). (or (not (lib.underlying/aggregation-sourced? query column)) (seq dimensions)) (not (lib.types.isa/structured? column)) (not (lib.types.isa/primary-key? column)) (not (lib.types.isa/foreign-key? column))) ;; For aggregate columns, we want to introduce a new stage when applying the drill-thru. ;; [[lib.drill-thru.column-filter/prepare-query-for-drill-addition]] handles this. (#34346) (when-let [drill-details (lib.drill-thru.column-filter/prepare-query-for-drill-addition query stage-number column column-ref :filter)] (let [temporal-unit (lib.temporal-bucket/temporal-bucket column-ref) column (cond-> (:column drill-details) temporal-unit (assoc :temporal-unit temporal-unit))] (merge drill-details {:lib/type :metabase.lib.drill-thru/drill-thru :type :drill-thru/quick-filter :operators (operators-for column value) :value value}))))) | |
(defmethod lib.drill-thru.common/drill-thru-info-method :drill-thru/quick-filter [_query _stage-number drill-thru] (-> (select-keys drill-thru [:type :operators :value]) (update :value lib.drill-thru.common/drill-value->js) (update :operators (fn [operators] (mapv :name operators))))) | |
(mu/defmethod lib.drill-thru.common/drill-thru-method :drill-thru/quick-filter :- ::lib.schema/query [_query :- ::lib.schema/query _stage-number :- :int {:keys [query stage-number] :as drill} :- ::lib.schema.drill-thru/drill-thru.quick-filter filter-op :- ::lib.schema.common/non-blank-string] (let [quick-filter (or (m/find-first #(= (:name %) filter-op) (:operators drill)) (throw (ex-info (str "No matching filter for operator " filter-op) {:drill-thru drill :operator filter-op :query query :stage-number stage-number})))] (lib.filter/filter query stage-number (:filter quick-filter)))) | |