Channel notifications from the server works

This commit is contained in:
Mikayla 2023-09-30 22:30:36 -07:00
parent 1469c02998
commit 9ba975d6ad
No known key found for this signature in database
16 changed files with 266 additions and 107 deletions

View file

@ -436,6 +436,7 @@ pub struct Channel {
pub struct ChannelsForUser {
pub channels: ChannelGraph,
pub channel_participants: HashMap<ChannelId, Vec<UserId>>,
pub channels_with_changed_notes: HashSet<ChannelId>,
pub channels_with_admin_privileges: HashSet<ChannelId>,
}

View file

@ -80,20 +80,20 @@ impl Database {
// Save the last observed operation
if let Some(max_operation) = max_operation {
observed_note_edits::Entity::insert(observed_note_edits::ActiveModel {
observed_buffer_edits::Entity::insert(observed_buffer_edits::ActiveModel {
user_id: ActiveValue::Set(user_id),
channel_id: ActiveValue::Set(channel_id),
buffer_id: ActiveValue::Set(buffer.id),
epoch: ActiveValue::Set(max_operation.0),
lamport_timestamp: ActiveValue::Set(max_operation.1),
})
.on_conflict(
OnConflict::columns([
observed_note_edits::Column::UserId,
observed_note_edits::Column::ChannelId,
observed_buffer_edits::Column::UserId,
observed_buffer_edits::Column::BufferId,
])
.update_columns([
observed_note_edits::Column::Epoch,
observed_note_edits::Column::LamportTimestamp,
observed_buffer_edits::Column::Epoch,
observed_buffer_edits::Column::LamportTimestamp,
])
.to_owned(),
)
@ -110,20 +110,20 @@ impl Database {
.map(|model| (model.epoch, model.lamport_timestamp));
if let Some(buffer_max) = buffer_max {
observed_note_edits::Entity::insert(observed_note_edits::ActiveModel {
observed_buffer_edits::Entity::insert(observed_buffer_edits::ActiveModel {
user_id: ActiveValue::Set(user_id),
channel_id: ActiveValue::Set(channel_id),
buffer_id: ActiveValue::Set(buffer.id),
epoch: ActiveValue::Set(buffer_max.0),
lamport_timestamp: ActiveValue::Set(buffer_max.1),
})
.on_conflict(
OnConflict::columns([
observed_note_edits::Column::UserId,
observed_note_edits::Column::ChannelId,
observed_buffer_edits::Column::UserId,
observed_buffer_edits::Column::BufferId,
])
.update_columns([
observed_note_edits::Column::Epoch,
observed_note_edits::Column::LamportTimestamp,
observed_buffer_edits::Column::Epoch,
observed_buffer_edits::Column::LamportTimestamp,
])
.to_owned(),
)
@ -463,7 +463,7 @@ impl Database {
channel_id: ChannelId,
user: UserId,
operations: &[proto::Operation],
) -> Result<Vec<ConnectionId>> {
) -> Result<(Vec<ConnectionId>, Vec<UserId>)> {
self.transaction(move |tx| async move {
self.check_user_is_channel_member(channel_id, user, &*tx)
.await?;
@ -483,10 +483,23 @@ impl Database {
.filter_map(|op| operation_to_storage(op, &buffer, serialization_version))
.collect::<Vec<_>>();
let mut channel_members;
if !operations.is_empty() {
// get current channel participants and save the max operation above
self.save_max_operation_for_collaborators(operations.as_slice(), channel_id, &*tx)
self.save_max_operation_for_collaborators(
operations.as_slice(),
channel_id,
buffer.id,
&*tx,
)
.await?;
channel_members = self.get_channel_members_internal(channel_id, &*tx).await?;
let collaborators = self
.get_channel_buffer_collaborators_internal(channel_id, &*tx)
.await?;
channel_members.retain(|member| !collaborators.contains(member));
buffer_operation::Entity::insert_many(operations)
.on_conflict(
@ -501,6 +514,8 @@ impl Database {
)
.exec(&*tx)
.await?;
} else {
channel_members = Vec::new();
}
let mut connections = Vec::new();
@ -519,7 +534,7 @@ impl Database {
});
}
Ok(connections)
Ok((connections, channel_members))
})
.await
}
@ -528,6 +543,7 @@ impl Database {
&self,
operations: &[buffer_operation::ActiveModel],
channel_id: ChannelId,
buffer_id: BufferId,
tx: &DatabaseTransaction,
) -> Result<()> {
let max_operation = operations
@ -553,22 +569,22 @@ impl Database {
.get_channel_buffer_collaborators_internal(channel_id, tx)
.await?;
observed_note_edits::Entity::insert_many(users.iter().map(|id| {
observed_note_edits::ActiveModel {
observed_buffer_edits::Entity::insert_many(users.iter().map(|id| {
observed_buffer_edits::ActiveModel {
user_id: ActiveValue::Set(*id),
channel_id: ActiveValue::Set(channel_id),
buffer_id: ActiveValue::Set(buffer_id),
epoch: max_operation.0.clone(),
lamport_timestamp: ActiveValue::Set(*max_operation.1.as_ref()),
}
}))
.on_conflict(
OnConflict::columns([
observed_note_edits::Column::UserId,
observed_note_edits::Column::ChannelId,
observed_buffer_edits::Column::UserId,
observed_buffer_edits::Column::BufferId,
])
.update_columns([
observed_note_edits::Column::Epoch,
observed_note_edits::Column::LamportTimestamp,
observed_buffer_edits::Column::Epoch,
observed_buffer_edits::Column::LamportTimestamp,
])
.to_owned(),
)
@ -699,54 +715,75 @@ impl Database {
Ok(())
}
pub async fn has_buffer_changed(&self, user_id: UserId, channel_id: ChannelId) -> Result<bool> {
self.transaction(|tx| async move {
let user_max = observed_note_edits::Entity::find()
.filter(observed_note_edits::Column::UserId.eq(user_id))
.filter(observed_note_edits::Column::ChannelId.eq(channel_id))
.one(&*tx)
.await?
.map(|model| (model.epoch, model.lamport_timestamp));
#[cfg(test)]
pub async fn test_has_note_changed(
&self,
user_id: UserId,
channel_id: ChannelId,
) -> Result<bool> {
self.transaction(|tx| async move { self.has_note_changed(user_id, channel_id, &*tx).await })
.await
}
let channel_buffer = channel::Model {
id: channel_id,
..Default::default()
}
.find_related(buffer::Entity)
pub async fn has_note_changed(
&self,
user_id: UserId,
channel_id: ChannelId,
tx: &DatabaseTransaction,
) -> Result<bool> {
let Some(buffer_id) = channel::Model {
id: channel_id,
..Default::default()
}
.find_related(buffer::Entity)
.one(&*tx)
.await?
.map(|buffer| buffer.id) else {
return Ok(false);
};
let user_max = observed_buffer_edits::Entity::find()
.filter(observed_buffer_edits::Column::UserId.eq(user_id))
.filter(observed_buffer_edits::Column::BufferId.eq(buffer_id))
.one(&*tx)
.await?;
.await?
.map(|model| (model.epoch, model.lamport_timestamp));
let Some(channel_buffer) = channel_buffer else {
return Ok(false);
};
let channel_buffer = channel::Model {
id: channel_id,
..Default::default()
}
.find_related(buffer::Entity)
.one(&*tx)
.await?;
let mut channel_max = buffer_operation::Entity::find()
let Some(channel_buffer) = channel_buffer else {
return Ok(false);
};
let mut channel_max = buffer_operation::Entity::find()
.filter(buffer_operation::Column::BufferId.eq(channel_buffer.id))
.filter(buffer_operation::Column::Epoch.eq(channel_buffer.epoch))
.order_by(buffer_operation::Column::Epoch, Desc)
.order_by(buffer_operation::Column::LamportTimestamp, Desc)
.one(&*tx)
.await?
.map(|model| (model.epoch, model.lamport_timestamp));
// If there are no edits in this epoch
if channel_max.is_none() {
// check if this user observed the last edit of the previous epoch
channel_max = buffer_operation::Entity::find()
.filter(buffer_operation::Column::BufferId.eq(channel_buffer.id))
.filter(buffer_operation::Column::Epoch.eq(channel_buffer.epoch))
.filter(buffer_operation::Column::Epoch.eq(channel_buffer.epoch.saturating_sub(1)))
.order_by(buffer_operation::Column::Epoch, Desc)
.order_by(buffer_operation::Column::LamportTimestamp, Desc)
.one(&*tx)
.await?
.map(|model| (model.epoch, model.lamport_timestamp));
}
// If there are no edits in this epoch
if channel_max.is_none() {
// check if this user observed the last edit of the previous epoch
channel_max = buffer_operation::Entity::find()
.filter(buffer_operation::Column::BufferId.eq(channel_buffer.id))
.filter(
buffer_operation::Column::Epoch.eq(channel_buffer.epoch.saturating_sub(1)),
)
.order_by(buffer_operation::Column::Epoch, Desc)
.order_by(buffer_operation::Column::LamportTimestamp, Desc)
.one(&*tx)
.await?
.map(|model| (model.epoch, model.lamport_timestamp));
}
Ok(user_max != channel_max)
})
.await
Ok(user_max != channel_max)
}
}

View file

@ -391,7 +391,8 @@ impl Database {
.all(&*tx)
.await?;
self.get_user_channels(channel_memberships, &tx).await
self.get_user_channels(user_id, channel_memberships, &tx)
.await
})
.await
}
@ -414,13 +415,15 @@ impl Database {
.all(&*tx)
.await?;
self.get_user_channels(channel_membership, &tx).await
self.get_user_channels(user_id, channel_membership, &tx)
.await
})
.await
}
pub async fn get_user_channels(
&self,
user_id: UserId,
channel_memberships: Vec<channel_member::Model>,
tx: &DatabaseTransaction,
) -> Result<ChannelsForUser> {
@ -460,10 +463,18 @@ impl Database {
}
}
let mut channels_with_changed_notes = HashSet::default();
for channel in graph.channels.iter() {
if self.has_note_changed(user_id, channel.id, tx).await? {
channels_with_changed_notes.insert(channel.id);
}
}
Ok(ChannelsForUser {
channels: graph,
channel_participants,
channels_with_admin_privileges,
channels_with_changed_notes,
})
}

View file

@ -12,7 +12,7 @@ pub mod contact;
pub mod feature_flag;
pub mod follower;
pub mod language_server;
pub mod observed_note_edits;
pub mod observed_buffer_edits;
pub mod project;
pub mod project_collaborator;
pub mod room;

View file

@ -1,12 +1,12 @@
use crate::db::{ChannelId, UserId};
use crate::db::{BufferId, UserId};
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "observed_channel_note_edits")]
#[sea_orm(table_name = "observed_buffer_edits")]
pub struct Model {
#[sea_orm(primary_key)]
pub user_id: UserId,
pub channel_id: ChannelId,
pub buffer_id: BufferId,
pub epoch: i32,
pub lamport_timestamp: i32,
}
@ -14,11 +14,11 @@ pub struct Model {
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::channel::Entity",
from = "Column::ChannelId",
to = "super::channel::Column::Id"
belongs_to = "super::buffer::Entity",
from = "Column::BufferId",
to = "super::buffer::Column::Id"
)]
Channel,
Buffer,
#[sea_orm(
belongs_to = "super::user::Entity",
from = "Column::UserId",
@ -27,9 +27,9 @@ pub enum Relation {
User,
}
impl Related<super::channel::Entity> for Entity {
impl Related<super::buffer::Entity> for Entity {
fn to() -> RelationDef {
Relation::Channel.def()
Relation::Buffer.def()
}
}

View file

@ -220,7 +220,7 @@ async fn test_channel_buffers_diffs(db: &Database) {
};
// Zero test: A should not register as changed on an unitialized channel buffer
assert!(!db.has_buffer_changed(a_id, zed_id).await.unwrap());
assert!(!db.test_has_note_changed(a_id, zed_id).await.unwrap());
let _ = db
.join_channel_buffer(zed_id, a_id, connection_id_a)
@ -228,7 +228,7 @@ async fn test_channel_buffers_diffs(db: &Database) {
.unwrap();
// Zero test: A should register as changed on an empty channel buffer
assert!(!db.has_buffer_changed(a_id, zed_id).await.unwrap());
assert!(!db.test_has_note_changed(a_id, zed_id).await.unwrap());
let mut buffer_a = Buffer::new(0, 0, "".to_string());
let mut operations = Vec::new();
@ -245,15 +245,16 @@ async fn test_channel_buffers_diffs(db: &Database) {
.unwrap();
// Smoke test: Does B register as changed, A as unchanged?
assert!(db.has_buffer_changed(b_id, zed_id).await.unwrap());
assert!(!db.has_buffer_changed(a_id, zed_id).await.unwrap());
assert!(db.test_has_note_changed(b_id, zed_id).await.unwrap());
assert!(!db.test_has_note_changed(a_id, zed_id).await.unwrap());
db.leave_channel_buffer(zed_id, connection_id_a)
.await
.unwrap();
// Snapshotting from leaving the channel buffer should not have a diff
assert!(!db.has_buffer_changed(a_id, zed_id).await.unwrap());
assert!(!db.test_has_note_changed(a_id, zed_id).await.unwrap());
let _ = db
.join_channel_buffer(zed_id, b_id, connection_id_b)
@ -261,13 +262,13 @@ async fn test_channel_buffers_diffs(db: &Database) {
.unwrap();
// B has opened the channel buffer, so we shouldn't have any diff
assert!(!db.has_buffer_changed(b_id, zed_id).await.unwrap());
assert!(!db.test_has_note_changed(b_id, zed_id).await.unwrap());
db.leave_channel_buffer(zed_id, connection_id_b)
.await
.unwrap();
// Since B just opened and closed the buffer without editing, neither should have a diff
assert!(!db.has_buffer_changed(a_id, zed_id).await.unwrap());
assert!(!db.has_buffer_changed(b_id, zed_id).await.unwrap());
assert!(!db.test_has_note_changed(a_id, zed_id).await.unwrap());
assert!(!db.test_has_note_changed(b_id, zed_id).await.unwrap());
}

View file

@ -2691,7 +2691,7 @@ async fn update_channel_buffer(
let db = session.db().await;
let channel_id = ChannelId::from_proto(request.channel_id);
let collaborators = db
let (collaborators, non_collaborators) = db
.update_channel_buffer(channel_id, session.user_id, &request.operations)
.await?;
@ -2704,6 +2704,25 @@ async fn update_channel_buffer(
},
&session.peer,
);
let pool = &*session.connection_pool().await;
broadcast(
None,
non_collaborators
.iter()
.flat_map(|user_id| pool.user_connection_ids(*user_id)),
|peer_id| {
session.peer.send(
peer_id.into(),
proto::UpdateChannels {
notes_changed: vec![channel_id.to_proto()],
..Default::default()
},
)
},
);
Ok(())
}
@ -2986,6 +3005,12 @@ fn build_initial_channels_update(
});
}
update.notes_changed = channels
.channels_with_changed_notes
.iter()
.map(|channel_id| channel_id.to_proto())
.collect();
update.insert_edge = channels.channels.edges;
for (channel_id, participants) in channels.channel_participants {

View file

@ -410,10 +410,7 @@ async fn test_channel_buffer_disconnect(
channel_buffer_a.update(cx_a, |buffer, _| {
assert_eq!(
buffer.channel().as_ref(),
&Channel {
id: channel_id,
name: "the-channel".to_string()
}
&channel(channel_id, "the-channel")
);
assert!(!buffer.is_connected());
});
@ -438,15 +435,20 @@ async fn test_channel_buffer_disconnect(
channel_buffer_b.update(cx_b, |buffer, _| {
assert_eq!(
buffer.channel().as_ref(),
&Channel {
id: channel_id,
name: "the-channel".to_string()
}
&channel(channel_id, "the-channel")
);
assert!(!buffer.is_connected());
});
}
fn channel(id: u64, name: &'static str) -> Channel {
Channel {
id,
name: name.to_string(),
has_changed: false,
}
}
#[gpui::test]
async fn test_rejoin_channel_buffer(
deterministic: Arc<Deterministic>,
@ -627,6 +629,7 @@ async fn test_following_to_channel_notes_without_a_shared_project(
let mut server = TestServer::start(&deterministic).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_c = server.create_client(cx_c, "user_c").await;
cx_a.update(editor::init);
@ -757,6 +760,50 @@ async fn test_following_to_channel_notes_without_a_shared_project(
});
}
#[gpui::test]
async fn test_channel_buffer_changes(
deterministic: Arc<Deterministic>,
cx_a: &mut TestAppContext,
cx_b: &mut TestAppContext,
) {
deterministic.forbid_parking();
let mut server = TestServer::start(&deterministic).await;
let client_a = server.create_client(cx_a, "user_a").await;
let client_b = server.create_client(cx_b, "user_b").await;
let channel_id = server
.make_channel(
"the-channel",
None,
(&client_a, cx_a),
&mut [(&client_b, cx_b)],
)
.await;
let channel_buffer_a = client_a
.channel_store()
.update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
.await
.unwrap();
channel_buffer_a.update(cx_a, |buffer, cx| {
buffer.buffer().update(cx, |buffer, cx| {
buffer.edit([(0..0, "1")], None, cx);
})
});
deterministic.run_until_parked();
let has_buffer_changed = cx_b.read(|cx| {
client_b
.channel_store()
.read(cx)
.has_channel_buffer_changed(channel_id)
.unwrap()
});
assert!(has_buffer_changed);
}
#[track_caller]
fn assert_collaborators(collaborators: &HashMap<PeerId, Collaborator>, ids: &[Option<UserId>]) {
let mut user_ids = collaborators