(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.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 (mc/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)))) |