From f3c2c0bee2090276e9a1dd49ca1b9820ca3ffb4c Mon Sep 17 00:00:00 2001 From: Pablo Alba Date: Thu, 7 May 2026 23:17:53 +0200 Subject: [PATCH] :sparkles: Change team organization structure on state --- backend/src/app/nitrate.clj | 3 +- backend/src/app/rpc/commands/teams.clj | 6 +- .../app/rpc/commands/teams_invitations.clj | 2 +- common/src/app/common/types/organization.cljc | 36 +++++------ frontend/src/app/main/data/dashboard.cljs | 2 +- frontend/src/app/main/data/nitrate.cljs | 6 +- frontend/src/app/main/data/team.cljs | 46 ++++++------- .../src/app/main/ui/dashboard/sidebar.cljs | 10 +-- frontend/src/app/main/ui/dashboard/team.cljs | 64 +++++++++---------- 9 files changed, 85 insertions(+), 90 deletions(-) diff --git a/backend/src/app/nitrate.clj b/backend/src/app/nitrate.clj index eb9735c751..35d8d4f3ad 100644 --- a/backend/src/app/nitrate.clj +++ b/backend/src/app/nitrate.clj @@ -7,6 +7,7 @@ (ns app.nitrate "Module that make calls to the external nitrate aplication" (:require + [app.common.data.macros :as dm] [app.common.exceptions :as ex] [app.common.json :as json] [app.common.logging :as l] @@ -268,7 +269,7 @@ organization-id "/add-team") cto/schema:team-with-organization params) - custom-photo (when-let [logo-id (get-in team [:organization :logo-id])] + custom-photo (when-let [logo-id (dm/get-in team [:organization :logo-id])] (str (cf/get :public-uri) "/assets/by-id/" logo-id))] (cond-> team custom-photo diff --git a/backend/src/app/rpc/commands/teams.clj b/backend/src/app/rpc/commands/teams.clj index 3f142f1ce7..56e14f1c4b 100644 --- a/backend/src/app/rpc/commands/teams.clj +++ b/backend/src/app/rpc/commands/teams.clj @@ -789,9 +789,9 @@ ;; Check delete permissions based on organization settings. ;; For non-org teams or when nitrate is disabled, only owners can delete. - (if (and (:organization-id team) (contains? cf/flags :nitrate)) - (let [org-perms {:owner-id (:organization-owner-id team) - :permissions (:organization-permissions team)}] + (if (and (:organization team) (contains? cf/flags :nitrate)) + (let [org-perms {:owner-id (dm/get-in team [:organization :owner-id]) + :permissions (dm/get-in team [:organization :permissions])}] (when-not (nitrate-perms/allowed? :delete-team {:org-perms org-perms :profile-id profile-id diff --git a/backend/src/app/rpc/commands/teams_invitations.clj b/backend/src/app/rpc/commands/teams_invitations.clj index abf0b934b4..a41b33b33f 100644 --- a/backend/src/app/rpc/commands/teams_invitations.clj +++ b/backend/src/app/rpc/commands/teams_invitations.clj @@ -232,7 +232,7 @@ :to email :invited-by (:fullname profile) :team (:name team) - :organization (:organization-name team) + :organization (dm/get-in team [:organization :name]) :token itoken :extra-data ptoken})))) diff --git a/common/src/app/common/types/organization.cljc b/common/src/app/common/types/organization.cljc index 41d5dbe949..fcf244f444 100644 --- a/common/src/app/common/types/organization.cljc +++ b/common/src/app/common/types/organization.cljc @@ -29,27 +29,23 @@ [:organization schema: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] - [:permissions :organization-permissions]]) + "Organization field keys to include in the nested :organization map." + [:id :name :custom-photo :slug :avatar-bg-url :owner-id :permissions]) (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)." + "Updates a team map with organization fields in a nested :organization map. + Associates each org field within :organization when the value is non-nil; + dissociates the field 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))) + (if id + (assoc team :organization + (reduce (fn [acc k] + (let [v (get organization k)] + (if (some? v) + (assoc acc k v) + (dissoc acc k)))) + (or (:organization team) {}) + organization->team-keys)) + (dissoc team :organization)))) diff --git a/frontend/src/app/main/data/dashboard.cljs b/frontend/src/app/main/data/dashboard.cljs index 7810d15a95..65aec5ca4d 100644 --- a/frontend/src/app/main/data/dashboard.cljs +++ b/frontend/src/app/main/data/dashboard.cljs @@ -726,7 +726,7 @@ :timeout nil}) (dtm/fetch-teams) ;; When the user is currently on a team of the org - (when (= organization-id (:organization-id team)) + (when (= organization-id (dm/get-in team [:organization :id])) (dcm/go-to-dashboard-recent {:team-id :default})))))))) diff --git a/frontend/src/app/main/data/nitrate.cljs b/frontend/src/app/main/data/nitrate.cljs index 7f24c77cb8..b05f1a63d8 100644 --- a/frontend/src/app/main/data/nitrate.cljs +++ b/frontend/src/app/main/data/nitrate.cljs @@ -204,7 +204,7 @@ {:info-message-key "dashboard.select-org-modal.permission-info"})] (modal/show :select-organization-modal (merge {:organizations orgs - :current-organization-id (:organization-id team) + :current-organization-id (dm/get-in team [:organization :id]) :on-confirm on-confirm :title-key "dashboard.select-org-modal.title" :choose-key "dashboard.select-org-modal.choose" @@ -226,7 +226,7 @@ (rx/mapcat (fn [teams] (let [all-orgs (map dt/team->organization - (filter #(and (:is-default %) (:organization-id %)) teams)) + (filter #(and (:is-default %) (:organization %)) teams)) orgs (filter (fn [org] (let [perm (get-in org [:permissions :create-teams]) is-own? (= profile-id (:owner-id org))] @@ -243,7 +243,7 @@ {:info-message-key "dashboard.select-org-modal.permission-info"})] (modal/show :select-organization-modal (merge {:organizations orgs - :current-organization-id (:organization-id team) + :current-organization-id (dm/get-in team [:organization :id]) :on-confirm on-confirm :title-key "dashboard.change-org-modal.title" :choose-key "dashboard.change-org-modal.choose" diff --git a/frontend/src/app/main/data/team.cljs b/frontend/src/app/main/data/team.cljs index 55803cedfd..2ef225eddb 100644 --- a/frontend/src/app/main/data/team.cljs +++ b/frontend/src/app/main/data/team.cljs @@ -78,23 +78,24 @@ (->> (rp/cmd! :get-teams) (rx/mapcat (fn [teams] - (let [team (d/seek #(= (:id %) team-id) teams) - in-org? (and (contains? cf/flags :nitrate) (:organization-id team)) - can-create? (if in-org? - (nitrate-perms/allowed? :create-team - {:org-perms {:owner-id (:organization-owner-id team) - :permissions (:organization-permissions team)} - :profile-id profile-id - :team-perms (:permissions team)}) - true)] + (let [team (d/seek #(= (:id %) team-id) teams) + organization (:organization team) + in-org? (and (contains? cf/flags :nitrate) organization) + can-create? (if in-org? + (nitrate-perms/allowed? :create-team + {:org-perms {:owner-id (:owner-id organization) + :permissions (:permissions organization)} + :profile-id profile-id + :team-perms (:permissions team)}) + true)] (rx/of (teams-fetched teams) (if can-create? (modal/show :team-form (if in-org? - {:organization-id (:organization-id team) - :organization-name (:organization-name team)} + {:organization-id (:id organization) + :organization-name (:name organization)} {})) (modal/show :no-permission-modal {:type :create-team - :organization-name (:organization-name team)}))))))))))) + :organization-name (:name organization)}))))))))))) (defn check-and-delete-team "Fetches fresh team data from the server to ensure up-to-date org @@ -108,16 +109,17 @@ (rx/mapcat (fn [teams] (let [team (d/seek #(= (:id %) team-id) teams) - in-org? (and (contains? cf/flags :nitrate) (:organization-id team)) + org (:organization team) + in-org? (and (contains? cf/flags :nitrate) org) can-delete? (if in-org? (nitrate-perms/allowed? :delete-team - {:org-perms {:owner-id (:organization-owner-id team) - :permissions (:organization-permissions team)} + {:org-perms {:owner-id (:owner-id org) + :permissions (:permissions org)} :profile-id profile-id :team-perms (:permissions team)}) (boolean (dm/get-in team [:permissions :is-owner]))) message (if in-org? - (tr "modals.delete-org-team-confirm.message" (:organization-name team)) + (tr "modals.delete-org-team-confirm.message" (:name org)) (tr "modals.delete-team-confirm.message"))] (rx/of (teams-fetched teams) (if can-delete? @@ -128,7 +130,7 @@ :accept-label (tr "modals.delete-team-confirm.accept") :on-accept delete-fn}) (modal/show :no-permission-modal {:type :delete-team - :organization-name (:organization-name team)}))))))))))) + :organization-name (:name org)}))))))))))) ;; --- EVENT: fetch-members @@ -659,12 +661,6 @@ (defn team->organization [team] - {:id (:organization-id team) - :slug (:organization-slug team) - :owner-id (:organization-owner-id team) - :avatar-bg-url (:organization-avatar-bg-url team) - :custom-photo (:organization-custom-photo team) - :name (:organization-name team) - :default-team-id (:id team) - :permissions (:organization-permissions team)}) + (when-let [org (:organization team)] + (assoc org :default-team-id (:id team)))) diff --git a/frontend/src/app/main/ui/dashboard/sidebar.cljs b/frontend/src/app/main/ui/dashboard/sidebar.cljs index cba9c78fe7..adcccb48b6 100644 --- a/frontend/src/app/main/ui/dashboard/sidebar.cljs +++ b/frontend/src/app/main/ui/dashboard/sidebar.cljs @@ -470,7 +470,7 @@ :not-allowed (rx/of (modal/show :no-permission-modal {:type :delete-team - :organization-name (:organization-name team)})) + :organization-name (dm/get-in team [:organization :name])})) (rx/throw error)))) @@ -581,7 +581,8 @@ (let [is-owner? (get-in team [:permissions :is-owner]) is-admin? (get-in team [:permissions :is-admin]) - is-org-team? (some? (:organization-id team)) + organization (:organization team) + is-org-team? (some? organization) in-org? (and (contains? cf/flags :nitrate) is-org-team?) show-delete? (if in-org? (or is-owner? is-admin?) @@ -825,10 +826,11 @@ (mf/defc sidebar-team-switch* [{:keys [team profile]}] (let [nitrate? (contains? cf/flags :nitrate) - organization-id (when nitrate? (:organization-id team)) + org (:organization team) + organization-id (when nitrate? (:id org)) teams (cond->> (mf/deref refs/teams) nitrate? - (filter #(= (-> % val :organization-id) organization-id)) + (filter #(= (dm/get-in (val %) [:organization :id]) organization-id)) nitrate? (into {})) diff --git a/frontend/src/app/main/ui/dashboard/team.cljs b/frontend/src/app/main/ui/dashboard/team.cljs index 106a01d3de..aefa5a67e1 100644 --- a/frontend/src/app/main/ui/dashboard/team.cljs +++ b/frontend/src/app/main/ui/dashboard/team.cljs @@ -1419,8 +1419,8 @@ (mf/deps team) (fn [] (st/emit! (dnt/remove-team-from-org {:team-id (:id team) - :organization-id (:organization-id team) - :organization-name (:organization-name team)})))) + :organization-id (dm/get-in team [:organization :id]) + :organization-name (dm/get-in team [:organization :name])})))) on-remove-team-from-org (mf/use-fn @@ -1428,7 +1428,7 @@ (fn [] (let [params {:type :confirm :title (tr "modals.remove-team-org.title") - :message (tr "modals.remove-team-org.text" (:name team) (:organization-name team)) + :message (tr "modals.remove-team-org.text" (:name team) (dm/get-in team [:organization :name])) :hint (tr "modals.remove-team-org.info") :hint-level :default :accept-label (tr "modals.remove-team-org.accept") @@ -1484,39 +1484,40 @@ [:div {:class (stl/css :block)} [:div {:class (stl/css :block-label)} (tr "dashboard.team-organization")] - (if (:organization-id team) - [:div {:class (stl/css :block-content)} - [:div {:class (stl/css :org-block-content)} - [:> org-avatar* {:org (dtm/team->organization team) :size "xxxl"}] - [:span {:class (stl/css :block-text)} - (:organization-name team)] + (let [organization (:organization team)] + (if organization + [:div {:class (stl/css :block-content)} + [:div {:class (stl/css :org-block-content)} + [:> org-avatar* {:org (dtm/team->organization team) :size "xxxl"}] + [:span {:class (stl/css :block-text)} + (:name organization)] - (when (and (:is-owner permissions) (not (:is-default team))) - [:* - [:> button* {:variant "ghost" - :type "button" - :class (stl/css-case :org-options-btn (not show-org-options-menu?) :org-options-btn-open show-org-options-menu?) - :on-click on-show-options-click} - org-menu-icon + (when (and (:is-owner permissions) (not (:is-default team))) + [:* + [:> button* {:variant "ghost" + :type "button" + :class (stl/css-case :org-options-btn (not show-org-options-menu?) :org-options-btn-open show-org-options-menu?) + :on-click on-show-options-click} + org-menu-icon - [:& dropdown {:show show-org-options-menu? :on-close close-org-options-menu :dropdown-id "org-options"} - [:ul {:class (stl/css :org-dropdown) - :role "listbox"} - (when can-change-organization? - [:li {:on-click on-change-team-org + [:& dropdown {:show show-org-options-menu? :on-close close-org-options-menu :dropdown-id "org-options"} + [:ul {:class (stl/css :org-dropdown) + :role "listbox"} + (when can-change-organization? + [:li {:on-click on-change-team-org + :class (stl/css :org-dropdown-item)} + (tr "dashboard.team-organization.change")]) + [:li {:on-click on-remove-team-from-org :class (stl/css :org-dropdown-item)} - (tr "dashboard.team-organization.change")]) - [:li {:on-click on-remove-team-from-org - :class (stl/css :org-dropdown-item)} - (tr "dashboard.team-organization.remove")]]]]])]] - [:* - [:div {:class (stl/css :block-content)} - [:span {:class (stl/css :block-text)} - (tr "dashboard.team-organization.none")]] - (when can-add-to-organization? + (tr "dashboard.team-organization.remove")]]]]])]] + [:* [:div {:class (stl/css :block-content)} [:span {:class (stl/css :block-text)} - [:a {:on-click on-add-team-to-org} (tr "dashboard.team-organization.add")]]])])]) + (tr "dashboard.team-organization.none")]] + (when can-add-to-organization? + [:div {:class (stl/css :block-content)} + [:span {:class (stl/css :block-text)} + [:a {:on-click on-add-team-to-org} (tr "dashboard.team-organization.add")]]])]))]) [:div {:class (stl/css :block)} [:div {:class (stl/css :block-label)} @@ -1549,4 +1550,3 @@ (when (contains? cfg/flags :subscriptions) [:> team* {:is-owner (:is-owner permissions) :team team}])]]])) -