From 02d31a7947b44044007d5a50f0cc746c0bdcb96c Mon Sep 17 00:00:00 2001
From: "alonso.torres" <alonso.torres@kaleidos.net>
Date: Thu, 30 Sep 2021 11:10:48 +0200
Subject: [PATCH] :sparkles: Adds progress report to importing process

---
 CHANGES.md                                    |   6 +
 .../resources/styles/main/partials/modal.scss |   7 ++
 .../src/app/main/ui/dashboard/import.cljs     |  67 ++++++++---
 frontend/src/app/util/import/parser.cljs      |   2 +-
 frontend/src/app/worker/import.cljs           | 104 +++++++++++++-----
 frontend/translations/en.po                   |  22 ++++
 frontend/translations/es.po                   |  24 ++++
 7 files changed, 186 insertions(+), 46 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 3fff60c6a..767aec2f0 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -9,6 +9,12 @@
 ### :boom: Breaking changes
 ### :heart: Community contributions by (Thank you!)
 
+## 1.8.3-alpha
+
+### :sparkles: New features
+
+- Adds progress report to importing process
+
 ## 1.8.2-alpha
 
 ### :bug: Bugs fixed
diff --git a/frontend/resources/styles/main/partials/modal.scss b/frontend/resources/styles/main/partials/modal.scss
index 0522cb6cd..fb6d3c686 100644
--- a/frontend/resources/styles/main/partials/modal.scss
+++ b/frontend/resources/styles/main/partials/modal.scss
@@ -399,6 +399,13 @@
     font-style: italic;
   }
 
+  .progress-message {
+    margin: 0 2rem;
+    color: $color-info;
+    font-size: $fs12;
+    font-style: italic;
+  }
+
   .linked-libraries {
     display: flex;
     flex-wrap: wrap;
diff --git a/frontend/src/app/main/ui/dashboard/import.cljs b/frontend/src/app/main/ui/dashboard/import.cljs
index dcc0663b5..1e4202632 100644
--- a/frontend/src/app/main/ui/dashboard/import.cljs
+++ b/frontend/src/app/main/ui/dashboard/import.cljs
@@ -98,23 +98,53 @@
        (filter #(= :ready (:status %)))
        (mapv #(assoc % :status :importing))))
 
-(defn update-status [files file-id status]
+(defn update-status [files file-id status progress]
   (->> files
        (mapv (fn [file]
                (cond-> file
-                 (= file-id (:file-id file))
-                 (assoc :status status))))))
+                 (and (= file-id (:file-id file)) (not= status :import-progress))
+                 (assoc :status status)
+
+                 (and (= file-id (:file-id file)) (= status :import-progress))
+                 (assoc :progress progress))))))
+
+(defn parse-progress-message
+  [message]
+  (case (:type message)
+    :upload-data
+    (tr "dashboard.import.progress.upload-data" (:current message) (:total message))
+
+    :upload-media
+    (tr "dashboard.import.progress.upload-media" (:file message))
+
+    :process-page
+    (tr "dashboard.import.progress.process-page" (:file message))
+
+    :process-colors
+    (tr "dashboard.import.progress.process-colors")
+
+    :process-typographies
+    (tr "dashboard.import.progress.process-typographies")
+
+    :process-media
+    (tr "dashboard.import.progress.process-media")
+
+    :process-components
+    (tr "dashboard.import.progress.process-components")
+
+    (str message)))
 
 (mf/defc import-entry
   [{:keys [state file editing?]}]
 
-  (let [loading?      (or (= :analyzing (:status file))
-                          (= :importing (:status file)))
-        load-success? (= :import-success (:status file))
-        analyze-error?   (= :analyze-error (:status file))
-        import-error?   (= :import-error (:status file))
-        ready?        (= :ready (:status file))
-        is-shared?    (:shared file)
+  (let [loading?       (or (= :analyzing (:status file))
+                           (= :importing (:status file)))
+        load-success?  (= :import-success (:status file))
+        analyze-error? (= :analyze-error (:status file))
+        import-error?  (= :import-error (:status file))
+        ready?         (= :ready (:status file))
+        is-shared?     (:shared file)
+        progress       (:progress file)
 
         handle-edit-key-press
         (mf/use-callback
@@ -173,13 +203,17 @@
        [:button {:on-click handle-edit-entry}   i/pencil]
        [:button {:on-click handle-remove-entry} i/trash]]]
 
-     (when analyze-error?
+     (cond
+       analyze-error?
        [:div.error-message
-        (tr "dashboard.import.analyze-error")])
+        (tr "dashboard.import.analyze-error")]
 
-     (when import-error?
+       import-error?
        [:div.error-message
-        (tr "dashboard.import.import-error")])
+        (tr "dashboard.import.import-error")]
+
+       (and (not load-success?) (some? progress))
+       [:div.progress-message (parse-progress-message progress)])
 
      [:div.linked-libraries
       (for [library-id (:libraries file)]
@@ -223,11 +257,10 @@
                  {:cmd :import-files
                   :project-id project-id
                   :files files})
-                (rx/delay-emit emit-delay)
                 (rx/subs
-                 (fn [{:keys [file-id status] :as msg}]
+                 (fn [{:keys [file-id status message] :as msg}]
                    (log/debug :msg msg)
-                   (swap! state update :files update-status file-id status))))))
+                   (swap! state update :files update-status file-id status message))))))
 
         handle-cancel
         (mf/use-callback
diff --git a/frontend/src/app/util/import/parser.cljs b/frontend/src/app/util/import/parser.cljs
index f4628f478..8481543e2 100644
--- a/frontend/src/app/util/import/parser.cljs
+++ b/frontend/src/app/util/import/parser.cljs
@@ -628,7 +628,7 @@
 
 (defn get-image-name
   [node]
-  (get-in node [:attrs :penpot:name]))
+  (get-meta node :name))
 
 (defn get-image-data
   [node]
diff --git a/frontend/src/app/worker/import.cljs b/frontend/src/app/worker/import.cljs
index fc6c8a4c1..587e03eae 100644
--- a/frontend/src/app/worker/import.cljs
+++ b/frontend/src/app/worker/import.cljs
@@ -68,6 +68,33 @@
        no-parse?
        (rx/map :content)))))
 
+(defn progress!
+  ([context type]
+   (assert (keyword? type))
+   (progress! context type nil nil nil))
+
+  ([context type file]
+   (assert (keyword? type))
+   (assert (string? file))
+   (progress! context type file nil nil))
+
+  ([context type current total]
+   (keyword? type)
+   (assert (number? current))
+   (assert (number? total))
+   (progress! context type nil current total))
+
+  ([context type file current total]
+   (when (and context (contains? context :progress))
+     (let [msg {:type type
+                :file file
+                :current current
+                :total total}]
+       (log/debug :status :import-progress :message msg)
+       (rx/push! (:progress context) {:file-id (:file-id context)
+                                      :status :import-progress
+                                      :message msg})))))
+
 (defn resolve-factory
   "Creates a wrapper around the atom to remap ids to new ids and keep
   their relationship so they ids are coherent."
@@ -118,34 +145,42 @@
 
 (defn send-changes
   "Creates batches of changes to be sent to the backend"
-  [file]
+  [context file]
   (let [revn (atom (:revn file))
         file-id (:id file)
         session-id (uuid/next)
         changes-batches
         (->> (fb/generate-changes file)
              (partition change-batch-size change-batch-size nil)
-             (mapv vec))]
+             (mapv vec))
+
+        current (atom 0)
+        total (count changes-batches)]
 
     (rx/concat
      (->> (rx/from changes-batches)
           (rx/mapcat
-           #(rp/mutation
-             :update-file
-             {:id file-id
-              :session-id session-id
-              :revn @revn
-              :changes %}))
+           (fn [change-batch]
+             (->> (rp/mutation :update-file
+                               {:id file-id
+                                :session-id session-id
+                                :revn @revn
+                                :changes change-batch})
+                  (rx/tap #(do (swap! current inc)
+                               (progress! context
+                                          :upload-data @current total))))))
+          
           (rx/map first)
-          (rx/tap #(reset! revn (:revn %))))
+          (rx/tap #(reset! revn (:revn %)))
+          (rx/ignore))
 
      (rp/mutation :persist-temp-file {:id file-id}))))
 
 (defn upload-media-files
   "Upload a image to the backend and returns its id"
-  [file-id name data-uri]
+  [context file-id name data-uri]
 
-  (log/debug :action "uploading" :file-id file-id :name name)
+  (log/debug :action "Uploading" :file-id file-id :name name)
 
   (->> (http/send!
         {:uri data-uri
@@ -158,6 +193,7 @@
            :file-id file-id
            :content blob
            :is-local true}))
+       (rx/tap #(progress! context :upload-media name))
        (rx/flat-map #(rp/mutation! :upload-file-media-object %))))
 
 (defn resolve-text-content [node context]
@@ -249,12 +285,12 @@
     (-> file process-interactions)))
 
 (defn resolve-media
-  [file-id node]
+  [context file-id node]
   (if (and (not (cip/close? node))
            (cip/has-image? node))
     (let [name     (cip/get-image-name node)
           data-uri (cip/get-image-data node)]
-      (->> (upload-media-files file-id name data-uri)
+      (->> (upload-media-files context file-id name data-uri)
            (rx/catch #(do (.error js/console "Error uploading media: " name)
                           (rx/of node)))
            (rx/map
@@ -280,7 +316,7 @@
         file (-> file (fb/add-page page-data))]
     (->> (rx/from nodes)
          (rx/filter cip/shape?)
-         (rx/mapcat (partial resolve-media file-id))
+         (rx/mapcat (partial resolve-media context file-id))
          (rx/reduce (partial process-import-node context) file)
          (rx/map (comp fb/close-page setup-interactions)))))
 
@@ -316,6 +352,8 @@
         pages (->> (:pages context) (mapv get-page-data))]
 
     (->> (rx/from pages)
+         (rx/tap (fn [[_ page-name]]
+                   (progress! context :process-page page-name)))
          (rx/mapcat
           (fn [[page-id page-name]]
             (->> (get-file context :page page-id)
@@ -369,6 +407,7 @@
                                   :file-id (:id file)
                                   :content content
                                   :is-local false})))
+                     (rx/tap #(progress! context :upload-media (:name %)))
                      (rx/flat-map #(rp/mutation! :upload-file-media-object %))
                      (rx/map (constantly media))
                      (rx/catch #(do (.error js/console (str "Error uploading media: " (:name media)) )
@@ -392,13 +431,22 @@
 (defn process-file
   [context file]
 
-  (->> (rx/of file)
-       (rx/flat-map (partial process-pages context))
-       (rx/flat-map (partial process-library-colors context))
-       (rx/flat-map (partial process-library-typographies context))
-       (rx/flat-map (partial process-library-media context))
-       (rx/flat-map (partial process-library-components context))
-       (rx/flat-map send-changes)))
+  (let [progress-str (rx/subject)
+        context (assoc context :progress progress-str)]
+    (rx/merge
+     progress-str
+     (->> (rx/of file)
+          (rx/flat-map (partial process-pages context))
+          (rx/tap #(progress! context :process-colors))
+          (rx/flat-map (partial process-library-colors context))
+          (rx/tap #(progress! context :process-typographies))
+          (rx/flat-map (partial process-library-typographies context))
+          (rx/tap #(progress! context :process-media))
+          (rx/flat-map (partial process-library-media context))
+          (rx/tap #(progress! context :process-components))
+          (rx/flat-map (partial process-library-components context))
+          (rx/flat-map (partial send-changes context))
+          (rx/tap #(rx/end! progress-str))))))
 
 (defn create-files
   [context files]
@@ -439,13 +487,13 @@
          (rx/catch #(.error js/console "IMPORT ERROR" %))
          (rx/flat-map
           (fn [[file data]]
-            (->> (uz/load-from-url (:uri data))
-                 (rx/map #(-> context (assoc :zip %) (merge data)))
-                 (rx/flat-map #(process-file % file))
-                 (rx/map
-                  (fn [_]
-                    {:status :import-success
-                     :file-id (:file-id data)}))
+            (->> (rx/concat
+                  (->> (uz/load-from-url (:uri data))
+                       (rx/map #(-> context (assoc :zip %) (merge data)))
+                       (rx/flat-map #(process-file % file)))
+                  (rx/of
+                   {:status :import-success
+                    :file-id (:file-id data)}))
 
                  (rx/catch
                      (fn [err]
diff --git a/frontend/translations/en.po b/frontend/translations/en.po
index 80da61909..2a92e779b 100644
--- a/frontend/translations/en.po
+++ b/frontend/translations/en.po
@@ -323,6 +323,27 @@ msgstr "There was a problem importing the file. The file wasn't imported."
 msgid "dashboard.import.import-message"
 msgstr "%s files have been imported succesfully."
 
+msgid "dashboard.import.progress.upload-data"
+msgstr "Uploading data to server (%s/%s)"
+
+msgid "dashboard.import.progress.upload-media"
+msgstr "Uploading file: %s"
+
+msgid "dashboard.import.progress.process-page"
+msgstr "Processing page: %s"
+
+msgid "dashboard.import.progress.process-colors"
+msgstr "Processing colors"
+
+msgid "dashboard.import.progress.process-typographies"
+msgstr "Processing typographies"
+
+msgid "dashboard.import.progress.process-media"
+msgstr "Processing media"
+
+msgid "dashboard.import.progress.process-components"
+msgstr "Processing components"
+
 #: src/app/main/ui/dashboard/team.cljs
 msgid "dashboard.invite-profile"
 msgstr "Invite to team"
@@ -2975,3 +2996,4 @@ msgstr "Sorry!"
 
 msgid "viewer.breaking-change.description"
 msgstr "This shareable link is no longer valid. Create a new one or ask the owner for a new one.
+
diff --git a/frontend/translations/es.po b/frontend/translations/es.po
index f2c97d48e..265eb513c 100644
--- a/frontend/translations/es.po
+++ b/frontend/translations/es.po
@@ -325,6 +325,30 @@ msgstr "Hubo un problema importando el fichero. No se ha creado el fichero."
 msgid "dashboard.import.import-message"
 msgstr "%s ficheros han sido importados con éxito."
 
+msgid "dashboard.import.import-message"
+msgstr "%s files have been imported succesfully."
+
+msgid "dashboard.import.progress.upload-data"
+msgstr "Enviando datos al servidor (%s/%s)"
+
+msgid "dashboard.import.progress.upload-media"
+msgstr "Enviando fichero: %s"
+
+msgid "dashboard.import.progress.process-page"
+msgstr "Procesando página: %s"
+
+msgid "dashboard.import.progress.process-colors"
+msgstr "Procesando colores"
+
+msgid "dashboard.import.progress.process-typographies"
+msgstr "Procesando tipografías"
+
+msgid "dashboard.import.progress.process-media"
+msgstr "Procesando media"
+
+msgid "dashboard.import.progress.process-components"
+msgstr "Procesando componentes"
+
 #: src/app/main/ui/dashboard/team.cljs
 msgid "dashboard.invite-profile"
 msgstr "Invitar al equipo"