Skip to content
Snippets Groups Projects
Unverified Commit 9d08394c authored by Braden Shepherdson's avatar Braden Shepherdson Committed by GitHub
Browse files

Serdes v2: Several fixes that came out of the git <-> prod workflow (#25310)

- Limit the scanning of directories and files to those named after
  models; don't try to ingest `.git`, `README.md`, etc.
- `table_id` and `collection_id` are optional on Cards
- Deserialization was not resolving some deeply nested `:field`s inside MBQL
  queries.
parent 7f45c4d7
No related branches found
No related tags found
No related merge requests found
...@@ -74,6 +74,7 @@ ...@@ -74,6 +74,7 @@
; TODO This should be restored, but there's no manifest or other meta file written by v2 dumps. ; TODO This should be restored, but there's no manifest or other meta file written by v2 dumps.
;(when-not (load/compatible? path) ;(when-not (load/compatible? path)
; (log/warn (trs "Dump was produced using a different version of Metabase. Things may break!"))) ; (log/warn (trs "Dump was produced using a different version of Metabase. Things may break!")))
(log/info (trs "Loading serialized Metabase files from {0}" path))
(v2.load/load-metabase (v2.ingest/ingest-yaml path))) (v2.load/load-metabase (v2.ingest/ingest-yaml path)))
(defn load (defn load
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
to avoid confusion with filesystem paths." to avoid confusion with filesystem paths."
(:require [clojure.java.io :as io] (:require [clojure.java.io :as io]
[metabase-enterprise.serialization.v2.ingest :as ingest] [metabase-enterprise.serialization.v2.ingest :as ingest]
[metabase-enterprise.serialization.v2.models :as models]
[metabase-enterprise.serialization.v2.utils.yaml :as u.yaml] [metabase-enterprise.serialization.v2.utils.yaml :as u.yaml]
[metabase.util.date-2 :as u.date] [metabase.util.date-2 :as u.date]
[yaml.core :as yaml] [yaml.core :as yaml]
...@@ -29,7 +30,8 @@ ...@@ -29,7 +30,8 @@
(defn- read-timestamps [entity] (defn- read-timestamps [entity]
(->> (keys entity) (->> (keys entity)
(filter #(.endsWith (name %) "_at")) (filter #(or (#{:last_analyzed} %)
(.endsWith (name %) "_at")))
(reduce #(update %1 %2 u.date/parse) entity))) (reduce #(update %1 %2 u.date/parse) entity)))
(defn- ingest-entity (defn- ingest-entity
...@@ -50,9 +52,17 @@ ...@@ -50,9 +52,17 @@
(deftype YamlIngestion [^File root-dir settings] (deftype YamlIngestion [^File root-dir settings]
ingest/Ingestable ingest/Ingestable
(ingest-list [_] (ingest-list [_]
(eduction (comp (filter (fn [^File f] (.isFile f))) (let [model-set (set models/exported-models)]
(mapcat (partial build-metas root-dir))) (eduction (comp (filter (fn [^File f] (.isFile f)))
(file-seq root-dir))) ;; The immediate parent directory should be a recognized model name.
;; If it's not, this may be in .git, or .github/actions/... or similar extra files.
(filter (fn [^File f] (or (= (.getName f) "settings.yaml")
(-> f
(.getParentFile)
(.getName)
model-set))))
(mapcat (partial build-metas root-dir)))
(file-seq root-dir))))
(ingest-one [_ abs-path] (ingest-one [_ abs-path]
(let [{:keys [model id]} (first abs-path)] (let [{:keys [model id]} (first abs-path)]
......
...@@ -236,8 +236,9 @@ ...@@ -236,8 +236,9 @@
ser)) ser))
(is (not (contains? ser :id))) (is (not (contains? ser :id)))
(testing "cards depend on their Table and Collection, and any fields in their parameter_mappings" (testing "cards depend on their Database, Table and Collection, and any fields in their parameter_mappings"
(is (= #{[{:model "Database" :id "My Database"} (is (= #{[{:model "Database" :id "My Database"}]
[{:model "Database" :id "My Database"}
{:model "Schema" :id "PUBLIC"} {:model "Schema" :id "PUBLIC"}
{:model "Table" :id "Schema'd Table"}] {:model "Table" :id "Schema'd Table"}]
[{:model "Collection" :id coll-eid}] [{:model "Collection" :id coll-eid}]
...@@ -283,8 +284,9 @@ ...@@ -283,8 +284,9 @@
ser)) ser))
(is (not (contains? ser :id))) (is (not (contains? ser :id)))
(testing "cards depend on their Table and Collection, and any fields in their visualization_settings" (testing "cards depend on their Database, Table and Collection, and any fields in their visualization_settings"
(is (= #{[{:model "Database" :id "My Database"} (is (= #{[{:model "Database" :id "My Database"}]
[{:model "Database" :id "My Database"}
{:model "Schema" :id "PUBLIC"} {:model "Schema" :id "PUBLIC"}
{:model "Table" :id "Schema'd Table"}] {:model "Table" :id "Schema'd Table"}]
[{:model "Collection" :id coll-eid}] [{:model "Collection" :id coll-eid}]
......
...@@ -374,12 +374,13 @@ ...@@ -374,12 +374,13 @@
(update :visualization_settings serdes.util/import-visualization-settings))) (update :visualization_settings serdes.util/import-visualization-settings)))
(defmethod serdes.base/serdes-dependencies "Card" (defmethod serdes.base/serdes-dependencies "Card"
[{:keys [collection_id dataset_query parameter_mappings table_id visualization_settings]}] [{:keys [collection_id database_id dataset_query parameter_mappings table_id visualization_settings]}]
;; The Table implicitly depends on the Database.
(->> (map serdes.util/mbql-deps parameter_mappings) (->> (map serdes.util/mbql-deps parameter_mappings)
(reduce set/union) (reduce set/union)
(set/union #{(serdes.util/table->path table_id) (set/union #{[{:model "Database" :id database_id}]})
[{:model "Collection" :id collection_id}]}) ; table_id and collection_id are nullable.
(set/union (when table_id #{(serdes.util/table->path table_id)}))
(set/union (when collection_id #{[{:model "Collection" :id collection_id}]}))
(set/union (serdes.util/mbql-deps dataset_query)) (set/union (serdes.util/mbql-deps dataset_query))
(set/union (serdes.util/visualization-settings-deps visualization_settings)) (set/union (serdes.util/visualization-settings-deps visualization_settings))
vec)) vec))
......
...@@ -67,17 +67,21 @@ ...@@ -67,17 +67,21 @@
;; -------------------------------------------------- Tables --------------------------------------------------------- ;; -------------------------------------------------- Tables ---------------------------------------------------------
(defn export-table-fk (defn export-table-fk
"Given a numeric `table_id`, return a portable table reference. "Given a numeric `table_id`, return a portable table reference.
If the `table_id` is `nil`, return `nil`. This is legal for a native question.
That has the form `[db-name schema table-name]`, where the `schema` might be nil. That has the form `[db-name schema table-name]`, where the `schema` might be nil.
[[import-table-fk]] is the inverse." [[import-table-fk]] is the inverse."
[table-id] [table-id]
(let [{:keys [db_id name schema]} (db/select-one 'Table :id table-id) (when table-id
db-name (db/select-one-field :name 'Database :id db_id)] (let [{:keys [db_id name schema]} (db/select-one 'Table :id table-id)
[db-name schema name])) db-name (db/select-one-field :name 'Database :id db_id)]
[db-name schema name])))
(defn import-table-fk (defn import-table-fk
"Given a `table_id` as exported by [[export-table-fk]], resolve it back into a numeric `table_id`." "Given a `table_id` as exported by [[export-table-fk]], resolve it back into a numeric `table_id`.
[[db-name schema table-name]] The input might be nil, in which case so is the output. This is legal for a native question."
(db/select-one-field :id 'Table :name table-name :schema schema :db_id (db/select-one-field :id 'Database :name db-name))) [[db-name schema table-name :as table-id]]
(when table-id
(db/select-one-field :id 'Table :name table-name :schema schema :db_id (db/select-one-field :id 'Database :name db-name))))
(defn table->path (defn table->path
"Given a `table_id` as exported by [[export-table-fk]], turn it into a `[{:model ...}]` path for the Table. "Given a `table_id` as exported by [[export-table-fk]], turn it into a `[{:model ...}]` path for the Table.
...@@ -201,10 +205,10 @@ ...@@ -201,10 +205,10 @@
;; handle legacy `:field-id` forms encoded prior to 0.39.0 ;; handle legacy `:field-id` forms encoded prior to 0.39.0
;; and also *current* expresion forms used in parameter mapping dimensions ;; and also *current* expresion forms used in parameter mapping dimensions
;; example relevant clause - [:dimension [:fk-> [:field-id 1] [:field-id 2]]] ;; example relevant clause - [:dimension [:fk-> [:field-id 1] [:field-id 2]]]
[:field-id (fully-qualified-name :guard string?)] [(:or :field-id "field-id") (fully-qualified-name :guard string?)]
(mbql-fully-qualified-names->ids* [:field fully-qualified-name nil]) (mbql-fully-qualified-names->ids* [:field fully-qualified-name nil])
[:field (fully-qualified-name :guard vector?) opts] [(:or :field "field") (fully-qualified-name :guard vector?) opts]
[:field (import-field-fk fully-qualified-name) (mbql-fully-qualified-names->ids* opts)] [:field (import-field-fk fully-qualified-name) (mbql-fully-qualified-names->ids* opts)]
;; source-field is also used within parameter mapping dimensions ;; source-field is also used within parameter mapping dimensions
...@@ -217,15 +221,27 @@ ...@@ -217,15 +221,27 @@
(assoc :database (db/select-one-id 'Database :name fully-qualified-name)) (assoc :database (db/select-one-id 'Database :name fully-qualified-name))
mbql-fully-qualified-names->ids*) ; Process other keys mbql-fully-qualified-names->ids*) ; Process other keys
[:metric (fully-qualified-name :guard serdes.base/entity-id?)] {:card-id (entity-id :guard (every-pred string? serdes.base/entity-id?))}
(-> &match
(assoc :card-id (import-fk entity-id 'Card))
mbql-fully-qualified-names->ids*) ; Process other keys
[(:or :metric "metric") (fully-qualified-name :guard serdes.base/entity-id?)]
[:metric (import-fk fully-qualified-name 'Metric)] [:metric (import-fk fully-qualified-name 'Metric)]
[:segment (fully-qualified-name :guard serdes.base/entity-id?)] [(:or :segment "segment") (fully-qualified-name :guard serdes.base/entity-id?)]
[:segment (import-fk fully-qualified-name 'Segment)] [:segment (import-fk fully-qualified-name 'Segment)]
(_ :guard (every-pred map? #(vector? (:source-table %)))) (_ :guard (every-pred map? #(vector? (:source-table %))))
(-> &match (-> &match
(assoc :source-table (import-table-fk (:source-table &match))) (assoc :source-table (import-table-fk (:source-table &match)))
mbql-fully-qualified-names->ids*)
(_ :guard (every-pred map?
#(string? (:source-table %))
#(serdes.base/entity-id? (:source-table %))))
(-> &match
(assoc :source-table (str "card__" (import-fk (:source-table &match) 'Card)))
mbql-fully-qualified-names->ids*))) ;; process other keys mbql-fully-qualified-names->ids*))) ;; process other keys
(defn- mbql-fully-qualified-names->ids (defn- mbql-fully-qualified-names->ids
......
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