diff --git a/CHANGES.md b/CHANGES.md index 8db3d6660..3cf9e5bf4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -57,6 +57,7 @@ - Fix focus handling on comments edition [Taiga #5560](https://tree.taiga.io/project/penpot/issue/5560) - Fix incorrect fullname use on registring user after OIDC authentication [Taiga #5517](https://tree.taiga.io/project/penpot/issue/5517) - Fix incorrect modified-at on project after import file [Taiga #5268](https://tree.taiga.io/project/penpot/issue/5268) +- Fix incorrect message after sending invitation to already member [Taiga 5599](https://tree.taiga.io/project/penpot/issue/5599) ### :arrow_up: Deps updates diff --git a/backend/src/app/rpc/commands/teams.clj b/backend/src/app/rpc/commands/teams.clj index 6fa32a2a2..e846f0b34 100644 --- a/backend/src/app/rpc/commands/teams.clj +++ b/backend/src/app/rpc/commands/teams.clj @@ -10,6 +10,7 @@ [app.common.data.macros :as dm] [app.common.exceptions :as ex] [app.common.logging :as l] + [app.common.schema :as sm] [app.common.spec :as us] [app.common.uuid :as uuid] [app.config :as cf] @@ -719,29 +720,22 @@ itoken)))) -(s/def ::email ::us/email) -(s/def ::emails ::us/set-of-valid-emails) -(s/def ::create-team-invitations - (s/keys :req [::rpc/profile-id] - :req-un [::team-id ::role] - :opt-un [::email ::emails])) +(def ^:private schema:create-team-invitations + [:map {:title "create-team-invitations"} + [:team-id ::sm/uuid] + [:role [::sm/one-of #{:owner :admin :editor}]] + [:emails ::sm/set-of-emails]]) (sv/defmethod ::create-team-invitations "A rpc call that allow to send a single or multiple invitations to join the team." - {::doc/added "1.17"} - [{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id team-id email emails role] :as params}] + {::doc/added "1.17" + ::sm/params schema:create-team-invitations} + [{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id team-id emails role] :as params}] (db/with-atomic [conn pool] (let [perms (get-permissions conn profile-id team-id) profile (db/get-by-id conn :profile profile-id) - team (db/get-by-id conn :team team-id) - - ;; Members emails. We don't re-send inviation to already existing members - member? (into #{} - (map :email) - (db/exec! conn [sql:team-members team-id])) - - emails (cond-> (or emails #{}) (string? email) (conj email))] + team (db/get-by-id conn :team team-id)] (run! (partial quotes/check-quote! conn) (list {::quotes/id ::quotes/invitations-per-team @@ -764,9 +758,13 @@ :hint "looks like the profile has reported repeatedly as spam or has permanent bounces")) (let [cfg (assoc cfg ::db/conn conn) - invitations (into [] + members (->> (db/exec! conn [sql:team-members team-id]) + (into #{} (map :email))) + + invitations (into #{} (comp - (remove member?) + ;; We don't re-send inviation to already existing members + (remove (partial contains? members)) (map (fn [email] {:email (str/lower email) :team team @@ -774,7 +772,8 @@ :role role})) (keep (partial create-invitation cfg))) emails)] - (with-meta invitations + (with-meta {:total (count invitations) + :invitations invitations} {::audit/props {:invitations (count invitations)}}))))) diff --git a/backend/test/backend_tests/rpc_team_test.clj b/backend/test/backend_tests/rpc_team_test.clj index d6c78432c..1685837ae 100644 --- a/backend/test/backend_tests/rpc_team_test.clj +++ b/backend/test/backend_tests/rpc_team_test.clj @@ -37,7 +37,7 @@ :role :editor}] ;; invite external user without complaints - (let [data (assoc data :email "foo@bar.com") + (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! @@ -52,7 +52,7 @@ ;; invite internal user without complaints (th/reset-mock! mock) - (let [data (assoc data :email (:email profile2)) + (let [data (assoc data :emails [(:email profile2)]) out (th/command! data)] (t/is (th/success? out)) (t/is (= 1 (:call-count (deref mock))))) @@ -60,7 +60,7 @@ ;; invite user with complaint (th/create-global-complaint-for pool {:type :complaint :email "foo@bar.com"}) (th/reset-mock! mock) - (let [data (assoc data :email "foo@bar.com") + (let [data (assoc data :emails ["foo@bar.com"]) out (th/command! data)] (t/is (th/success? out)) (t/is (= 1 (:call-count (deref mock))))) @@ -79,7 +79,7 @@ (th/reset-mock! mock) (th/create-global-complaint-for pool {:type :bounce :email "foo@bar.com"}) - (let [data (assoc data :email "foo@bar.com") + (let [data (assoc data :emails ["foo@bar.com"]) out (th/command! data)] (t/is (not (th/success? out))) @@ -92,7 +92,7 @@ ;; invite internal user that is muted (th/reset-mock! mock) - (let [data (assoc data :email (:email profile3)) + (let [data (assoc data :emails [(:email profile3)]) out (th/command! data)] (t/is (not (th/success? out))) @@ -118,7 +118,7 @@ ;; Try to invite a not existing user (let [data {::th/type :create-team-invitations ::rpc/profile-id (:id profile1) - :email "notexisting@example.com" + :emails ["notexisting@example.com"] :team-id (:id team) :role :editor} out (th/command! data)] @@ -126,15 +126,15 @@ ;; (th/print-result! out) (t/is (th/success? out)) (t/is (= 1 (:call-count @mock))) - (t/is (= 1 (-> out :result count))) + (t/is (= 1 (-> out :result :total))) - (let [token (-> out :result first) + (let [token (-> out :result :invitations first) claims (tokens/decode sprops token)] (t/is (= :team-invitation (:iss claims))) (t/is (= (:id profile1) (:profile-id claims))) (t/is (= :editor (:role claims))) (t/is (= (:id team) (:team-id claims))) - (t/is (= (:email data) (:member-email claims))) + (t/is (= (first (:emails data)) (:member-email claims))) (t/is (nil? (:member-id claims))))) (th/reset-mock! mock) @@ -142,7 +142,7 @@ ;; Try to invite existing user (let [data {::th/type :create-team-invitations ::rpc/profile-id (:id profile1) - :email (:email profile2) + :emails [(:email profile2)] :team-id (:id team) :role :editor} out (th/command! data)] @@ -150,15 +150,15 @@ ;; (th/print-result! out) (t/is (th/success? out)) (t/is (= 1 (:call-count @mock))) - (t/is (= 1 (-> out :result count))) + (t/is (= 1 (-> out :result :total))) - (let [token (-> out :result first) + (let [token (-> out :result :invitations first) claims (tokens/decode sprops token)] (t/is (= :team-invitation (:iss claims))) (t/is (= (:id profile1) (:profile-id claims))) (t/is (= :editor (:role claims))) (t/is (= (:id team) (:team-id claims))) - (t/is (= (:email data) (:member-email claims))) + (t/is (= (first (:emails data)) (:member-email claims))) (t/is (= (:id profile2) (:member-id claims))))) ))) @@ -264,7 +264,7 @@ ;; invite internal user without complaints (with-redefs [app.config/flags #{}] (th/reset-mock! mock) - (let [data (assoc data :email (:email profile2)) + (let [data (assoc data :emails [(:email profile2)]) out (th/command! data)] (t/is (th/success? out)) (t/is (= 0 (:call-count (deref mock))))) diff --git a/frontend/src/app/main/ui/dashboard/team.cljs b/frontend/src/app/main/ui/dashboard/team.cljs index f2a6625c4..e394dcbc6 100644 --- a/frontend/src/app/main/ui/dashboard/team.cljs +++ b/frontend/src/app/main/ui/dashboard/team.cljs @@ -31,8 +31,9 @@ [rumext.v2 :as mf])) (mf/defc header - {::mf/wrap [mf/memo]} - [{:keys [section team] :as props}] + {::mf/wrap [mf/memo] + ::mf/wrap-props false} + [{:keys [section team]}] (let [go-members (mf/use-fn #(st/emit! (dd/go-to-team-members))) go-settings (mf/use-fn #(st/emit! (dd/go-to-team-settings))) go-invitations (mf/use-fn #(st/emit! (dd/go-to-team-invitations))) @@ -98,27 +99,29 @@ (mf/defc invite-members-modal {::mf/register modal/components - ::mf/register-as :invite-members} + ::mf/register-as :invite-members + ::mf/wrap-props false} [{:keys [team origin]}] (let [members-map (mf/deref refs/dashboard-team-members) + perms (:permissions team) - perms (:permissions team) - - roles (mf/use-memo (mf/deps perms) #(get-available-roles perms)) - initial (mf/use-memo (constantly {:role "editor" :team-id (:id team)})) - form (fm/use-form :spec ::invite-member-form - :initial initial) - error-text (mf/use-state "") - - on-success - (fn [] - (st/emit! (msg/success (tr "notifications.invitation-email-sent")) - (modal/hide) - (dd/fetch-team-invitations))) + roles (mf/use-memo (mf/deps perms) #(get-available-roles perms)) + initial (mf/use-memo (constantly {:role "editor" :team-id (:id team)})) + form (fm/use-form :spec ::invite-member-form + :initial initial) + error-text (mf/use-state "") current-data-emails (into #{} (dm/get-in @form [:clean-data :emails])) current-members-emails (into #{} (map (comp :email second)) members-map) + on-success + (fn [form {:keys [total]}] + (when (pos? total) + (st/emit! (msg/success (tr "notifications.invitation-email-sent")))) + + (st/emit! (modal/hide) + (dd/fetch-team-invitations))) + on-error (fn [{:keys [type code] :as error}] (cond