mirror of
https://github.com/penpot/penpot.git
synced 2025-05-10 13:26:37 +02:00
🐛 Fix incorrect message on sending invitation to a member
This commit is contained in:
parent
ea753da0ae
commit
23c8043f34
4 changed files with 52 additions and 49 deletions
|
@ -57,6 +57,7 @@
|
||||||
- Fix focus handling on comments edition [Taiga #5560](https://tree.taiga.io/project/penpot/issue/5560)
|
- 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 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 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
|
### :arrow_up: Deps updates
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
[app.common.data.macros :as dm]
|
[app.common.data.macros :as dm]
|
||||||
[app.common.exceptions :as ex]
|
[app.common.exceptions :as ex]
|
||||||
[app.common.logging :as l]
|
[app.common.logging :as l]
|
||||||
|
[app.common.schema :as sm]
|
||||||
[app.common.spec :as us]
|
[app.common.spec :as us]
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
[app.config :as cf]
|
[app.config :as cf]
|
||||||
|
@ -719,29 +720,22 @@
|
||||||
|
|
||||||
itoken))))
|
itoken))))
|
||||||
|
|
||||||
(s/def ::email ::us/email)
|
(def ^:private schema:create-team-invitations
|
||||||
(s/def ::emails ::us/set-of-valid-emails)
|
[:map {:title "create-team-invitations"}
|
||||||
(s/def ::create-team-invitations
|
[:team-id ::sm/uuid]
|
||||||
(s/keys :req [::rpc/profile-id]
|
[:role [::sm/one-of #{:owner :admin :editor}]]
|
||||||
:req-un [::team-id ::role]
|
[:emails ::sm/set-of-emails]])
|
||||||
:opt-un [::email ::emails]))
|
|
||||||
|
|
||||||
(sv/defmethod ::create-team-invitations
|
(sv/defmethod ::create-team-invitations
|
||||||
"A rpc call that allow to send a single or multiple invitations to
|
"A rpc call that allow to send a single or multiple invitations to
|
||||||
join the team."
|
join the team."
|
||||||
{::doc/added "1.17"}
|
{::doc/added "1.17"
|
||||||
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id team-id email emails role] :as params}]
|
::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]
|
(db/with-atomic [conn pool]
|
||||||
(let [perms (get-permissions conn profile-id team-id)
|
(let [perms (get-permissions conn profile-id team-id)
|
||||||
profile (db/get-by-id conn :profile profile-id)
|
profile (db/get-by-id conn :profile profile-id)
|
||||||
team (db/get-by-id conn :team team-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))]
|
|
||||||
|
|
||||||
(run! (partial quotes/check-quote! conn)
|
(run! (partial quotes/check-quote! conn)
|
||||||
(list {::quotes/id ::quotes/invitations-per-team
|
(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"))
|
:hint "looks like the profile has reported repeatedly as spam or has permanent bounces"))
|
||||||
|
|
||||||
(let [cfg (assoc cfg ::db/conn conn)
|
(let [cfg (assoc cfg ::db/conn conn)
|
||||||
invitations (into []
|
members (->> (db/exec! conn [sql:team-members team-id])
|
||||||
|
(into #{} (map :email)))
|
||||||
|
|
||||||
|
invitations (into #{}
|
||||||
(comp
|
(comp
|
||||||
(remove member?)
|
;; We don't re-send inviation to already existing members
|
||||||
|
(remove (partial contains? members))
|
||||||
(map (fn [email]
|
(map (fn [email]
|
||||||
{:email (str/lower email)
|
{:email (str/lower email)
|
||||||
:team team
|
:team team
|
||||||
|
@ -774,7 +772,8 @@
|
||||||
:role role}))
|
:role role}))
|
||||||
(keep (partial create-invitation cfg)))
|
(keep (partial create-invitation cfg)))
|
||||||
emails)]
|
emails)]
|
||||||
(with-meta invitations
|
(with-meta {:total (count invitations)
|
||||||
|
:invitations invitations}
|
||||||
{::audit/props {:invitations (count invitations)}})))))
|
{::audit/props {:invitations (count invitations)}})))))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
:role :editor}]
|
:role :editor}]
|
||||||
|
|
||||||
;; invite external user without complaints
|
;; 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)
|
out (th/command! data)
|
||||||
;; retrieve the value from the database and check its content
|
;; retrieve the value from the database and check its content
|
||||||
invitation (db/exec-one!
|
invitation (db/exec-one!
|
||||||
|
@ -52,7 +52,7 @@
|
||||||
|
|
||||||
;; invite internal user without complaints
|
;; invite internal user without complaints
|
||||||
(th/reset-mock! mock)
|
(th/reset-mock! mock)
|
||||||
(let [data (assoc data :email (:email profile2))
|
(let [data (assoc data :emails [(:email profile2)])
|
||||||
out (th/command! data)]
|
out (th/command! data)]
|
||||||
(t/is (th/success? out))
|
(t/is (th/success? out))
|
||||||
(t/is (= 1 (:call-count (deref mock)))))
|
(t/is (= 1 (:call-count (deref mock)))))
|
||||||
|
@ -60,7 +60,7 @@
|
||||||
;; invite user with complaint
|
;; invite user with complaint
|
||||||
(th/create-global-complaint-for pool {:type :complaint :email "foo@bar.com"})
|
(th/create-global-complaint-for pool {:type :complaint :email "foo@bar.com"})
|
||||||
(th/reset-mock! mock)
|
(th/reset-mock! mock)
|
||||||
(let [data (assoc data :email "foo@bar.com")
|
(let [data (assoc data :emails ["foo@bar.com"])
|
||||||
out (th/command! data)]
|
out (th/command! data)]
|
||||||
(t/is (th/success? out))
|
(t/is (th/success? out))
|
||||||
(t/is (= 1 (:call-count (deref mock)))))
|
(t/is (= 1 (:call-count (deref mock)))))
|
||||||
|
@ -79,7 +79,7 @@
|
||||||
(th/reset-mock! mock)
|
(th/reset-mock! mock)
|
||||||
|
|
||||||
(th/create-global-complaint-for pool {:type :bounce :email "foo@bar.com"})
|
(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)]
|
out (th/command! data)]
|
||||||
|
|
||||||
(t/is (not (th/success? out)))
|
(t/is (not (th/success? out)))
|
||||||
|
@ -92,7 +92,7 @@
|
||||||
;; invite internal user that is muted
|
;; invite internal user that is muted
|
||||||
(th/reset-mock! mock)
|
(th/reset-mock! mock)
|
||||||
|
|
||||||
(let [data (assoc data :email (:email profile3))
|
(let [data (assoc data :emails [(:email profile3)])
|
||||||
out (th/command! data)]
|
out (th/command! data)]
|
||||||
|
|
||||||
(t/is (not (th/success? out)))
|
(t/is (not (th/success? out)))
|
||||||
|
@ -118,7 +118,7 @@
|
||||||
;; Try to invite a not existing user
|
;; Try to invite a not existing user
|
||||||
(let [data {::th/type :create-team-invitations
|
(let [data {::th/type :create-team-invitations
|
||||||
::rpc/profile-id (:id profile1)
|
::rpc/profile-id (:id profile1)
|
||||||
:email "notexisting@example.com"
|
:emails ["notexisting@example.com"]
|
||||||
:team-id (:id team)
|
:team-id (:id team)
|
||||||
:role :editor}
|
:role :editor}
|
||||||
out (th/command! data)]
|
out (th/command! data)]
|
||||||
|
@ -126,15 +126,15 @@
|
||||||
;; (th/print-result! out)
|
;; (th/print-result! out)
|
||||||
(t/is (th/success? out))
|
(t/is (th/success? out))
|
||||||
(t/is (= 1 (:call-count @mock)))
|
(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)]
|
claims (tokens/decode sprops token)]
|
||||||
(t/is (= :team-invitation (:iss claims)))
|
(t/is (= :team-invitation (:iss claims)))
|
||||||
(t/is (= (:id profile1) (:profile-id claims)))
|
(t/is (= (:id profile1) (:profile-id claims)))
|
||||||
(t/is (= :editor (:role claims)))
|
(t/is (= :editor (:role claims)))
|
||||||
(t/is (= (:id team) (:team-id 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)))))
|
(t/is (nil? (:member-id claims)))))
|
||||||
|
|
||||||
(th/reset-mock! mock)
|
(th/reset-mock! mock)
|
||||||
|
@ -142,7 +142,7 @@
|
||||||
;; Try to invite existing user
|
;; Try to invite existing user
|
||||||
(let [data {::th/type :create-team-invitations
|
(let [data {::th/type :create-team-invitations
|
||||||
::rpc/profile-id (:id profile1)
|
::rpc/profile-id (:id profile1)
|
||||||
:email (:email profile2)
|
:emails [(:email profile2)]
|
||||||
:team-id (:id team)
|
:team-id (:id team)
|
||||||
:role :editor}
|
:role :editor}
|
||||||
out (th/command! data)]
|
out (th/command! data)]
|
||||||
|
@ -150,15 +150,15 @@
|
||||||
;; (th/print-result! out)
|
;; (th/print-result! out)
|
||||||
(t/is (th/success? out))
|
(t/is (th/success? out))
|
||||||
(t/is (= 1 (:call-count @mock)))
|
(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)]
|
claims (tokens/decode sprops token)]
|
||||||
(t/is (= :team-invitation (:iss claims)))
|
(t/is (= :team-invitation (:iss claims)))
|
||||||
(t/is (= (:id profile1) (:profile-id claims)))
|
(t/is (= (:id profile1) (:profile-id claims)))
|
||||||
(t/is (= :editor (:role claims)))
|
(t/is (= :editor (:role claims)))
|
||||||
(t/is (= (:id team) (:team-id 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)))))
|
(t/is (= (:id profile2) (:member-id claims)))))
|
||||||
|
|
||||||
)))
|
)))
|
||||||
|
@ -264,7 +264,7 @@
|
||||||
;; invite internal user without complaints
|
;; invite internal user without complaints
|
||||||
(with-redefs [app.config/flags #{}]
|
(with-redefs [app.config/flags #{}]
|
||||||
(th/reset-mock! mock)
|
(th/reset-mock! mock)
|
||||||
(let [data (assoc data :email (:email profile2))
|
(let [data (assoc data :emails [(:email profile2)])
|
||||||
out (th/command! data)]
|
out (th/command! data)]
|
||||||
(t/is (th/success? out))
|
(t/is (th/success? out))
|
||||||
(t/is (= 0 (:call-count (deref mock)))))
|
(t/is (= 0 (:call-count (deref mock)))))
|
||||||
|
|
|
@ -31,8 +31,9 @@
|
||||||
[rumext.v2 :as mf]))
|
[rumext.v2 :as mf]))
|
||||||
|
|
||||||
(mf/defc header
|
(mf/defc header
|
||||||
{::mf/wrap [mf/memo]}
|
{::mf/wrap [mf/memo]
|
||||||
[{:keys [section team] :as props}]
|
::mf/wrap-props false}
|
||||||
|
[{:keys [section team]}]
|
||||||
(let [go-members (mf/use-fn #(st/emit! (dd/go-to-team-members)))
|
(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-settings (mf/use-fn #(st/emit! (dd/go-to-team-settings)))
|
||||||
go-invitations (mf/use-fn #(st/emit! (dd/go-to-team-invitations)))
|
go-invitations (mf/use-fn #(st/emit! (dd/go-to-team-invitations)))
|
||||||
|
@ -98,27 +99,29 @@
|
||||||
|
|
||||||
(mf/defc invite-members-modal
|
(mf/defc invite-members-modal
|
||||||
{::mf/register modal/components
|
{::mf/register modal/components
|
||||||
::mf/register-as :invite-members}
|
::mf/register-as :invite-members
|
||||||
|
::mf/wrap-props false}
|
||||||
[{:keys [team origin]}]
|
[{:keys [team origin]}]
|
||||||
(let [members-map (mf/deref refs/dashboard-team-members)
|
(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)}))
|
||||||
roles (mf/use-memo (mf/deps perms) #(get-available-roles perms))
|
form (fm/use-form :spec ::invite-member-form
|
||||||
initial (mf/use-memo (constantly {:role "editor" :team-id (:id team)}))
|
:initial initial)
|
||||||
form (fm/use-form :spec ::invite-member-form
|
error-text (mf/use-state "")
|
||||||
: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)))
|
|
||||||
|
|
||||||
current-data-emails (into #{} (dm/get-in @form [:clean-data :emails]))
|
current-data-emails (into #{} (dm/get-in @form [:clean-data :emails]))
|
||||||
current-members-emails (into #{} (map (comp :email second)) members-map)
|
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
|
on-error
|
||||||
(fn [{:keys [type code] :as error}]
|
(fn [{:keys [type code] :as error}]
|
||||||
(cond
|
(cond
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue