Skip to content
Snippets Groups Projects
Commit 87a835f1 authored by Simon Belak's avatar Simon Belak
Browse files

Fix rule loading

parent db39083e
No related branches found
No related tags found
No related merge requests found
...@@ -32,6 +32,7 @@ ...@@ -32,6 +32,7 @@
[metabase.util :as u] [metabase.util :as u]
[puppetlabs.i18n.core :as i18n :refer [tru]] [puppetlabs.i18n.core :as i18n :refer [tru]]
[ring.util.codec :as codec] [ring.util.codec :as codec]
[schema.core :as s]
[toucan.db :as db])) [toucan.db :as db]))
(def ^:private public-endpoint "/auto/dashboard/") (def ^:private public-endpoint "/auto/dashboard/")
...@@ -50,7 +51,7 @@ ...@@ -50,7 +51,7 @@
:source table :source table
:database (:db_id table) :database (:db_id table)
:url (format "%stable/%s" public-endpoint (u/get-id table)) :url (format "%stable/%s" public-endpoint (u/get-id table))
:rules-prefix "table"}) :rules-prefix ["table"]})
(defmethod ->root (type Segment) (defmethod ->root (type Segment)
[segment] [segment]
...@@ -61,7 +62,7 @@ ...@@ -61,7 +62,7 @@
:database (:db_id table) :database (:db_id table)
:query-filter (-> segment :definition :filter) :query-filter (-> segment :definition :filter)
:url (format "%ssegment/%s" public-endpoint (u/get-id segment)) :url (format "%ssegment/%s" public-endpoint (u/get-id segment))
:rules-prefix "table"})) :rules-prefix ["table"]}))
(defmethod ->root (type Metric) (defmethod ->root (type Metric)
[metric] [metric]
...@@ -71,7 +72,7 @@ ...@@ -71,7 +72,7 @@
:source table :source table
:database (:db_id table) :database (:db_id table)
:url (format "%smetric/%s" public-endpoint (u/get-id metric)) :url (format "%smetric/%s" public-endpoint (u/get-id metric))
:rules-prefix "metric"})) :rules-prefix ["metric"]}))
(defmethod ->root (type Field) (defmethod ->root (type Field)
[field] [field]
...@@ -81,7 +82,7 @@ ...@@ -81,7 +82,7 @@
:source table :source table
:database (:db_id table) :database (:db_id table)
:url (format "%sfield/%s" public-endpoint (u/get-id field)) :url (format "%sfield/%s" public-endpoint (u/get-id field))
:rules-prefix "field"})) :rules-prefix ["field"]}))
(defmulti (defmulti
^{:doc "Get a reference for a given model to be injected into a template ^{:doc "Get a reference for a given model to be injected into a template
...@@ -420,14 +421,15 @@ ...@@ -420,14 +421,15 @@
(assoc :score score (assoc :score score
:dataset_query query)))))))) :dataset_query query))))))))
(def ^:private ^{:arglists '([rule])} rule-specificity (s/defn ^:private rule-specificity
(comp (partial transduce (map (comp count ancestors)) +) :applies_to)) [rule :- rules/Rule]
(transduce (map (comp count ancestors)) + (:applies_to rule)))
(defn- matching-rules (s/defn ^:private matching-rules
"Return matching rules orderd by specificity. "Return matching rules orderd by specificity.
Most specific is defined as entity type specification the longest ancestor Most specific is defined as entity type specification the longest ancestor
chain." chain."
[rules {:keys [source entity]}] [rules :- [rules/Rule], {:keys [source entity]}]
(let [table-type (or (:entity_type source) :entity/GenericTable)] (let [table-type (or (:entity_type source) :entity/GenericTable)]
(->> rules (->> rules
(filter (fn [{:keys [applies_to]}] (filter (fn [{:keys [applies_to]}]
...@@ -479,8 +481,8 @@ ...@@ -479,8 +481,8 @@
[context _] [context _]
context) context)
(defn- make-context (s/defn ^:private make-context
[root rule] [root, rule :- rules/Rule]
{:pre [(:source root)]} {:pre [(:source root)]}
(let [source (:source root) (let [source (:source root)
tables (concat [source] (when (instance? (type Table) source) tables (concat [source] (when (instance? (type Table) source)
...@@ -524,10 +526,10 @@ ...@@ -524,10 +526,10 @@
vals vals
(apply concat))) (apply concat)))
(defn- make-dashboard (s/defn ^:private make-dashboard
([root rule] ([root, rule :- rules/Rule]
(make-dashboard root rule {:tables [(:source root)]})) (make-dashboard root rule {:tables [(:source root)]}))
([root rule context] ([root, rule :- rules/Rule, context]
(let [this {"this" (-> root (let [this {"this" (-> root
:entity :entity
(assoc :full-name (:full-name root)))}] (assoc :full-name (:full-name root)))}]
...@@ -540,8 +542,8 @@ ...@@ -540,8 +542,8 @@
(update :groups (partial fill-templates :string context {})) (update :groups (partial fill-templates :string context {}))
(assoc :refinements (:cell-query root)))))) (assoc :refinements (:cell-query root))))))
(defn- apply-rule (s/defn ^:private apply-rule
[root rule] [root, rule :- rules/Rule]
(let [context (make-context root rule) (let [context (make-context root rule)
dashboard (make-dashboard root rule context) dashboard (make-dashboard root rule context)
filters (->> rule filters (->> rule
...@@ -568,7 +570,7 @@ ...@@ -568,7 +570,7 @@
[entity] [entity]
(let [root (->root entity) (let [root (->root entity)
rule (->> root rule (->> root
(matching-rules (rules/get-rules [(:rules-prefix root)])) (matching-rules (rules/get-rules (:rules-prefix root)))
first) first)
dashboard (make-dashboard root rule)] dashboard (make-dashboard root rule)]
{:url (:url root) {:url (:url root)
...@@ -587,9 +589,9 @@ ...@@ -587,9 +589,9 @@
(take n) (take n)
(map ->related-entity))))) (map ->related-entity)))))
(defn- indepth (s/defn ^:private indepth
[root rule] [root, rule :- rules/Rule]
(->> (rules/get-rules [(:rules-prefix root) (:rule rule)]) (->> (rules/get-rules (concat (:rules-prefix root) [(:rule rule)]))
(keep (fn [indepth] (keep (fn [indepth]
(when-let [[dashboard _] (apply-rule root indepth)] (when-let [[dashboard _] (apply-rule root indepth)]
{:title ((some-fn :short-title :title) dashboard) {:title ((some-fn :short-title :title) dashboard)
...@@ -598,8 +600,8 @@ ...@@ -598,8 +600,8 @@
(:rule indepth))}))) (:rule indepth))})))
(take max-related))) (take max-related)))
(defn- related (s/defn ^:private related
[root rule] [root, rule :- rules/Rule]
(let [indepth (indepth root rule)] (let [indepth (indepth root rule)]
{:indepth indepth {:indepth indepth
:tables (take (- max-related (count indepth)) (others root))})) :tables (take (- max-related (count indepth)) (others root))}))
...@@ -607,40 +609,48 @@ ...@@ -607,40 +609,48 @@
(defn- automagic-dashboard (defn- automagic-dashboard
"Create dashboards for table `root` using the best matching heuristics." "Create dashboards for table `root` using the best matching heuristics."
[{:keys [rule show rules-prefix query-filter cell-query full-name] :as root}] [{:keys [rule show rules-prefix query-filter cell-query full-name] :as root}]
(when-let [[dashboard rule] (if rule (if-let [[dashboard rule] (if rule
(apply-rule root (rules/get-rule rule)) (apply-rule root (rules/get-rule rule))
(->> root (->> root
(matching-rules (rules/get-rules [rules-prefix])) (matching-rules (rules/get-rules rules-prefix))
(keep (partial apply-rule root)) (keep (partial apply-rule root))
;; `matching-rules` returns an `ArraySeq` (via `sort-by`) so ;; `matching-rules` returns an `ArraySeq` (via `sort-by`) so
;; `first` realises one element at a time (no chunking). ;; `first` realises one element at a time (no chunking).
first))] first))]
(log/info (format "Applying heuristic %s to %s." (:rule rule) full-name)) (do
(log/info (format "Dimensions bindings:\n%s" (log/info (format "Applying heuristic %s to %s." (:rule rule) full-name))
(->> dashboard (log/info (format "Dimensions bindings:\n%s"
:context (->> dashboard
:dimensions :context
(m/map-vals #(update % :matches (partial map :name))) :dimensions
u/pprint-to-str))) (m/map-vals #(update % :matches (partial map :name)))
(log/info (format "Using definitions:\nMetrics:\n%s\nFilters:\n%s" u/pprint-to-str)))
(-> dashboard :context :metrics u/pprint-to-str) (log/info (format "Using definitions:\nMetrics:\n%s\nFilters:\n%s"
(-> dashboard :context :filters u/pprint-to-str))) (-> dashboard :context :metrics u/pprint-to-str)
(-> (cond-> dashboard (-> dashboard :context :filters u/pprint-to-str)))
(or query-filter cell-query) (-> (cond-> dashboard
(assoc :title (str (tru "A closer look at ") full-name))) (or query-filter cell-query)
(populate/create-dashboard (or show max-cards)) (assoc :title (str (tru "A closer look at ") full-name)))
(assoc :related (-> (related root rule) (populate/create-dashboard (or show max-cards))
(assoc :more (if (and (-> dashboard (assoc :related (-> (related root rule)
:cards (assoc :more (if (and (-> dashboard
count :cards
(> max-cards)) count
(not= show :all)) (> max-cards))
[{:title (tru "Show more about this") (not= show :all))
:description nil [{:title (tru "Show more about this")
:table (:source root) :description nil
:url (format "%s#show=all" :table (:source root)
(:url root))}] :url (format "%s#show=all"
[]))))))) (:url root))}]
[]))))))
(throw (ex-info (format "Can't create dashboard for %s" full-name)
{:path (or rule rules-prefix)
:path-type (if rule
:path
:prefix)
:available-rules (map :rule (or (some-> rule rules/get-rule vector)
(rules/get-rules rules-prefix)))}))))
(def ^:private ^{:arglists '([card])} table-like? (def ^:private ^{:arglists '([card])} table-like?
(comp empty? #(qp.util/get-in-normalized % [:dataset_query :query :aggregation]))) (comp empty? #(qp.util/get-in-normalized % [:dataset_query :query :aggregation])))
...@@ -698,7 +708,7 @@ ...@@ -698,7 +708,7 @@
(u/get-id card) (u/get-id card)
(encode-base64-json cell-query)) (encode-base64-json cell-query))
(format "%squestion/%s" public-endpoint (u/get-id card))) (format "%squestion/%s" public-endpoint (u/get-id card)))
:rules-prefix "table"} :rules-prefix ["table"]}
opts))) opts)))
nil)) nil))
...@@ -731,7 +741,7 @@ ...@@ -731,7 +741,7 @@
(encode-base64-json cell-query)) (encode-base64-json cell-query))
(format "%sadhoc/%s" public-endpoint (format "%sadhoc/%s" public-endpoint
(encode-base64-json query))) (encode-base64-json query)))
:rules-prefix "table"} :rules-prefix ["table"]}
(update opts :cell-query merge-filter-clauses (update opts :cell-query merge-filter-clauses
(qp.util/get-in-normalized query [:dataset_query :query :filter]))))) (qp.util/get-in-normalized query [:dataset_query :query :filter])))))
nil)) nil))
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
[core :as s]] [core :as s]]
[yaml.core :as yaml]) [yaml.core :as yaml])
(:import java.nio.file.Path java.nio.file.FileSystems java.nio.file.FileSystem (:import java.nio.file.Path java.nio.file.FileSystems java.nio.file.FileSystem
java.nio.file.Files )) java.nio.file.Files))
(def ^Long ^:const max-score (def ^Long ^:const max-score
"Maximal (and default) value for heuristics scores." "Maximal (and default) value for heuristics scores."
...@@ -186,7 +186,8 @@ ...@@ -186,7 +186,8 @@
schema schema
(partition 2 constraints))) (partition 2 constraints)))
(def ^:private Rules (def Rule
"Rules defining an automagic dashboard."
(constrained-all (constrained-all
{(s/required-key :title) s/Str {(s/required-key :title) s/Str
(s/required-key :dimensions) [Dimension] (s/required-key :dimensions) [Dimension]
...@@ -234,7 +235,7 @@ ...@@ -234,7 +235,7 @@
(def ^:private rules-validator (def ^:private rules-validator
(sc/coercer! (sc/coercer!
Rules Rule
{[s/Str] ensure-seq {[s/Str] ensure-seq
[OrderByPair] ensure-seq [OrderByPair] ensure-seq
OrderByPair (fn [x] OrderByPair (fn [x]
...@@ -277,7 +278,7 @@ ...@@ -277,7 +278,7 @@
(def ^:private rules-dir "automagic_dashboards/") (def ^:private rules-dir "automagic_dashboards/")
(def ^:private ^{:arglists '([f])} file->entity-type (def ^:private ^{:arglists '([f])} file->entity-type
(comp (partial re-find #".+(?=\.yaml)") str (memfn ^Path getFileName))) (comp (partial re-find #".+(?=\.yaml$)") str (memfn ^Path getFileName)))
(defn- load-rule (defn- load-rule
[^Path f] [^Path f]
...@@ -300,6 +301,12 @@ ...@@ -300,6 +301,12 @@
e))) e)))
nil))) nil)))
(defn- trim-trailing-slash
[s]
(if (str/ends-with? s "/")
(subs s 0 (-> s count dec))
s))
(defn- load-rule-dir (defn- load-rule-dir
([dir] (load-rule-dir dir [] {})) ([dir] (load-rule-dir dir [] {}))
([dir path rules] ([dir path rules]
...@@ -307,7 +314,7 @@ ...@@ -307,7 +314,7 @@
(reduce (fn [rules ^Path f] (reduce (fn [rules ^Path f]
(cond (cond
(Files/isDirectory f (into-array java.nio.file.LinkOption [])) (Files/isDirectory f (into-array java.nio.file.LinkOption []))
(load-rule-dir f (conj path (str (.getFileName f))) rules) (load-rule-dir f (->> f (.getFileName) str trim-trailing-slash (conj path)) rules)
(file->entity-type f) (file->entity-type f)
(assoc-in rules (concat path [(file->entity-type f) ::leaf]) (load-rule f)) (assoc-in rules (concat path [(file->entity-type f) ::leaf]) (load-rule f))
......
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