Browse Source

enhance: db graph importer supports optional parent class property

Gabriel Horner 1 year ago
parent
commit
e08e52acd3

+ 5 - 1
deps/graph-parser/script/db_import.cljs

@@ -89,7 +89,11 @@
            :desc "Additional files to import"}
    :property-classes {:alias :p
                       :coerce []
-                      :desc "List of properties whose values convert to classes"}})
+                      :desc "List of properties whose values convert to classes"}
+   :property-parent-classes
+   {:alias :P
+    :coerce []
+    :desc "List of properties whose values convert to a parent class"}})
 
 (defn -main [args]
   (let [[file-graph db-graph-dir] args

+ 48 - 22
deps/graph-parser/src/logseq/graph_parser/exporter.cljs

@@ -303,13 +303,13 @@
 
 (defn- pre-update-properties
   "Updates page and block properties before their property types are inferred"
-  [properties property-classes]
+  [properties class-related-properties]
   (let [dissoced-props (concat ignored-built-in-properties
                                ;; TODO: Add import support for these dissoced built-in properties
                                [:title :id :created-at :updated-at
                                 :card-last-interval :card-repeats :card-last-reviewed :card-next-schedule
                                 :card-ease-factor :card-last-score]
-                               property-classes)]
+                               class-related-properties)]
     (->> (apply dissoc properties dissoced-props)
          (keep (fn [[prop val]]
                  (if (not (contains? built-in-property-names prop))
@@ -324,19 +324,19 @@
                    [prop val])))
          (into {}))))
 
-(defn- handle-page-properties
+(defn- handle-page-and-block-properties
   "Handles modifying :block/properties, updating classes from property-classes
   and removing any deprecated property related attributes. Before updating most
   :block/properties, their property schemas are inferred as that can affect how
   a property is updated. Only infers property schemas on user properties as
   built-in ones must not change"
   [{:block/keys [properties] :as block} db page-names-to-uuids refs
-   {:keys [import-state macros property-classes] :as options}]
+   {:keys [import-state macros property-classes property-parent-classes] :as options}]
   (-> (if (seq properties)
         (let [classes-from-properties (->> (select-keys properties property-classes)
                                            (mapcat (fn [[_k v]] (if (coll? v) v [v])))
                                            distinct)
-              properties' (pre-update-properties properties property-classes)
+              properties' (pre-update-properties properties (into property-classes property-parent-classes))
               properties-to-infer (if (:template properties')
                                     ;; Ignore template properties as they don't consistently have representative property values
                                     {}
@@ -364,10 +364,32 @@
         block)
       (dissoc :block/properties-text-values :block/properties-order :block/invalid-properties)))
 
+(defn- handle-page-properties
+  [{:block/keys [properties] :as block} db page-names-to-uuids refs
+   {:keys [property-parent-classes log-fn] :as options}]
+  (-> (if (seq properties)
+        (let [parent-classes-from-properties (->> (select-keys properties property-parent-classes)
+                                                  (mapcat (fn [[_k v]] (if (coll? v) v [v])))
+                                                  distinct)]
+          (cond-> block
+            (seq parent-classes-from-properties)
+            (assoc :block/type "class")
+            (seq parent-classes-from-properties)
+            (assoc :class/parent
+                   (let [new-class (first parent-classes-from-properties)]
+                     (when (> (count parent-classes-from-properties) 1)
+                       (log-fn :skipped-parent-classes "Only one parent class is allowed so skipped ones after the first one" :classes parent-classes-from-properties))
+                     (sqlite-util/build-new-class
+                      {:block/original-name new-class
+                       :block/uuid (or (get-pid db new-class) (d/squuid))
+                       :block/name (common-util/page-name-sanity-lc new-class)})))))
+        block)
+      (handle-page-and-block-properties db page-names-to-uuids refs options)))
+
 (defn- handle-block-properties
   "Does everything page properties does and updates a couple of block specific attributes"
   [block db page-names-to-uuids refs {:keys [property-classes] :as options}]
-  (cond-> (handle-page-properties block db page-names-to-uuids refs options)
+  (cond-> (handle-page-and-block-properties block db page-names-to-uuids refs options)
     (and (seq property-classes) (seq (:block/refs block)))
     ;; remove unused, nonexistent property page
     (update :block/refs (fn [refs] (remove #(property-classes (keyword (:block/name %))) refs)))
@@ -465,10 +487,11 @@
 
 (defn- build-pages-tx
   "Given all the pages and blocks parsed from a file, return all non-whiteboard pages to be transacted"
-  [conn pages blocks {:keys [page-tags-uuid import-state tag-classes property-classes notify-user] :as options}]
+  [conn pages blocks {:keys [page-tags-uuid import-state tag-classes property-classes property-parent-classes notify-user]
+                      :as options}]
   (let [all-pages (->> (extract/with-ref-pages pages blocks)
                        ;; remove unused property pages unless the page has content
-                       (remove #(and (contains? property-classes (keyword (:block/name %)))
+                       (remove #(and (contains? (into property-classes property-parent-classes) (keyword (:block/name %)))
                                      (not (:block/file %))))
                        ;; remove file path relative
                        (map #(dissoc % :block/file)))
@@ -487,8 +510,8 @@
                                 ;; These attributes are not allowed to be transacted because they must not change across files
                                 ;; block/uuid was particularly bad as it actually changed the page's identity across files
                                 disallowed-attributes [:block/name :block/uuid :block/format :block/journal? :block/original-name :block/journal-day
-                                                       :block/type :block/created-at :block/updated-at]
-                                allowed-attributes [:block/properties :block/tags :block/alias :block/namespace]
+                                                       :block/created-at :block/updated-at]
+                                allowed-attributes [:block/properties :block/tags :block/alias :block/namespace :class/parent :block/type]
                                 block-changes (select-keys % allowed-attributes)]
                             (when-let [ignored-attrs (not-empty (apply dissoc % (into disallowed-attributes allowed-attributes)))]
                               (notify-user {:msg (str "Import ignored the following attributes on page " (pr-str (:block/original-name %)) ": "
@@ -572,7 +595,10 @@
     :tag-classes (set (map string/lower-case (:tag-classes user-options)))
     :property-classes (set/difference
                        (set (map (comp keyword string/lower-case) (:property-classes user-options)))
-                       built-in-property-names)}))
+                       built-in-property-names)
+    :property-parent-classes (set/difference
+                              (set (map (comp keyword string/lower-case) (:property-parent-classes user-options)))
+                              built-in-property-names)}))
 
 (defn add-file-to-db-graph
   "Parse file and save parsed data to the given db graph. Options available:
@@ -816,15 +842,15 @@
 (defn build-doc-options
   "Builds options for use with export-doc-files"
   [conn config options]
-(-> {:extract-options {:date-formatter (common-config/get-date-formatter config)
-                       :user-config config
-                       :filename-format (or (:file/name-format config) :legacy)
-                       :verbose (:verbose options)}
-     :user-options (select-keys options [:tag-classes :property-classes])
-     :page-tags-uuid (:block/uuid (d/entity @conn :logseq.property/page-tags))
-     :import-state (new-import-state)
-     :macros (or (:macros options) (:macros config))}
-    (merge (select-keys options [:set-ui-state :export-file :notify-user]))))
+  (-> {:extract-options {:date-formatter (common-config/get-date-formatter config)
+                         :user-config config
+                         :filename-format (or (:file/name-format config) :legacy)
+                         :verbose (:verbose options)}
+       :user-options (select-keys options [:tag-classes :property-classes :property-parent-classes])
+       :page-tags-uuid (:block/uuid (d/entity @conn :logseq.property/page-tags))
+       :import-state (new-import-state)
+       :macros (or (:macros options) (:macros config))}
+      (merge (select-keys options [:set-ui-state :export-file :notify-user]))))
 
 (defn export-file-graph
   "Main fn which exports a file graph given its files and imports them
@@ -844,8 +870,8 @@
    
    Note: See export-doc-files for additional options that are only for it"
   [repo-or-conn conn config-file *files {:keys [<read-file <copy-asset rpath-key log-fn]
-                                 :or {rpath-key :path log-fn println}
-                                 :as options}]
+                                         :or {rpath-key :path log-fn println}
+                                         :as options}]
   (p/let [config (export-config-file
                   repo-or-conn config-file <read-file
                   (-> (select-keys options [:notify-user :default-config :<save-config-file])

+ 20 - 2
src/main/frontend/components/imports.cljs

@@ -164,10 +164,12 @@
   (let [[graph-input set-graph-input!] (rum/use-state initial-name)
         [tag-classes-input set-tag-classes-input!] (rum/use-state "")
         [property-classes-input set-property-classes-input!] (rum/use-state "")
+        [property-parent-classes-input set-property-parent-classes-input!] (rum/use-state "")
         on-submit #(do (on-graph-name-confirmed
                         {:graph-name graph-input
                          :tag-classes tag-classes-input
-                         :property-classes property-classes-input})
+                         :property-classes property-classes-input
+                         :property-parent-classes property-parent-classes-input})
                        (state/close-modal!))]
     [:div.container
      [:div.sm:flex.sm:items-start
@@ -213,6 +215,21 @@
        :on-key-down (fn [e]
                       (when (= "Enter" (util/ekey e))
                         (on-submit)))})
+     
+     [:div.sm:flex.sm:items-start
+      [:div.mt-3.text-center.sm:mt-0.sm:text-left
+       [:h3#modal-headline.leading-6.font-medium
+        "(Optional) Properties whose values are imported as parent classes e.g. 'parent':"]
+       [:span.text-xs
+        "Properties are case insensitive and separated by commas"]]]
+     (shui/input
+      {:class "my-2 mb-4"
+       :default-value property-parent-classes-input
+       :on-change (fn [e]
+                    (set-property-parent-classes-input! (util/evalue e)))
+       :on-key-down (fn [e]
+                      (when (= "Enter" (util/ekey e))
+                        (on-submit)))})
 
      (shui/button {:on-click on-submit} "Submit")]))
 
@@ -281,7 +298,7 @@
                    (fs/write-file! repo repo-dir (:path file) content {:skip-transact? true})))))))
 
 (defn- import-file-graph
-  [*files {:keys [graph-name tag-classes property-classes]} config-file]
+  [*files {:keys [graph-name tag-classes property-classes property-parent-classes]} config-file]
   (state/set-state! :graph/importing :file-graph)
   (state/set-state! [:graph/importing-state :current-page] "Config files")
   (p/let [start-time (t/now)
@@ -291,6 +308,7 @@
           options {;; user options
                    :tag-classes (set (string/split tag-classes #",\s*"))
                    :property-classes (set (string/split property-classes #",\s*"))
+                   :property-parent-classes (set (string/split property-parent-classes #",\s*"))
                    ;; common options
                    :notify-user show-notification
                    :set-ui-state state/set-state!