🐛 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.json :as json]
[app.common.logging :as l]
[app.common.organization :as co]
[app.common.schema :as sm]
[app.common.schema.generators :as sg]
[app.common.time :as ct]
[app.common.types.team :as ctt]
[app.config :as cf]
[app.http.client :as http]
[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
[:map
[:id ::sm/uuid]
@ -129,12 +121,6 @@
[:id ::sm/uuid]
[: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
[:map
[:is-member :boolean]
@ -221,7 +207,7 @@
(str baseuri
"/api/teams/"
team-id)
schema:organization params)))
ctt/schema:team-with-organization params)))
(defn- get-org-membership-api
[cfg {:keys [profile-id organization-id] :as params}]
@ -261,13 +247,18 @@
[cfg {:keys [organization-id team-id is-default] :as params}]
(let [baseuri (cf/get :nitrate-backend-uri)
params (assoc params :request-params {:team-id team-id
:is-your-penpot (true? is-default)})]
(request-to-nitrate cfg :post
(str baseuri
"/api/organizations/"
organization-id
"/add-team")
schema:team params)))
:is-your-penpot (true? is-default)})
team (request-to-nitrate cfg :post
(str baseuri
"/api/organizations/"
organization-id
"/add-team")
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
[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."
[cfg team params]
(try
(let [params (assoc (or params {}) :team-id (:id team))
org (call cfg :get-team-org params)]
(let [params (assoc (or params {}) :team-id (:id team))
team-with-org (call cfg :get-team-org params)
org (:organization team-with-org)]
(if (some? org)
(assoc team
:organization-id (:id org)
:organization-name (:name org)
:organization-slug (:slug 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))))
(-> (co/apply-organization team (assoc org :custom-photo
(when-let [logo-id (:logo-id org)]
(str (cf/get :public-uri) "/assets/by-id/" logo-id))))
(assoc :is-default (or (:is-default team) (true? (:is-your-penpot team-with-org)))))
team))
(catch Throwable cause
(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})
;; 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)
(def ^:private schema:add-team-to-org
[:map
[:team-id ::sm/uuid]
[:organization-id ::sm/uuid]
[:organization-name ::sm/text]])
[:organization-id ::sm/uuid]])
(sv/defmethod ::add-team-to-org
{::rpc/auth true
::doc/added "2.17"
::sm/params schema:add-team-to-org
::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-not-default-team cfg team-id)
@ -277,8 +276,8 @@
(teams/initialize-user-in-nitrate-org cfg member-id organization-id)))
;; 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
(notifications/notify-team-change cfg team-id nil organization-id organization-name "dashboard.team-belong-org")
;; Notify connected users
(notifications/notify-team-change cfg team "dashboard.team-belong-org"))
nil)

View File

@ -12,7 +12,7 @@
[app.common.exceptions :as ex]
[app.common.schema :as sm]
[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.db :as db]
[app.media :as media]
@ -117,22 +117,13 @@
;; ---- 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
"Notify to Penpot a team change from nitrate"
{::doc/added "2.14"
::sm/params schema:notify-team-change
::sm/params schema:team-with-organization
::rpc/auth false}
[cfg {:keys [id organization-id organization-name]}]
(notifications/notify-team-change cfg id nil organization-id organization-name nil)
[cfg team]
(notifications/notify-team-change cfg (select-keys team [:id :is-your-penpot :organization]) nil)
nil)
;; ---- API: notify-user-added-to-organization
@ -143,8 +134,6 @@
[:organization-id ::sm/uuid]
[:role ::sm/text]])
(sv/defmethod ::notify-user-added-to-organization
"Notify to Penpot that an user has joined an org from nitrate"
{::doc/added "2.14"
@ -271,7 +260,7 @@ RETURNING id, name;")
;; Notify users
(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

View File

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

View File

@ -74,24 +74,32 @@
(t/deftest notify-team-change-publishes-event
(let [team-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 [])
out (with-redefs [mbus/pub! (fn [_cfg & {:keys [topic message]}]
(swap! calls conj {:topic topic
:message message}))]
(management-command-with-nitrate! {::th/type :notify-team-change
:id team-id
:organization-id organization-id
:organization-name "Acme Inc"}))]
:is-your-penpot false
:organization organization}))]
(t/is (th/success? out))
(t/is (= 1 (count @calls)))
(t/is (= uuid/zero (-> @calls first :topic)))
(t/is (= {:type :team-org-change
:team-id team-id
:team-name nil
:organization-id organization-id
:organization-name "Acme Inc"
:notification nil}
(-> @calls first :message)))))
(let [msg (-> @calls first :message)]
(t/is (= :team-org-change (:type msg)))
(t/is (= nil (:notification msg)))
(t/is (= team-id (-> msg :team :id)))
(t/is (= false (-> msg :team :is-your-penpot)))
(t/is (= (:id organization) (-> msg :team :organization :id)))
(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
(let [profile (th/create-profile* 1 {:is-active true})
@ -181,7 +189,7 @@
(doseq [call @calls]
(t/is (= uuid/zero (:topic call)))
(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/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]
[: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.files.helpers :as cfh]
[app.common.logging :as log]
[app.common.organization :as co]
[app.common.schema :as sm]
[app.common.time :as ct]
[app.common.types.project :refer [valid-project?]]
@ -686,29 +687,29 @@
(modal/hide)))))
(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/WatchEvent
(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)
notification
(= team-id current-team-id))
(rx/of (ntf/show {:content (tr notification organization-name)
(= (:id team) current-team-id))
(rx/of (ntf/show {:content (tr notification (:name organization))
:type :toast
:level :info
:timeout nil})))))
ptk/UpdateEvent
(update [_ state]
(if (contains? cf/flags :nitrate)
(d/update-in-when state [:teams team-id]
(fn [team]
(cond-> team
(some? organization-id) (assoc :organization-id organization-id)
(nil? organization-id) (dissoc :organization-id)
(some? organization-name) (assoc :organization-name organization-name)
(nil? organization-name) (dissoc :organization-name)
team-name (assoc :name team-name))))
(let [team-id (:id team)
team-name (:name team)
organization (:organization team)]
(d/update-in-when state [:teams team-id]
(fn [team]
(cond-> (co/apply-organization team organization)
team-name (assoc :name team-name)))))
state))))
(defn- handle-user-org-change

View File

@ -126,11 +126,11 @@
(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/WatchEvent
(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
(fn [_]
(rx/of (modal/hide))))))))

View File

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

View File

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

View File

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