diff --git a/backend/src/app/rpc.clj b/backend/src/app/rpc.clj index 08ccd8cdb5..2f999a08ea 100644 --- a/backend/src/app/rpc.clj +++ b/backend/src/app/rpc.clj @@ -149,9 +149,11 @@ (let [params (decode params)] (if (validate params) (f cfg params) - (ex/raise :type :validation - :code :params-validation - ::sm/explain (explain params)))))) + + (let [params (d/without-qualified params)] + (ex/raise :type :validation + :code :params-validation + ::sm/explain (explain params))))))) f)) (defn- wrap-output-validation diff --git a/backend/src/app/rpc/commands/files_update.clj b/backend/src/app/rpc/commands/files_update.clj index fade957e03..cd7436742e 100644 --- a/backend/src/app/rpc/commands/files_update.clj +++ b/backend/src/app/rpc/commands/files_update.clj @@ -42,30 +42,28 @@ (def ^:private schema:update-file - (sm/define - [:map {:title "update-file"} - [:id ::sm/uuid] - [:session-id ::sm/uuid] - [:revn {:min 0} :int] - [:features {:optional true} ::cfeat/features] - [:changes {:optional true} [:vector ::cpc/change]] - [:changes-with-metadata {:optional true} - [:vector [:map - [:changes [:vector ::cpc/change]] - [:hint-origin {:optional true} :keyword] - [:hint-events {:optional true} [:vector :string]]]]] - [:skip-validate {:optional true} :boolean]])) + [:map {:title "update-file"} + [:id ::sm/uuid] + [:session-id ::sm/uuid] + [:revn {:min 0} :int] + [:features {:optional true} ::cfeat/features] + [:changes {:optional true} [:vector ::cpc/change]] + [:changes-with-metadata {:optional true} + [:vector [:map + [:changes [:vector ::cpc/change]] + [:hint-origin {:optional true} :keyword] + [:hint-events {:optional true} [:vector :string]]]]] + [:skip-validate {:optional true} :boolean]]) (def ^:private schema:update-file-result - (sm/define - [:vector {:title "update-file-result"} - [:map - [:changes [:vector ::cpc/change]] - [:file-id ::sm/uuid] - [:id ::sm/uuid] - [:revn {:min 0} :int] - [:session-id ::sm/uuid]]])) + [:vector {:title "update-file-result"} + [:map + [:changes [:vector ::cpc/change]] + [:file-id ::sm/uuid] + [:id ::sm/uuid] + [:revn {:min 0} :int] + [:session-id ::sm/uuid]]]) ;; --- HELPERS @@ -73,14 +71,26 @@ ;; to all clients using it. (def ^:private library-change-types - #{:add-color :mod-color :del-color - :add-media :mod-media :del-media - :add-component :mod-component :del-component :restore-component - :add-typography :mod-typography :del-typography}) + #{:add-color + :mod-color + :del-color + :add-media + :mod-media + :del-media + :add-component + :mod-component + :del-component + :restore-component + :add-typography + :mod-typography + :del-typography}) (def ^:private file-change-types - #{:add-obj :mod-obj :del-obj - :reg-objects :mov-objects}) + #{:add-obj + :mod-obj + :del-obj + :reg-objects + :mov-objects}) (defn- library-change? [{:keys [type] :as change}] diff --git a/backend/src/app/tasks/tasks_gc.clj b/backend/src/app/tasks/tasks_gc.clj index 69dd11dfd7..77f1f92fa8 100644 --- a/backend/src/app/tasks/tasks_gc.clj +++ b/backend/src/app/tasks/tasks_gc.clj @@ -16,8 +16,7 @@ (def ^:private sql:delete-completed-tasks - "delete from task_completed - where scheduled_at < now() - ?::interval") + "DELETE FROM task WHERE scheduled_at < now() - ?::interval") (defmethod ig/pre-init-spec ::handler [_] (s/keys :req [::db/pool])) diff --git a/common/src/app/common/data.cljc b/common/src/app/common/data.cljc index 958a3b9b08..bcfd556488 100644 --- a/common/src/app/common/data.cljc +++ b/common/src/app/common/data.cljc @@ -242,7 +242,12 @@ ([] (remove (comp qualified-keyword? key))) ([data] - (into {} (without-qualified) data))) + (reduce-kv (fn [data k _] + (if (qualified-keyword? k) + (dissoc data k) + data)) + data + data))) (defn without-keys "Return a map without the keys provided diff --git a/common/src/app/common/media.cljc b/common/src/app/common/media.cljc index 064f11fb2c..212d43f2a5 100644 --- a/common/src/app/common/media.cljc +++ b/common/src/app/common/media.cljc @@ -58,6 +58,7 @@ "application/zip" ".zip" "application/penpot" ".penpot" "application/pdf" ".pdf" + "text/plain" ".txt" nil)) (s/def ::id uuid?) diff --git a/frontend/src/app/main/data/workspace/persistence.cljs b/frontend/src/app/main/data/workspace/persistence.cljs index 8cfc5a87f4..bcfc60b58a 100644 --- a/frontend/src/app/main/data/workspace/persistence.cljs +++ b/frontend/src/app/main/data/workspace/persistence.cljs @@ -16,7 +16,6 @@ [app.main.features :as features] [app.main.repo :as rp] [app.main.store :as st] - [app.util.router :as rt] [app.util.time :as dt] [beicon.v2.core :as rx] [okulary.core :as l] @@ -177,19 +176,11 @@ (rx/of (shapes-changes-persisted-finished)))))) (rx/catch (fn [cause] - (cond - (= :authentication (:type cause)) - (rx/throw cause) - - (instance? js/TypeError cause) + (if (instance? js/TypeError cause) (->> (rx/timer 2000) (rx/map (fn [_] (persist-changes file-id file-revn changes pending-commits)))) - - :else - (rx/concat - (rx/of (rt/assign-exception cause)) - (rx/throw cause)))))))))) + (rx/throw cause))))))))) ;; Event to be thrown after the changes have been persisted (defn shapes-changes-persisted-finished diff --git a/frontend/src/app/main/errors.cljs b/frontend/src/app/main/errors.cljs index ed0ad65980..2c70a31cd2 100644 --- a/frontend/src/app/main/errors.cljs +++ b/frontend/src/app/main/errors.cljs @@ -116,7 +116,6 @@ (defmethod ptk/handle-error :validation [{:keys [code] :as error}] - (print-group! "Validation Error" (fn [] (print-data! error) @@ -130,12 +129,7 @@ :timeout 3000}))) :else - (let [message (tr "errors.generic-validation")] - (st/async-emit! - (msg/show {:content message - :type :error - :timeout 3000}))))) - + (st/async-emit! (rt/assign-exception error)))) ;; This is a pure frontend error that can be caused by an active @@ -256,12 +250,7 @@ (defmethod ptk/handle-error :server-error [error] - (ts/schedule - #(st/emit! - (msg/show {:content "Something wrong has happened (on backend)." - :type :error - :timeout 3000}))) - + (st/async-emit! (rt/assign-exception error)) (print-group! "Server Error" (fn [] (print-data! (dissoc error :data)) diff --git a/frontend/src/app/main/repo.cljs b/frontend/src/app/main/repo.cljs index 56a49dd586..ed71b827a5 100644 --- a/frontend/src/app/main/repo.cljs +++ b/frontend/src/app/main/repo.cljs @@ -23,28 +23,31 @@ (rx/of nil) (= 502 status) - (rx/throw {:type :bad-gateway}) + (rx/throw (ex-info "http error" {:type :bad-gateway})) (= 503 status) - (rx/throw {:type :service-unavailable}) + (rx/throw (ex-info "http error" {:type :service-unavailable})) (= 0 (:status response)) - (rx/throw {:type :offline}) + (rx/throw (ex-info "http error" {:type :offline})) (= 200 status) (rx/of body) (= 413 status) - (rx/throw {:type :validation - :code :request-body-too-large}) + (rx/throw (ex-info "http error" + {:type :validation + :code :request-body-too-large})) (and (>= status 400) (map? body)) - (rx/throw body) + (rx/throw (ex-info "http error" body)) :else - (rx/throw {:type :unexpected-error + (rx/throw + (ex-info "http error" + {:type :unexpected-error :status status - :data body}))) + :data body})))) (def default-options {:update-file {:query-params [:id]} diff --git a/frontend/src/app/main/store.cljs b/frontend/src/app/main/store.cljs index 7c162d030d..7b02335e76 100644 --- a/frontend/src/app/main/store.cljs +++ b/frontend/src/app/main/store.cljs @@ -58,22 +58,22 @@ (defonce last-events (let [buffer (atom []) - allowed #{:app.main.data.workspace/initialize-page - :app.main.data.workspace/finalize-page - :app.main.data.workspace/initialize-file - :app.main.data.workspace/finalize-file}] + omitset #{:potok.v2.core/undefined + :app.main.data.workspace.persistence/update-persistence-status + :app.main.data.websocket/send-message + :app.main.data.workspace.notifications/handle-pointer-send + :app.util.router/assign-exception}] (->> (rx/merge (->> stream (rx/filter (ptk/type? :app.main.data.workspace.changes/commit-changes)) - (rx/map #(-> % deref :hint-origin str)) - (rx/pipe (rxo/distinct-contiguous))) - (->> stream - (rx/map ptk/type) - (rx/filter #(contains? allowed %)) - (rx/map str))) + (rx/map #(-> % deref :hint-origin))) + (rx/map ptk/type stream)) + (rx/filter #(not (contains? omitset %))) + (rx/map str) + (rx/pipe (rxo/distinct-contiguous)) (rx/scan (fn [buffer event] (cond-> (conj buffer event) - (> (count buffer) 20) + (> (count buffer) 50) (pop))) #queue []) (rx/subs! #(reset! buffer (vec %)))) diff --git a/frontend/src/app/main/ui.cljs b/frontend/src/app/main/ui.cljs index 08f714c3b7..68bd5d58af 100644 --- a/frontend/src/app/main/ui.cljs +++ b/frontend/src/app/main/ui.cljs @@ -128,12 +128,11 @@ {:keys [file-id]} path-params] [:? {} (if (:token query-params) - [:> static/static-header {} + [:> static/error-container {} [:div.image i/unchain] [:div.main-message (tr "viewer.breaking-change.message")] [:div.desc-message (tr "viewer.breaking-change.description")]] - [:& viewer-page {:page-id page-id :file-id file-id diff --git a/frontend/src/app/main/ui/static.cljs b/frontend/src/app/main/ui/static.cljs index e0f9a4581b..efac4c3c1b 100644 --- a/frontend/src/app/main/ui/static.cljs +++ b/frontend/src/app/main/ui/static.cljs @@ -7,19 +7,21 @@ (ns app.main.ui.static (:require-macros [app.main.style :as stl]) (:require + [app.common.data :as d] + [app.common.pprint :as pp] [app.main.store :as st] [app.main.ui.icons :as i] + [app.util.dom :as dom] [app.util.globals :as globals] [app.util.i18n :refer [tr]] - [app.util.object :as obj] [app.util.router :as rt] + [app.util.webapi :as wapi] [rumext.v2 :as mf])) -(mf/defc static-header +(mf/defc error-container {::mf/wrap-props false} - [props] - (let [children (obj/get props "children") - on-click (mf/use-callback #(set! (.-href globals/location) "/"))] + [{:keys [children]}] + (let [on-click (mf/use-callback #(set! (.-href globals/location) "/"))] [:section {:class (stl/css :exception-layout)} [:button {:class (stl/css :exception-header) @@ -34,13 +36,13 @@ (mf/defc invalid-token [] - [:> static-header {} + [:> error-container {} [:div {:class (stl/css :main-message)} (tr "errors.invite-invalid")] [:div {:class (stl/css :desc-message)} (tr "errors.invite-invalid.info")]]) (mf/defc not-found [] - [:> static-header {} + [:> error-container {} [:div {:class (stl/css :main-message)} (tr "labels.not-found.main-message")] [:div {:class (stl/css :desc-message)} (tr "labels.not-found.desc-message")]]) @@ -49,7 +51,7 @@ (let [handle-retry (mf/use-callback (fn [] (st/emit! (rt/assign-exception nil))))] - [:> static-header {} + [:> error-container {} [:div {:class (stl/css :main-message)} (tr "labels.bad-gateway.main-message")] [:div {:class (stl/css :desc-message)} (tr "labels.bad-gateway.desc-message")] [:div {:class (stl/css :sign-info)} @@ -57,27 +59,88 @@ (mf/defc service-unavailable [] - (let [handle-retry - (mf/use-callback - (fn [] (st/emit! (rt/assign-exception nil))))] - [:> static-header {} + (let [on-click (mf/use-fn #(st/emit! (rt/assign-exception nil)))] + [:> error-container {} [:div {:class (stl/css :main-message)} (tr "labels.service-unavailable.main-message")] [:div {:class (stl/css :desc-message)} (tr "labels.service-unavailable.desc-message")] [:div {:class (stl/css :sign-info)} - [:button {:on-click handle-retry} (tr "labels.retry")]]])) + [:button {:on-click on-click} (tr "labels.retry")]]])) + + +(defn generate-report + [data] + (let [team-id (:current-team-id @st/state) + profile-id (:profile-id @st/state) + + trace (:app.main.errors/trace data) + instance (:app.main.errors/instance data) + content (with-out-str + (println "Hint: " (or (:hint data) (ex-message instance) "--")) + (println "Prof ID:" (str (or profile-id "--"))) + (println "Team ID:" (str (or team-id "--"))) + + (when-let [file-id (:file-id data)] + (println "File ID:" (str file-id))) + + (println) + + (println "Data:") + (loop [data data] + (-> (d/without-qualified data) + (dissoc :explain) + (d/update-when :data (constantly "(...)")) + (pp/pprint {:level 8 :length 10})) + + (println) + + (when-let [explain (:explain data)] + (print explain)) + + (when (and (= :server-error (:type data)) + (contains? data :data)) + (recur (:data data)))) + + (println "Trace:") + (println trace) + (println) + + (println "Last events:") + (pp/pprint @st/last-events {:length 200}) + + (println))] + + (wapi/create-blob content "text/plain"))) + (mf/defc internal-error - [] - (let [handle-retry - (mf/use-callback - (fn [] (st/emit! (rt/assign-exception nil))))] - [:> static-header {} + {::mf/props :obj} + [{:keys [data]}] + (let [on-click (mf/use-fn #(st/emit! (rt/assign-exception nil))) + report-uri (mf/use-ref nil) + + on-download + (mf/use-fn + (fn [event] + (dom/prevent-default event) + (when-let [uri (mf/ref-val report-uri)] + (dom/trigger-download-uri "report" "text/plain" uri))))] + + (mf/with-effect [data] + (let [report (generate-report data) + uri (wapi/create-uri report)] + (mf/set-ref-val! report-uri uri) + (fn [] + (wapi/revoke-uri uri)))) + + [:> error-container {} [:div {:class (stl/css :main-message)} (tr "labels.internal-error.main-message")] [:div {:class (stl/css :desc-message)} (tr "labels.internal-error.desc-message")] + [:a {:on-click on-download} "Download report.txt"] [:div {:class (stl/css :sign-info)} - [:button {:on-click handle-retry} (tr "labels.retry")]]])) + [:button {:on-click on-click} (tr "labels.retry")]]])) (mf/defc exception-page + {::mf/props :obj} [{:keys [data] :as props}] (case (:type data) :not-found @@ -89,4 +152,4 @@ :service-unavailable [:& service-unavailable] - [:& internal-error])) + [:> internal-error props]))