From 869a412c74e2a7ea4580dea3e7efce5463639310 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 18 Feb 2025 18:19:08 +0100 Subject: [PATCH 1/8] :bug: Fix unexpected exception on clicking empty area on creating comment --- frontend/src/app/main/ui/comments.cljs | 123 +++++++++++++------------ frontend/src/app/util/webapi.cljs | 5 +- 2 files changed, 69 insertions(+), 59 deletions(-) diff --git a/frontend/src/app/main/ui/comments.cljs b/frontend/src/app/main/ui/comments.cljs index e607e07e7..1e4363e59 100644 --- a/frontend/src/app/main/ui/comments.cljs +++ b/frontend/src/app/main/ui/comments.cljs @@ -90,22 +90,26 @@ (dom/set-data! "fullname" fullname) (obj/set! "textContent" fullname))) +(defn- current-text-node* + "Retrieves the text node and the offset that the cursor is positioned on" + [node anchor-node] + (when (.contains node anchor-node) + (let [span-node (if (instance? js/Text anchor-node) + (dom/get-parent anchor-node) + anchor-node) + container (dom/get-parent span-node)] + (when (= node container) + span-node)))) + (defn- current-text-node "Retrieves the text node and the offset that the cursor is positioned on" - [node] - - (let [selection (wapi/get-selection) - range (wapi/get-range selection 0) - anchor-node (wapi/range-start-container range) - anchor-offset (wapi/range-start-offset range)] - (when (and node (.contains node anchor-node)) - (let [span-node - (if (instance? js/Text anchor-node) - (dom/get-parent anchor-node) - anchor-node) - container (dom/get-parent span-node)] - (when (= node container) - [span-node anchor-offset]))))) + ([node] (current-text-node node (wapi/get-selection))) + ([node selection] + (when (and node selection) + (let [range (wapi/get-range selection 0) + anchor-node (wapi/range-start-container range) + anchor-offset (wapi/range-start-offset range)] + [(current-text-node* node anchor-node) anchor-offset])))) (defn- absolute-offset [node child offset] @@ -156,7 +160,8 @@ mentions-s (mf/use-ctx mentions-context) cur-mention (mf/use-var nil) - prev-selection (mf/use-var nil) + prev-selection-ref + (mf/use-ref) init-input (mf/use-fn @@ -203,58 +208,59 @@ handle-select (mf/use-fn (fn [] - (let [node (mf/ref-val local-ref) - selection (wapi/get-selection) - range (wapi/get-range selection 0) - anchor-node (wapi/range-start-container range)] - (when (and (= node anchor-node) (.-collapsed range)) - (wapi/set-cursor-after! anchor-node))) + (when-let [node (mf/ref-val local-ref)] + (when-let [selection (wapi/get-selection)] + (let [range (wapi/get-range selection 0) + anchor-node (wapi/range-start-container range) + offset (wapi/range-start-offset range)] - (let [node (mf/ref-val local-ref) - [span-node offset] (current-text-node node) - [prev-span prev-offset] @prev-selection] + (when (and (= node anchor-node) (.-collapsed ^js range)) + (wapi/set-cursor-after! anchor-node)) - (reset! prev-selection #js [span-node offset]) + (when-let [span-node (current-text-node* node anchor-node)] + (let [[prev-span prev-offset] + (mf/ref-val prev-selection-ref) - (when (= (dom/get-data span-node "type") "mention") - (let [from-offset (absolute-offset node prev-span prev-offset) - to-offset (absolute-offset node span-node offset) + node-text + (subs (dom/get-text span-node) 0 offset) - [_ prev next] - (->> node - (dom/seq-nodes) - (d/with-prev-next) - (filter (fn [[elem _ _]] (= elem span-node))) - (first))] + current-at-symbol + (str/last-index-of (subs node-text 0 offset) "@") - (if (> from-offset to-offset) - (wapi/set-cursor-after! prev) - (wapi/set-cursor-before! next)))) + mention-text + (subs node-text current-at-symbol) - (when span-node - (let [node-text (subs (dom/get-text span-node) 0 offset) + at-symbol-inside-word? + (and (> current-at-symbol 0) + (str/word? (str/slice node-text (- current-at-symbol 1) current-at-symbol)))] - current-at-symbol - (str/last-index-of (subs node-text 0 offset) "@") + (mf/set-ref-val! prev-selection-ref #js [span-node offset]) - mention-text - (subs node-text current-at-symbol) + (when (= (dom/get-data span-node "type") "mention") + (let [from-offset (absolute-offset node prev-span prev-offset) + to-offset (absolute-offset node span-node offset) - at-symbol-inside-word? - (and (> current-at-symbol 0) - (str/word? (str/slice node-text (- current-at-symbol 1) current-at-symbol)))] + [_ prev next] + (->> node + (dom/seq-nodes) + (d/with-prev-next) + (filter (fn [[elem _ _]] (= elem span-node))) + (first))] + (if (> from-offset to-offset) + (wapi/set-cursor-after! prev) + (wapi/set-cursor-before! next)))) - (if (and (not at-symbol-inside-word?) - (re-matches #"@\w*" mention-text)) - (do - (reset! cur-mention mention-text) - (rx/push! mentions-s {:type :display-mentions}) - (let [mention (subs mention-text 1)] - (when (d/not-empty? mention) - (rx/push! mentions-s {:type :filter-mentions :data mention})))) - (do - (reset! cur-mention nil) - (rx/push! mentions-s {:type :hide-mentions})))))))) + (if (and (not at-symbol-inside-word?) + (re-matches #"@\w*" mention-text)) + (do + (reset! cur-mention mention-text) + (rx/push! mentions-s {:type :display-mentions}) + (let [mention (subs mention-text 1)] + (when (d/not-empty? mention) + (rx/push! mentions-s {:type :filter-mentions :data mention})))) + (do + (reset! cur-mention nil) + (rx/push! mentions-s {:type :hide-mentions})))))))))) handle-focus (mf/use-fn @@ -314,7 +320,8 @@ handle-insert-at-symbol (mf/use-fn (fn [] - (let [node (mf/ref-val local-ref) [span-node] (current-text-node node)] + (let [node (mf/ref-val local-ref) + [span-node] (current-text-node node)] (when span-node (let [node-text (dom/get-text span-node) at-symbol (if (blank-content? node-text) "@" " @")] diff --git a/frontend/src/app/util/webapi.cljs b/frontend/src/app/util/webapi.cljs index 4a280f57f..39cab9a7b 100644 --- a/frontend/src/app/util/webapi.cljs +++ b/frontend/src/app/util/webapi.cljs @@ -282,9 +282,12 @@ (.selectAllChildren selection node)) (defn get-selection + "Only returns valid selection" [] (when-let [document globals/document] - (.getSelection document))) + (let [selection (.getSelection document)] + (when (not= (.-type selection) "None") + selection)))) (defn get-anchor-node [^js selection] From cd1eefb214227006172cc59e83328eda7410170d Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 18 Feb 2025 18:33:02 +0100 Subject: [PATCH 2/8] :sparkles: Add safety checks for node on comment-input* component --- frontend/src/app/main/ui/comments.cljs | 129 ++++++++++++------------- 1 file changed, 63 insertions(+), 66 deletions(-) diff --git a/frontend/src/app/main/ui/comments.cljs b/frontend/src/app/main/ui/comments.cljs index 1e4363e59..4cd01a6d5 100644 --- a/frontend/src/app/main/ui/comments.cljs +++ b/frontend/src/app/main/ui/comments.cljs @@ -103,13 +103,16 @@ (defn- current-text-node "Retrieves the text node and the offset that the cursor is positioned on" - ([node] (current-text-node node (wapi/get-selection))) - ([node selection] - (when (and node selection) - (let [range (wapi/get-range selection 0) - anchor-node (wapi/range-start-container range) - anchor-offset (wapi/range-start-offset range)] - [(current-text-node* node anchor-node) anchor-offset])))) + [node] + (assert (some? node) "expected valid node") + + (when-let [selection (wapi/get-selection)] + (let [range (wapi/get-range selection 0) + anchor-node (wapi/range-start-container range) + offset (wapi/range-start-offset range) + span-node (current-text-node* node anchor-node)] + (when span-node + [span-node offset])))) (defn- absolute-offset [node child offset] @@ -285,9 +288,8 @@ (mf/use-fn (mf/deps on-change) (fn [data] - (let [node (mf/ref-val local-ref) - [span-node offset] (current-text-node node)] - (when span-node + (when-let [node (mf/ref-val local-ref)] + (when-let [[span-node offset] (current-text-node node)] (let [node-text (dom/get-text span-node) @@ -320,9 +322,8 @@ handle-insert-at-symbol (mf/use-fn (fn [] - (let [node (mf/ref-val local-ref) - [span-node] (current-text-node node)] - (when span-node + (when-let [node (mf/ref-val local-ref)] + (when-let [[span-node] (current-text-node node)] (let [node-text (dom/get-text span-node) at-symbol (if (blank-content? node-text) "@" " @")] @@ -334,66 +335,62 @@ (mf/deps on-esc on-ctrl-enter handle-select handle-input) (fn [event] (handle-select event) + (when-let [node (mf/ref-val local-ref)] + (when-let [[span-node offset] (current-text-node node)] + (cond + (and @cur-mention (kbd/enter? event)) + (do (dom/prevent-default event) + (dom/stop-propagation event) + (rx/push! mentions-s {:type :insert-selected-mention})) - (let [node (mf/ref-val local-ref) - [span-node offset] (current-text-node node)] + (and @cur-mention (kbd/down-arrow? event)) + (do (dom/prevent-default event) + (dom/stop-propagation event) + (rx/push! mentions-s {:type :insert-next-mention})) - (cond - (and @cur-mention (kbd/enter? event)) - (do (dom/prevent-default event) - (dom/stop-propagation event) - (rx/push! mentions-s {:type :insert-selected-mention})) + (and @cur-mention (kbd/up-arrow? event)) + (do (dom/prevent-default event) + (dom/stop-propagation event) + (rx/push! mentions-s {:type :insert-prev-mention})) - (and @cur-mention (kbd/down-arrow? event)) - (do (dom/prevent-default event) - (dom/stop-propagation event) - (rx/push! mentions-s {:type :insert-next-mention})) + (and @cur-mention (kbd/esc? event)) + (do (dom/prevent-default event) + (dom/stop-propagation event) + (rx/push! mentions-s {:type :hide-mentions})) - (and @cur-mention (kbd/up-arrow? event)) - (do (dom/prevent-default event) - (dom/stop-propagation event) - (rx/push! mentions-s {:type :insert-prev-mention})) + (and (kbd/esc? event) (fn? on-esc)) + (on-esc event) - (and @cur-mention (kbd/esc? event)) - (do (dom/prevent-default event) - (dom/stop-propagation event) - (rx/push! mentions-s {:type :hide-mentions})) + (and (kbd/mod? event) (kbd/enter? event) (fn? on-ctrl-enter)) + (on-ctrl-enter event) - (and (kbd/esc? event) (fn? on-esc)) - (on-esc event) - - (and (kbd/mod? event) (kbd/enter? event) (fn? on-ctrl-enter)) - (on-ctrl-enter event) - - (kbd/enter? event) - (let [sel (wapi/get-selection) - range (.getRangeAt sel 0)] - (dom/prevent-default event) - (dom/stop-propagation event) - (let [[span-node offset] (current-text-node node)] - (.deleteContents range) - (handle-input) - - (when span-node - (let [txt (.-textContent span-node)] - (dom/set-html! span-node (dm/str (subs txt 0 offset) "\n" zero-width-space (subs txt offset))) - (wapi/set-cursor! span-node (inc offset)) - (handle-input))))) - - (kbd/backspace? event) - (let [prev-node (get-prev-node node span-node)] - (when (and (some? prev-node) - (= "mention" (dom/get-data prev-node "type")) - (= offset 1)) + (kbd/enter? event) + (let [sel (wapi/get-selection) + range (.getRangeAt sel 0)] (dom/prevent-default event) (dom/stop-propagation event) - (.remove prev-node)))))))] + (let [[span-node offset] (current-text-node node)] + (.deleteContents range) + (handle-input) - (mf/use-layout-effect - (mf/deps autofocus) - (fn [] - (when autofocus - (dom/focus! (mf/ref-val local-ref))))) + (when span-node + (let [txt (.-textContent span-node)] + (dom/set-html! span-node (dm/str (subs txt 0 offset) "\n" zero-width-space (subs txt offset))) + (wapi/set-cursor! span-node (inc offset)) + (handle-input))))) + + (kbd/backspace? event) + (let [prev-node (get-prev-node node span-node)] + (when (and (some? prev-node) + (= "mention" (dom/get-data prev-node "type")) + (= offset 1)) + (dom/prevent-default event) + (dom/stop-propagation event) + (.remove prev-node))))))))] + + (mf/with-layout-effect [autofocus] + (when ^boolean autofocus + (dom/focus! (mf/ref-val local-ref)))) ;; Creates the handlers for selection (mf/with-effect [handle-select] @@ -417,12 +414,12 @@ ;; Auto resize input to display the comment (mf/with-layout-effect nil - (let [^js node (mf/ref-val local-ref)] + (when-let [^js node (mf/ref-val local-ref)] (set! (.-height (.-style node)) "0") (set! (.-height (.-style node)) (str (+ 2 (.-scrollHeight node)) "px")))) (mf/with-effect [value prev-value] - (let [node (mf/ref-val local-ref)] + (when-let [node (mf/ref-val local-ref)] (cond (and (d/not-empty? prev-value) (empty? value)) (do (dom/set-html! node "") From 474408542605fca3462255950751f3a4c6d1c55a Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 19 Feb 2025 11:04:19 +0100 Subject: [PATCH 3/8] :bug: Fix incorrect handling request access with deleted profiles * :paperclip: Add minor improvements to team tests * :bug: Fix incorrect handling request access with deleted profiles * :bug: Fix redirect loop on empty route Happens when the current profile is deleted from team * :bug: Fix urls on request access emails * :paperclip: Revert url changes on emails --- CHANGES.md | 1 + .../en.html | 4 +- .../en.txt | 2 +- .../request-file-access-yourpenpot/en.html | 8 +- .../request-file-access-yourpenpot/en.txt | 2 +- .../app/email/request-file-access/en.html | 10 +- .../app/email/request-file-access/en.txt | 5 +- .../app/email/request-team-access/en.html | 4 +- .../app/email/request-team-access/en.txt | 2 +- .../app/rpc/commands/teams_invitations.clj | 43 +++--- backend/test/backend_tests/rpc_team_test.clj | 130 +++++++++++++++--- frontend/src/app/main/data/profile.cljs | 1 + frontend/src/app/main/ui/routes.cljs | 16 ++- 13 files changed, 166 insertions(+), 62 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index a9550c037..e235a8f00 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -67,6 +67,7 @@ is a number of cores) - Fix problem with grid layout crashing [Taiga #10127](https://tree.taiga.io/project/penpot/issue/10127) - Fix rename locked boards [Taiga #10174](https://tree.taiga.io/project/penpot/issue/10174) - Fix update-libraries dialog disappear when clicking outside [Taiga #10238](https://tree.taiga.io/project/penpot/issue/10238) +- Fix incorrect handling of team access requests with deleted/recreated users ## 2.4.3 diff --git a/backend/resources/app/email/request-file-access-yourpenpot-view/en.html b/backend/resources/app/email/request-file-access-yourpenpot-view/en.html index 11f8b8dea..e80d46488 100644 --- a/backend/resources/app/email/request-file-access-yourpenpot-view/en.html +++ b/backend/resources/app/email/request-file-access-yourpenpot-view/en.html @@ -207,7 +207,7 @@ - SEND A VIEW-ONLY LINK @@ -251,4 +251,4 @@ - \ No newline at end of file + diff --git a/backend/resources/app/email/request-file-access-yourpenpot-view/en.txt b/backend/resources/app/email/request-file-access-yourpenpot-view/en.txt index 67eb6cedf..397f3c821 100644 --- a/backend/resources/app/email/request-file-access-yourpenpot-view/en.txt +++ b/backend/resources/app/email/request-file-access-yourpenpot-view/en.txt @@ -6,7 +6,7 @@ Since this file is in your Penpot team, you can provide access by sending a view To proceed, please click the link below to generate and send the view-only link: -{{ public-uri }}/#/view/{{file-id}}?page-id={{page-id}}§ion=interactions&index=0&share=true +{{ public-uri }}/#/view?file-id={{file-id}}&page-id={{page-id}}§ion=interactions&index=0&share=true diff --git a/backend/resources/app/email/request-file-access-yourpenpot/en.html b/backend/resources/app/email/request-file-access-yourpenpot/en.html index 72d8e4282..596e59bf3 100644 --- a/backend/resources/app/email/request-file-access-yourpenpot/en.html +++ b/backend/resources/app/email/request-file-access-yourpenpot/en.html @@ -230,9 +230,9 @@ - SEND A VIEW-ONLY LINK + SEND A VIEW-ONLY LINK @@ -274,4 +274,4 @@ - \ No newline at end of file + diff --git a/backend/resources/app/email/request-file-access-yourpenpot/en.txt b/backend/resources/app/email/request-file-access-yourpenpot/en.txt index 140cb0445..81f5d5c72 100644 --- a/backend/resources/app/email/request-file-access-yourpenpot/en.txt +++ b/backend/resources/app/email/request-file-access-yourpenpot/en.txt @@ -19,7 +19,7 @@ Alternatively, you can create and share a view-only link to the file. This will Click the link below to generate and send the link: -{{ public-uri }}/#/view/{{file-id}}?page-id={{page-id}}§ion=interactions&index=0&share=true +{{ public-uri }}/#/view?file-id={{file-id}}&page-id={{page-id}}§ion=interactions&index=0&share=true diff --git a/backend/resources/app/email/request-file-access/en.html b/backend/resources/app/email/request-file-access/en.html index 4ea4acfcc..bda16c658 100644 --- a/backend/resources/app/email/request-file-access/en.html +++ b/backend/resources/app/email/request-file-access/en.html @@ -214,7 +214,7 @@ - GIVE ACCESS TO “{{team-name|abbreviate:25}}” TEAM @@ -247,9 +247,9 @@ - SEND A VIEW-ONLY LINK + SEND A VIEW-ONLY LINK @@ -292,4 +292,4 @@ - \ No newline at end of file + diff --git a/backend/resources/app/email/request-file-access/en.txt b/backend/resources/app/email/request-file-access/en.txt index d327e4780..d12e44ee7 100644 --- a/backend/resources/app/email/request-file-access/en.txt +++ b/backend/resources/app/email/request-file-access/en.txt @@ -13,7 +13,7 @@ This will automatically include {{requested-by|abbreviate:25}} in the team, so t Click the link below to provide team access: -{{ public-uri }}/#/dashboard/team/{{team-id}}/members?invite-email={{requested-by-email|urlescape}} +{{ public-uri }}/#/dashboard/members?team-id{{team-id}}&invite-email={{requested-by-email|urlescape}} @@ -23,8 +23,7 @@ Alternatively, you can create and share a view-only link to the file. This will Click the link below to generate and send the link: -{{ public-uri }}/#/view/{{file-id}}?page-id={{page-id}}§ion=interactions&index=0&share=true - +{{ public-uri }}/#/view?file-id={{file-id}}&page-id={{page-id}}§ion=interactions&index=0&share=true If you do not wish to grant access at this time, you can simply disregard this email. diff --git a/backend/resources/app/email/request-team-access/en.html b/backend/resources/app/email/request-team-access/en.html index 2d9e26648..b8045fa1e 100644 --- a/backend/resources/app/email/request-team-access/en.html +++ b/backend/resources/app/email/request-team-access/en.html @@ -205,7 +205,7 @@ - GIVE ACCESS TO “{{team-name|abbreviate:25}}” TEAM @@ -249,4 +249,4 @@ - \ No newline at end of file + diff --git a/backend/resources/app/email/request-team-access/en.txt b/backend/resources/app/email/request-team-access/en.txt index 225bc1e26..c05d2b869 100644 --- a/backend/resources/app/email/request-team-access/en.txt +++ b/backend/resources/app/email/request-team-access/en.txt @@ -4,7 +4,7 @@ Hello! To provide access, please click the link below: -{{ public-uri }}/#/dashboard/team/{{team-id}}/members?invite-email={{requested-by-email|urlescape}} +{{ public-uri }}/#/dashboard/members?team-id={{team-id}}&invite-email={{requested-by-email|urlescape}} If you do not wish to grant access at this time, you can simply disregard this email. diff --git a/backend/src/app/rpc/commands/teams_invitations.clj b/backend/src/app/rpc/commands/teams_invitations.clj index f8a0dfbcf..12669d9bb 100644 --- a/backend/src/app/rpc/commands/teams_invitations.clj +++ b/backend/src/app/rpc/commands/teams_invitations.clj @@ -6,6 +6,7 @@ (ns app.rpc.commands.teams-invitations (:require + [app.common.data :as d] [app.common.data.macros :as dm] [app.common.exceptions :as ex] [app.common.features :as cfeat] @@ -15,7 +16,6 @@ [app.common.uuid :as uuid] [app.config :as cf] [app.db :as db] - [app.db.sql :as sql] [app.email :as eml] [app.loggers.audit :as audit] [app.main :as-alias main] @@ -168,19 +168,16 @@ itoken)))) -(defn- add-user-to-team - [conn profile team role email] +(defn- add-member-to-team + [conn profile team role member] (let [team-id (:id team) - member (db/get* conn :profile - {:email (str/lower email)} - {::sql/columns [:id :email]}) params (merge {:team-id team-id :profile-id (:id member)} (get types.team/permissions-for-role role))] - ;; Do not allow blocked users to join teams. + ;; Do not allow blocked users to join teams. (when (:is-blocked member) (ex/raise :type :restriction :code :profile-blocked)) @@ -205,29 +202,33 @@ (eml/send! {::eml/conn conn ::eml/factory eml/join-team :public-uri (cf/get :public-uri) - :to email + :to (:email member) :invited-by (:fullname profile) :team (:name team) :team-id (:id team)}))) -(def sql:valid-requests-email - "SELECT p.email +(def ^:private sql:valid-access-request-profiles + "SELECT p.id, p.email, p.is_blocked FROM team_access_request AS tr JOIN profile AS p ON (tr.requester_id = p.id) WHERE tr.team_id = ? - AND tr.auto_join_until > now()") + AND tr.auto_join_until > now() + AND (p.deleted_at IS NULL OR + p.deleted_at > now())") -(defn- get-valid-requests-email +(defn- get-valid-access-request-profiles [conn team-id] - (db/exec! conn [sql:valid-requests-email team-id])) + (db/exec! conn [sql:valid-access-request-profiles team-id])) -(def ^:private xf:map-email - (map :email)) +(def ^:private xf:map-email (map :email)) (defn- create-team-invitations [{:keys [::db/conn] :as cfg} {:keys [profile team role emails] :as params}] - (let [join-requests (into #{} xf:map-email - (get-valid-requests-email conn (:id team))) + (let [emails (set emails) + + join-requests (->> (get-valid-access-request-profiles conn (:id team)) + (d/index-by :email)) + team-members (into #{} xf:map-email (teams/get-team-members conn (:id team))) @@ -245,8 +246,10 @@ ;; For requested invitations, do not send invitation emails, add ;; the user directly to the team - (->> (filter join-requests emails) - (run! (partial add-user-to-team conn profile team role))) + (->> join-requests + (filter #(contains? emails (key %))) + (map val) + (run! (partial add-member-to-team conn profile team role))) invitations)) @@ -572,5 +575,3 @@ (with-meta {:request request} {::audit/props {:request 1}})))) - - diff --git a/backend/test/backend_tests/rpc_team_test.clj b/backend/test/backend_tests/rpc_team_test.clj index dd614151e..66fa5d74c 100644 --- a/backend/test/backend_tests/rpc_team_test.clj +++ b/backend/test/backend_tests/rpc_team_test.clj @@ -37,18 +37,17 @@ :role :editor}] ;; invite external user without complaints - (let [data (assoc data :emails ["foo@bar.com"]) - out (th/command! data) + (let [data (assoc data :emails ["foo@bar.com"]) + out (th/command! data) ;; retrieve the value from the database and check its content - invitation (db/exec-one! - th/*pool* - ["select count(*) as num from team_invitation where team_id = ? and email_to = ?" - (:team-id data) "foo@bar.com"])] + invitations (th/db-query :team-invitation + {:team-id (:team-id data) + :email-to "foo@bar.com"})] ;; (th/print-result! out) (t/is (th/success? out)) (t/is (= 1 (:call-count (deref mock)))) - (t/is (= 1 (:num invitation)))) + (t/is (= 1 (count invitations)))) ;; invite internal user without complaints (th/reset-mock! mock) @@ -102,6 +101,105 @@ (t/is (= :validation (:type edata))) (t/is (= :member-is-muted (:code edata)))))))) +(t/deftest create-team-invitations-with-request-access + (with-mocks [mock {:target 'app.email/send! :return nil}] + (let [profile1 (th/create-profile* 1 {:is-active true}) + requester (th/create-profile* 2 {:is-active true :email "requester@example.com"}) + + team (th/create-team* 1 {:profile-id (:id profile1)}) + proj (th/create-project* 1 {:profile-id (:id profile1) + :team-id (:id team)}) + file (th/create-file* 1 {:profile-id (:id profile1) + :project-id (:id proj)})] + (let [data {::th/type :create-team-access-request + ::rpc/profile-id (:id requester) + :file-id (:id file)} + out (th/command! data)] + (t/is (th/success? out)) + (t/is (= 1 (:call-count @mock)))) + + (th/reset-mock! mock) + + (let [data {::th/type :create-team-invitations + ::rpc/profile-id (:id profile1) + :team-id (:id team) + :role :editor + :emails ["requester@example.com"]} + out (th/command! data)] + (t/is (th/success? out)) + (t/is (= 1 (:call-count @mock))) + + ;; Check that request is properly removed + (let [requests (th/db-query :team-access-request + {:requester-id (:id requester)})] + (t/is (= 0 (count requests)))) + + (let [rows (th/db-query :team-profile-rel {:team-id (:id team)})] + (t/is (= 2 (count rows)))))))) + + +(t/deftest create-team-invitations-with-request-access-2 + (with-mocks [mock {:target 'app.email/send! :return nil}] + (let [profile1 (th/create-profile* 1 {:is-active true}) + requester (th/create-profile* 2 {:is-active true + :email "requester@example.com"}) + + team (th/create-team* 1 {:profile-id (:id profile1)}) + proj (th/create-project* 1 {:profile-id (:id profile1) + :team-id (:id team)}) + file (th/create-file* 1 {:profile-id (:id profile1) + :project-id (:id proj)})] + + ;; Create the first access request + (let [data {::th/type :create-team-access-request + ::rpc/profile-id (:id requester) + :file-id (:id file)} + out (th/command! data)] + (t/is (th/success? out)) + (t/is (= 1 (:call-count @mock)))) + + (th/reset-mock! mock) + + ;; Proceed to delete the requester user + (th/db-update! :profile + {:deleted-at (dt/in-past "1h")} + {:id (:id requester)}) + + ;; Create a new profile with the same email + (let [requester' (th/create-profile* 3 {:is-active true :email "requester@example.com"})] + + ;; Create a request access with new requester + (let [data {::th/type :create-team-access-request + ::rpc/profile-id (:id requester') + :file-id (:id file)} + out (th/command! data)] + (t/is (th/success? out)) + (t/is (= 1 (:call-count @mock)))) + + (th/reset-mock! mock) + + ;; Create an invitation for the requester email + (let [data {::th/type :create-team-invitations + ::rpc/profile-id (:id profile1) + :team-id (:id team) + :role :editor + :emails ["requester@example.com"]} + out (th/command! data)] + (t/is (th/success? out)) + (t/is (= 1 (:call-count @mock)))) + + ;; Check that request is properly removed + (let [requests (th/db-query :team-access-request + {:requester-id (:id requester')})] + (t/is (= 0 (count requests)))) + + (let [[r1 r2 :as rows] (th/db-query :team-profile-rel + {:team-id (:id team)} + {:order-by [:created-at]})] + (t/is (= 2 (count rows))) + (t/is (= (:profile-id r1) (:id profile1))) + (t/is (= (:profile-id r2) (:id requester')))))))) + (t/deftest invitation-tokens (with-mocks [mock {:target 'app.email/send! :return nil}] @@ -486,14 +584,12 @@ ;; request success (let [out (th/command! data) ;; retrieve the value from the database and check its content - request (db/exec-one! - th/*pool* - ["select count(*) as num from team_access_request where team_id = ? and requester_id = ?" - (:id team) (:id requester)])] - + requests (th/db-query :team-access-request + {:team-id (:id team) + :requester-id (:id requester)})] (t/is (th/success? out)) (t/is (= 1 (:call-count @mock))) - (t/is (= 1 (:num request)))) + (t/is (= 1 (count requests)))) ;; request again fails (th/reset-mock! mock) @@ -509,10 +605,10 @@ ;; request again when is expired success (th/reset-mock! mock) - (db/exec-one! - th/*pool* - ["update team_access_request set valid_until = ? where team_id = ? and requester_id = ?" - (dt/in-past "1h") (:id team) (:id requester)]) + (th/db-update! :team-access-request + {:valid-until (dt/in-past "1h")} + {:team-id (:id team) + :requester-id (:id requester)}) (t/is (th/success? (th/command! data))) (t/is (= 1 (:call-count @mock)))))) diff --git a/frontend/src/app/main/data/profile.cljs b/frontend/src/app/main/data/profile.cljs index 1427e2317..c69a3fa45 100644 --- a/frontend/src/app/main/data/profile.cljs +++ b/frontend/src/app/main/data/profile.cljs @@ -72,6 +72,7 @@ (def profile-fetched? (ptk/type? ::profile-fetched)) +;; FIXME: make it as general purpose handler, not only on profile (defn- on-fetch-profile-exception [cause] (let [data (ex-data cause)] diff --git a/frontend/src/app/main/ui/routes.cljs b/frontend/src/app/main/ui/routes.cljs index 03fa26bb8..6e04498d8 100644 --- a/frontend/src/app/main/ui/routes.cljs +++ b/frontend/src/app/main/ui/routes.cljs @@ -109,7 +109,11 @@ ;; avoids some race conditions that causes unexpected redirects ;; on invitations workflows (and probably other cases). (->> (rp/cmd! :get-profile) - (rx/subs! (fn [{:keys [id] :as profile}] + (rx/mapcat (fn [profile] + (->> (rp/cmd! :get-teams {}) + (rx/map (fn [teams] + (assoc profile ::teams (into #{} (map :id) teams))))))) + (rx/subs! (fn [{:keys [id ::teams] :as profile}] (cond (= id uuid/zero) (do @@ -117,10 +121,12 @@ (st/emit! (rt/nav :auth-login))) empty-path? - (let [team-id (or (dtm/get-last-team-id) - (:default-team-id profile))] - (st/emit! (rt/nav :dashboard-recent - (assoc query-params :team-id team-id)))) + (let [team-id (dtm/get-last-team-id)] + (if (contains? teams team-id) + (st/emit! (rt/nav :dashboard-recent + (assoc query-params :team-id team-id))) + (st/emit! (rt/nav :dashboard-recent + (assoc query-params :team-id (:default-team-id profile)))))) :else (st/emit! (rt/assign-exception {:type :not-found}))))))))) From e7b9ae6415660b0c5909463d497f20655b30406a Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 19 Feb 2025 12:24:16 +0100 Subject: [PATCH 4/8] :bug: Remove fit content shortcut --- frontend/src/app/main/data/workspace/shortcuts.cljs | 5 ----- frontend/translations/en.po | 4 ---- frontend/translations/es.po | 4 ---- 3 files changed, 13 deletions(-) diff --git a/frontend/src/app/main/data/workspace/shortcuts.cljs b/frontend/src/app/main/data/workspace/shortcuts.cljs index f648c7479..f7a7b591c 100644 --- a/frontend/src/app/main/data/workspace/shortcuts.cljs +++ b/frontend/src/app/main/data/workspace/shortcuts.cljs @@ -587,11 +587,6 @@ :subsections [:shape] :fn #(emit-when-no-readonly (dw/create-bool :exclude))} - :fit-content-selected {:tooltip (ds/meta-shift (ds/alt "R")) - :command (ds/c-mod "shift+alt+r") - :subsections [:shape] - :fn #(emit-when-no-readonly (dwt/selected-fit-content))} - ;; THEME :toggle-theme {:tooltip (ds/alt "M") :command (ds/a-mod "m") diff --git a/frontend/translations/en.po b/frontend/translations/en.po index bfeefcde0..aba49b67a 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -3602,10 +3602,6 @@ msgstr "Export shapes" msgid "shortcuts.fit-all" msgstr "Zoom to fit all" -#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:590 -msgid "shortcuts.fit-content-selected" -msgstr "Resize board to fit content" - #: src/app/main/ui/workspace/sidebar/shortcuts.cljs:113 msgid "shortcuts.flip-horizontal" msgstr "Flip horizontally" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 544c4a958..8ef63e217 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -3506,10 +3506,6 @@ msgstr "Diferencia" msgid "shortcuts.bool-exclude" msgstr "Exclusión" -#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:590 -msgid "shortcuts.fit-content-selected" -msgstr "Redimensionar para ajustar al contenido" - #: src/app/main/ui/workspace/sidebar/shortcuts.cljs:86 msgid "shortcuts.bool-intersection" msgstr "Interescción" From 4b5d304a40c603d408a2e0103c03cbb5a3ea5a0b Mon Sep 17 00:00:00 2001 From: Yamila Moreno Date: Wed, 19 Feb 2025 14:35:40 +0100 Subject: [PATCH 5/8] :books: Improve technical guide * :books: Improve technical guide * :books: Improve technical guide * :books: Improve technical guide * :books: Improve technical guide * :books: Improve technical guide * :books: Improve technical guide * :books: Improve technical guide * :books: Improve technical guide * :books: Improve technical guide * :books: Improve technical guide --- docs/_includes/layouts/base.njk | 4 +- docs/technical-guide/configuration.md | 399 ++++++++++++++---------- docs/technical-guide/getting-started.md | 26 +- 3 files changed, 240 insertions(+), 189 deletions(-) diff --git a/docs/_includes/layouts/base.njk b/docs/_includes/layouts/base.njk index 724393deb..54a3845d1 100644 --- a/docs/_includes/layouts/base.njk +++ b/docs/_includes/layouts/base.njk @@ -71,7 +71,9 @@