Change team organization structure on state

This commit is contained in:
Pablo Alba 2026-05-07 23:17:53 +02:00 committed by Pablo Alba
parent 18e289b15a
commit f3c2c0bee2
9 changed files with 85 additions and 90 deletions

View File

@ -7,6 +7,7 @@
(ns app.nitrate (ns app.nitrate
"Module that make calls to the external nitrate aplication" "Module that make calls to the external nitrate aplication"
(:require (:require
[app.common.data.macros :as dm]
[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]
@ -268,7 +269,7 @@
organization-id organization-id
"/add-team") "/add-team")
cto/schema:team-with-organization params) 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))] (str (cf/get :public-uri) "/assets/by-id/" logo-id))]
(cond-> team (cond-> team
custom-photo custom-photo

View File

@ -789,9 +789,9 @@
;; Check delete permissions based on organization settings. ;; Check delete permissions based on organization settings.
;; For non-org teams or when nitrate is disabled, only owners can delete. ;; For non-org teams or when nitrate is disabled, only owners can delete.
(if (and (:organization-id team) (contains? cf/flags :nitrate)) (if (and (:organization team) (contains? cf/flags :nitrate))
(let [org-perms {:owner-id (:organization-owner-id team) (let [org-perms {:owner-id (dm/get-in team [:organization :owner-id])
:permissions (:organization-permissions team)}] :permissions (dm/get-in team [:organization :permissions])}]
(when-not (nitrate-perms/allowed? :delete-team (when-not (nitrate-perms/allowed? :delete-team
{:org-perms org-perms {:org-perms org-perms
:profile-id profile-id :profile-id profile-id

View File

@ -232,7 +232,7 @@
:to email :to email
:invited-by (:fullname profile) :invited-by (:fullname profile)
:team (:name team) :team (:name team)
:organization (:organization-name team) :organization (dm/get-in team [:organization :name])
:token itoken :token itoken
:extra-data ptoken})))) :extra-data ptoken}))))

View File

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

View File

@ -726,7 +726,7 @@
:timeout nil}) :timeout nil})
(dtm/fetch-teams) (dtm/fetch-teams)
;; When the user is currently on a team of the org ;; 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})))))))) (dcm/go-to-dashboard-recent {:team-id :default}))))))))

View File

@ -204,7 +204,7 @@
{:info-message-key "dashboard.select-org-modal.permission-info"})] {:info-message-key "dashboard.select-org-modal.permission-info"})]
(modal/show :select-organization-modal (modal/show :select-organization-modal
(merge {:organizations orgs (merge {:organizations orgs
:current-organization-id (:organization-id team) :current-organization-id (dm/get-in team [:organization :id])
:on-confirm on-confirm :on-confirm on-confirm
:title-key "dashboard.select-org-modal.title" :title-key "dashboard.select-org-modal.title"
:choose-key "dashboard.select-org-modal.choose" :choose-key "dashboard.select-org-modal.choose"
@ -226,7 +226,7 @@
(rx/mapcat (rx/mapcat
(fn [teams] (fn [teams]
(let [all-orgs (map dt/team->organization (let [all-orgs (map dt/team->organization
(filter #(and (:is-default %) (:organization-id %)) teams)) (filter #(and (:is-default %) (:organization %)) teams))
orgs (filter (fn [org] orgs (filter (fn [org]
(let [perm (get-in org [:permissions :create-teams]) (let [perm (get-in org [:permissions :create-teams])
is-own? (= profile-id (:owner-id org))] is-own? (= profile-id (:owner-id org))]
@ -243,7 +243,7 @@
{:info-message-key "dashboard.select-org-modal.permission-info"})] {:info-message-key "dashboard.select-org-modal.permission-info"})]
(modal/show :select-organization-modal (modal/show :select-organization-modal
(merge {:organizations orgs (merge {:organizations orgs
:current-organization-id (:organization-id team) :current-organization-id (dm/get-in team [:organization :id])
:on-confirm on-confirm :on-confirm on-confirm
:title-key "dashboard.change-org-modal.title" :title-key "dashboard.change-org-modal.title"
:choose-key "dashboard.change-org-modal.choose" :choose-key "dashboard.change-org-modal.choose"

View File

@ -78,23 +78,24 @@
(->> (rp/cmd! :get-teams) (->> (rp/cmd! :get-teams)
(rx/mapcat (rx/mapcat
(fn [teams] (fn [teams]
(let [team (d/seek #(= (:id %) team-id) teams) (let [team (d/seek #(= (:id %) team-id) teams)
in-org? (and (contains? cf/flags :nitrate) (:organization-id team)) organization (:organization team)
can-create? (if in-org? in-org? (and (contains? cf/flags :nitrate) organization)
(nitrate-perms/allowed? :create-team can-create? (if in-org?
{:org-perms {:owner-id (:organization-owner-id team) (nitrate-perms/allowed? :create-team
:permissions (:organization-permissions team)} {:org-perms {:owner-id (:owner-id organization)
:profile-id profile-id :permissions (:permissions organization)}
:team-perms (:permissions team)}) :profile-id profile-id
true)] :team-perms (:permissions team)})
true)]
(rx/of (teams-fetched teams) (rx/of (teams-fetched teams)
(if can-create? (if can-create?
(modal/show :team-form (if in-org? (modal/show :team-form (if in-org?
{:organization-id (:organization-id team) {:organization-id (:id organization)
:organization-name (:organization-name team)} :organization-name (:name organization)}
{})) {}))
(modal/show :no-permission-modal {:type :create-team (modal/show :no-permission-modal {:type :create-team
:organization-name (:organization-name team)}))))))))))) :organization-name (:name organization)})))))))))))
(defn check-and-delete-team (defn check-and-delete-team
"Fetches fresh team data from the server to ensure up-to-date org "Fetches fresh team data from the server to ensure up-to-date org
@ -108,16 +109,17 @@
(rx/mapcat (rx/mapcat
(fn [teams] (fn [teams]
(let [team (d/seek #(= (:id %) team-id) 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? can-delete? (if in-org?
(nitrate-perms/allowed? :delete-team (nitrate-perms/allowed? :delete-team
{:org-perms {:owner-id (:organization-owner-id team) {:org-perms {:owner-id (:owner-id org)
:permissions (:organization-permissions team)} :permissions (:permissions org)}
:profile-id profile-id :profile-id profile-id
:team-perms (:permissions team)}) :team-perms (:permissions team)})
(boolean (dm/get-in team [:permissions :is-owner]))) (boolean (dm/get-in team [:permissions :is-owner])))
message (if in-org? 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"))] (tr "modals.delete-team-confirm.message"))]
(rx/of (teams-fetched teams) (rx/of (teams-fetched teams)
(if can-delete? (if can-delete?
@ -128,7 +130,7 @@
:accept-label (tr "modals.delete-team-confirm.accept") :accept-label (tr "modals.delete-team-confirm.accept")
:on-accept delete-fn}) :on-accept delete-fn})
(modal/show :no-permission-modal {:type :delete-team (modal/show :no-permission-modal {:type :delete-team
:organization-name (:organization-name team)}))))))))))) :organization-name (:name org)})))))))))))
;; --- EVENT: fetch-members ;; --- EVENT: fetch-members
@ -659,12 +661,6 @@
(defn team->organization [team] (defn team->organization [team]
{:id (:organization-id team) (when-let [org (:organization team)]
:slug (:organization-slug team) (assoc org :default-team-id (:id 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)})

View File

@ -470,7 +470,7 @@
:not-allowed :not-allowed
(rx/of (modal/show :no-permission-modal {:type :delete-team (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)))) (rx/throw error))))
@ -581,7 +581,8 @@
(let [is-owner? (get-in team [:permissions :is-owner]) (let [is-owner? (get-in team [:permissions :is-owner])
is-admin? (get-in team [:permissions :is-admin]) 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?) in-org? (and (contains? cf/flags :nitrate) is-org-team?)
show-delete? (if in-org? show-delete? (if in-org?
(or is-owner? is-admin?) (or is-owner? is-admin?)
@ -825,10 +826,11 @@
(mf/defc sidebar-team-switch* (mf/defc sidebar-team-switch*
[{:keys [team profile]}] [{:keys [team profile]}]
(let [nitrate? (contains? cf/flags :nitrate) (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) teams (cond->> (mf/deref refs/teams)
nitrate? nitrate?
(filter #(= (-> % val :organization-id) organization-id)) (filter #(= (dm/get-in (val %) [:organization :id]) organization-id))
nitrate? nitrate?
(into {})) (into {}))

View File

@ -1419,8 +1419,8 @@
(mf/deps team) (mf/deps team)
(fn [] (fn []
(st/emit! (dnt/remove-team-from-org {:team-id (:id team) (st/emit! (dnt/remove-team-from-org {:team-id (:id team)
:organization-id (:organization-id team) :organization-id (dm/get-in team [:organization :id])
:organization-name (:organization-name team)})))) :organization-name (dm/get-in team [:organization :name])}))))
on-remove-team-from-org on-remove-team-from-org
(mf/use-fn (mf/use-fn
@ -1428,7 +1428,7 @@
(fn [] (fn []
(let [params {:type :confirm (let [params {:type :confirm
:title (tr "modals.remove-team-org.title") :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 (tr "modals.remove-team-org.info")
:hint-level :default :hint-level :default
:accept-label (tr "modals.remove-team-org.accept") :accept-label (tr "modals.remove-team-org.accept")
@ -1484,39 +1484,40 @@
[:div {:class (stl/css :block)} [:div {:class (stl/css :block)}
[:div {:class (stl/css :block-label)} [:div {:class (stl/css :block-label)}
(tr "dashboard.team-organization")] (tr "dashboard.team-organization")]
(if (:organization-id team) (let [organization (:organization team)]
[:div {:class (stl/css :block-content)} (if organization
[:div {:class (stl/css :org-block-content)} [:div {:class (stl/css :block-content)}
[:> org-avatar* {:org (dtm/team->organization team) :size "xxxl"}] [:div {:class (stl/css :org-block-content)}
[:span {:class (stl/css :block-text)} [:> org-avatar* {:org (dtm/team->organization team) :size "xxxl"}]
(:organization-name team)] [:span {:class (stl/css :block-text)}
(:name organization)]
(when (and (:is-owner permissions) (not (:is-default team))) (when (and (:is-owner permissions) (not (:is-default team)))
[:* [:*
[:> button* {:variant "ghost" [:> button* {:variant "ghost"
:type "button" :type "button"
:class (stl/css-case :org-options-btn (not show-org-options-menu?) :org-options-btn-open show-org-options-menu?) :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} :on-click on-show-options-click}
org-menu-icon org-menu-icon
[:& dropdown {:show show-org-options-menu? :on-close close-org-options-menu :dropdown-id "org-options"} [:& dropdown {:show show-org-options-menu? :on-close close-org-options-menu :dropdown-id "org-options"}
[:ul {:class (stl/css :org-dropdown) [:ul {:class (stl/css :org-dropdown)
:role "listbox"} :role "listbox"}
(when can-change-organization? (when can-change-organization?
[:li {:on-click on-change-team-org [: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)} :class (stl/css :org-dropdown-item)}
(tr "dashboard.team-organization.change")]) (tr "dashboard.team-organization.remove")]]]]])]]
[: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?
[:div {:class (stl/css :block-content)} [:div {:class (stl/css :block-content)}
[:span {:class (stl/css :block-text)} [: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)}
[:div {:class (stl/css :block-label)} [:div {:class (stl/css :block-label)}
@ -1549,4 +1550,3 @@
(when (contains? cfg/flags :subscriptions) (when (contains? cfg/flags :subscriptions)
[:> team* {:is-owner (:is-owner permissions) :team team}])]]])) [:> team* {:is-owner (:is-owner permissions) :team team}])]]]))