client: Add organizations to `UserStore` (#49320)

Marshall Bowers created

This PR adds the list of the user's organizations to the `UserStore`.

Closes CLO-292.

Release Notes:

- N/A

Change summary

crates/client/src/test.rs                     |  1 +
crates/client/src/user.rs                     | 15 ++++++++++++++-
crates/cloud_api_types/src/cloud_api_types.rs | 13 +++++++++++++
3 files changed, 28 insertions(+), 1 deletion(-)

Detailed changes

crates/client/src/test.rs 🔗

@@ -265,6 +265,7 @@ pub fn make_get_authenticated_user_response(
             accepted_tos_at: None,
         },
         feature_flags: vec![],
+        organizations: vec![],
         plan: PlanInfo {
             plan: KnownOrUnknown::Known(Plan::ZedPro),
             subscription_period: None,

crates/client/src/user.rs 🔗

@@ -2,7 +2,7 @@ use super::{Client, Status, TypedEnvelope, proto};
 use anyhow::{Context as _, Result};
 use chrono::{DateTime, Utc};
 use cloud_api_client::websocket_protocol::MessageToClient;
-use cloud_api_client::{GetAuthenticatedUserResponse, Plan, PlanInfo};
+use cloud_api_client::{GetAuthenticatedUserResponse, Organization, Plan, PlanInfo};
 use cloud_llm_client::{
     EDIT_PREDICTIONS_USAGE_AMOUNT_HEADER_NAME, EDIT_PREDICTIONS_USAGE_LIMIT_HEADER_NAME, UsageLimit,
 };
@@ -109,6 +109,8 @@ pub struct UserStore {
     edit_prediction_usage: Option<EditPredictionUsage>,
     plan_info: Option<PlanInfo>,
     current_user: watch::Receiver<Option<Arc<User>>>,
+    current_organization: Option<Arc<Organization>>,
+    organizations: Vec<Arc<Organization>>,
     contacts: Vec<Arc<Contact>>,
     incoming_contact_requests: Vec<Arc<User>>,
     outgoing_contact_requests: Vec<Arc<User>>,
@@ -178,6 +180,8 @@ impl UserStore {
             users: Default::default(),
             by_github_login: Default::default(),
             current_user: current_user_rx,
+            current_organization: None,
+            organizations: Vec::new(),
             plan_info: None,
             edit_prediction_usage: None,
             contacts: Default::default(),
@@ -257,6 +261,7 @@ impl UserStore {
                         Status::SignedOut => {
                             current_user_tx.send(None).await.ok();
                             this.update(cx, |this, cx| {
+                                this.clear_organizations();
                                 this.clear_plan_and_usage();
                                 cx.emit(Event::PrivateUserInfoUpdated);
                                 cx.notify();
@@ -731,6 +736,11 @@ impl UserStore {
         cx.notify();
     }
 
+    pub fn clear_organizations(&mut self) {
+        self.organizations.clear();
+        self.current_organization = None;
+    }
+
     pub fn clear_plan_and_usage(&mut self) {
         self.plan_info = None;
         self.edit_prediction_usage = None;
@@ -749,6 +759,9 @@ impl UserStore {
                 .set_authenticated_user_info(Some(response.user.metrics_id.clone()), staff);
         }
 
+        self.organizations = response.organizations.into_iter().map(Arc::new).collect();
+        self.current_organization = self.organizations.first().cloned();
+
         self.edit_prediction_usage = Some(EditPredictionUsage(RequestUsage {
             limit: response.plan.usage.edit_predictions.limit,
             amount: response.plan.usage.edit_predictions.used as i32,

crates/cloud_api_types/src/cloud_api_types.rs 🔗

@@ -4,6 +4,8 @@ mod plan;
 mod timestamp;
 pub mod websocket_protocol;
 
+use std::sync::Arc;
+
 use serde::{Deserialize, Serialize};
 
 pub use crate::extension::*;
@@ -17,6 +19,8 @@ pub const ZED_SYSTEM_ID_HEADER_NAME: &str = "x-zed-system-id";
 pub struct GetAuthenticatedUserResponse {
     pub user: AuthenticatedUser,
     pub feature_flags: Vec<String>,
+    #[serde(default)]
+    pub organizations: Vec<Organization>,
     pub plan: PlanInfo,
 }
 
@@ -31,6 +35,15 @@ pub struct AuthenticatedUser {
     pub accepted_tos_at: Option<Timestamp>,
 }
 
+#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
+pub struct OrganizationId(Arc<str>);
+
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+pub struct Organization {
+    pub id: OrganizationId,
+    pub name: Arc<str>,
+}
+
 #[derive(Debug, PartialEq, Serialize, Deserialize)]
 pub struct AcceptTermsOfServiceResponse {
     pub user: AuthenticatedUser,