Enables "Filter by this column" menu item. The caveat here is that for aggregation and breakout columns we need to append a stage before adding a filter. There
is a helper function called Another caveat is that we need to verify that Entry points:
Requirements:
Query transformation:
Question transformation: - None | (ns metabase.lib.drill-thru.column-filter (:require [medley.core :as m] [metabase.lib.drill-thru.common :as lib.drill-thru.common] [metabase.lib.equality :as lib.equality] [metabase.lib.filter :as lib.filter] [metabase.lib.filter.operator :as lib.filter.operator] [metabase.lib.schema :as lib.schema] [metabase.lib.schema.drill-thru :as lib.schema.drill-thru] [metabase.lib.schema.metadata :as lib.schema.metadata] [metabase.lib.schema.ref :as lib.schema.ref] [metabase.lib.stage :as lib.stage] [metabase.lib.types.isa :as lib.types.isa] [metabase.lib.util :as lib.util] [metabase.util.malli :as mu])) |
(mu/defn prepare-query-for-drill-addition :- [:maybe [:map
[:query ::lib.schema/query]
[:stage-number :int]
[:column lib.filter/ColumnWithOperators]]]
"If the column we're filtering on is an aggregation, the filtering must happen in a later stage. This function returns
a map with that possibly-updated `:query` and `:stage-number`, plus the `:column` for filtering in that stage (with
filter operators, as returned by [[lib.filter/filterable-columns]]).
If the column is an aggregation but the query already has a later stage, that stage is reused.
If the column is not an aggregation, the query and stage-number are returned unchanged, but the
[[lib.filter/filterable-columns]] counterpart of the input `column` is still returned.
This query and filterable column are exactly what the FE needs to render the filtering UI for a column filter drill,
or certain tricky cases of quick filter."
[query :- ::lib.schema/query
stage-number :- :int
column :- ::lib.schema.metadata/column
column-ref :- ::lib.schema.ref/ref
adding :- [:enum :filter :expression]]
(let [next-stage (->> (lib.util/canonical-stage-index query stage-number)
(lib.util/next-stage-number query))
base (cond
;; An extra stage is needed if:
;; - The target column is an aggregation
;; - OR the target column is a breakout AND we are adding a custom expression based on it.
;;
;; So if neither of those cases apply, we can just return the original query and stage index.
(not (or (= (:lib/source column) :source/aggregations)
(and (= (:lib/source column) :source/breakouts)
(= adding :expression))))
{:query query
:stage-number stage-number}
;; An extra stage is needed.
;; If there's a later stage, then use it.
next-stage {:query query
:stage-number next-stage}
;; And if there isn't a later stage, add one.
:else {:query (lib.stage/append-stage query)
:stage-number -1})
columns (lib.filter/filterable-columns (:query base) (:stage-number base))
filter-column (or (lib.equality/find-matching-column
(:query base) (:stage-number base) column-ref columns)
(and (:lib/source-uuid column)
(m/find-first #(= (:lib/source-uuid %) (:lib/source-uuid column))
columns)))]
;; If we cannot find the matching column, don't allow to drill
(when filter-column
(assoc base :column filter-column)))) | |
(mu/defn column-filter-drill :- [:maybe ::lib.schema.drill-thru/drill-thru.column-filter]
"Filtering at the column level, based on its type. Displays a submenu of eg. \"Today\", \"This Week\", etc. for date
columns.
Note that if the clicked column is an aggregation, filtering by it will require a new stage. Therefore this drill
returns a possibly-updated `:query` and `:stage-number` along with a `:column` referencing that later stage."
[query :- ::lib.schema/query
stage-number :- :int
{:keys [column column-ref value]} :- ::lib.schema.drill-thru/context]
(when (and (lib.drill-thru.common/mbql-stage? query stage-number)
column
(nil? value)
(not (lib.types.isa/structured? column)))
;; When the column we would be filtering on is an aggregation, it can't be filtered without adding a stage.
(when-let [drill-details (prepare-query-for-drill-addition query stage-number column column-ref :filter)]
(let [initial-op (when-not (lib.types.isa/temporal? column) ; Date fields have special handling in the FE.
(-> (lib.filter.operator/filter-operators column)
first
(assoc :lib/type :operator/filter)))]
(merge
drill-details
{:lib/type :metabase.lib.drill-thru/drill-thru
:type :drill-thru/column-filter
:initial-op initial-op}))))) | |
(defmethod lib.drill-thru.common/drill-thru-info-method :drill-thru/column-filter
[_query _stage-number {:keys [initial-op]}]
{:type :drill-thru/column-filter
:initial-op initial-op}) | |
(mu/defmethod lib.drill-thru.common/drill-thru-method :drill-thru/column-filter :- ::lib.schema/query
[query :- ::lib.schema/query
stage-number :- :int
{:keys [column] :as _drill-thru} :- ::lib.schema.drill-thru/drill-thru.column-filter
filter-op :- [:or :keyword :string] ; filter tag
value :- :any]
(lib.filter/filter query stage-number (lib.filter/filter-clause filter-op column value))) | |