mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-24 17:28:40 +00:00
Fix get_most_public_ancestor
This commit is contained in:
parent
465d726bd4
commit
851701cb6f
6 changed files with 126 additions and 50 deletions
|
@ -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),
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue