Update db followers table when user leaves a project

Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
Julia 2023-03-07 18:56:03 -05:00
parent 4f4af55329
commit d173b1d412
5 changed files with 156 additions and 31 deletions

View File

@ -1757,17 +1757,14 @@ impl Database {
.add(follower::Column::ProjectId.eq(project_id)) .add(follower::Column::ProjectId.eq(project_id))
.add( .add(
follower::Column::LeaderConnectionServerId follower::Column::LeaderConnectionServerId
.eq(leader_connection.owner_id) .eq(leader_connection.owner_id),
.and(follower::Column::LeaderConnectionId.eq(leader_connection.id)),
) )
.add(follower::Column::LeaderConnectionId.eq(leader_connection.id))
.add( .add(
follower::Column::FollowerConnectionServerId follower::Column::FollowerConnectionServerId
.eq(follower_connection.owner_id) .eq(follower_connection.owner_id),
.and( )
follower::Column::FollowerConnectionId .add(follower::Column::FollowerConnectionId.eq(follower_connection.id)),
.eq(follower_connection.id),
),
),
) )
.exec(&*tx) .exec(&*tx)
.await?; .await?;
@ -2560,7 +2557,7 @@ impl Database {
&self, &self,
project_id: ProjectId, project_id: ProjectId,
connection: ConnectionId, connection: ConnectionId,
) -> Result<RoomGuard<LeftProject>> { ) -> Result<RoomGuard<(proto::Room, LeftProject)>> {
let room_id = self.room_id_for_project(project_id).await?; let room_id = self.room_id_for_project(project_id).await?;
self.room_transaction(room_id, |tx| async move { self.room_transaction(room_id, |tx| async move {
let result = project_collaborator::Entity::delete_many() let result = project_collaborator::Entity::delete_many()
@ -2592,13 +2589,39 @@ impl Database {
.map(|collaborator| collaborator.connection()) .map(|collaborator| collaborator.connection())
.collect(); .collect();
follower::Entity::delete_many()
.filter(
Condition::any()
.add(
Condition::all()
.add(follower::Column::ProjectId.eq(project_id))
.add(
follower::Column::LeaderConnectionServerId
.eq(connection.owner_id),
)
.add(follower::Column::LeaderConnectionId.eq(connection.id)),
)
.add(
Condition::all()
.add(follower::Column::ProjectId.eq(project_id))
.add(
follower::Column::FollowerConnectionServerId
.eq(connection.owner_id),
)
.add(follower::Column::FollowerConnectionId.eq(connection.id)),
),
)
.exec(&*tx)
.await?;
let room = self.get_room(project.room_id, &tx).await?;
let left_project = LeftProject { let left_project = LeftProject {
id: project_id, id: project_id,
host_user_id: project.host_user_id, host_user_id: project.host_user_id,
host_connection_id: project.host_connection()?, host_connection_id: project.host_connection()?,
connection_ids, connection_ids,
}; };
Ok(left_project) Ok((room, left_project))
}) })
.await .await
} }

View File

@ -1408,7 +1408,7 @@ async fn leave_project(request: proto::LeaveProject, session: Session) -> Result
let sender_id = session.connection_id; let sender_id = session.connection_id;
let project_id = ProjectId::from_proto(request.project_id); let project_id = ProjectId::from_proto(request.project_id);
let project = session let (room, project) = &*session
.db() .db()
.await .await
.leave_project(project_id, sender_id) .leave_project(project_id, sender_id)
@ -1419,7 +1419,9 @@ async fn leave_project(request: proto::LeaveProject, session: Session) -> Result
host_connection_id = %project.host_connection_id, host_connection_id = %project.host_connection_id,
"leave project" "leave project"
); );
project_left(&project, &session); project_left(&project, &session);
room_updated(&room, &session.peer);
Ok(()) Ok(())
} }

View File

@ -5792,11 +5792,12 @@ async fn test_contact_requests(
} }
#[gpui::test(iterations = 10)] #[gpui::test(iterations = 10)]
async fn test_following( async fn test_basic_following(
deterministic: Arc<Deterministic>, deterministic: Arc<Deterministic>,
cx_a: &mut TestAppContext, cx_a: &mut TestAppContext,
cx_b: &mut TestAppContext, cx_b: &mut TestAppContext,
cx_c: &mut TestAppContext, cx_c: &mut TestAppContext,
cx_d: &mut TestAppContext,
) { ) {
deterministic.forbid_parking(); deterministic.forbid_parking();
cx_a.update(editor::init); cx_a.update(editor::init);
@ -5806,11 +5807,14 @@ async fn test_following(
let client_a = server.create_client(cx_a, "user_a").await; let client_a = server.create_client(cx_a, "user_a").await;
let client_b = server.create_client(cx_b, "user_b").await; let client_b = server.create_client(cx_b, "user_b").await;
let client_c = server.create_client(cx_c, "user_c").await; let client_c = server.create_client(cx_c, "user_c").await;
let client_d = server.create_client(cx_d, "user_d").await;
server server
.create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)]) .create_room(&mut [
.await; (&client_a, cx_a),
server (&client_b, cx_b),
.make_contacts(&mut [(&client_a, cx_a), (&client_c, cx_c)]) (&client_c, cx_c),
(&client_d, cx_d),
])
.await; .await;
let active_call_a = cx_a.read(ActiveCall::global); let active_call_a = cx_a.read(ActiveCall::global);
let active_call_b = cx_b.read(ActiveCall::global); let active_call_b = cx_b.read(ActiveCall::global);
@ -5877,6 +5881,7 @@ async fn test_following(
let peer_id_a = client_a.peer_id().unwrap(); let peer_id_a = client_a.peer_id().unwrap();
let peer_id_b = client_b.peer_id().unwrap(); let peer_id_b = client_b.peer_id().unwrap();
let peer_id_c = client_c.peer_id().unwrap(); let peer_id_c = client_c.peer_id().unwrap();
let peer_id_d = client_d.peer_id().unwrap();
// Client A updates their selections in those editors // Client A updates their selections in those editors
editor_a1.update(cx_a, |editor, cx| { editor_a1.update(cx_a, |editor, cx| {
@ -5896,25 +5901,15 @@ async fn test_following(
.await .await
.unwrap(); .unwrap();
// Client A invites client C to the call.
active_call_a
.update(cx_a, |call, cx| {
call.invite(client_c.current_user_id(cx_c).to_proto(), None, cx)
})
.await
.unwrap();
cx_c.foreground().run_until_parked(); cx_c.foreground().run_until_parked();
let active_call_c = cx_c.read(ActiveCall::global); let active_call_c = cx_c.read(ActiveCall::global);
active_call_c
.update(cx_c, |call, cx| call.accept_incoming(cx))
.await
.unwrap();
let project_c = client_c.build_remote_project(project_id, cx_c).await; let project_c = client_c.build_remote_project(project_id, cx_c).await;
let workspace_c = client_c.build_workspace(&project_c, cx_c); let workspace_c = client_c.build_workspace(&project_c, cx_c);
active_call_c active_call_c
.update(cx_c, |call, cx| call.set_location(Some(&project_c), cx)) .update(cx_c, |call, cx| call.set_location(Some(&project_c), cx))
.await .await
.unwrap(); .unwrap();
drop(project_c);
// Client C also follows client A. // Client C also follows client A.
workspace_c workspace_c
@ -5926,12 +5921,23 @@ async fn test_following(
.await .await
.unwrap(); .unwrap();
cx_d.foreground().run_until_parked();
let active_call_d = cx_d.read(ActiveCall::global);
let project_d = client_d.build_remote_project(project_id, cx_d).await;
let workspace_d = client_d.build_workspace(&project_d, cx_d);
active_call_d
.update(cx_d, |call, cx| call.set_location(Some(&project_d), cx))
.await
.unwrap();
drop(project_d);
// All clients see that clients B and C are following client A. // All clients see that clients B and C are following client A.
cx_c.foreground().run_until_parked(); cx_c.foreground().run_until_parked();
for (name, active_call, cx) in [ for (name, active_call, cx) in [
("A", &active_call_a, &cx_a), ("A", &active_call_a, &cx_a),
("B", &active_call_b, &cx_b), ("B", &active_call_b, &cx_b),
("C", &active_call_c, &cx_c), ("C", &active_call_c, &cx_c),
("D", &active_call_d, &cx_d),
] { ] {
active_call.read_with(*cx, |call, cx| { active_call.read_with(*cx, |call, cx| {
let room = call.room().unwrap().read(cx); let room = call.room().unwrap().read(cx);
@ -5954,6 +5960,7 @@ async fn test_following(
("A", &active_call_a, &cx_a), ("A", &active_call_a, &cx_a),
("B", &active_call_b, &cx_b), ("B", &active_call_b, &cx_b),
("C", &active_call_c, &cx_c), ("C", &active_call_c, &cx_c),
("D", &active_call_d, &cx_d),
] { ] {
active_call.read_with(*cx, |call, cx| { active_call.read_with(*cx, |call, cx| {
let room = call.room().unwrap().read(cx); let room = call.room().unwrap().read(cx);
@ -5965,6 +5972,90 @@ async fn test_following(
}); });
} }
// Client C re-follows client A.
workspace_c.update(cx_c, |workspace, cx| {
workspace.toggle_follow(&ToggleFollow(peer_id_a), cx);
});
// All clients see that clients B and C are following client A.
cx_c.foreground().run_until_parked();
for (name, active_call, cx) in [
("A", &active_call_a, &cx_a),
("B", &active_call_b, &cx_b),
("C", &active_call_c, &cx_c),
("D", &active_call_d, &cx_d),
] {
active_call.read_with(*cx, |call, cx| {
let room = call.room().unwrap().read(cx);
assert_eq!(
room.followers_for(peer_id_a, project_id),
&[peer_id_b, peer_id_c],
"checking followers for A as {name}"
);
});
}
// Client D follows client C.
workspace_d
.update(cx_d, |workspace, cx| {
workspace
.toggle_follow(&ToggleFollow(peer_id_c), cx)
.unwrap()
})
.await
.unwrap();
// All clients see that D is following C
cx_d.foreground().run_until_parked();
for (name, active_call, cx) in [
("A", &active_call_a, &cx_a),
("B", &active_call_b, &cx_b),
("C", &active_call_c, &cx_c),
("D", &active_call_d, &cx_d),
] {
active_call.read_with(*cx, |call, cx| {
let room = call.room().unwrap().read(cx);
assert_eq!(
room.followers_for(peer_id_c, project_id),
&[peer_id_d],
"checking followers for C as {name}"
);
});
}
// Client C closes the project.
cx_c.drop_last(workspace_c);
// Clients A and B see that client B is following A, and client C is not present in the followers.
cx_c.foreground().run_until_parked();
for (name, active_call, cx) in [("A", &active_call_a, &cx_a), ("B", &active_call_b, &cx_b)] {
active_call.read_with(*cx, |call, cx| {
let room = call.room().unwrap().read(cx);
assert_eq!(
room.followers_for(peer_id_a, project_id),
&[peer_id_b],
"checking followers for A as {name}"
);
});
}
// All clients see that no-one is following C
for (name, active_call, cx) in [
("A", &active_call_a, &cx_a),
("B", &active_call_b, &cx_b),
("C", &active_call_c, &cx_c),
("D", &active_call_d, &cx_d),
] {
active_call.read_with(*cx, |call, cx| {
let room = call.room().unwrap().read(cx);
assert_eq!(
room.followers_for(peer_id_c, project_id),
&[],
"checking followers for C as {name}"
);
});
}
let editor_b2 = workspace_b.read_with(cx_b, |workspace, cx| { let editor_b2 = workspace_b.read_with(cx_b, |workspace, cx| {
workspace workspace
.active_item(cx) .active_item(cx)

View File

@ -18,9 +18,10 @@ use smol::stream::StreamExt;
use crate::{ use crate::{
executor, geometry::vector::Vector2F, keymap_matcher::Keystroke, platform, Action, executor, geometry::vector::Vector2F, keymap_matcher::Keystroke, platform, Action,
AnyViewHandle, AppContext, Appearance, Entity, Event, FontCache, InputHandler, KeyDownEvent, AnyViewHandle, AppContext, Appearance, Entity, Event, FontCache, Handle, InputHandler,
ModelContext, ModelHandle, MutableAppContext, Platform, ReadModelWith, ReadViewWith, KeyDownEvent, ModelContext, ModelHandle, MutableAppContext, Platform, ReadModelWith,
RenderContext, Task, UpdateModel, UpdateView, View, ViewContext, ViewHandle, WeakHandle, ReadViewWith, RenderContext, Task, UpdateModel, UpdateView, View, ViewContext, ViewHandle,
WeakHandle,
}; };
use collections::BTreeMap; use collections::BTreeMap;
@ -329,6 +330,14 @@ impl TestAppContext {
.assert_dropped(handle.id()) .assert_dropped(handle.id())
} }
/// Drop a handle, assuming it is the last. If it is not the last, panic with debug information about
/// where the stray handles were created.
pub fn drop_last<T, W: WeakHandle, H: Handle<T, Weak = W>>(&mut self, handle: H) {
let weak = handle.downgrade();
self.update(|_| drop(handle));
self.assert_dropped(weak);
}
fn window_mut(&self, window_id: usize) -> std::cell::RefMut<platform::test::Window> { fn window_mut(&self, window_id: usize) -> std::cell::RefMut<platform::test::Window> {
std::cell::RefMut::map(self.cx.borrow_mut(), |state| { std::cell::RefMut::map(self.cx.borrow_mut(), |state| {
let (_, window) = state let (_, window) = state

View File

@ -42,6 +42,7 @@ impl View for ToggleDockButton {
let workspace = workspace.unwrap(); let workspace = workspace.unwrap();
let dock_position = workspace.read(cx).dock.position; let dock_position = workspace.read(cx).dock.position;
let dock_pane = workspace.read(cx.app).dock_pane().clone();
let theme = cx.global::<Settings>().theme.clone(); let theme = cx.global::<Settings>().theme.clone();
@ -67,7 +68,6 @@ impl View for ToggleDockButton {
}) })
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.on_up(MouseButton::Left, move |event, cx| { .on_up(MouseButton::Left, move |event, cx| {
let dock_pane = workspace.read(cx.app).dock_pane();
let drop_index = dock_pane.read(cx.app).items_len() + 1; let drop_index = dock_pane.read(cx.app).items_len() + 1;
handle_dropped_item(event, &dock_pane.downgrade(), drop_index, false, None, cx); handle_dropped_item(event, &dock_pane.downgrade(), drop_index, false, None, cx);
}); });