Skip to content
Snippets Groups Projects
Unverified Commit ea05f6ff authored by Braden Shepherdson's avatar Braden Shepherdson Committed by GitHub
Browse files

[MLv2] fk-details drill should preserve `:=` filters for other PKs (#38296)

That is, **if** there is a table with multiple PKs, and another table with
multiple FKs for those PKs, **then** any `:=` filters for other PKs on
that table should be preserved.

This allows using 1 or more quick-filter drills to add `FOO_ID = 7` filters
for some of the foreign table's PKs, followed by a fk-details drill that
jumps to the filtered view of that table.

Progress towards #36253.
parent b2b9e637
Branches
Tags
No related merge requests found
......@@ -63,8 +63,26 @@
[query stage-number {:keys [column object-id]} & _]
;; generate a NEW query against the FK target table and column, e.g. if the original query was
;; ORDERS/ORDERS.USER_ID, the new query should by PEOPLE/PEOPLE.ID.
(let [fk-column-id (:fk-target-field-id column)
fk-column (some->> fk-column-id (lib.metadata/field query))
fk-column-table (some->> (:table-id fk-column) (lib.metadata/table query))]
(-> (lib.query/query query fk-column-table)
(lib.filter/filter stage-number (lib.filter/= fk-column object-id)))))
;; If there are filters on the original query which are:
;; - := filters, and
;; - Not for this same column, but
;; - Relevant to OTHER FKs which point to PKs on the target table;
;; then preserve those filters.
(let [fk-column-id (:fk-target-field-id column)
fk-column (some->> fk-column-id (lib.metadata/field query))
fk-column-table (some->> (:table-id fk-column) (lib.metadata/table query))
other-fk-filters (for [filter-clause (lib.filter/filters query stage-number)
:let [parts (lib.filter/filter-parts query stage-number filter-clause)]
:when (= (:short (:operator parts)) :=)
:let [other-fk-target (some->> parts
:column
:fk-target-field-id
(lib.metadata/field query))]
:when (and other-fk-target
(= (:table-id other-fk-target) (:id fk-column-table)) ; FK to this table
(not= (:id other-fk-target) fk-column-id))] ; But not this column
(lib.filter/= other-fk-target (first (:args parts))))]
(reduce #(lib.filter/filter %1 stage-number %2)
(lib.query/query query fk-column-table)
(concat other-fk-filters
[(lib.filter/= fk-column object-id)]))))
......@@ -5,7 +5,9 @@
[metabase.lib.core :as lib]
[metabase.lib.drill-thru.test-util :as lib.drill-thru.tu]
[metabase.lib.drill-thru.test-util.canned :as canned]
[metabase.lib.metadata :as lib.metadata]
[metabase.lib.test-metadata :as meta]
[metabase.lib.test-util.metadata-providers.merged-mock :as merged-mock]
#?@(:cljs ([metabase.test-runner.assert-exprs.approximately-equal]))))
#?(:cljs (comment metabase.test-runner.assert-exprs.approximately-equal/keep-me))
......@@ -140,3 +142,85 @@
:value nil}]}]
(is (not (m/find-first #(= (:type %) :drill-thru/fk-details)
(lib/available-drill-thrus query -1 context)))))))
(deftest ^:parallel preserve-filters-for-other-fks-forming-multi-column-pk-test
(testing "with multiple FKs forming a multi-column PK on another table"
(let [provider (merged-mock/merged-mock-metadata-provider
meta/metadata-provider
{:fields [;; Make Checkins.VENUE_ID + Checkins.USER_ID into a two-part primary key.
;; Turn Checkins.ID into a basic numeric field.
{:id (meta/id :checkins :id)
:semantic-type :type/Quantity}
{:id (meta/id :checkins :venue-id)
:semantic-type :type/PK
:fk-target-field-id nil}
{:id (meta/id :checkins :user-id)
:semantic-type :type/PK
:fk-target-field-id nil}
;; Then turn Orders.USER_ID and Orders.PRODUCT_ID into FKs pointing at Checkins.
{:id (meta/id :orders :user-id)
:semantic-type :type/FK
:fk-target-field-id (meta/id :checkins :user-id)}
{:id (meta/id :orders :product-id)
:semantic-type :type/FK
:fk-target-field-id (meta/id :checkins :venue-id)}]})
;; Then we can treat them as a two-part FK aimed at a two-part PK.
query (-> (lib/query provider (meta/table-metadata :orders))
;; This filter should get removed when filtering by the FK.
(lib/filter (lib/= (meta/field-metadata :orders :quantity) 1)))
venue-id (get-in lib.drill-thru.tu/test-queries ["ORDERS" :unaggregated :row "PRODUCT_ID"])
user-id (get-in lib.drill-thru.tu/test-queries ["ORDERS" :unaggregated :row "USER_ID"])
#_#_filtered-venue (-> basic
(lib/filter (lib/= (lib.metadata/field basic (meta/id :orders :product-id))
(get-in lib.drill-thru.tu/test-queries
["ORDERS" :unaggregated :row "PRODUCT_ID"]))))
]
(testing "work as normal with no related filter"
(lib.drill-thru.tu/test-drill-application
{:column-name "PRODUCT_ID"
:click-type :cell
:query-type :unaggregated
:custom-query query
:drill-type :drill-thru/fk-details
:expected {:type :drill-thru/fk-details
:column (m/find-first #(= (:name %) "PRODUCT_ID") (lib/returned-columns query))
:object-id venue-id
;; TODO: This field actually refers to the source table, not the target one. Is that right?
:many-pks? false}
:expected-query {:stages [{:filters [[:= {} [:field {} (meta/id :checkins :venue-id)] venue-id]]}]}}))
(testing "preserve any existing filter for another PK on the same table"
(testing "existing USER_ID, new \"VENUE_ID\" (really PRODUCT_ID)"
(let [filtered-user (lib/filter query (lib/= (lib.metadata/field query (meta/id :orders :user-id)) user-id))]
(lib.drill-thru.tu/test-drill-application
{:column-name "PRODUCT_ID"
:click-type :cell
:query-type :unaggregated
:custom-query filtered-user
:drill-type :drill-thru/fk-details
:expected {:type :drill-thru/fk-details
:column (m/find-first #(= (:name %) "PRODUCT_ID")
(lib/returned-columns filtered-user))
:object-id venue-id
:many-pks? false}
:expected-query
{:stages [{:filters [[:= {} [:field {} (meta/id :checkins :user-id)] user-id]
[:= {} [:field {} (meta/id :checkins :venue-id)] venue-id]]}]}})))
(testing "existing \"VENUE_ID\" (really PRODUCT_ID), new USER_ID"
(let [filtered-venue (lib/filter query (lib/= (lib.metadata/field query (meta/id :orders :product-id))
venue-id))]
(lib.drill-thru.tu/test-drill-application
{:column-name "USER_ID"
:click-type :cell
:query-type :unaggregated
:custom-query filtered-venue
:drill-type :drill-thru/fk-details
:expected {:type :drill-thru/fk-details
:column (m/find-first #(= (:name %) "USER_ID")
(lib/returned-columns filtered-venue))
:object-id user-id
:many-pks? false}
:expected-query
{:stages [{:filters [[:= {} [:field {} (meta/id :checkins :venue-id)] venue-id]
[:= {} [:field {} (meta/id :checkins :user-id)] user-id]]}]}})))))))
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment