🐛 Fix switching a team nitrate organization lose the background

This commit is contained in:
Pablo Alba 2026-04-23 13:26:01 +02:00 committed by Pablo Alba
parent 01d68ec09b
commit debfe5490f
12 changed files with 115 additions and 90 deletions

View File

@ -10,9 +10,11 @@
[app.common.exceptions :as ex] [app.common.exceptions :as ex]
[app.common.json :as json] [app.common.json :as json]
[app.common.logging :as l] [app.common.logging :as l]
[app.common.organization :as co]
[app.common.schema :as sm] [app.common.schema :as sm]
[app.common.schema.generators :as sg] [app.common.schema.generators :as sg]
[app.common.time :as ct] [app.common.time :as ct]
[app.common.types.team :as ctt]
[app.config :as cf] [app.config :as cf]
[app.http.client :as http] [app.http.client :as http]
[app.rpc :as-alias rpc] [app.rpc :as-alias rpc]
@ -108,16 +110,6 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def ^:private schema:organization
[:map
[:id ::sm/uuid]
[:name ::sm/text]
[:slug ::sm/text]
[:is-your-penpot :boolean]
[:owner-id ::sm/uuid]
[:avatar-bg-url [::sm/text]]
[:logo-id {:optional true} [:maybe ::sm/uuid]]])
(def ^:private schema:org-summary (def ^:private schema:org-summary
[:map [:map
[:id ::sm/uuid] [:id ::sm/uuid]
@ -129,12 +121,6 @@
[:id ::sm/uuid] [:id ::sm/uuid]
[:is-your-penpot :boolean]]]]]) [:is-your-penpot :boolean]]]]])
(def ^:private schema:team
[:map
[:id ::sm/uuid]
[:organization-id ::sm/uuid]
[:is-your-penpot :boolean]])
(def ^:private schema:profile-org (def ^:private schema:profile-org
[:map [:map
[:is-member :boolean] [:is-member :boolean]
@ -221,7 +207,7 @@
(str baseuri (str baseuri
"/api/teams/" "/api/teams/"
team-id) team-id)
schema:organization params))) ctt/schema:team-with-organization params)))
(defn- get-org-membership-api (defn- get-org-membership-api
[cfg {:keys [profile-id organization-id] :as params}] [cfg {:keys [profile-id organization-id] :as params}]
@ -261,13 +247,18 @@
[cfg {:keys [organization-id team-id is-default] :as params}] [cfg {:keys [organization-id team-id is-default] :as params}]
(let [baseuri (cf/get :nitrate-backend-uri) (let [baseuri (cf/get :nitrate-backend-uri)
params (assoc params :request-params {:team-id team-id params (assoc params :request-params {:team-id team-id
:is-your-penpot (true? is-default)})] :is-your-penpot (true? is-default)})
(request-to-nitrate cfg :post team (request-to-nitrate cfg :post
(str baseuri (str baseuri
"/api/organizations/" "/api/organizations/"
organization-id organization-id
"/add-team") "/add-team")
schema:team params))) ctt/schema:team-with-organization params)
custom-photo (when-let [logo-id (get-in team [:organization :logo-id])]
(str (cf/get :public-uri) "/assets/by-id/" logo-id))]
(cond-> team
custom-photo
(assoc-in [:organization :custom-photo] custom-photo))))
(defn- add-profile-to-org-api (defn- add-profile-to-org-api
[cfg {:keys [profile-id organization-id team-id email] :as params}] [cfg {:keys [profile-id organization-id team-id email] :as params}]
@ -385,18 +376,14 @@
Returns the original team unchanged if the request fails or org data is nil." Returns the original team unchanged if the request fails or org data is nil."
[cfg team params] [cfg team params]
(try (try
(let [params (assoc (or params {}) :team-id (:id team)) (let [params (assoc (or params {}) :team-id (:id team))
org (call cfg :get-team-org params)] team-with-org (call cfg :get-team-org params)
org (:organization team-with-org)]
(if (some? org) (if (some? org)
(assoc team (-> (co/apply-organization team (assoc org :custom-photo
:organization-id (:id org) (when-let [logo-id (:logo-id org)]
:organization-name (:name org) (str (cf/get :public-uri) "/assets/by-id/" logo-id))))
:organization-slug (:slug org) (assoc :is-default (or (:is-default team) (true? (:is-your-penpot team-with-org)))))
:organization-owner-id (:owner-id org)
:organization-avatar-bg-url (:avatar-bg-url org)
:organization-custom-photo (when-let [logo-id (:logo-id org)]
(str (cf/get :public-uri) "/assets/by-id/" logo-id))
:is-default (or (:is-default team) (true? (:is-your-penpot org))))
team)) team))
(catch Throwable cause (catch Throwable cause
(l/error :hint "failed to get team organization info" (l/error :hint "failed to get team organization info"

View File

@ -249,22 +249,21 @@
(nitrate/call cfg :remove-team-from-org {:team-id team-id :organization-id organization-id}) (nitrate/call cfg :remove-team-from-org {:team-id team-id :organization-id organization-id})
;; Notify connected users ;; Notify connected users
(notifications/notify-team-change cfg team-id nil nil organization-name "dashboard.team-no-longer-belong-org") (notifications/notify-team-change cfg {:id team-id :organization {:name organization-name}} "dashboard.team-no-longer-belong-org")
nil) nil)
(def ^:private schema:add-team-to-org (def ^:private schema:add-team-to-org
[:map [:map
[:team-id ::sm/uuid] [:team-id ::sm/uuid]
[:organization-id ::sm/uuid] [:organization-id ::sm/uuid]])
[:organization-name ::sm/text]])
(sv/defmethod ::add-team-to-org (sv/defmethod ::add-team-to-org
{::rpc/auth true {::rpc/auth true
::doc/added "2.17" ::doc/added "2.17"
::sm/params schema:add-team-to-org ::sm/params schema:add-team-to-org
::db/transaction true} ::db/transaction true}
[cfg {:keys [::rpc/profile-id team-id organization-id organization-name]}] [cfg {:keys [::rpc/profile-id team-id organization-id]}]
(assert-is-owner cfg profile-id team-id) (assert-is-owner cfg profile-id team-id)
(assert-not-default-team cfg team-id) (assert-not-default-team cfg team-id)
@ -277,8 +276,8 @@
(teams/initialize-user-in-nitrate-org cfg member-id organization-id))) (teams/initialize-user-in-nitrate-org cfg member-id organization-id)))
;; Api call to nitrate ;; Api call to nitrate
(nitrate/call cfg :set-team-org {:team-id team-id :organization-id organization-id :is-default false}) (let [team (nitrate/call cfg :set-team-org {:team-id team-id :organization-id organization-id :is-default false})]
;; Notify connected users ;; Notify connected users
(notifications/notify-team-change cfg team-id nil organization-id organization-name "dashboard.team-belong-org") (notifications/notify-team-change cfg team "dashboard.team-belong-org"))
nil) nil)

View File

@ -12,7 +12,7 @@
[app.common.exceptions :as ex] [app.common.exceptions :as ex]
[app.common.schema :as sm] [app.common.schema :as sm]
[app.common.types.profile :refer [schema:profile, schema:basic-profile]] [app.common.types.profile :refer [schema:profile, schema:basic-profile]]
[app.common.types.team :refer [schema:team]] [app.common.types.team :refer [schema:team schema:team-with-organization]]
[app.config :as cf] [app.config :as cf]
[app.db :as db] [app.db :as db]
[app.media :as media] [app.media :as media]
@ -117,22 +117,13 @@
;; ---- API: notify-team-change ;; ---- API: notify-team-change
(def ^:private schema:notify-team-change
[:map
[:id ::sm/uuid]
[:organization-id ::sm/uuid]
[:organization-name ::sm/text]])
(sv/defmethod ::notify-team-change (sv/defmethod ::notify-team-change
"Notify to Penpot a team change from nitrate" "Notify to Penpot a team change from nitrate"
{::doc/added "2.14" {::doc/added "2.14"
::sm/params schema:notify-team-change ::sm/params schema:team-with-organization
::rpc/auth false} ::rpc/auth false}
[cfg {:keys [id organization-id organization-name]}] [cfg team]
(notifications/notify-team-change cfg id nil organization-id organization-name nil) (notifications/notify-team-change cfg (select-keys team [:id :is-your-penpot :organization]) nil)
nil) nil)
;; ---- API: notify-user-added-to-organization ;; ---- API: notify-user-added-to-organization
@ -143,8 +134,6 @@
[:organization-id ::sm/uuid] [:organization-id ::sm/uuid]
[:role ::sm/text]]) [:role ::sm/text]])
(sv/defmethod ::notify-user-added-to-organization (sv/defmethod ::notify-user-added-to-organization
"Notify to Penpot that an user has joined an org from nitrate" "Notify to Penpot that an user has joined an org from nitrate"
{::doc/added "2.14" {::doc/added "2.14"
@ -271,7 +260,7 @@ RETURNING id, name;")
;; Notify users ;; Notify users
(doseq [team updated-teams] (doseq [team updated-teams]
(notifications/notify-team-change cfg (:id team) (:name team) nil organization-name "dashboard.org-deleted")))))))) (notifications/notify-team-change cfg {:id (:id team) :name (:name team) :organization {:name organization-name}} "dashboard.org-deleted"))))))))
;; ---- API: get-profile-by-email ;; ---- API: get-profile-by-email

View File

@ -10,17 +10,14 @@
[app.msgbus :as mbus])) [app.msgbus :as mbus]))
(defn notify-team-change (defn notify-team-change
[cfg team-id team-name organization-id organization-name notification] [cfg team notification]
(let [msgbus (::mbus/msgbus cfg)] (let [msgbus (::mbus/msgbus cfg)]
(mbus/pub! msgbus (mbus/pub! msgbus
;;TODO There is a bug on dashboard with teams notifications. ;;TODO There is a bug on dashboard with teams notifications.
;;For now we send it to uuid/zero instead of team-id ;;For now we send it to uuid/zero instead of team-id
:topic uuid/zero :topic uuid/zero
:message {:type :team-org-change :message {:type :team-org-change
:team-id team-id :team team
:team-name team-name
:organization-id organization-id
:organization-name organization-name
:notification notification}))) :notification notification})))

View File

@ -74,24 +74,32 @@
(t/deftest notify-team-change-publishes-event (t/deftest notify-team-change-publishes-event
(let [team-id (uuid/random) (let [team-id (uuid/random)
organization-id (uuid/random) organization-id (uuid/random)
organization {:id organization-id
:name "Acme Inc"
:slug "acme-inc"
:owner-id (uuid/random)
:avatar-bg-url "http://example.com/avatar.svg"}
calls (atom []) calls (atom [])
out (with-redefs [mbus/pub! (fn [_cfg & {:keys [topic message]}] out (with-redefs [mbus/pub! (fn [_cfg & {:keys [topic message]}]
(swap! calls conj {:topic topic (swap! calls conj {:topic topic
:message message}))] :message message}))]
(management-command-with-nitrate! {::th/type :notify-team-change (management-command-with-nitrate! {::th/type :notify-team-change
:id team-id :id team-id
:organization-id organization-id :is-your-penpot false
:organization-name "Acme Inc"}))] :organization organization}))]
(t/is (th/success? out)) (t/is (th/success? out))
(t/is (= 1 (count @calls))) (t/is (= 1 (count @calls)))
(t/is (= uuid/zero (-> @calls first :topic))) (t/is (= uuid/zero (-> @calls first :topic)))
(t/is (= {:type :team-org-change (let [msg (-> @calls first :message)]
:team-id team-id (t/is (= :team-org-change (:type msg)))
:team-name nil (t/is (= nil (:notification msg)))
:organization-id organization-id (t/is (= team-id (-> msg :team :id)))
:organization-name "Acme Inc" (t/is (= false (-> msg :team :is-your-penpot)))
:notification nil} (t/is (= (:id organization) (-> msg :team :organization :id)))
(-> @calls first :message))))) (t/is (= (:name organization) (-> msg :team :organization :name)))
(t/is (= (:slug organization) (-> msg :team :organization :slug)))
(t/is (= (:owner-id organization) (-> msg :team :organization :owner-id)))
(t/is (= (:avatar-bg-url organization) (str (-> msg :team :organization :avatar-bg-url)))))))
(t/deftest notify-user-added-to-organization-creates-default-org-team (t/deftest notify-user-added-to-organization-creates-default-org-team
(let [profile (th/create-profile* 1 {:is-active true}) (let [profile (th/create-profile* 1 {:is-active true})
@ -181,7 +189,7 @@
(doseq [call @calls] (doseq [call @calls]
(t/is (= uuid/zero (:topic call))) (t/is (= uuid/zero (:topic call)))
(t/is (= :team-org-change (-> call :message :type))) (t/is (= :team-org-change (-> call :message :type)))
(t/is (= organization-name (-> call :message :organization-name))) (t/is (= organization-name (-> call :message :team :organization :name)))
(t/is (= "dashboard.org-deleted" (-> call :message :notification)))))) (t/is (= "dashboard.org-deleted" (-> call :message :notification))))))
(t/deftest get-profile-by-email-success-and-not-found (t/deftest get-profile-by-email-success-and-not-found

View File

@ -0,0 +1,32 @@
;; 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 app.common.organization)
(def organization->team-keys
"Mapping from organization field keys to their corresponding :organization-* team keys."
[[:id :organization-id]
[:name :organization-name]
[:custom-photo :organization-custom-photo]
[:slug :organization-slug]
[:avatar-bg-url :organization-avatar-bg-url]
[:owner-id :organization-owner-id]])
(defn apply-organization
"Updates a team map with organization fields sourced from org.
Associates each org field to the corresponding :organization-* team key when
the value is non-nil; dissociates the key otherwise. This correctly handles
both attaching an org (all values present) and detaching one (org is nil or
all fields absent)."
[team organization]
(let [id (:id organization)]
(reduce (fn [acc [org-k team-k]]
(let [v (get organization org-k)]
(if (and id (some? v))
(assoc acc team-k v)
(dissoc acc team-k))))
team
organization->team-keys)))

View File

@ -26,3 +26,16 @@
[:id ::sm/uuid] [:id ::sm/uuid]
[:name :string]]) [:name :string]])
(def schema:team-with-organization
[:map
[:id ::sm/uuid]
[:is-your-penpot :boolean]
[:organization
[:map
[:id ::sm/uuid]
[:name ::sm/text]
[:slug ::sm/text]
[:owner-id ::sm/uuid]
[:avatar-bg-url ::sm/uri]
[:logo-id {:optional true} [:maybe ::sm/uuid]]]]])

View File

@ -11,6 +11,7 @@
[app.common.features :as cfeat] [app.common.features :as cfeat]
[app.common.files.helpers :as cfh] [app.common.files.helpers :as cfh]
[app.common.logging :as log] [app.common.logging :as log]
[app.common.organization :as co]
[app.common.schema :as sm] [app.common.schema :as sm]
[app.common.time :as ct] [app.common.time :as ct]
[app.common.types.project :refer [valid-project?]] [app.common.types.project :refer [valid-project?]]
@ -686,29 +687,29 @@
(modal/hide))))) (modal/hide)))))
(defn handle-change-team-org (defn handle-change-team-org
[{:keys [team-id team-name organization-id organization-name notification]}] [{:keys [team notification]}]
(ptk/reify ::handle-change-team-org (ptk/reify ::handle-change-team-org
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
(let [current-team-id (:current-team-id state)] (let [current-team-id (:current-team-id state)
organization (:organization team)]
(when (and (contains? cf/flags :nitrate) (when (and (contains? cf/flags :nitrate)
notification notification
(= team-id current-team-id)) (= (:id team) current-team-id))
(rx/of (ntf/show {:content (tr notification organization-name) (rx/of (ntf/show {:content (tr notification (:name organization))
:type :toast :type :toast
:level :info :level :info
:timeout nil}))))) :timeout nil})))))
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(if (contains? cf/flags :nitrate) (if (contains? cf/flags :nitrate)
(d/update-in-when state [:teams team-id] (let [team-id (:id team)
(fn [team] team-name (:name team)
(cond-> team organization (:organization team)]
(some? organization-id) (assoc :organization-id organization-id) (d/update-in-when state [:teams team-id]
(nil? organization-id) (dissoc :organization-id) (fn [team]
(some? organization-name) (assoc :organization-name organization-name) (cond-> (co/apply-organization team organization)
(nil? organization-name) (dissoc :organization-name) team-name (assoc :name team-name)))))
team-name (assoc :name team-name))))
state)))) state))))
(defn- handle-user-org-change (defn- handle-user-org-change

View File

@ -126,11 +126,11 @@
(defn add-team-to-org (defn add-team-to-org
[{:keys [team-id organization-id organization-name] :as params}] [{:keys [team-id organization-id] :as params}]
(ptk/reify ::add-team-to-org (ptk/reify ::add-team-to-org
ptk/WatchEvent ptk/WatchEvent
(watch [_ _ _] (watch [_ _ _]
(->> (rp/cmd! ::add-team-to-org {:team-id team-id :organization-id organization-id :organization-name organization-name}) (->> (rp/cmd! ::add-team-to-org {:team-id team-id :organization-id organization-id})
(rx/mapcat (rx/mapcat
(fn [_] (fn [_]
(rx/of (modal/hide)))))))) (rx/of (modal/hide))))))))

View File

@ -1437,8 +1437,7 @@
(let [organization (d/seek #(= organization-id (:id %)) organizations)] (let [organization (d/seek #(= organization-id (:id %)) organizations)]
(when organization (when organization
(st/emit! (dnt/add-team-to-org {:team-id (:id team) (st/emit! (dnt/add-team-to-org {:team-id (:id team)
:organization-id organization-id :organization-id organization-id}))))))
:organization-name (:name organization)}))))))
on-add-team-to-org on-add-team-to-org
(mf/use-fn (mf/use-fn

View File

@ -874,7 +874,6 @@
// SELECT ORGANIZATION MODAL // SELECT ORGANIZATION MODAL
.modal-select-org-container { .modal-select-org-container {
overflow: hidden;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: $sz-512; width: $sz-512;

View File

@ -25,6 +25,7 @@
outline-offset: calc(-1 * $b-1); outline-offset: calc(-1 * $b-1);
background-color: var(--options-bg-color); background-color: var(--options-bg-color);
color: var(--options-fg-color); color: var(--options-fg-color);
cursor: default;
&:hover, &:hover,
&[aria-selected="true"] { &[aria-selected="true"] {