Skip to content
Snippets Groups Projects
Unverified Commit ff4878b0 authored by Sameer Al-Sakran's avatar Sameer Al-Sakran Committed by GitHub
Browse files

Merge pull request #7432 from metabase/automagic-dashboards-nested-questions

xray/nested questions
parents 1b13a35b daff0851
No related branches found
No related tags found
No related merge requests found
......@@ -47,7 +47,7 @@
(tru "invalid value for rule name")))
(def ^:private ^{:arglists '([s])} decode-base64-json
(comp json/decode codecs/bytes->str codec/base64-decode))
(comp #(json/decode % keyword) codecs/bytes->str codec/base64-decode))
(def ^:private Base64EncodedJSON
(su/with-api-error-message
......
......@@ -26,8 +26,11 @@
[segment :refer [Segment]]
[table :refer [Table]]]
[metabase.query-processor.middleware.expand-macros :refer [merge-filter-clauses]]
[metabase.query-processor.util :as qp.util]
[metabase.related :as related]
[metabase.sync.analyze.classify :as classify]
[metabase.util :as u]
[puppetlabs.i18n.core :as i18n :refer [tru]]
[ring.util.codec :as codec]
[toucan.db :as db]))
......@@ -41,41 +44,41 @@
(defmethod ->root (type Table)
[table]
{:entity table
:source-table table
:source table
:database (:db_id table)
:full-name (:display_name table)
:url (format "%stable/%s" public-endpoint (:id table))
:url (format "%stable/%s" public-endpoint (u/get-id table))
:rules-prefix "table"})
(defmethod ->root (type Segment)
[segment]
(let [table (-> segment :table_id Table) ]
{:entity segment
:source-table table
:source table
:database (:db_id table)
:query-filter (-> segment :definition :filter)
:full-name (str (:name segment) " segment")
:url (format "%ssegment/%s" public-endpoint (:id segment))
:full-name (str (:name segment) (tru " segment"))
:url (format "%ssegment/%s" public-endpoint (u/get-id segment))
:rules-prefix "table"}))
(defmethod ->root (type Metric)
[metric]
(let [table (-> metric :table_id Table)]
{:entity metric
:source-table table
:source table
:database (:db_id table)
:full-name (str (:name metric) " metric")
:url (format "%smetric/%s" public-endpoint (:id metric))
:full-name (str (:name metric) (tru " metric"))
:url (format "%smetric/%s" public-endpoint (u/get-id metric))
:rules-prefix "metric"}))
(defmethod ->root (type Field)
[field]
(let [table (field/table field)]
{:entity field
:source-table table
:source table
:database (:db_id table)
:full-name (str (:display_name field) "field ")
:url (format "%sfield/%s" public-endpoint (:id field))
:full-name (str (:display_name field) (tru " field"))
:url (format "%sfield/%s" public-endpoint (u/get-id field))
:rules-prefix "field"}))
(defmulti
......@@ -103,11 +106,12 @@
:day))
(defmethod ->reference [:mbql (type Field)]
[_ {:keys [fk_target_field_id id link aggregation base_type fingerprint] :as field}]
[_ {:keys [fk_target_field_id id link aggregation base_type fingerprint name base_type] :as field}]
(let [reference (cond
link [:fk-> link id]
fk_target_field_id [:fk-> id fk_target_field_id]
:else [:field-id id])]
id [:field-id id]
:else [:field-literal name base_type])]
(cond
(isa? base_type :type/DateTime)
[:datetime-field reference (or aggregation
......@@ -153,8 +157,8 @@
(defn- field-isa?
[{:keys [base_type special_type]} t]
(or (isa? special_type t)
(isa? base_type t)))
(or (isa? (keyword special_type) t)
(isa? (keyword base_type) t)))
(defn- key-col?
"Workaround for our leaky type system which conflates types with properties."
......@@ -228,7 +232,7 @@
(filter (comp (->> (filter-tables links_to (:tables context))
(keep :link)
set)
:id)
u/get-id)
(field-candidates context (dissoc constraints :links_to)))
(let [[tablespec fieldspec] field_type]
(if fieldspec
......@@ -243,14 +247,14 @@
(filter-fields {:fieldspec tablespec
:named named
:max-cardinality max_cardinality}
(-> context :source-table :fields))))))
(-> context :source :fields))))))
(defn- make-binding
[context [identifier definition]]
(->> definition
(field-candidates context)
(map #(->> (merge % definition)
vector
vector ; we wrap these in a vector to make merging easier (see `bind-dimensions`)
(assoc definition :matches)
(hash-map (name identifier))))))
......@@ -278,7 +282,7 @@
[context dimensions]
(->> dimensions
(mapcat (comp (partial make-binding context) first))
(group-by (comp :id first :matches val first))
(group-by (comp (some-fn :id :name) first :matches val first))
(map (comp most-specific-definition val))
(apply merge-with (fn [a b]
(case (compare (:score a) (:score b))
......@@ -308,7 +312,9 @@
subform))
{:type :query
:database (:database context)
:query (cond-> {:source_table (-> context :source-table :id)}
:query (cond-> {:source_table (if (->> context :source (instance? (type Table)))
(-> context :source u/get-id)
(->> context :source u/get-id (str "card__")))}
(not-empty filters)
(assoc :filter (transduce (map :filter)
merge-filter-clauses
......@@ -416,8 +422,8 @@
"Return matching rules orderd by specificity.
Most specific is defined as entity type specification the longest ancestor
chain."
[rules {:keys [source-table entity]}]
(let [table-type (:entity_type source-table)]
[rules {:keys [source entity]}]
(let [table-type (:entity_type source)]
(->> rules
(filter (fn [{:keys [applies_to]}]
(let [[entity-type field-type] applies_to]
......@@ -431,13 +437,12 @@
If there are multiple FKs pointing to the same table, multiple entries will
be returned."
[table]
(->> (db/select Field
:table_id (:id table)
:fk_target_field_id [:not= nil])
field/with-targets
(filter #(some-> % :target mi/can-read?))
(map (fn [{:keys [id target]}]
(-> target field/table (assoc :link id))))))
(for [{:keys [id target]} (field/with-targets
(db/select Field
:table_id (u/get-id table)
:fk_target_field_id [:not= nil]))
:when (some-> target mi/can-read?)]
(-> target field/table (assoc :link id))))
(defmulti
^{:private true
......@@ -452,7 +457,7 @@
(keep (fn [[identifier definition]]
(when-let [matches (->> definition
:matches
(remove (comp #{(:id field)} :id))
(remove (comp #{(u/get-id field)} u/get-id))
not-empty)]
[identifier (assoc definition :matches matches)])))
(concat [["this" {:matches [field]
......@@ -471,15 +476,27 @@
(defn- make-context
[root rule]
(let [source-table (:source-table root)
tables (concat [source-table] (linked-tables source-table))
table->fields (comp (->> (db/select Field
:table_id [:in (map :id tables)]
:visibility_type "normal")
field/with-targets
(group-by :table_id))
:id)]
(as-> {:source-table (assoc source-table :fields (table->fields source-table))
{:pre [(:source root)]}
(let [source (:source root)
tables (concat [source] (when (instance? (type Table) source)
(linked-tables source)))
table->fields (if (instance? (type Table) source)
(comp (->> (db/select Field
:table_id [:in (map u/get-id tables)]
:visibility_type "normal")
field/with-targets
(group-by :table_id))
u/get-id)
(->> source
:result_metadata
(map (fn [field]
(-> field
(update :base_type keyword)
(update :special_type keyword)
field/map->FieldInstance
(classify/run-classifiers {}))))
constantly))]
(as-> {:source (assoc source :fields (table->fields source))
:tables (map #(assoc % :fields (table->fields %)) tables)
:database (:database root)
:query-filter (merge-filter-clauses (:query-filter root)
......@@ -504,7 +521,7 @@
(defn- make-dashboard
([root rule]
(make-dashboard root rule {:tables [(:source-table root)]}))
(make-dashboard root rule {:tables [(:source root)]}))
([root rule context]
(-> rule
(select-keys [:title :description :groups :transient_title])
......@@ -521,9 +538,15 @@
cards (make-cards context rule)]
(when cards
[(assoc dashboard
:filters filters
:cards cards
:context context)
:filters filters
:cards cards
:context context
:fieldset (->> context
:tables
(mapcat :fields)
(map (fn [field]
[((some-fn :id :name) field) field]))
(into {})))
rule])))
(def ^:private ^:const ^Long max-related 6)
......@@ -593,7 +616,7 @@
(-> dashboard :context :filters u/pprint-to-str)))
(-> (cond-> dashboard
(or query-filter cell-query)
(assoc :title (str "Here's a closer look at your " full-name)))
(assoc :title (str (tru "A closer look at your ") full-name)))
(populate/create-dashboard (or show max-cards))
(assoc :related (-> (related root rule)
(assoc :more (if (and (-> dashboard
......@@ -601,15 +624,15 @@
count
(> max-cards))
(not= show :all))
[{:title "Show more about this"
[{:title (tru "Show more about this")
:description nil
:table (:source-table root)
:table (:source root)
:url (format "%s#show=all"
(:url root))}]
[])))))))
(def ^:private ^{:arglists '([card])} table-like?
(comp empty? :aggregation :query :dataset_query))
(comp empty? #(qp.util/get-in-normalized % [:dataset_query :query :aggregation])))
(defmulti
^{:doc "Create a transient dashboard analyzing given entity."
......@@ -632,22 +655,38 @@
(def ^:private ^{:arglists '([x])} endocde-base64-json
(comp codec/base64-encode codecs/str->bytes json/encode))
(def ^:private ^{:arglists '([card-or-question])} nested-query?
(comp (every-pred string? #(str/starts-with? % "card__"))
#(qp.util/get-in-normalized % [:dataset_query :query :source_table])))
(def ^:private ^{:arglists '([card-or-question])} native-query?
(comp #{:native} qp.util/normalize-token #(qp.util/get-in-normalized % [:dataset_query :type])))
(def ^:private ^{:arglists '([card-or-question])} source-question
(comp Card #(Integer/parseInt %) second #(str/split % #"__")
#(qp.util/get-in-normalized % [:dataset_query :query :source_table])))
(defmethod automagic-analysis (type Card)
[card {:keys [cell-query] :as opts}]
(if (or (table-like? card)
cell-query)
(let [table (-> card :table_id Table)]
(let [source (cond
(nested-query? card) (-> card
source-question
(assoc :entity_type :entity/GenericTable))
(native-query? card) (-> card (assoc :entity_type :entity/GenericTable))
:else (-> card :table_id Table))]
(automagic-dashboard
(merge {:entity table
:source-table table
:query-filter (-> card :dataset_query :query :filter)
:database (:db_id table)
:full-name (str (:name card) " question")
(merge {:entity source
:source source
:query-filter (qp.util/get-in-normalized card [:dataset_query :query :filter])
:database (:database_id card)
:full-name (str (:name card) (tru " question"))
:url (if cell-query
(format "%squestion/%s/cell/%s" public-endpoint
(:id card)
(u/get-id card)
(endocde-base64-json cell-query))
(format "%squestion/%s" public-endpoint (:id card)))
(format "%squestion/%s" public-endpoint (u/get-id card)))
:rules-prefix "table"}
opts)))
nil))
......@@ -656,23 +695,28 @@
[query {:keys [cell-query] :as opts}]
(if (or (table-like? query)
(:cell-query opts))
(let [table (-> query :table-id Table)]
(let [source (cond
(nested-query? query) (-> query
source-question
(assoc :entity_type :entity/GenericTable))
(native-query? query) (-> query (assoc :entity_type :entity/GenericTable))
:else (-> query :table-id Table))]
(automagic-dashboard
(merge {:entity table
:source-table table
:database (:db_id table)
:full-name (:display_name table)
(merge {:entity source
:source source
:database (:database-id query)
:full-name (if (nested-query? query)
(:name source)
(:display_name source))
:url (if cell-query
(format "%sadhoc/%s/cell/%s" public-endpoint
(endocde-base64-json (:dataset_query query))
(endocde-base64-json cell-query))
(format "%sadhoc/%s" public-endpoint
(endocde-base64-json query)))
(endocde-base64-json query)))
:rules-prefix "table"}
(update opts :cell-query merge-filter-clauses (-> query
:dataset_query
:query
:filter)))))
(update opts :cell-query merge-filter-clauses
(qp.util/get-in-normalized query [:dataset_query :query :filter])))))
nil))
(defmethod automagic-analysis (type Field)
......@@ -681,7 +725,7 @@
(defn- enhanced-table-stats
[table]
(let [field-types (->> (db/select [Field :special_type] :table_id (:id table))
(let [field-types (->> (db/select [Field :special_type] :table_id (u/get-id table))
(map :special_type))]
(assoc table :stats {:num-fields (count field-types)
:list-like? (= (count (remove #{:type/PK} field-types)) 1)
......@@ -705,7 +749,7 @@
([database schema]
(let [rules (rules/load-rules "table")]
(->> (apply db/select Table
(cond-> [:db_id (:id database)
(cond-> [:db_id (u/get-id database)
:visibility_type nil
:entity_type [:not= nil]] ; only consider tables that have alredy
; been analyzed
......@@ -719,7 +763,7 @@
(matching-rules rules)
first)
dashboard (make-dashboard root rule)]
{:url (format "%stable/%s" public-endpoint (:id table))
{:url (format "%stable/%s" public-endpoint (u/get-id table))
:title (:full-name root)
:score (+ (math/sq (rule-specificity rule))
(math/log (-> table :stats :num-fields)))
......
......@@ -5,49 +5,79 @@
[field :refer [Field] :as field]
[table :refer [Table]]]
[metabase.query-processor.util :as qp.util]
[metabase.util :as u]
[metabase.util.schema :as su]
[schema.core :as s]
[toucan.db :as db]))
(def ^:private FieldIdForm
(def ^:private FieldReference
[(s/one (s/constrained su/KeywordOrString
(comp #{:field-id :fk->} qp.util/normalize-token))
(comp #{:field-id :fk-> :field-literal} qp.util/normalize-token))
"head")
s/Any])
(def ^{:arglists '([form])} field-form?
"Is given form an MBQL field form?"
(complement (s/checker FieldIdForm)))
(def ^:private ^{:arglists '([form])} field-reference?
"Is given form an MBQL field reference?"
(complement (s/checker FieldReference)))
(defmulti
^{:doc "Extract field ID from a given field reference form."
:arglists '([op & args])}
field-reference->id (comp qp.util/normalize-token first))
(defmethod field-reference->id :field-id
[[_ id]]
id)
(defmethod field-reference->id :fk->
[[_ _ id]]
id)
(defmethod field-reference->id :field-literal
[[_ name _]]
name)
(defn collect-field-references
"Collect all field references (`[:field-id]` or `[:fk->]` forms) from a given form."
[form]
(->> form
(tree-seq (some-fn sequential? map?) identity)
(filter field-form?)))
(filter field-reference?)))
(def ^:private ^{:arglists '([field])} periodic-datetime?
(comp #{:minute-of-hour :hour-of-day :day-of-week :day-of-month :day-of-year :week-of-year
:month-of-year :quarter-of-year}
:unit))
(defn- datetime?
[field]
(and (not (periodic-datetime? field))
(or (isa? (:base_type field) :type/DateTime)
(field/unix-timestamp? field))))
(defn- candidates-for-filtering
[cards]
[fieldset cards]
(->> cards
(mapcat collect-field-references)
(map last)
(map field-reference->id)
distinct
(map Field)
(filter (fn [{:keys [base_type special_type] :as field}]
(or (isa? base_type :type/DateTime)
(isa? special_type :type/Category)
(field/unix-timestamp? field))))))
(map fieldset)
(filter (fn [{:keys [special_type] :as field}]
(or (datetime? field)
(isa? special_type :type/Category))))))
(defn- build-fk-map
[fks field]
(->> fks
(filter (comp #{(:table_id field)} :table_id :target))
(group-by :table_id)
(keep (fn [[_ [fk & fks]]]
;; Bail out if there is more than one FK from the same table
(when (empty? fks)
[(:table_id fk) [:fk-> (:id fk) (:id field)]])))
(into {(:table_id field) [:field-id (:id field)]})))
(if (:id field)
(->> fks
(filter (comp #{(:table_id field)} :table_id :target))
(group-by :table_id)
(keep (fn [[_ [fk & fks]]]
;; Bail out if there is more than one FK from the same table
(when (empty? fks)
[(:table_id fk) [:fk-> (u/get-id fk) (u/get-id field)]])))
(into {(:table_id field) [:field-id (u/get-id field)]}))
(constantly [:field-literal (:name field) (:base_type field)])))
(defn- filter-for-card
[card field]
......@@ -67,18 +97,6 @@
(nil? (:card dashcard)) dashcard
mappings (update dashcard :parameter_mappings concat mappings))))
(def ^:private ^{:arglists '([field])} periodic-datetime?
(comp #{:minute-of-hour :hour-of-day :day-of-week :day-of-month :day-of-year :week-of-year
:month-of-year :quarter-of-year}
:unit))
(defn- datetime?
[field]
(and (not (periodic-datetime? field))
(or (isa? (:base_type field) :type/DateTime)
(field/unix-timestamp? field))))
(defn- filter-type
"Return filter type for a given field."
[{:keys [base_type special_type] :as field}]
......@@ -111,13 +129,12 @@
([dashboard max-filters]
(->> dashboard
:orderd_cards
candidates-for-filtering
(candidates-for-filtering (:fieldset dashboard))
(add-filters dashboard max-filters)))
([dashboard dimensions max-filters]
(let [fks (->> (db/select Field
:fk_target_field_id [:not= nil]
:table_id [:in (keep (comp :table_id :card)
(:ordered_cards dashboard))])
:table_id [:in (keep (comp :table_id :card) (:ordered_cards dashboard))])
field/with-targets)]
(->> dimensions
remove-unqualified
......@@ -152,15 +169,16 @@
(t.format/parse dt)))
(defn- field-reference->field
[field-reference]
(cond-> (-> field-reference collect-field-references first last Field)
[fieldset field-reference]
(cond-> (-> field-reference collect-field-references first field-reference->id fieldset)
(-> field-reference first qp.util/normalize-token (= :datetime-field))
(assoc :unit (-> field-reference last qp.util/normalize-token))))
(defmulti
^{:private true
:arglists '([op & args])}
humanize-filter-value (comp qp.util/normalize-token first))
:arglists '([fieldset [op & args]])}
humanize-filter-value (fn [_ [op & args]]
(qp.util/normalize-token op)))
(defn- either
[v & vs]
......@@ -174,83 +192,86 @@
:else (recur (format "%s, %s" acc v) vs)))))
(defmethod humanize-filter-value :=
[[_ field-reference value & values]]
(let [field (field-reference->field field-reference)]
[fieldset [_ field-reference value & values]]
(let [field (field-reference->field fieldset field-reference)]
[{:field field-reference
:value (if (datetime? field)
(format "is on %s" (humanize-datetime value))
(format "is %s" (apply either value values)))}]))
(defmethod humanize-filter-value :!=
[[_ field-reference value]]
[{:field field-reference
:value (format "is not %s" value)}])
[fieldset [_ field-reference value & values]]
(let [field (field-reference->field fieldset field-reference)]
[{:field field-reference
:value (if (datetime? field)
(format "is not on %s" (humanize-datetime value))
(format "is not %s" (apply either value values)))}]))
(defmethod humanize-filter-value :>
[[_ field-reference value]]
(let [field (field-reference->field field-reference)]
[fieldset [_ field-reference value]]
(let [field (field-reference->field fieldset field-reference)]
[{:field field-reference
:value (if (datetime? field)
(format "is after %s" (humanize-datetime value))
(format "is greater than %s" value))}]))
(defmethod humanize-filter-value :<
[[_ field-reference value]]
(let [field (field-reference->field field-reference)]
[fieldset [_ field-reference value]]
(let [field (field-reference->field fieldset field-reference)]
[{:field field-reference
:value (if (datetime? field)
(format "is before %s" (humanize-datetime value))
(format "is less than %s" value))}]))
(defmethod humanize-filter-value :>=
[[_ field-reference value]]
[_ [_ field-reference value]]
[{:field field-reference
:value (format "is greater than or equal to %s" value)}])
(defmethod humanize-filter-value :<=
[[_ field-reference value]]
[{:field field-reference
[_ [_ field-reference value]]
[_ {:field field-reference
:value (format "is less than or equal to %s" value)}])
(defmethod humanize-filter-value :is-null
[[_ field-reference]]
[_ [_ field-reference]]
[{:field field-reference
:value "is null"}])
(defmethod humanize-filter-value :not-null
[[_ field-reference]]
[_ [_ field-reference]]
[{:field field-reference
:value "is not null"}])
(defmethod humanize-filter-value :between
[[_ field-reference min-value max-value]]
[_ [_ field-reference min-value max-value]]
[{:field field-reference
:value (format "is between %s and %s" min-value max-value)}])
(defmethod humanize-filter-value :inside
[[_ lat-reference lon-reference lat-max lon-min lat-min lon-max]]
[_ [_ lat-reference lon-reference lat-max lon-min lat-min lon-max]]
[{:field lat-reference
:value (format "is between %s and %s" lat-min lat-max)}
{:field lon-reference
:value (format "is between %s and %s" lon-min lon-max)}])
(defmethod humanize-filter-value :starts-with
[[_ field-reference value]]
[_ [_ field-reference value]]
[{:field field-reference
:value (format "starts with %s" value)}])
(defmethod humanize-filter-value :contains
[[_ field-reference value]]
[_ [_ field-reference value]]
[{:field field-reference
:value (format "contains %s" value)}])
(defmethod humanize-filter-value :does-not-contain
[[_ field-reference value]]
[_ [_ field-reference value]]
[{:field field-reference
:value (format "does not contain %s" value)}])
(defmethod humanize-filter-value :ends-with
[[_ field-reference value]]
[_ [_ field-reference value]]
[{:field field-reference
:value (format "ends with %s" value)}])
......@@ -265,13 +286,13 @@
(neg? n) (format "previous %s %ss" n unit))))
(defmethod humanize-filter-value :time-interval
[[_ field-reference n unit]]
[_ [_ field-reference n unit]]
[{:field field-reference
:value (format "is during the %s" (time-interval n unit))}])
(defmethod humanize-filter-value :and
[[_ & clauses]]
(mapcat humanize-filter-value clauses))
[fieldset [_ & clauses]]
(mapcat (partial humanize-filter-value fieldset) clauses))
(def ^:private unit-name (comp {:minute-of-hour "minute of hour"
:hour-of-day "hour of day"
......@@ -293,11 +314,11 @@
(defn applied-filters
"Extract fields and their values from MBQL filter clauses."
[filter-clause]
[fieldset filter-clause]
(for [{field-reference :field value :value} (some->> filter-clause
not-empty
humanize-filter-value)]
(let [field (field-reference->field field-reference)]
(humanize-filter-value fieldset))]
(let [field (field-reference->field fieldset field-reference)]
{:field (field-name field field-reference)
:field_id (:id field)
:type (filter-type field)
......
......@@ -6,8 +6,6 @@
[metabase.automagic-dashboards.filters :as magic.filters]
[metabase.models.card :as card]
[metabase.query-processor.util :as qp.util]
[metabase.util.schema :as su]
[schema.core :as s]
[toucan.db :as db]))
(def ^Long ^:const grid-width
......@@ -68,9 +66,8 @@
qp.util/normalize-token
(= :count)))
(->> breakout
(tree-seq sequential? identity)
(filter magic.filters/field-form?)
(map last))
magic.filters/collect-field-references
(map magic.filters/field-reference->id))
aggregation)]
{:graph.colors (->> color-keys
(map (comp colors #(mod % (count colors)) hash))
......@@ -234,14 +231,14 @@
(defn create-dashboard
"Create dashboard and populate it with cards."
([dashboard] (create-dashboard dashboard :all))
([{:keys [title transient_title description groups filters cards refinements]} n]
([{:keys [title transient_title description groups filters cards refinements fieldset]} n]
(let [n (cond
(= n :all) (count cards)
(keyword? n) (Integer/parseInt (name n))
:else n)
dashboard {:name title
:transient_name (or transient_title title)
:transient_filters (magic.filters/applied-filters refinements)
:transient_filters (magic.filters/applied-filters fieldset refinements)
:description description
:creator_id api/*current-user-id*
:parameters []}
......
......@@ -73,7 +73,7 @@
no-preview-display/infer-no-preview-display
text-fingerprint/infer-special-type])
(s/defn ^:private run-classifiers :- i/FieldInstance
(s/defn run-classifiers :- i/FieldInstance
"Run all the available `classifiers` against FIELD and FINGERPRINT, and return the resulting FIELD with changes
decided upon by the classifiers."
[field :- i/FieldInstance, fingerprint :- (s/maybe i/Fingerprint)]
......
......@@ -47,12 +47,11 @@
(expect
[:entity/UserTable :entity/GenericTable :entity/*]
(let [table (table/map->TableInstance {:entity_type :entity/UserTable})]
(->> {:entity table
:source-table table}
(#'magic/matching-rules (rules/load-rules "table"))
(map (comp first :applies_to)))))
(->> (data/id :users)
Table
(#'magic/->root)
(#'magic/matching-rules (rules/load-rules "table"))
(map (comp first :applies_to))))
(defn- collect-urls
[dashboard]
......@@ -72,8 +71,8 @@
(defn- valid-dashboard?
[dashboard]
(and dashboard
(:name dashboard)
(and (:name dashboard)
(-> dashboard :ordered_cards count pos?)
(valid-urls? dashboard)))
(defmacro ^:private with-dashboard-cleanup
......@@ -83,19 +82,16 @@
~@body))
(expect
true
(with-rasta
(with-dashboard-cleanup
(->> (Table) (keep #(automagic-analysis % {})) (every? valid-dashboard?)))))
(expect
true
(with-rasta
(with-dashboard-cleanup
(->> (Field) (keep #(automagic-analysis % {})) (every? valid-dashboard?)))))
(expect
true
(tt/with-temp* [Metric [{metric-id :id} {:table_id (data/id :venues)
:definition {:query {:aggregation ["count"]}}}]]
(with-rasta
......@@ -103,7 +99,6 @@
(->> (Metric) (keep #(automagic-analysis % {})) (every? valid-dashboard?))))))
(expect
true
(tt/with-temp* [Card [{card-id :id} {:table_id (data/id :venues)
:dataset_query {:query {:filter [:> [:field-id (data/id :venues :price)] 10]
:source_table (data/id :venues)}
......@@ -114,7 +109,43 @@
(-> card-id Card (automagic-analysis {}) valid-dashboard?)))))
(expect
true
(tt/with-temp* [Card [{card-id :id} {:table_id nil
:dataset_query {:native {:query "select * from users"}
:type :native
:database (data/id)}}]]
(with-rasta
(with-dashboard-cleanup
(-> card-id Card (automagic-analysis {}) valid-dashboard?)))))
(expect
(tt/with-temp* [Card [{source-id :id} {:table_id (data/id :venues)
:dataset_query {:query {:source_table (data/id :venues)}
:type :query
:database (data/id)}}]
Card [{card-id :id} {:table_id (data/id :venues)
:dataset_query {:query {:filter [:> [:field-id (data/id :venues :price)] 10]
:source_table (str "card__" source-id)}
:type :query
:database -1337}}]]
(with-rasta
(with-dashboard-cleanup
(-> card-id Card (automagic-analysis {}) valid-dashboard?)))))
(expect
(tt/with-temp* [Card [{source-id :id} {:table_id nil
:dataset_query {:native {:query "select * from users"}
:type :native
:database (data/id)}}]
Card [{card-id :id} {:table_id (data/id :venues)
:dataset_query {:query {:filter [:> [:field-id (data/id :venues :price)] 10]
:source_table (str "card__" source-id)}
:type :query
:database -1337}}]]
(with-rasta
(with-dashboard-cleanup
(-> card-id Card (automagic-analysis {}) valid-dashboard?)))))
(expect
(tt/with-temp* [Card [{card-id :id} {:table_id (data/id :venues)
:dataset_query {:query {:filter [:> [:field-id (data/id :venues :price)] 10]
:source_table (data/id :venues)}
......@@ -129,7 +160,6 @@
(expect
true
(tt/with-temp* [Card [{card-id :id} {:table_id (data/id :venues)
:dataset_query {:query {:filter [:> [:field-id (data/id :venues :price)] 10]
:source_table (data/id :venues)}
......@@ -144,7 +174,6 @@
(expect
true
(with-rasta
(with-dashboard-cleanup
(let [q (query/adhoc-query {:query {:filter [:> [:field-id (data/id :venues :price)] 10]
......@@ -154,7 +183,6 @@
(-> q (automagic-analysis {}) valid-dashboard?)))))
(expect
true
(with-rasta
(with-dashboard-cleanup
(let [q (query/adhoc-query {:query {:filter [:> [:field-id (data/id :venues :price)] 10]
......@@ -179,7 +207,6 @@
[(nil? not-analyzed-result) (some? analyzed-result)])))))
(expect
true
(tt/with-temp* [Database [{db-id :id}]
Table [{table-id :id} {:db_id db-id}]
Field [{} {:table_id table-id}]
......
......@@ -14,9 +14,9 @@
(expect "ga:foo" (#'rules/->type "ga:foo"))
(expect :type/Foo (#'rules/->type "Foo"))
(expect true (every? some? (load-rules "table")))
(expect true (every? some? (load-rules "metrics")))
(expect true (every? some? (load-rules "fields")))
(expect (every? some? (load-rules "table")))
(expect (every? some? (load-rules "metrics")))
(expect (every? some? (load-rules "fields")))
(expect true (dimension-form? [:dimension "Foo"]))
(expect true (dimension-form? ["dimension" "Foo"]))
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment