diff --git a/backend/src/app/features/components_v2.clj b/backend/src/app/features/components_v2.clj index 2db63044f..7e29e7bc7 100644 --- a/backend/src/app/features/components_v2.clj +++ b/backend/src/app/features/components_v2.clj @@ -1745,8 +1745,8 @@ (fn [system] (binding [*system* system] (when (string? label) - (fsnap/take-file-snapshot! system {:file-id file-id - :label (str "migration/" label)})) + (fsnap/create-file-snapshot! system nil file-id (str "migration/" label))) + (let [file (get-file system file-id) file (process-file! system file :validate? validate?)] diff --git a/backend/src/app/rpc/commands/files_snapshot.clj b/backend/src/app/rpc/commands/files_snapshot.clj index 6b42e2530..83626fcae 100644 --- a/backend/src/app/rpc/commands/files_snapshot.clj +++ b/backend/src/app/rpc/commands/files_snapshot.clj @@ -29,17 +29,13 @@ "SELECT id, label, revn, created_at, created_by, profile_id FROM file_change WHERE file_id = ? - AND created_at < ? AND data IS NOT NULL ORDER BY created_at DESC - LIMIT ?") + LIMIT 20") (defn get-file-snapshots - [{:keys [::db/conn]} {:keys [file-id limit start-at] - :or {limit Long/MAX_VALUE}}] - (let [start-at (or start-at (dt/now)) - limit (min limit 20)] - (db/exec! conn [sql:get-file-snapshots file-id start-at limit]))) + [conn file-id] + (db/exec! conn [sql:get-file-snapshots file-id])) (def ^:private schema:get-file-snapshots [:map {:title "get-file-snapshots"} @@ -48,29 +44,121 @@ (sv/defmethod ::get-file-snapshots {::doc/added "1.20" ::sm/params schema:get-file-snapshots} - [cfg params] - (db/run! cfg get-file-snapshots params)) + [cfg {:keys [::rpc/profile-id file-id] :as params}] + (db/run! cfg (fn [{:keys [::db/conn]}] + (files/check-read-permissions! conn profile-id file-id) + (get-file-snapshots conn file-id)))) + +(defn- get-file + [cfg file-id] + (let [file (->> (db/get cfg :file {:id file-id}) + (feat.fdata/resolve-file-data cfg))] + (binding [pmap/*load-fn* (partial feat.fdata/load-pointer cfg file-id)] + (-> file + (update :data blob/decode) + (update :data feat.fdata/process-pointers deref) + (update :data feat.fdata/process-objects (partial into {})) + (update :data blob/encode))))) + +(defn- generate-snapshot-label + [] + (let [ts (-> (dt/now) + (dt/format-instant) + (str/replace #"[T:\.]" "-") + (str/rtrim "Z"))] + (str "snapshot-" ts))) + +(defn create-file-snapshot! + [cfg profile-id file-id label] + (let [file (-> (get-file cfg file-id) + (update :data + (fn [data] + (-> data + (blob/decode) + (assoc :id file-id))))) + + ;; NOTE: final user never can provide label as `:system` + ;; keyword because the validator implies label always as + ;; string; keyword is used for signal a special case + created-by + (if (= label :system) + "system" + "user") + + label + (if (= label :system) + (str "internal/snapshot/" (:revn file)) + (or label (generate-snapshot-label))) + + snapshot-id + (uuid/next) + + snapshot-data + (-> (:data file) + (feat.fdata/process-pointers deref) + (feat.fdata/process-objects (partial into {})) + (blob/encode))] + + (l/debug :hint "creating file snapshot" + :file-id (str file-id) + :id (str snapshot-id) + :label label) + + (db/insert! cfg :file-change + {:id snapshot-id + :revn (:revn file) + :data snapshot-data + :version (:version file) + :features (:features file) + :profile-id profile-id + :file-id (:id file) + :label label + :created-by created-by} + {::db/return-keys false}) + + {:id snapshot-id :label label})) + +(def ^:private schema:create-file-snapshot + [:map + [:file-id ::sm/uuid] + [:label {:optional true} :string]]) + +(sv/defmethod ::create-file-snapshot + {::doc/added "1.20" + ::sm/params schema:create-file-snapshot} + [cfg {:keys [::rpc/profile-id file-id label]}] + (db/tx-run! cfg + (fn [{:keys [::db/conn] :as cfg}] + (files/check-edition-permissions! conn profile-id file-id) + (create-file-snapshot! cfg profile-id file-id label)))) (defn restore-file-snapshot! - [{:keys [::db/conn ::mbus/msgbus] :as cfg} {:keys [file-id id]}] + [{:keys [::db/conn ::mbus/msgbus] :as cfg} file-id snapshot-id] (let [storage (sto/resolve cfg {::db/reuse-conn true}) file (files/get-minimal-file conn file-id {::db/for-update true}) vern (rand-int Integer/MAX_VALUE) snapshot (db/get* conn :file-change {:file-id file-id - :id id} + :id snapshot-id} {::db/for-share true})] (when-not snapshot (ex/raise :type :not-found :code :snapshot-not-found :hint "unable to find snapshot with the provided label" - :id id + :snapshot-id snapshot-id :file-id file-id)) + ;; (when (= (:revn snapshot) (:revn file)) + ;; (ex/raise :type :validation + ;; :code :snapshot-identical-to-file + ;; :hint "you can't restore a snapshot that is identical to a file" + ;; :snapshot-id snapshot-id + ;; :file-id file-id)) + (let [snapshot (feat.fdata/resolve-file-data cfg snapshot)] (when-not (:data snapshot) - (ex/raise :type :precondition + (ex/raise :type :validation :code :snapshot-without-data :hint "snapshot has no data" :label (:label snapshot) @@ -123,125 +211,77 @@ {:id (:id snapshot) :label (:label snapshot)}))) -(def ^:private - schema:restore-file-snapshot - [:and - [:map - [:file-id ::sm/uuid] - [:id {:optional true} ::sm/uuid]] - [::sm/contains-any #{:id :label}]]) +(def ^:private schema:restore-file-snapshot + [:map {:title "restore-file-snapshot"} + [:file-id ::sm/uuid] + [:id ::sm/uuid]]) (sv/defmethod ::restore-file-snapshot {::doc/added "1.20" ::sm/params schema:restore-file-snapshot} - [cfg params] - (db/tx-run! cfg restore-file-snapshot! params)) - -(defn- get-file - [cfg file-id] - (let [file (->> (db/get cfg :file {:id file-id}) - (feat.fdata/resolve-file-data cfg))] - (binding [pmap/*load-fn* (partial feat.fdata/load-pointer cfg file-id)] - (-> file - (update :data blob/decode) - (update :data feat.fdata/process-pointers deref) - (update :data feat.fdata/process-objects (partial into {})) - (update :data blob/encode))))) - -(defn- generate-snapshot-label - [] - (let [ts (-> (dt/now) - (dt/format-instant) - (str/replace #"[T:\.]" "-") - (str/rtrim "Z"))] - (str "snapshot-" ts))) - -(defn take-file-snapshot! - [cfg {:keys [file-id label ::rpc/profile-id]}] - (let [label (or label (generate-snapshot-label)) - file (-> (get-file cfg file-id) - (update :data - (fn [data] - (-> data - (blob/decode) - (assoc :id file-id))))) - - snapshot-id - (uuid/next) - - snapshot-data - (-> (:data file) - (feat.fdata/process-pointers deref) - (feat.fdata/process-objects (partial into {})) - (blob/encode))] - - (l/debug :hint "creating file snapshot" - :file-id (str file-id) - :id (str snapshot-id) - :label label) - - (db/insert! cfg :file-change - {:id snapshot-id - :revn (:revn file) - :data snapshot-data - :version (:version file) - :features (:features file) - :profile-id profile-id - :file-id (:id file) - :label label - :created-by "user"} - {::db/return-keys false}) - - {:id snapshot-id :label label})) - -(def ^:private schema:take-file-snapshot - [:map - [:file-id ::sm/uuid] - [:label {:optional true} :string]]) - -(sv/defmethod ::take-file-snapshot - {::doc/added "1.20" - ::sm/params schema:take-file-snapshot} - [cfg params] - (db/tx-run! cfg take-file-snapshot! params)) + [cfg {:keys [::rpc/profile-id file-id id] :as params}] + (db/tx-run! cfg + (fn [{:keys [::db/conn] :as cfg}] + (files/check-edition-permissions! conn profile-id file-id) + (create-file-snapshot! cfg profile-id file-id :system) + (restore-file-snapshot! cfg file-id id)))) (def ^:private schema:update-file-snapshot [:map {:title "update-file-snapshot"} [:id ::sm/uuid] [:label ::sm/text]]) -(defn update-file-snapshot! - [{:keys [::db/conn] :as cfg} {:keys [id label]}] - (let [result - (db/update! conn :file-change - {:label label - :created-by "user"} - {:id id} - {::db/return-keys true})] +(defn- update-file-snapshot! + [conn snapshot-id label] + (-> (db/update! conn :file-change + {:label label + :created-by "user"} + {:id snapshot-id} + {::db/return-keys true}) + (dissoc :data :features))) - (select-keys result [:id :label :revn :created-at :profile-id :created-by]))) +(defn- get-snapshot + "Get a minimal snapshot from database and lock for update" + [conn id] + (db/get conn :file-change + {:id id} + {::sql/columns [:id :file-id :created-by] + ::db/for-update true})) (sv/defmethod ::update-file-snapshot {::doc/added "1.20" ::sm/params schema:update-file-snapshot} - [cfg params] - (db/tx-run! cfg update-file-snapshot! params)) + [cfg {:keys [::rpc/profile-id id label]}] + (db/tx-run! cfg + (fn [{:keys [::db/conn]}] + (let [snapshot (get-snapshot conn id)] + (files/check-edition-permissions! conn profile-id (:file-id snapshot)) + (update-file-snapshot! conn id label))))) (def ^:private schema:remove-file-snapshot [:map {:title "remove-file-snapshot"} [:id ::sm/uuid]]) -(defn remove-file-snapshot! - [{:keys [::db/conn] :as cfg} {:keys [id]}] +(defn- delete-file-snapshot! + [conn snapshot-id] (db/delete! conn :file-change - {:id id :created-by "user"} + {:id snapshot-id} {::db/return-keys false}) nil) -(sv/defmethod ::remove-file-snapshot +(sv/defmethod ::delete-file-snapshot {::doc/added "1.20" ::sm/params schema:remove-file-snapshot} - [cfg params] - (db/tx-run! cfg remove-file-snapshot! params)) + [cfg {:keys [::rpc/profile-id id]}] + (db/tx-run! cfg + (fn [{:keys [::db/conn]}] + (let [snapshot (get-snapshot conn id)] + (files/check-edition-permissions! conn profile-id (:file-id snapshot)) + (when (not= (:created-by snapshot) "user") + (ex/raise :type :validation + :code :system-snapshots-cant-be-deleted + :snapshot-id id + :profile-id profile-id)) + (delete-file-snapshot! conn id))))) diff --git a/backend/src/app/srepl/helpers.clj b/backend/src/app/srepl/helpers.clj index 702790eba..609e99568 100644 --- a/backend/src/app/srepl/helpers.clj +++ b/backend/src/app/srepl/helpers.clj @@ -122,22 +122,19 @@ WHERE file_id = ANY(?) AND id IS NOT NULL") -(defn get-file-snapshots +(defn search-file-snapshots "Get a seq parirs of file-id and snapshot-id for a set of files and specified label" - [conn label ids] + [conn file-ids label] (db/exec! conn [sql:snapshots-with-file label - (db/create-array conn "uuid" ids)])) + (db/create-array conn "uuid" file-ids)])) (defn take-team-snapshot! [system team-id label] (let [conn (db/get-connection system)] (->> (feat.comp-v2/get-and-lock-team-files conn team-id) - (map (fn [file-id] - {:file-id file-id - :label label})) - (reduce (fn [result params] - (fsnap/take-file-snapshot! conn params) + (reduce (fn [result file-id] + (fsnap/create-file-snapshot! system nil file-id label) (inc result)) 0)))) @@ -147,7 +144,7 @@ ids (->> (feat.comp-v2/get-and-lock-team-files conn team-id) (into #{})) - snap (get-file-snapshots conn label ids) + snap (search-file-snapshots conn ids label) ids' (into #{} (map :file-id) snap) team (-> (feat.comp-v2/get-team conn team-id) @@ -157,8 +154,8 @@ (throw (RuntimeException. "no uniform snapshot available"))) (feat.comp-v2/update-team! conn team) - (reduce (fn [result params] - (fsnap/restore-file-snapshot! conn params) + (reduce (fn [result {:keys [file-id id]}] + (fsnap/restore-file-snapshot! system file-id id) (inc result)) 0 snap))) @@ -167,7 +164,7 @@ [system file-id update-fn & {:keys [label validate? with-libraries?] :or {validate? true} :as opts}] (when (string? label) - (fsnap/take-file-snapshot! system {:file-id file-id :label label})) + (fsnap/create-file-snapshot! system nil file-id label)) (let [conn (db/get-connection system) file (get-file system file-id opts) diff --git a/backend/src/app/srepl/main.clj b/backend/src/app/srepl/main.clj index 404ab6c5e..c3eb47e92 100644 --- a/backend/src/app/srepl/main.clj +++ b/backend/src/app/srepl/main.clj @@ -311,28 +311,25 @@ collectable file-changes entry." [& {:keys [file-id label]}] (let [file-id (h/parse-uuid file-id)] - (db/tx-run! main/system fsnap/take-file-snapshot! {:file-id file-id :label label}))) + (db/tx-run! main/system fsnap/create-file-snapshot! {:file-id file-id :label label}))) (defn restore-file-snapshot! [file-id label] (let [file-id (h/parse-uuid file-id)] (db/tx-run! main/system (fn [{:keys [::db/conn] :as system}] - (when-let [snapshot (->> (h/get-file-snapshots conn label #{file-id}) + (when-let [snapshot (->> (h/search-file-snapshots conn #{file-id} label) (map :id) (first))] - (fsnap/restore-file-snapshot! system - {:id (:id snapshot) - :file-id file-id})))))) + (fsnap/restore-file-snapshot! system file-id (:id snapshot))))))) (defn list-file-snapshots! - [file-id & {:keys [limit]}] + [file-id & {:as _}] (let [file-id (h/parse-uuid file-id)] (db/tx-run! main/system - (fn [system] - (let [params {:file-id file-id :limit limit}] - (->> (fsnap/get-file-snapshots system (d/without-nils params)) - (print-table [:label :id :revn :created-at]))))))) + (fn [{:keys [::db/conn]}] + (->> (fsnap/get-file-snapshots conn file-id) + (print-table [:label :id :revn :created-at])))))) (defn take-team-snapshot! [team-id & {:keys [label rollback?] :or {rollback? true}}] diff --git a/backend/test/backend_tests/rpc_file_snapshot_test.clj b/backend/test/backend_tests/rpc_file_snapshot_test.clj new file mode 100644 index 000000000..90e7366ee --- /dev/null +++ b/backend/test/backend_tests/rpc_file_snapshot_test.clj @@ -0,0 +1,134 @@ +;; 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 backend-tests.rpc-file-snapshot-test + (:require + [app.common.features :as cfeat] + [app.common.pprint :as pp] + [app.common.pprint :as pp] + [app.common.thumbnails :as thc] + [app.common.types.shape :as cts] + [app.common.uuid :as uuid] + [app.db :as db] + [app.db.sql :as sql] + [app.http :as http] + [app.rpc :as-alias rpc] + [app.storage :as sto] + [app.util.time :as dt] + [backend-tests.helpers :as th] + [clojure.test :as t] + [cuerdas.core :as str])) + +(t/use-fixtures :once th/state-init) +(t/use-fixtures :each th/database-reset) + +(defn- update-file! + [& {:keys [profile-id file-id changes revn] :or {revn 0}}] + (let [params {::th/type :update-file + ::rpc/profile-id profile-id + :id file-id + :session-id (uuid/random) + :revn revn + :vern 0 + :features cfeat/supported-features + :changes changes} + out (th/command! params)] + ;; (th/print-result! out) + (t/is (nil? (:error out))) + (:result out))) + +(t/deftest generic-ops + (let [profile (th/create-profile* 1 {:is-active true}) + team-id (:default-team-id profile) + proj-id (:default-project-id profile) + + file (th/create-file* 1 {:profile-id (:id profile) + :project-id proj-id + :is-shared false}) + snapshot-id (volatile! nil)] + + (t/testing "create snapshot" + (let [params {::th/type :create-file-snapshot + ::rpc/profile-id (:id profile) + :file-id (:id file) + :label "label1"} + out (th/command! params)] + ;; (th/print-result! out) + + (t/is (nil? (:error out))) + (let [result (:result out)] + (t/is (= "label1" (:label result))) + (t/is (uuid? (:id result))) + (vswap! snapshot-id (constantly (:id result)))))) + + (t/testing "list snapshots" + (let [params {::th/type :get-file-snapshots + ::rpc/profile-id (:id profile) + :file-id (:id file)} + out (th/command! params)] + ;; (th/print-result! out) + (t/is (nil? (:error out))) + (let [[row :as result] (:result out)] + (t/is (= 1 (count result))) + (t/is (= "label1" (:label row))) + (t/is (uuid? (:id row))) + (t/is (= @snapshot-id (:id row))) + (t/is (= 0 (:revn row))) + (t/is (= (:id profile) (:profile-id row)))))) + + (t/testing "restore snapshot" + (let [params {::th/type :restore-file-snapshot + ::rpc/profile-id (:id profile) + :file-id (:id file) + :id @snapshot-id} + out (th/command! params)] + + ;; (th/print-result! out) + (t/is (nil? (:error out))) + + (let [result (:result out)] + (t/is (= "label1" (:label result))) + (t/is (uuid? (:id result))))) + + (let [[row1 row2 :as rows] + (th/db-query :file-change + {:file-id (:id file)} + {:order-by [:created-at]})] + (t/is (= 2 (count rows))) + (t/is (= "user" (:created-by row1))) + (t/is (= "system" (:created-by row2))))) + + (t/testing "delete snapshot" + (let [[row1 row2 :as rows] + (th/db-query :file-change + {:file-id (:id file)} + {:order-by [:created-at]})] + + (t/testing "delete user created snapshot" + (let [params {::th/type :delete-file-snapshot + ::rpc/profile-id (:id profile) + :file-id (:id file) + :id (:id row1)} + out (th/command! params)] + + ;; (th/print-result! out) + (t/is (nil? (:error out))) + (t/is (nil? (:result out))))) + + (t/testing "delete system created snapshot" + (let [params {::th/type :delete-file-snapshot + ::rpc/profile-id (:id profile) + :file-id (:id file) + :id (:id row2)} + out (th/command! params)] + + ;; (th/print-result! out) + (let [error (:error out) + data (ex-data error)] + (t/is (th/ex-info? error)) + (t/is (= (:type data) :validation)) + (t/is (= (:code data) :system-snapshots-cant-be-deleted))))))))) + diff --git a/frontend/playwright/ui/specs/versions.spec.js b/frontend/playwright/ui/specs/versions.spec.js index ab753a374..40fac7d22 100644 --- a/frontend/playwright/ui/specs/versions.spec.js +++ b/frontend/playwright/ui/specs/versions.spec.js @@ -33,7 +33,7 @@ test("Save and restore version", async ({ page }) => { await page.getByLabel("History (Alt+H)").click(); await workspacePage.mockRPC( - "take-file-snapshot", + "create-file-snapshot", "workspace/versions-take-snapshot-1.json", ); @@ -57,7 +57,11 @@ test("Save and restore version", async ({ page }) => { await page.getByRole("textbox").fill("INIT"); await page.getByRole("textbox").press("Enter"); - await page.getByTestId("version(INIT)").getByRole("button").click(); + await page + .locator("li") + .filter({ hasText: "INIT" }) + .getByRole("button") + .click(); await page.getByRole("button", { name: "Restore" }).click(); await workspacePage.mockRPC( diff --git a/frontend/src/app/main/data/workspace/versions.cljs b/frontend/src/app/main/data/workspace/versions.cljs index d3d7fd314..f8e626de8 100644 --- a/frontend/src/app/main/data/workspace/versions.cljs +++ b/frontend/src/app/main/data/workspace/versions.cljs @@ -64,7 +64,7 @@ (->> (rx/from-atom refs/persistence-state {:emit-current-value? true}) (rx/filter #(or (nil? %) (= :saved %))) (rx/take 1) - (rx/mapcat #(rp/cmd! :take-file-snapshot {:file-id file-id :label label})) + (rx/mapcat #(rp/cmd! :create-file-snapshot {:file-id file-id :label label})) (rx/mapcat (fn [{:keys [id]}] (rx/of @@ -101,7 +101,6 @@ (->> (rx/from-atom refs/persistence-state {:emit-current-value? true}) (rx/filter #(or (nil? %) (= :saved %))) (rx/take 1) - (rx/mapcat #(rp/cmd! :take-file-snapshot {:file-id file-id :created-by "system" :label (dt/format (dt/now) :date-full)})) (rx/mapcat #(rp/cmd! :restore-file-snapshot {:file-id file-id :id id})) (rx/map #(dw/initialize-file project-id file-id))) (rx/of (ptk/event ::ev/event {::ev/name "restore-version"})))))) @@ -114,7 +113,7 @@ (ptk/reify ::delete-version ptk/WatchEvent (watch [_ _ _] - (->> (rp/cmd! :remove-file-snapshot {:id id}) + (->> (rp/cmd! :delete-file-snapshot {:id id}) (rx/map #(fetch-versions file-id)))))) (defn pin-version diff --git a/frontend/src/app/main/ui/workspace/sidebar/versions.cljs b/frontend/src/app/main/ui/workspace/sidebar/versions.cljs index c867e50c8..fd0108c4c 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/versions.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/versions.cljs @@ -109,8 +109,7 @@ (kbd/esc? event) (st/emit! (dwv/update-version-state {:editing nil})))))] - [:li {:data-testid (dm/str "version(" (:label entry) ")") - :class (stl/css :version-entry-wrap)} + [:li {:class (stl/css :version-entry-wrap)} [:div {:class (stl/css :version-entry :is-snapshot)} [:img {:class (stl/css :version-entry-avatar) :alt (:fullname profile) diff --git a/frontend/src/debug.cljs b/frontend/src/debug.cljs index 92642ddc0..98364b27e 100644 --- a/frontend/src/debug.cljs +++ b/frontend/src/debug.cljs @@ -12,12 +12,9 @@ [app.common.files.validate :as cfv] [app.common.json :as json] [app.common.logging :as l] - [app.common.schema :as sm] [app.common.transit :as t] [app.common.types.file :as ctf] - [app.common.uri :as u] [app.common.uuid :as uuid] - [app.config :as cf] [app.main.data.changes :as dwc] [app.main.data.dashboard.shortcuts] [app.main.data.preview :as dp] @@ -33,7 +30,6 @@ [app.main.store :as st] [app.util.debug :as dbg] [app.util.dom :as dom] - [app.util.http :as http] [app.util.object :as obj] [app.util.timers :as timers] [beicon.v2.core :as rx] @@ -454,66 +450,6 @@ [id shape-ref] (st/emit! (dw/set-shape-ref id shape-ref))) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; SNAPSHOTS -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(defn ^:export list-available-snapshots - [file-id] - (let [file-id (or (d/parse-uuid file-id) - (:current-file-id @st/state))] - (->> (http/send! {:method :get - :uri (u/join cf/public-uri "api/rpc/command/get-file-snapshots") - :query {:file-id file-id}}) - (rx/map http/conditional-decode-transit) - (rx/mapcat rp/handle-response) - (rx/subs! (fn [result] - (let [result (map (fn [row] - (update row :id str)) - result)] - (js/console.table (json/->js result)))) - (fn [cause] - (js/console.log "EE:" cause)))) - nil)) - -(defn ^:export take-snapshot - [label file-id] - (when-let [file-id (or (d/parse-uuid file-id) - (:current-file-id @st/state))] - (->> (http/send! {:method :post - :uri (u/join cf/public-uri "api/rpc/command/take-file-snapshot") - :body (http/transit-data {:file-id file-id :label label})}) - (rx/map http/conditional-decode-transit) - (rx/mapcat rp/handle-response) - (rx/subs! (fn [{:keys [id]}] - (println "Snapshot saved:" (str id) label)) - (fn [cause] - (js/console.log "EE:" cause)))))) - -(defn ^:export restore-snapshot - [label file-id] - (when-let [file-id (or (d/parse-uuid file-id) - (:current-file-id @st/state))] - (let [snapshot-id (sm/parse-uuid label) - label (if snapshot-id nil label) - params (cond-> {:file-id file-id} - (uuid? snapshot-id) - (assoc :id snapshot-id) - - (string? label) - (assoc :label label))] - (->> (http/send! {:method :post - :uri (u/join cf/public-uri "api/rpc/command/restore-file-snapshot") - :body (http/transit-data params)}) - (rx/map http/conditional-decode-transit) - (rx/mapcat rp/handle-response) - (rx/subs! (fn [_] - (println "Snapshot restored " (or snapshot-id label))) - #_(.reload js/location) - (fn [cause] - (js/console.log "EE:" cause))))))) - - (defn ^:export enable-text-v2 [] (st/emit! (features/enable-feature "text-editor/v2")))