Skip to content
This repository was archived by the owner on May 30, 2025. It is now read-only.

Commit 85d29f2

Browse files
authored
Support dashboard params for Cards with nested queries (metabase#12531)
* Fix metabase#12501 * Make sure Query results_metadata comes back with inferred base type from annotate * Support filtering against questions that use another question as their source [ci drivers] * Test fixes 🔧 * Lint fix [ci drivers] * Clean up metabase.query-processor.parameters.mbql-test * Tweak codecov requirements [ci drivers]
1 parent e2e3691 commit 85d29f2

File tree

19 files changed

+625
-486
lines changed

19 files changed

+625
-486
lines changed

backend/mbql/src/metabase/mbql/util.clj

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
[amount :as t.amount]
77
[core :as t.core]]
88
[metabase.mbql.schema :as mbql.s]
9+
[metabase.mbql.schema.helpers :as mbql.s.helpers]
910
[metabase.mbql.util.match :as mbql.match]
1011
[metabase.util
1112
[i18n :refer [tru]]
@@ -418,9 +419,7 @@
418419
:else
419420
source-table-id))
420421

421-
(s/defn unwrap-field-clause :- (s/if (partial is-clause? :field-id)
422-
mbql.s/field-id
423-
mbql.s/field-literal)
422+
(s/defn unwrap-field-clause :- (mbql.s.helpers/one-of mbql.s/field-id mbql.s/field-literal)
424423
"Un-wrap a `Field` clause and return the lowest-level clause it wraps, either a `:field-id` or `:field-literal`."
425424
[clause :- mbql.s/Field]
426425
(match-one clause

codecov.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ coverage:
66
status:
77
project:
88
default:
9-
# New code must have minimum 80% test coverage
10-
target: 75%
9+
# Project must always have at least 78% coverage (by line)
10+
target: 78%
1111
# Whole-project test coverage is allowed to drop up to 5%. (For situtations where we delete code with full coverage)
1212
threshold: 5%
13+
patch:
14+
default:
15+
# Changes must have at least 75% test coverage (by line)
16+
target: 75%

frontend/src/metabase-lib/lib/metadata/Table.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export default class Table extends Base {
3636
entity_type: ?EntityType;
3737

3838
hasSchema(): boolean {
39-
return (this.schema_name && this.db.schemas.length > 1) || false;
39+
return (this.schema_name && this.db && this.db.schemas.length > 1) || false;
4040
}
4141

4242
// $FlowFixMe Could be replaced with hydrated database property in selectors/metadata.js (instead / in addition to `table.db`)
@@ -52,7 +52,7 @@ export default class Table extends Base {
5252

5353
question(): Question {
5454
return Question.create({
55-
databaseId: this.db.id,
55+
databaseId: this.db && this.db.id,
5656
tableId: this.id,
5757
metadata: this.metadata,
5858
});

frontend/src/metabase/parameters/components/Parameters.jsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import type {
2121
} from "metabase/meta/types/Parameter";
2222

2323
import type { DashboardWithCards } from "metabase/meta/types/Dashboard";
24+
import Dimension from "metabase-lib/lib/Dimension";
2425
import type Field from "metabase-lib/lib/metadata/Field";
2526
import type Metadata from "metabase-lib/lib/metadata/Metadata";
2627

@@ -81,9 +82,13 @@ export default class Parameters extends Component {
8182
// widget, we should start with an array to match.
8283
value = [value];
8384
}
85+
// field IDs can be either ["field-id", <id>] or ["field-literal", <name>, <type>]
8486
const fieldIds = parameter.field_ids || [];
85-
// $FlowFixMe
86-
const fields = fieldIds.map(id => metadata.field(id));
87+
const fields = fieldIds.map(
88+
id =>
89+
// $FlowFixMe
90+
metadata.field(id) || Dimension.parseMBQL(id, metadata).field(),
91+
);
8792
// $FlowFixMe
8893
setParameterValue(parameter.id, parseQueryParam(value, fields));
8994
}

modules/drivers/mongo/src/metabase/driver/mongo/query_processor.clj

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -602,10 +602,22 @@
602602
handle-limit
603603
handle-page]))
604604

605+
(defn- query->collection-name
606+
"Return `:collection` from a source query, if it exists."
607+
[query]
608+
(mbql.u/match-one query
609+
(_ :guard (every-pred map? :collection))
610+
;; ignore source queries inside `:joins` or `:collection` outside of a `:source-query`
611+
(when (and (= (last &parents) :source-query)
612+
(not (contains? (set &parents) :joins)))
613+
(:collection &match))))
614+
605615
(defn mbql->native
606616
"Process and run an MBQL query."
607-
[{{source-table-id :source-table} :query, :as query}]
608-
(let [{source-table-name :name} (qp.store/table source-table-id)]
617+
[query]
618+
(let [source-table-name (if-let [source-table-id (mbql.u/query->source-table-id query)]
619+
(:name (qp.store/table source-table-id))
620+
(query->collection-name query))]
609621
(binding [*query* query]
610622
(let [{proj :projections, generated-pipeline :query} (generate-aggregation-pipeline (:query query))]
611623
(log-aggregation-pipeline generated-pipeline)
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
(ns metabase.driver.mongo.query-processor-test
2+
(:require [clojure.test :refer :all]
3+
[metabase.driver.mongo.query-processor :as mongo.qp]))
4+
5+
(deftest query->collection-name-test
6+
(testing "query->collection-name"
7+
(testing "should be able to extract :collection from :source-query")
8+
(is (= "checkins"
9+
(#'mongo.qp/query->collection-name {:query {:source-query
10+
{:collection "checkins"
11+
:native []}}})))
12+
(testing "should work for nested-nested queries"
13+
(is (= "checkins"
14+
(#'mongo.qp/query->collection-name {:query {:source-query {:source-query
15+
{:collection "checkins"
16+
:native []}}}}))))
17+
18+
(testing "should ignore :joins"
19+
(is (= nil
20+
(#'mongo.qp/query->collection-name {:query {:source-query
21+
{:native []}
22+
:joins [{:source-query "wow"}]}}))))
23+
24+
(testing "should ignore other :collection keys"
25+
(is (= nil
26+
(#'mongo.qp/query->collection-name {:query {:source-query
27+
{:native [{:collection "wow"}]}}}))))))

modules/drivers/mongo/test/metabase/driver/mongo_test.clj

Lines changed: 29 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,9 @@
1515
[metabase.models
1616
[field :refer [Field]]
1717
[table :as table :refer [Table]]]
18-
[metabase.test.data :as data]
1918
[metabase.test.data.interface :as tx]
2019
[taoensso.nippy :as nippy]
21-
[toucan.db :as db]
22-
[toucan.util.test :as tt])
20+
[toucan.db :as db])
2321
(:import org.bson.types.ObjectId))
2422

2523
;; ## Constants + Helper Fns/Macros
@@ -83,7 +81,7 @@
8381
(-> (qp/process-query {:native {:query native-query
8482
:collection "venues"}
8583
:type :native
86-
:database (data/id)})
84+
:database (mt/id)})
8785
(m/dissoc-in [:data :results_metadata] [:data :insights]))))))
8886

8987
;; ## Tests for individual syncing functions
@@ -94,7 +92,7 @@
9492
{:schema nil, :name "categories"}
9593
{:schema nil, :name "users"}
9694
{:schema nil, :name "venues"}}}
97-
(driver/describe-database :mongo (data/db))))))
95+
(driver/describe-database :mongo (mt/db))))))
9896

9997
(deftest describe-table-test
10098
(mt/test-driver :mongo
@@ -119,7 +117,7 @@
119117
:database-type "java.lang.Long"
120118
:base-type :type/Integer
121119
:pk? true}}}
122-
(driver/describe-table :mongo (data/db) (Table (data/id :venues)))))))
120+
(driver/describe-table :mongo (mt/db) (Table (mt/id :venues)))))))
123121

124122
(deftest nested-columns-test
125123
(mt/test-driver :mongo
@@ -141,28 +139,28 @@
141139

142140
(deftest all-num-columns-test
143141
(mt/test-driver :mongo
144-
(data/dataset all-null-columns
142+
(mt/dataset all-null-columns
145143
(is (= [{:name "_id", :database_type "java.lang.Long", :base_type :type/Integer, :special_type :type/PK}
146144
{:name "favorite_snack", :database_type "NULL", :base_type :type/*, :special_type nil}
147145
{:name "name", :database_type "java.lang.String", :base_type :type/Text, :special_type :type/Name}]
148146
(map
149147
(partial into {})
150148
(db/select [Field :name :database_type :base_type :special_type]
151-
:table_id (data/id :bird_species)
149+
:table_id (mt/id :bird_species)
152150
{:order-by [:name]})))))))
153151

154152
(deftest table-rows-sample-test
155153
(mt/test-driver :mongo
156-
(driver/sync-in-context :mongo (data/db)
154+
(driver/sync-in-context :mongo (mt/db)
157155
(fn []
158156
(is (= [[1 "Red Medicine"]
159157
[2 "Stout Burgers & Beers"]
160158
[3 "The Apple Pan"]
161159
[4 "Wurstküche"]
162160
[5 "Brite Spot Family Restaurant"]]
163-
(vec (take 5 (metadata-queries/table-rows-sample (Table (data/id :venues))
164-
[(Field (data/id :venues :id))
165-
(Field (data/id :venues :name))])))))))))
161+
(vec (take 5 (metadata-queries/table-rows-sample (Table (mt/id :venues))
162+
[(Field (mt/id :venues :id))
163+
(Field (mt/id :venues :name))])))))))))
166164

167165

168166
;; ## Big-picture tests for the way data should look post-sync
@@ -173,7 +171,7 @@
173171
{:active true, :name "users"}
174172
{:active true, :name "venues"}]
175173
(for [field (db/select [Table :name :active]
176-
:db_id (data/id)
174+
:db_id (mt/id)
177175
{:order-by [:name]})]
178176
(into {} field)))
179177
"Test that Tables got synced correctly")))
@@ -200,7 +198,7 @@
200198
(vec (for [table-name table-names]
201199
(vec (for [field (db/select [Field :name :base_type :special_type]
202200
:active true
203-
:table_id (data/id table-name)
201+
:table_id (mt/id table-name)
204202
{:order-by [:name]})]
205203
(into {} field))))))))))
206204

@@ -215,8 +213,8 @@
215213
(deftest bson-ids-test
216214
(mt/test-driver :mongo
217215
(is (= [[2 "Lucky Pigeon" (ObjectId. "abcdefabcdefabcdefabcdef")]]
218-
(rows (data/dataset with-bson-ids
219-
(data/run-mbql-query birds
216+
(rows (mt/dataset with-bson-ids
217+
(mt/run-mbql-query birds
220218
{:filter [:= $bird_id "abcdefabcdefabcdefabcdef"]}))))
221219
"Check that we support Mongo BSON ID and can filter by it (#1367)")))
222220

@@ -226,8 +224,8 @@
226224
(letfn [(rows-count [query]
227225
(count (rows (qp/process-query {:native query
228226
:type :native
229-
:database (data/id)}))))]
230-
(data/dataset with-bson-ids
227+
:database (mt/id)}))))]
228+
(mt/dataset with-bson-ids
231229
(is (= 1
232230
(rows-count {:query "[{\"$match\": {\"bird_id\": ObjectId(\"abcdefabcdefabcdefabcdef\")}}]"
233231
:collection "birds"}))))
@@ -251,17 +249,17 @@
251249
(mt/test-driver :mongo
252250
(testing "make sure x-rays don't use features that the driver doesn't support"
253251
(is (= true
254-
(->> (magic/automagic-analysis (Field (data/id :venues :price)) {})
252+
(->> (magic/automagic-analysis (Field (mt/id :venues :price)) {})
255253
:ordered_cards
256254
(mapcat (comp :breakout :query :dataset_query :card))
257-
(not-any? #{[:binning-strategy [:field-id (data/id :venues :price)] "default"]})))))))
255+
(not-any? #{[:binning-strategy [:field-id (mt/id :venues :price)] "default"]})))))))
258256

259257
(deftest no-values-test
260258
(mt/test-driver :mongo
261259
(testing (str "if we query a something an there are no values for the Field, the query should still return "
262260
"successfully! (#8929 and #8894)")
263261
;; add a temporary Field that doesn't actually exist to test data categories
264-
(tt/with-temp Field [_ {:name "parent_id", :table_id (data/id :categories)}]
262+
(mt/with-temp Field [_ {:name "parent_id", :table_id (mt/id :categories)}]
265263
;; ok, now run a basic MBQL query against categories Table. When implicit Field IDs get added the `parent_id`
266264
;; Field will be included
267265
(testing (str "if the column does not come back in the results for a given document we should fill in the "
@@ -270,7 +268,7 @@
270268
[2 "American" nil]
271269
[3 "Artisan" nil]]}
272270
(->
273-
(data/run-mbql-query categories
271+
(mt/run-mbql-query categories
274272
{:order-by [[:asc [:field-id $id]]]
275273
:limit 3})
276274
qp.t/data
@@ -287,12 +285,12 @@
287285
(is (= [[22]]
288286
(mt/rows
289287
(qp/process-query
290-
{:database (data/id)
291-
:type :native
292-
:native {:projections [:count]
293-
:query [{"$project" {"price" "$price"}}
294-
{"$match" {"price" {"$eq" 1}}}
295-
{"$group" {"_id" nil, "count" {"$sum" 1}}}
296-
{"$sort" {"_id" 1}}
297-
{"$project" {"_id" false, "count" true}}]
298-
:collection "venues"}})))))))
288+
{:database (mt/id)
289+
:type :native
290+
:native {:projections [:count]
291+
:query [{"$project" {"price" "$price"}}
292+
{"$match" {"price" {"$eq" 1}}}
293+
{"$group" {"_id" nil, "count" {"$sum" 1}}}
294+
{"$sort" {"_id" 1}}
295+
{"$project" {"_id" false, "count" true}}]
296+
:collection "venues"}})))))))

0 commit comments

Comments
 (0)