(ns metabase.xrays.transforms.specs (:require [malli.core :as mc] [malli.transform :as mtx] [medley.core :as m] [metabase.legacy-mbql.normalize :as mbql.normalize] [metabase.legacy-mbql.schema :as mbql.s] [metabase.lib.util.match :as lib.util.match] [metabase.util :as u] [metabase.util.malli.registry :as mr] [metabase.util.yaml :as yaml] [metabase.xrays.domain-entities.specs :refer [MBQL]])) | |
(def ^:private DecodableString
[:string
{:decode/transform-spec (fn [x]
(if (string? x)
x
(u/qualified-name x)))}]) | |
(def ^:private Source DecodableString) | |
(def ^:private Dimension DecodableString) | |
(def ^:private Breakout
[:sequential
{:decode/transform-spec (fn [breakouts]
(for [breakout (u/one-or-many breakouts)]
(if-not (mr/validate MBQL breakout)
[:dimension breakout]
breakout)))}
MBQL]) | |
(defn- extract-dimensions [mbql] (lib.util.match/match (mbql.normalize/normalize mbql) [:dimension dimension & _] dimension)) | |
(def ^:private ^{:arglists '([m])} stringify-keys
(partial m/map-keys name)) | |
(def ^:private Dimension->MBQL
[:map-of
;; Since `Aggregation` and `Expressions` are structurally the same, we can't use them directly
{:decode/transform-spec
(comp (partial u/topological-sort extract-dimensions)
stringify-keys)}
Dimension
MBQL]) | |
(def ^:private Aggregation Dimension->MBQL) | |
(def ^:private Expressions Dimension->MBQL) | |
(def ^:private Description DecodableString) | |
(def ^:private Filter MBQL) | |
(def ^:private Limit pos-int?) | |
(def ^:private JoinStrategy
[:schema
{:decode/transform-spec keyword}
mbql.s/JoinStrategy]) | |
(def ^:private Joins
[:sequential
[:map
[:source Source]
[:condition MBQL]
[:strategy {:optional true} JoinStrategy]]]) | |
(def ^:private TransformName DecodableString) | |
Transform step | (def Step
[:map
[:source Source]
[:name Source]
[:transform TransformName]
[:aggregation {:optional true} Aggregation]
[:breakout {:optional true} Breakout]
[:expressions {:optional true} Expressions]
[:joins {:optional true} Joins]
[:description {:optional true} Description]
[:limit {:optional true} Limit]
[:filter {:optional true} Filter]]) |
(def ^:private Steps
[:map-of
{:decode/tranform-spec (fn [source->step]
(->> source->step
stringify-keys
(u/topological-sort (fn [{:keys [source joins]}]
(conj (map :source joins) source)))))}
Source
Step]) | |
(def ^:private DomainEntity DecodableString) | |
(def ^:private Requires
[:sequential
{:decode/transform-spec u/one-or-many}
DomainEntity]) | |
(def ^:private Provides
[:sequential
{:decode/transform-spec u/one-or-many}
DomainEntity]) | |
Transform spec | (def TransformSpec
[:map
[:name TransformName]
[:requires Requires]
[:provides Provides]
[:steps Steps]
[:description {:optional true} Description]]) |
(defn- add-metadata-to-steps
[spec]
(update spec :steps (partial m/map-kv-vals (fn [step-name step]
(assoc step
:name step-name
:transform (:name spec)))))) | |
(defn- coerce-to-transform-spec [spec]
(mc/coerce TransformSpec
spec
(mtx/transformer
mtx/string-transformer
mtx/json-transformer
(mtx/transformer {:name :transform-spec})))) | |
(def ^:private transforms-dir "transforms/") | |
List of registered dataset transforms. | (def transform-specs (delay (yaml/load-dir transforms-dir (comp coerce-to-transform-spec add-metadata-to-steps)))) |