Pārlūkot izejas kodu

enhance: add EDN export for any view

Gabriel Horner 7 mēneši atpakaļ
vecāks
revīzija
1d855d012f

+ 65 - 15
deps/db/src/logseq/db/sqlite/export.cljs

@@ -231,6 +231,21 @@
       (seq classes)
       (seq classes)
       (assoc :classes classes))))
       (assoc :classes classes))))
 
 
+(defn- build-mixed-properties-and-classes-export
+  "Builds an export of properties and classes from a mixed group of nodes that may both"
+  [db ents export-opts]
+  (let [properties
+        (when-let [prop-ids (seq (map :db/ident (filter entity-util/property? ents)))]
+          (build-export-properties db prop-ids export-opts))
+        classes
+        (when-let [class-ents (seq (filter ldb/class? ents))]
+          (->> class-ents
+               (map #(vector (:db/ident %) (build-export-class % export-opts)))
+               (into {})))]
+    (cond-> {}
+      properties (assoc :properties properties)
+      classes (assoc :classes classes))))
+
 (defn- build-content-ref-export
 (defn- build-content-ref-export
   "Builds an export config (and additional info) for refs in the given blocks. All the exported
   "Builds an export config (and additional info) for refs in the given blocks. All the exported
    entities found in block refs include their uuid in order to preserve the relationship to the blocks"
    entities found in block refs include their uuid in order to preserve the relationship to the blocks"
@@ -239,20 +254,13 @@
         blocks (remove :logseq.property/value blocks*)
         blocks (remove :logseq.property/value blocks*)
         content-ref-uuids (set (mapcat (comp db-content/get-matched-ids block-title) blocks))
         content-ref-uuids (set (mapcat (comp db-content/get-matched-ids block-title) blocks))
         content-ref-ents (map #(d/entity db [:block/uuid %]) content-ref-uuids)
         content-ref-ents (map #(d/entity db [:block/uuid %]) content-ref-uuids)
-        content-ref-pages (filter #(or (ldb/internal-page? %) (entity-util/journal? %)) content-ref-ents)
-        content-ref-properties (when-let [prop-ids (seq (map :db/ident (filter ldb/property? content-ref-ents)))]
-                                 (update-vals (build-export-properties db prop-ids {:include-uuid? true :shallow-copy? true})
-                                              #(merge % {:build/keep-uuid? true})))
-        content-ref-classes (when-let [class-ents (seq (filter ldb/class? content-ref-ents))]
-                              (->> class-ents
-                                   (map #(vector (:db/ident %)
-                                                 (assoc (build-export-class % {:include-uuid? true :shallow-copy? true})
-                                                        :build/keep-uuid? true)))
-                                   (into {})))]
+        content-ref-pages (filter #(or (entity-util/internal-page? %) (entity-util/journal? %)) content-ref-ents)
+        {:keys [properties classes]}
+        (build-mixed-properties-and-classes-export db content-ref-ents {:include-uuid? true :shallow-copy? true})]
     {:content-ref-uuids content-ref-uuids
     {:content-ref-uuids content-ref-uuids
      :content-ref-ents content-ref-ents
      :content-ref-ents content-ref-ents
-     :properties content-ref-properties
-     :classes content-ref-classes
+     :properties (update-vals properties #(merge % {:build/keep-uuid? true}))
+     :classes (update-vals classes #(merge % {:build/keep-uuid? true}))
      :pages-and-blocks (mapv #(hash-map :page (merge (shallow-copy-page %)
      :pages-and-blocks (mapv #(hash-map :page (merge (shallow-copy-page %)
                                                      {:block/uuid (:block/uuid %) :build/keep-uuid? true}))
                                                      {:block/uuid (:block/uuid %) :build/keep-uuid? true}))
                              content-ref-pages)}))
                              content-ref-pages)}))
@@ -278,14 +286,14 @@
      :properties properties}))
      :properties properties}))
 
 
 (defn- build-blocks-export
 (defn- build-blocks-export
-  "Given a page's block entities, returns the blocks in a sqlite.build EDN format
+  "Given a vec of block entities, returns the blocks in a sqlite.build EDN format
    and all properties and classes used in these blocks"
    and all properties and classes used in these blocks"
-  [db blocks opts]
+  [db blocks {:keys [include-children?] :or {include-children? true} :as opts}]
   (let [*properties (atom {})
   (let [*properties (atom {})
         *classes (atom {})
         *classes (atom {})
         *pvalue-uuids (atom #{})
         *pvalue-uuids (atom #{})
         id-map (into {} (map (juxt :db/id identity)) blocks)
         id-map (into {} (map (juxt :db/id identity)) blocks)
-        children (group-by #(get-in % [:block/parent :db/id]) blocks)
+        children (if include-children? (group-by #(get-in % [:block/parent :db/id]) blocks) {})
         build-block (fn build-block [block*]
         build-block (fn build-block [block*]
                       (let [child-nodes (mapv build-block (get children (:db/id block*) []))
                       (let [child-nodes (mapv build-block (get children (:db/id block*) []))
                             {:keys [node properties classes]}
                             {:keys [node properties classes]}
@@ -377,6 +385,46 @@
         page-export (finalize-export-maps db page-blocks-export uuid-block-export content-ref-export)]
         page-export (finalize-export-maps db page-blocks-export uuid-block-export content-ref-export)]
     page-export))
     page-export))
 
 
+(defn build-view-nodes-export* [db nodes opts]
+  (let [node-pages (filter entity-util/page? nodes)
+        pages-export
+        (merge
+         (build-mixed-properties-and-classes-export db node-pages {:shallow-copy? true})
+         {:pages-and-blocks (mapv #(hash-map :page (shallow-copy-page %))
+                                  (filter #(or (entity-util/internal-page? %) (entity-util/journal? %)) node-pages))})
+        node-blocks (remove entity-util/page? nodes)
+        ;; Similar to build-uuid-block-export
+        pages-to-blocks
+        (->> node-blocks
+             (group-by :block/parent)
+             (map (fn [[parent-page-ent blocks]]
+                    (merge (build-blocks-export db
+                                                (sort-by :block/order blocks)
+                                                (merge opts {:include-children? false}))
+                           {:page (shallow-copy-page parent-page-ent)}))))
+        pages-to-blocks-export
+        {:properties (apply merge (map :properties pages-to-blocks))
+         :classes (apply merge (map :classes pages-to-blocks))
+         :pages-and-blocks (mapv #(select-keys % [:page :blocks]) pages-to-blocks)}]
+    (merge (merge-export-maps pages-export pages-to-blocks-export)
+           {:pvalue-uuids (apply set/union (map :pvalue-uuids pages-to-blocks))})))
+
+(defn- build-view-nodes-export
+  "Exports given nodes from a view. Nodes are a random mix of blocks and pages"
+  [db eids]
+  (let [nodes (map #(d/entity db %) eids)
+        property-value-ents (mapcat #(->> (dissoc (db-property/properties %) :block/tags)
+                                          vals
+                                          (filter de/entity?))
+                                    nodes)
+        {:keys [content-ref-uuids content-ref-ents] :as content-ref-export}
+        (build-content-ref-export db (into nodes property-value-ents))
+        {:keys [pvalue-uuids] :as nodes-export}
+        (build-view-nodes-export* db nodes {:include-uuid-fn content-ref-uuids})
+        uuid-block-export (build-uuid-block-export db pvalue-uuids content-ref-ents {})
+        view-nodes-export (finalize-export-maps db nodes-export uuid-block-export content-ref-export)]
+    view-nodes-export))
+
 (defn- build-graph-ontology-export
 (defn- build-graph-ontology-export
   "Exports a graph's tags and properties"
   "Exports a graph's tags and properties"
   [db]
   [db]
@@ -467,6 +515,8 @@
           (build-block-export db (:block-id options))
           (build-block-export db (:block-id options))
           :page
           :page
           (build-page-export db (:page-id options))
           (build-page-export db (:page-id options))
+          :view-nodes
+          (build-view-nodes-export db (:node-ids options))
           :graph-ontology
           :graph-ontology
           (build-graph-ontology-export db))]
           (build-graph-ontology-export db))]
     (ensure-export-is-valid (dissoc export-map ::block))
     (ensure-export-is-valid (dissoc export-map ::block))

+ 47 - 8
deps/db/test/logseq/db/sqlite/export_test.cljs

@@ -11,6 +11,13 @@
 
 
 ;; Test helpers
 ;; Test helpers
 ;; ============
 ;; ============
+(defn- validate-db
+  "Validate db, usually after transacting an import"
+  [db]
+  (let [validation (db-validate/validate-db! db)]
+    (when (seq (:errors validation)) (cljs.pprint/pprint {:validate (:errors validation)}))
+    (is (empty? (map :entity (:errors validation))) "Imported graph has no validation errors")))
+
 (defn- export-block-and-import-to-another-block
 (defn- export-block-and-import-to-another-block
   "Exports given block from one graph/conn, imports it to a 2nd block and then
   "Exports given block from one graph/conn, imports it to a 2nd block and then
    exports the 2nd block. The two blocks do not have to be in the same graph"
    exports the 2nd block. The two blocks do not have to be in the same graph"
@@ -23,10 +30,8 @@
             (sqlite-export/build-import @import-conn {:current-block import-block}))
             (sqlite-export/build-import @import-conn {:current-block import-block}))
         ;; _ (cljs.pprint/pprint _txs)
         ;; _ (cljs.pprint/pprint _txs)
         _ (d/transact! import-conn init-tx)
         _ (d/transact! import-conn init-tx)
-        _ (d/transact! import-conn block-props-tx)
-        validation (db-validate/validate-db! @import-conn)
-        _ (when (seq (:errors validation)) (cljs.pprint/pprint {:validate (:errors validation)}))
-        _  (is (empty? (map :entity (:errors validation))) "Imported graph has no validation errors")]
+        _ (d/transact! import-conn block-props-tx)]
+    (validate-db @import-conn)
     (sqlite-export/build-export @import-conn {:export-type :block
     (sqlite-export/build-export @import-conn {:export-type :block
                                               :block-id (:db/id import-block)})))
                                               :block-id (:db/id import-block)})))
 
 
@@ -79,7 +84,7 @@
           [{:page {:build/journal 20250220}
           [{:page {:build/journal 20250220}
             :blocks [{:block/title "b1"}]}
             :blocks [{:block/title "b1"}]}
            {:page {:build/journal 20250221}}]}
            {:page {:build/journal 20250221}}]}
-       (#'sqlite-export/merge-export-maps
+         (#'sqlite-export/merge-export-maps
           {:pages-and-blocks
           {:pages-and-blocks
            [{:page {:build/journal 20250220}
            [{:page {:build/journal 20250220}
              :blocks [{:block/title "b1"}]}]}
              :blocks [{:block/title "b1"}]}]}
@@ -180,9 +185,7 @@
         ;; _ (cljs.pprint/pprint _txs)
         ;; _ (cljs.pprint/pprint _txs)
         _ (d/transact! import-conn init-tx)
         _ (d/transact! import-conn init-tx)
         _ (d/transact! import-conn block-props-tx)
         _ (d/transact! import-conn block-props-tx)
-        validation (db-validate/validate-db! @import-conn)
-        _ (when (seq (:errors validation)) (cljs.pprint/pprint {:validate (:errors validation)}))
-        _  (is (empty? (map :entity (:errors validation))) "Imported graph has no validation errors")
+        _ (validate-db @import-conn)
         page2 (db-test/find-page-by-title @import-conn page-title)]
         page2 (db-test/find-page-by-title @import-conn page-title)]
     (sqlite-export/build-export @import-conn {:export-type :page :page-id (:db/id page2)})))
     (sqlite-export/build-export @import-conn {:export-type :page :page-id (:db/id page2)})))
 
 
@@ -442,7 +445,43 @@
         ;; _ (cljs.pprint/pprint _txs)
         ;; _ (cljs.pprint/pprint _txs)
         _ (d/transact! conn2 init-tx)
         _ (d/transact! conn2 init-tx)
         _ (d/transact! conn2 block-props-tx)
         _ (d/transact! conn2 block-props-tx)
+        _ (validate-db @conn2)
         imported-ontology (sqlite-export/build-export @conn2 {:export-type :graph-ontology})]
         imported-ontology (sqlite-export/build-export @conn2 {:export-type :graph-ontology})]
 
 
     (is (= (expand-properties (:properties original-data)) (:properties imported-ontology)))
     (is (= (expand-properties (:properties original-data)) (:properties imported-ontology)))
     (is (= (expand-classes (:classes original-data)) (:classes imported-ontology)))))
     (is (= (expand-classes (:classes original-data)) (:classes imported-ontology)))))
+
+(deftest import-view-blocks
+  (let [original-data
+        ;; Test a mix of page and block types
+        {:properties {:user.property/p1 {:logseq.property/type :default}
+                      :user.property/p2 {:logseq.property/type :default}}
+         :classes {:user.class/class1 {}}
+         :pages-and-blocks [{:page {:block/title "page1"}}
+                            {:page {:build/journal 20250226}}
+                            {:page {:block/title "page2"}
+                             :blocks [{:block/title "b1"
+                                       :build/properties {:user.property/p2 "ok"}}]}]}
+        conn (db-test/create-conn-with-blocks original-data)
+        get-node-ids (fn [db]
+                       (->> [(d/entity db :user.property/p1)
+                             (d/entity db :user.class/class1)
+                             (db-test/find-page-by-title db "page1")
+                             (db-test/find-journal-by-journal-day db 20250226)
+                             (db-test/find-block-by-content db "b1")]
+                            (remove nil?)
+                            (mapv #(vector :block/uuid (:block/uuid %)))))
+        conn2 (db-test/create-conn)
+        {:keys [init-tx block-props-tx] :as _txs}
+        (-> (sqlite-export/build-export @conn {:export-type :view-nodes :node-ids (get-node-ids @conn)})
+            (sqlite-export/build-import @conn2 {}))
+        ;; _ (cljs.pprint/pprint _txs)
+        _ (d/transact! conn2 init-tx)
+        _ (d/transact! conn2 block-props-tx)
+        _ (validate-db @conn2)
+        imported-nodes (sqlite-export/build-export @conn2 {:export-type :view-nodes
+                                                           :node-ids (get-node-ids @conn2)})]
+
+    (is (= (:pages-and-blocks original-data) (:pages-and-blocks imported-nodes)))
+    (is (= (expand-properties (:properties original-data)) (:properties imported-nodes)))
+    (is (= (expand-classes (:classes original-data)) (:classes imported-nodes)))))

+ 7 - 2
src/main/frontend/components/views.cljs

@@ -19,6 +19,7 @@
             [frontend.db :as db]
             [frontend.db :as db]
             [frontend.db-mixins :as db-mixins]
             [frontend.db-mixins :as db-mixins]
             [frontend.db.async :as db-async]
             [frontend.db.async :as db-async]
+            [frontend.handler.db-based.export :as db-export-handler]
             [frontend.handler.db-based.property :as db-property-handler]
             [frontend.handler.db-based.property :as db-property-handler]
             [frontend.handler.editor :as editor-handler]
             [frontend.handler.editor :as editor-handler]
             [frontend.handler.property :as property-handler]
             [frontend.handler.property :as property-handler]
@@ -315,7 +316,7 @@
     columns))
     columns))
 
 
 (rum/defc more-actions
 (rum/defc more-actions
-  [view-entity columns {:keys [column-visible? column-toggle-visibility]}]
+  [view-entity columns {:keys [column-visible? rows column-toggle-visibility]}]
   (let [display-type (:db/ident (:logseq.property.view/type view-entity))
   (let [display-type (:db/ident (:logseq.property.view/type view-entity))
         table? (= display-type :logseq.property.view/type.table)
         table? (= display-type :logseq.property.view/type.table)
         columns' (filter (fn [column]
         columns' (filter (fn [column]
@@ -366,7 +367,11 @@
                                                                                (:db/id (db/entity (:id column))))
                                                                                (:db/id (db/entity (:id column))))
                                       (db-property-handler/remove-block-property! (:db/id view-entity) :logseq.property.view/group-by-property)))
                                       (db-property-handler/remove-block-property! (:db/id view-entity) :logseq.property.view/group-by-property)))
                  :onSelect (fn [e] (.preventDefault e))}
                  :onSelect (fn [e] (.preventDefault e))}
-                (:name column))))))))))))
+                (:name column))))))
+         (shui/dropdown-menu-item
+          {:key "export-edn"
+           :on-click #(db-export-handler/export-view-nodes-data rows)}
+          "Export EDN")))))))
 
 
 (defn- get-column-size
 (defn- get-column-size
   [column sized-columns]
   [column sized-columns]

+ 12 - 0
src/main/frontend/handler/db_based/export.cljs

@@ -27,6 +27,18 @@
         (notification/show! "Copied block's data!" :success)))
         (notification/show! "Copied block's data!" :success)))
     (notification/show! "No block found" :warning)))
     (notification/show! "No block found" :warning)))
 
 
+(defn export-view-nodes-data [nodes]
+  (let [block-uuids (mapv #(vector :block/uuid (:block/uuid %)) nodes)]
+    (when-let [^Object worker @state/*db-worker]
+      (p/let [result* (.export-edn worker
+                                   (state/get-current-repo)
+                                   (ldb/write-transit-str {:export-type :view-nodes :node-ids block-uuids}))
+              result (ldb/read-transit-str result*)
+              pull-data (with-out-str (pprint/pprint result))]
+        (.writeText js/navigator.clipboard pull-data)
+        (println pull-data)
+        (notification/show! "Copied block's data!" :success)))))
+
 (defn ^:export export-page-data []
 (defn ^:export export-page-data []
   (if-let [page-id (page-util/get-current-page-id)]
   (if-let [page-id (page-util/get-current-page-id)]
     (when-let [^Object worker @state/*db-worker]
     (when-let [^Object worker @state/*db-worker]