client: Use cloud as source of truth for the selected organization (#57140)

Now that the cloud platform exposes a system-settings endpoint for the
user's selected organization, switch `UserStore` over to it and drop the
local database persistence. The server already returns the current
selected organization for `get_authenticated_user()`.

set_current_organization becomes a optimistic, with the in-memory
selection updated immediately. hen a background task POSTs to the system
settings endpoint.

Stale current_organization_id rows from older installs are left dangling
— harmless and not worth a migration. This is the second step in moving
organization selection to cloud; the first added the
update_system_settings client method.

Part of CLO-716

Release Notes:

- N/A

---------

Co-authored-by: Marshall Bowers <git@maxdeviant.com>
This commit is contained in:
Tom Houlé 2026-05-20 09:00:02 +02:00 committed by GitHub
parent 0f6ebdd269
commit 5656f3eb3f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 45 additions and 41 deletions

1
Cargo.lock generated
View file

@ -3006,7 +3006,6 @@ dependencies = [
"cloud_llm_client",
"collections",
"credentials_provider",
"db",
"derive_more",
"feature_flags",
"fs",

View file

@ -27,7 +27,6 @@ cloud_api_types.workspace = true
cloud_llm_client.workspace = true
collections.workspace = true
credentials_provider.workspace = true
db.workspace = true
derive_more.workspace = true
feature_flags.workspace = true
fs.workspace = true

View file

@ -4,19 +4,19 @@ use chrono::{DateTime, Utc};
use cloud_api_client::websocket_protocol::MessageToClient;
use cloud_api_client::{
GetAuthenticatedUserResponse, KnownOrUnknown, Organization, OrganizationId, Plan, PlanInfo,
UpdateSystemSettingsBody,
};
use cloud_api_types::OrganizationConfiguration;
use cloud_llm_client::{
EDIT_PREDICTIONS_USAGE_AMOUNT_HEADER_NAME, EDIT_PREDICTIONS_USAGE_LIMIT_HEADER_NAME, UsageLimit,
};
use collections::{HashMap, HashSet, hash_map::Entry};
use db::kvp::KeyValueStore;
use derive_more::Deref;
use feature_flags::FeatureFlagAppExt;
use futures::{Future, StreamExt, channel::mpsc};
use gpui::{
App, AsyncApp, Context, Entity, EventEmitter, SharedString, SharedUri, Task, TaskExt,
WeakEntity,
App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, SharedString, SharedUri, Task,
TaskExt, WeakEntity,
};
use http_client::http::{HeaderMap, HeaderValue};
use postage::{sink::Sink, watch};
@ -28,8 +28,6 @@ use std::{
use text::ReplicaId;
use util::{ResultExt, TryFutureExt as _};
const CURRENT_ORGANIZATION_ID_KEY: &str = "current_organization_id";
pub type LegacyUserId = u64;
#[derive(
@ -708,24 +706,41 @@ impl UserStore {
&mut self,
organization: Arc<Organization>,
cx: &mut Context<Self>,
) {
) -> Task<Result<()>> {
let is_same_organization = self
.current_organization
.as_ref()
.is_some_and(|current| current.id == organization.id);
if !is_same_organization {
let organization_id = organization.id.0.to_string();
self.current_organization.replace(organization);
cx.emit(Event::OrganizationChanged);
cx.notify();
let kvp = KeyValueStore::global(cx);
db::write_and_log(cx, move || async move {
kvp.write_kvp(CURRENT_ORGANIZATION_ID_KEY.into(), organization_id)
.await
});
if is_same_organization {
return Task::ready(Ok(()));
}
let organization_id = organization.id.clone();
self.current_organization.replace(organization);
cx.emit(Event::OrganizationChanged);
cx.notify();
let Some(client) = self.client.upgrade() else {
return Task::ready(Ok(()));
};
let Some(system_id) = client.telemetry().system_id().map(|id| id.to_string()) else {
// Without a system ID we have no addressable target row on the
// server, so the selection stays purely session-local.
return Task::ready(Ok(()));
};
let cloud_client = client.cloud_client();
cx.background_spawn(async move {
let body = UpdateSystemSettingsBody {
selected_organization_id: Some(organization_id),
};
cloud_client
.update_system_settings(system_id, body)
.await
.context("failed to persist selected organization")?;
Ok(())
})
}
pub fn organizations(&self) -> &Vec<Arc<Organization>> {
@ -861,29 +876,15 @@ impl UserStore {
}
self.organizations = response.organizations.into_iter().map(Arc::new).collect();
let persisted_org_id = KeyValueStore::global(cx)
.read_kvp(CURRENT_ORGANIZATION_ID_KEY)
.log_err()
.flatten()
.map(|id| OrganizationId(Arc::from(id)));
self.current_organization = persisted_org_id
.and_then(|persisted_id| {
self.current_organization = response
.default_organization_id
.and_then(|default_organization_id| {
self.organizations
.iter()
.find(|org| org.id == persisted_id)
.find(|organization| organization.id == default_organization_id)
.cloned()
})
.or_else(|| {
response
.default_organization_id
.and_then(|default_organization_id| {
self.organizations
.iter()
.find(|organization| organization.id == default_organization_id)
.cloned()
})
})
.or_else(|| self.organizations.first().cloned());
self.plans_by_organization = response
.plans_by_organization

View file

@ -51,7 +51,8 @@ use ui::{
use update_version::UpdateVersion;
use util::ResultExt;
use workspace::{
MultiWorkspace, ToggleWorktreeSecurity, Workspace, notifications::NotifyResultExt,
MultiWorkspace, ToggleWorktreeSecurity, Workspace,
notifications::{NotifyResultExt, NotifyTaskExt as _},
};
use zed_actions::OpenRemote;
@ -1159,6 +1160,7 @@ impl TitleBar {
let show_update_button = self.update_version.read(cx).show_update_in_menu_bar();
let user_store = self.user_store.clone();
let workspace = self.workspace.clone();
let user_store_read = user_store.read(cx);
let user = user_store_read.current_user();
@ -1225,6 +1227,7 @@ impl TitleBar {
let current_organization = current_organization.clone();
let organizations = organizations.clone();
let user_store = user_store.clone();
let workspace = workspace.clone();
let ai_enabled = !project::DisableAiSettings::get_global(cx).disable_ai;
let current_layout = AgentSettings::get_layout(cx);
@ -1316,11 +1319,13 @@ impl TitleBar {
{
let user_store = user_store.clone();
let organization = organization.clone();
move |_window, cx| {
user_store.update(cx, |user_store, cx| {
let workspace = workspace.clone();
move |window, cx| {
let task = user_store.update(cx, |user_store, cx| {
user_store
.set_current_organization(organization.clone(), cx);
.set_current_organization(organization.clone(), cx)
});
task.detach_and_notify_err(workspace.clone(), window, cx);
}
},
);