mirror of
https://github.com/penpot/penpot.git
synced 2025-06-04 04:21:38 +02:00
✨ Add the ability to stream events on rpc methods
This commit is contained in:
parent
f3e9efa6fe
commit
03518a8da1
21 changed files with 408 additions and 106 deletions
|
@ -10,6 +10,7 @@
|
|||
[app.common.data.macros :as dm]
|
||||
[app.common.features :as cfeat]
|
||||
[app.common.files.helpers :as cfh]
|
||||
[app.common.logging :as log]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.uri :as u]
|
||||
[app.common.uuid :as uuid]
|
||||
|
@ -25,6 +26,7 @@
|
|||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.router :as rt]
|
||||
[app.util.sse :as sse]
|
||||
[app.util.time :as dt]
|
||||
[app.util.timers :as tm]
|
||||
[app.util.webapi :as wapi]
|
||||
|
@ -32,6 +34,8 @@
|
|||
[clojure.set :as set]
|
||||
[potok.core :as ptk]))
|
||||
|
||||
(log/set-level! :warn)
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Initialization
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
@ -949,7 +953,17 @@
|
|||
(let [{:keys [on-success on-error]
|
||||
:or {on-success identity
|
||||
on-error rx/throw}} (meta params)]
|
||||
(->> (rp/cmd! :clone-template {:project-id project-id :template-id template-id})
|
||||
(->> (rp/cmd! ::sse/clone-template {:project-id project-id
|
||||
:template-id template-id})
|
||||
(rx/tap (fn [event]
|
||||
(let [payload (sse/get-payload event)
|
||||
type (sse/get-type event)]
|
||||
(if (= type "event")
|
||||
(log/dbg :hint "clone-template: progress" :section (:section payload) :name (:name payload))
|
||||
(log/dbg :hint "clone-template: end")))))
|
||||
|
||||
(rx/filter sse/end-of-stream?)
|
||||
(rx/map sse/get-payload)
|
||||
(rx/tap on-success)
|
||||
(rx/catch on-error))))))
|
||||
|
||||
|
|
|
@ -7,9 +7,11 @@
|
|||
(ns app.main.repo
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.transit :as t]
|
||||
[app.common.uri :as u]
|
||||
[app.config :as cf]
|
||||
[app.util.http :as http]
|
||||
[app.util.sse :as sse]
|
||||
[beicon.core :as rx]
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
|
@ -56,8 +58,14 @@
|
|||
{:query-params [:file-id :revn]
|
||||
:form-data? true}
|
||||
|
||||
::sse/clone-template
|
||||
{:response-type ::sse/stream}
|
||||
|
||||
::sse/import-binfile
|
||||
{:response-type ::sse/stream
|
||||
:form-data? true}
|
||||
|
||||
:export-binfile {:response-type :blob}
|
||||
:import-binfile {:form-data? true}
|
||||
:retrieve-list-of-builtin-templates {:query-params :all}
|
||||
})
|
||||
|
||||
|
@ -85,9 +93,9 @@
|
|||
:else :post)
|
||||
|
||||
request {:method method
|
||||
:uri (u/join cf/public-uri "api/rpc/command/" (name id))
|
||||
:uri (u/join cf/public-uri "api/rpc/command/" nid)
|
||||
:credentials "include"
|
||||
:headers {"accept" "application/transit+json"}
|
||||
:headers {"accept" "application/transit+json,text/event-stream,*/*"}
|
||||
:body (when (= method :post)
|
||||
(if form-data?
|
||||
(http/form-data params)
|
||||
|
@ -97,11 +105,21 @@
|
|||
(if query-params
|
||||
(select-keys params query-params)
|
||||
nil))
|
||||
:response-type (or response-type :text)}]
|
||||
|
||||
(->> (http/send! request)
|
||||
(rx/map decode-fn)
|
||||
(rx/mapcat handle-response))))
|
||||
:response-type
|
||||
(if (= response-type ::sse/stream)
|
||||
:stream
|
||||
(or response-type :text))}
|
||||
|
||||
result (->> (http/send! request)
|
||||
(rx/map decode-fn)
|
||||
(rx/mapcat handle-response))]
|
||||
|
||||
(cond->> result
|
||||
(= ::sse/stream response-type)
|
||||
(rx/mapcat (fn [body]
|
||||
(-> (sse/create-stream body)
|
||||
(sse/read-stream t/decode-str)))))))
|
||||
|
||||
(defmulti cmd! (fn [id _] id))
|
||||
|
||||
|
|
|
@ -147,8 +147,10 @@
|
|||
(mf/use-fn
|
||||
(mf/deps template default-project-id)
|
||||
(fn []
|
||||
(let [mdata {:on-success on-template-cloned-success :on-error on-template-cloned-error}
|
||||
params {:project-id default-project-id :template-id (:id template)}]
|
||||
(let [mdata {:on-success on-template-cloned-success
|
||||
:on-error on-template-cloned-error}
|
||||
params {:project-id default-project-id
|
||||
:template-id (:id template)}]
|
||||
(swap! state #(assoc % :status :importing))
|
||||
(st/emit! (with-meta (dd/clone-template (with-meta params mdata))
|
||||
{::ev/origin "get-started-hero-block"})))))]
|
||||
|
|
|
@ -105,17 +105,22 @@
|
|||
|
||||
(defn send!
|
||||
[{:keys [response-type] :or {response-type :text} :as params}]
|
||||
(letfn [(on-response [response]
|
||||
(let [body (case response-type
|
||||
:json (.json ^js response)
|
||||
:text (.text ^js response)
|
||||
:blob (.blob ^js response))]
|
||||
(->> (rx/from body)
|
||||
(rx/map (fn [body]
|
||||
{::response response
|
||||
:status (.-status ^js response)
|
||||
:headers (parse-headers (.-headers ^js response))
|
||||
:body body})))))]
|
||||
(letfn [(on-response [^js response]
|
||||
(if (= :stream response-type)
|
||||
(rx/of {:status (.-status response)
|
||||
:headers (parse-headers (.-headers response))
|
||||
:body (.-body response)
|
||||
::response response})
|
||||
(let [body (case response-type
|
||||
:json (.json ^js response)
|
||||
:text (.text ^js response)
|
||||
:blob (.blob ^js response))]
|
||||
(->> (rx/from body)
|
||||
(rx/map (fn [body]
|
||||
{::response response
|
||||
:status (.-status ^js response)
|
||||
:headers (parse-headers (.-headers ^js response))
|
||||
:body body}))))))]
|
||||
(->> (fetch params)
|
||||
(rx/mapcat on-response))))
|
||||
|
||||
|
|
54
frontend/src/app/util/sse.cljs
Normal file
54
frontend/src/app/util/sse.cljs
Normal file
|
@ -0,0 +1,54 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.util.sse
|
||||
(:require
|
||||
["eventsource-parser/stream" :as sse]
|
||||
[beicon.core :as rx]))
|
||||
|
||||
(defn create-stream
|
||||
[^js/ReadableStream stream]
|
||||
(.. stream
|
||||
(pipeThrough (js/TextDecoderStream.))
|
||||
(pipeThrough (sse/EventSourceParserStream.))))
|
||||
|
||||
(defn read-stream
|
||||
[^js/ReadableStream stream decode-fn]
|
||||
(letfn [(read-items [^js reader]
|
||||
(->> (rx/from (.read reader))
|
||||
(rx/mapcat (fn [result]
|
||||
(if (.-done result)
|
||||
(rx/empty)
|
||||
(rx/concat
|
||||
(rx/of (.-value result))
|
||||
(read-items reader)))))))]
|
||||
(->> (read-items (.getReader stream))
|
||||
(rx/mapcat (fn [^js event]
|
||||
(let [type (.-event event)
|
||||
data (.-data event)
|
||||
data (decode-fn data)]
|
||||
(if (= "error" type)
|
||||
(rx/throw (ex-info "stream exception" data))
|
||||
(rx/of #js {:type type :data data}))))))))
|
||||
|
||||
(defn get-type
|
||||
[event]
|
||||
(unchecked-get event "type"))
|
||||
|
||||
(defn get-payload
|
||||
[event]
|
||||
(unchecked-get event "data"))
|
||||
|
||||
(defn end-of-stream?
|
||||
[event]
|
||||
(= "end" (get-type event)))
|
||||
|
||||
(defn event?
|
||||
[event]
|
||||
(= "event" (get-type event)))
|
||||
|
||||
|
||||
|
|
@ -22,6 +22,7 @@
|
|||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.import.parser :as cip]
|
||||
[app.util.json :as json]
|
||||
[app.util.sse :as sse]
|
||||
[app.util.webapi :as wapi]
|
||||
[app.util.zip :as uz]
|
||||
[app.worker.impl :as impl]
|
||||
|
@ -329,7 +330,7 @@
|
|||
(map #(assoc % :type :fill)))
|
||||
stroke-images-data (->> (cip/get-stroke-images-data node)
|
||||
(map #(assoc % :type :stroke)))
|
||||
|
||||
|
||||
images-data (concat
|
||||
fill-images-data
|
||||
stroke-images-data
|
||||
|
@ -709,15 +710,22 @@
|
|||
:response-type :blob
|
||||
:method :get})
|
||||
(rx/map :body)
|
||||
(rx/mapcat #(rp/cmd! :import-binfile {:file % :project-id project-id}))
|
||||
(rx/map (fn [_]
|
||||
{:status :import-finish
|
||||
:file-id (:file-id data)}))
|
||||
(rx/mapcat (fn [file]
|
||||
(->> (rp/cmd! ::sse/import-binfile {:file file :project-id project-id})
|
||||
(rx/tap (fn [event]
|
||||
(let [payload (sse/get-payload event)
|
||||
type (sse/get-type event)]
|
||||
(if (= type "event")
|
||||
(log/dbg :hint "import-binfile: progress" :section (:section payload) :name (:name payload))
|
||||
(log/dbg :hint "import-binfile: end")))))
|
||||
(rx/filter sse/end-of-stream?)
|
||||
(rx/map (fn [_]
|
||||
{:status :import-finish
|
||||
:file-id (:file-id data)})))))
|
||||
(rx/catch (fn [cause]
|
||||
(log/error :hint "unexpected error on import process"
|
||||
:project-id project-id
|
||||
::log/sync? true)
|
||||
;; TODO: consider do thi son logging directly ?
|
||||
|
||||
(when (map? cause)
|
||||
(println "Error data:")
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue