Fetch team organization in a single batch (#11140)

This commit is contained in:
Marina López 2026-08-10 13:43:27 +02:00 committed by GitHub
parent 900a7ef498
commit 5d2cb22966
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 115 additions and 16 deletions

View File

@ -259,6 +259,14 @@
(generate-nitrate-uri "api/teams/" team-id) (generate-nitrate-uri "api/teams/" team-id)
cto/schema:team-with-organization params)) cto/schema:team-with-organization params))
(defn- get-teams-organizations-api
[cfg {:keys [team-ids] :as params}]
(let [params (assoc params :request-params {:team-ids team-ids})]
(request-to-nitrate cfg :post
(generate-nitrate-uri "api/teams/organizations")
[:vector cto/schema:team-with-organization]
params)))
(defn- get-organization-membership-api (defn- get-organization-membership-api
[cfg {:keys [profile-id organization-id] :as params}] [cfg {:keys [profile-id organization-id] :as params}]
(request-to-nitrate cfg :get (request-to-nitrate cfg :get
@ -489,6 +497,7 @@
[_ cfg] [_ cfg]
(when (contains? cf/flags :admin-console) (when (contains? cf/flags :admin-console)
{:get-team-organization (partial get-team-organization-api cfg) {:get-team-organization (partial get-team-organization-api cfg)
:get-teams-organizations (partial get-teams-organizations-api cfg)
:set-team-organization (partial set-team-organization-api cfg) :set-team-organization (partial set-team-organization-api cfg)
:get-organization-membership (partial get-organization-membership-api cfg) :get-organization-membership (partial get-organization-membership-api cfg)
:get-organization-membership-by-team (partial get-organization-membership-by-team-api cfg) :get-organization-membership-by-team (partial get-organization-membership-by-team-api cfg)
@ -596,22 +605,25 @@
:cause cause) :cause cause)
profile))))) profile)))))
(defn- apply-organization-info-to-team
[team team-with-organization]
(let [organization (:organization team-with-organization)]
(if (some? organization)
(-> (cto/apply-organization team (assoc organization :custom-photo
(when-let [logo-id (:logo-id organization)]
(generate-public-uri "assets/by-id/" logo-id))))
(assoc :is-default (or (:is-default team) (true? (:is-your-penpot team-with-organization)))))
team)))
(defn add-organization-info-to-team (defn add-organization-info-to-team
"Enriches a team map with organization information from Nitrate. "Enriches a team map with organization information from Nitrate.
Adds organization-id, organization-name, organization-slug, organization-owner-id, and your-penpot fields.
Returns the original team unchanged if the request fails or organization data is nil. Returns the original team unchanged if the request fails or organization data is nil.
Propagates `:nitrate-unavailable` so the request is rejected when Nitrate is unreachable." Propagates `:nitrate-unavailable` so the request is rejected when Nitrate is unreachable."
[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))
team-with-organization (call cfg :get-team-organization params) team-with-organization (call cfg :get-team-organization params)]
organization (:organization team-with-organization)] (apply-organization-info-to-team team team-with-organization))
(if (some? organization)
(-> (cto/apply-organization team (assoc organization :custom-photo
(when-let [logo-id (:logo-id organization)]
(generate-public-uri "assets/by-id/" logo-id))))
(assoc :is-default (or (:is-default team) (true? (:is-your-penpot team-with-organization)))))
team))
(catch Throwable cause (catch Throwable cause
(if (= :nitrate-unavailable (-> cause ex-data :type)) (if (= :nitrate-unavailable (-> cause ex-data :type))
(throw cause) (throw cause)
@ -621,6 +633,23 @@
:cause cause) :cause cause)
team))))) team)))))
(defn add-organization-info-to-teams
"Enriches teams with organization information using one batched Nitrate request.
Teams absent from the Nitrate response are returned unchanged.
Rejects the request when Nitrate does not return a valid batch response."
[cfg teams params]
(let [request-params (assoc (or params {}) :team-ids (mapv :id teams))
teams-with-organization (call cfg :get-teams-organizations request-params)]
(when (nil? teams-with-organization)
(ex/raise :type :nitrate-unavailable
:hint "nitrate did not return a valid teams organization response"))
(let [organizations-by-team (into {} (map (juxt :id identity)) teams-with-organization)]
(mapv (fn [{:keys [id] :as team}]
(if-let [team-with-organization (get organizations-by-team id)]
(apply-organization-info-to-team team team-with-organization)
team))
teams))))
(defn set-team-organization (defn set-team-organization
"Associates a team with an organization in Nitrate. "Associates a team with an organization in Nitrate.
Requires organization-id and is-default in params. Requires organization-id and is-default in params.

View File

@ -196,11 +196,11 @@
::sm/params schema:get-teams} ::sm/params schema:get-teams}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id] :as params}] [{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id] :as params}]
(dm/with-open [conn (db/open pool)] (dm/with-open [conn (db/open pool)]
(cond->> (get-teams conn profile-id) (let [teams (get-teams conn profile-id)]
(contains? cf/flags :admin-console) (if (contains? cf/flags :admin-console)
(map #(nitrate/add-organization-info-to-team cfg % params)) (->> (nitrate/add-organization-info-to-teams cfg teams params)
(contains? cf/flags :admin-console) (remove #(get-in % [:organization :expired-license])))
(remove #(get-in % [:organization :expired-license]))))) teams))))
(def ^:private sql:get-owned-teams (def ^:private sql:get-owned-teams
"SELECT t.id, t.name, "SELECT t.id, t.name,

View File

@ -50,7 +50,16 @@
:organization (organization-data organization-id organization-owner-id)} :organization (organization-data organization-id organization-owner-id)}
{:id (:team-id params) {:id (:team-id params)
:is-your-penpot false :is-your-penpot false
:organization nil})))] :organization nil})
:get-teams-organizations
(->> (:team-ids params)
(keep (fn [candidate-team-id]
(when (= team-id candidate-team-id)
{:id team-id
:is-your-penpot false
:organization (organization-data organization-id organization-owner-id)})))
vec)))]
(f))) (f)))
(defn- with-captured-messages (defn- with-captured-messages

View File

@ -719,6 +719,67 @@
(t/is (not= (:default-team-id profile1) (:id item1)))))) (t/is (not= (:default-team-id profile1) (:id item1))))))
(t/deftest get-teams-fetches-organizations-in-one-batch
(let [profile (th/create-profile* 1 {:is-active true})
organization-team (th/create-team* 1 {:profile-id (:id profile)})
plain-team (th/create-team* 2 {:profile-id (:id profile)})
expired-team (th/create-team* 3 {:profile-id (:id profile)})
organization-id (uuid/random)
calls (atom [])
organization {:id organization-id
:name "Acme"
:slug "acme"
:owner-id (:id profile)
:avatar-bg-url "https://example.com/avatar.svg"}
nitrate-call (fn [_cfg method params]
(swap! calls conj [method params])
[{:id (:id organization-team)
:is-your-penpot false
:organization organization}
{:id (:id expired-team)
:is-your-penpot false
:organization (assoc organization :expired-license true)}])
params {::th/type :get-teams
::rpc/profile-id (:id profile)}]
(with-redefs [cf/flags (conj cf/flags :admin-console)
nitrate/call nitrate-call]
(let [out (th/command! params)
teams (:result out)]
(t/is (th/success? out))
(t/is (= 1 (count @calls)))
(t/is (= :get-teams-organizations (ffirst @calls)))
(t/is (= #{(:default-team-id profile)
(:id organization-team)
(:id plain-team)
(:id expired-team)}
(-> @calls first second :team-ids set)))
(t/is (= #{(:default-team-id profile)
(:id organization-team)
(:id plain-team)}
(into #{} (map :id) teams)))
(t/is (= organization
(->> teams
(filter #(= (:id organization-team) (:id %)))
first
:organization)))))))
(t/deftest get-teams-rejects-invalid-organization-batch-response
(let [profile (th/create-profile* 1 {:is-active true})
calls (atom [])
params {::th/type :get-teams
::rpc/profile-id (:id profile)}]
(with-redefs [cf/flags (conj cf/flags :admin-console)
nitrate/call (fn [_cfg method call-params]
(swap! calls conj [method call-params])
nil)]
(let [out (th/command! params)]
(t/is (not (th/success? out)))
(t/is (= :nitrate-unavailable (th/ex-type (:error out))))
(t/is (= 1 (count @calls)))
(t/is (= :get-teams-organizations (ffirst @calls)))))))
(t/deftest team-deletion-1 (t/deftest team-deletion-1
(let [profile1 (th/create-profile* 1 {:is-active true}) (let [profile1 (th/create-profile* 1 {:is-active true})
team (th/create-team* 1 {:profile-id (:id profile1)}) team (th/create-team* 1 {:profile-id (:id profile1)})