Fix get_most_public_ancestor

This commit is contained in:
Conrad Irwin 2023-10-17 09:41:34 -06:00
parent 465d726bd4
commit 851701cb6f
6 changed files with 126 additions and 50 deletions

View File

@ -82,6 +82,31 @@ pub struct ChannelMembership {
pub kind: proto::channel_member::Kind,
pub role: proto::ChannelRole,
}
impl ChannelMembership {
pub fn sort_key(&self) -> MembershipSortKey {
MembershipSortKey {
role_order: match self.role {
proto::ChannelRole::Admin => 0,
proto::ChannelRole::Member => 1,
proto::ChannelRole::Banned => 2,
proto::ChannelRole::Guest => 3,
},
kind_order: match self.kind {
proto::channel_member::Kind::Member => 0,
proto::channel_member::Kind::AncestorMember => 1,
proto::channel_member::Kind::Invitee => 2,
},
username_order: self.user.github_login.as_str(),
}
}
}
#[derive(PartialOrd, Ord, PartialEq, Eq)]
pub struct MembershipSortKey<'a> {
role_order: u8,
kind_order: u8,
username_order: &'a str,
}
pub enum ChannelEvent {
ChannelCreated(ChannelId),

View File

@ -108,11 +108,10 @@ impl ChannelRole {
}
pub fn max(&self, other: Self) -> Self {
match (self, other) {
(ChannelRole::Admin, _) | (_, ChannelRole::Admin) => ChannelRole::Admin,
(ChannelRole::Member, _) | (_, ChannelRole::Member) => ChannelRole::Member,
(ChannelRole::Banned, _) | (_, ChannelRole::Banned) => ChannelRole::Banned,
(ChannelRole::Guest, _) | (_, ChannelRole::Guest) => ChannelRole::Guest,
if self.should_override(other) {
*self
} else {
other
}
}
}

View File

@ -1,5 +1,3 @@
use std::cmp::Ordering;
use super::*;
use rpc::proto::{channel_member::Kind, ChannelEdge};
@ -544,6 +542,12 @@ impl Database {
if !channels_to_remove.is_empty() {
// Note: this code assumes each channel has one parent.
// If there are multiple valid public paths to a channel,
// e.g.
// If both of these paths are present (* indicating public):
// - zed* -> projects -> vim*
// - zed* -> conrad -> public-projects* -> vim*
// Users would only see one of them (based on edge sort order)
let mut replacement_parent: HashMap<u64, u64> = HashMap::default();
for ChannelEdge {
parent_id,
@ -707,14 +711,14 @@ impl Database {
}
let mut user_details: HashMap<UserId, UserDetail> = HashMap::default();
while let Some(row) = stream.next().await {
while let Some(user_membership) = stream.next().await {
let (user_id, channel_role, is_direct_member, is_invite_accepted, visibility): (
UserId,
ChannelRole,
bool,
bool,
ChannelVisibility,
) = row?;
) = user_membership?;
let kind = match (is_direct_member, is_invite_accepted) {
(true, true) => proto::channel_member::Kind::Member,
(true, false) => proto::channel_member::Kind::Invitee,
@ -745,33 +749,7 @@ impl Database {
}
}
// sort by permissions descending, within each section, show members, then ancestor members, then invitees.
let mut results: Vec<(UserId, UserDetail)> = user_details.into_iter().collect();
results.sort_by(|a, b| {
if a.1.channel_role.should_override(b.1.channel_role) {
return Ordering::Less;
} else if b.1.channel_role.should_override(a.1.channel_role) {
return Ordering::Greater;
}
if a.1.kind == Kind::Member && b.1.kind != Kind::Member {
return Ordering::Less;
} else if b.1.kind == Kind::Member && a.1.kind != Kind::Member {
return Ordering::Greater;
}
if a.1.kind == Kind::AncestorMember && b.1.kind != Kind::AncestorMember {
return Ordering::Less;
} else if b.1.kind == Kind::AncestorMember && a.1.kind != Kind::AncestorMember {
return Ordering::Greater;
}
// would be nice to sort alphabetically instead of by user id.
// (or defer all sorting to the UI, but we need something to help the tests)
return a.0.cmp(&b.0);
});
Ok(results
Ok(user_details
.into_iter()
.map(|(user_id, details)| proto::ChannelMember {
user_id: user_id.to_proto(),
@ -810,7 +788,7 @@ impl Database {
user_id: UserId,
tx: &DatabaseTransaction,
) -> Result<()> {
match self.channel_role_for_user(channel_id, user_id, tx).await? {
match dbg!(self.channel_role_for_user(channel_id, user_id, tx).await)? {
Some(ChannelRole::Admin) => Ok(()),
Some(ChannelRole::Member)
| Some(ChannelRole::Banned)
@ -874,10 +852,26 @@ impl Database {
channel_id: ChannelId,
tx: &DatabaseTransaction,
) -> Result<Option<ChannelId>> {
let channel_ids = self.get_channel_ancestors(channel_id, tx).await?;
// Note: if there are many paths to a channel, this will return just one
let arbitary_path = channel_path::Entity::find()
.filter(channel_path::Column::ChannelId.eq(channel_id))
.order_by(channel_path::Column::IdPath, sea_orm::Order::Desc)
.one(tx)
.await?;
let Some(path) = arbitary_path else {
return Ok(None);
};
let ancestor_ids: Vec<ChannelId> = path
.id_path
.trim_matches('/')
.split('/')
.map(|id| ChannelId::from_proto(id.parse().unwrap()))
.collect();
let rows = channel::Entity::find()
.filter(channel::Column::Id.is_in(channel_ids.iter().copied()))
.filter(channel::Column::Id.is_in(ancestor_ids.iter().copied()))
.filter(channel::Column::Visibility.eq(ChannelVisibility::Public))
.all(&*tx)
.await?;
@ -888,7 +882,7 @@ impl Database {
visible_channels.insert(row.id);
}
for ancestor in channel_ids.into_iter().rev() {
for ancestor in ancestor_ids {
if visible_channels.contains(&ancestor) {
return Ok(Some(ancestor));
}
@ -929,11 +923,6 @@ impl Database {
.await?;
let mut user_role: Option<ChannelRole> = None;
let max_role = |role| {
user_role
.map(|user_role| user_role.max(role))
.get_or_insert(role);
};
let mut is_participant = false;
let mut current_channel_visibility = None;
@ -946,8 +935,15 @@ impl Database {
ChannelRole,
ChannelVisibility,
) = row?;
match role {
ChannelRole::Admin | ChannelRole::Member | ChannelRole::Banned => max_role(role),
ChannelRole::Admin | ChannelRole::Member | ChannelRole::Banned => {
if let Some(users_role) = user_role {
user_role = Some(users_role.max(role));
} else {
user_role = Some(role)
}
}
ChannelRole::Guest if visibility == ChannelVisibility::Public => {
is_participant = true
}

View File

@ -1028,6 +1028,54 @@ async fn test_user_is_channel_participant(db: &Arc<Database>) {
)
}
test_both_dbs!(
test_user_joins_correct_channel,
test_user_joins_correct_channel_postgres,
test_user_joins_correct_channel_sqlite
);
async fn test_user_joins_correct_channel(db: &Arc<Database>) {
let admin = new_test_user(db, "admin@example.com").await;
let zed_channel = db.create_root_channel("zed", admin).await.unwrap();
let active_channel = db
.create_channel("active", Some(zed_channel), admin)
.await
.unwrap();
let vim_channel = db
.create_channel("vim", Some(active_channel), admin)
.await
.unwrap();
let vim2_channel = db
.create_channel("vim2", Some(vim_channel), admin)
.await
.unwrap();
db.set_channel_visibility(zed_channel, crate::db::ChannelVisibility::Public, admin)
.await
.unwrap();
db.set_channel_visibility(vim_channel, crate::db::ChannelVisibility::Public, admin)
.await
.unwrap();
db.set_channel_visibility(vim2_channel, crate::db::ChannelVisibility::Public, admin)
.await
.unwrap();
let most_public = db
.transaction(
|tx| async move { db.most_public_ancestor_for_channel(vim_channel, &*tx).await },
)
.await
.unwrap();
assert_eq!(most_public, Some(zed_channel))
}
#[track_caller]
fn assert_dag(actual: ChannelGraph, expected: &[(ChannelId, Option<ChannelId>)]) {
let mut actual_map: HashMap<ChannelId, HashSet<ChannelId>> = HashMap::default();

View File

@ -100,11 +100,14 @@ impl ChannelModal {
let channel_id = self.channel_id;
cx.spawn(|this, mut cx| async move {
if mode == Mode::ManageMembers {
let members = channel_store
let mut members = channel_store
.update(&mut cx, |channel_store, cx| {
channel_store.get_channel_member_details(channel_id, cx)
})
.await?;
members.sort_by(|a, b| a.sort_key().cmp(&b.sort_key()));
this.update(&mut cx, |this, cx| {
this.picker
.update(cx, |picker, _| picker.delegate_mut().members = members);
@ -675,11 +678,16 @@ impl ChannelModalDelegate {
invite_member.await?;
this.update(&mut cx, |this, cx| {
this.delegate_mut().members.push(ChannelMembership {
let new_member = ChannelMembership {
user,
kind: proto::channel_member::Kind::Invitee,
role: ChannelRole::Member,
});
};
let members = &mut this.delegate_mut().members;
match members.binary_search_by_key(&new_member.sort_key(), |k| k.sort_key()) {
Ok(ix) | Err(ix) => members.insert(ix, new_member),
}
cx.notify();
})
})

View File

@ -7,7 +7,7 @@ use gpui::{
use picker::{Picker, PickerDelegate, PickerEvent};
use std::cmp::{self, Reverse};
use util::{
channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL, RELEASE_CHANNEL_NAME},
channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL},
ResultExt,
};
use workspace::Workspace;