channel projects (#8456)
Add plumbing for hosted projects. This will currently show them if they exist but provides no UX to create/rename/delete them. Also changed the `ChannelId` type to not auto-cast to u64; this avoids type confusion if you have multiple id types. Release Notes: - N/A
This commit is contained in:
parent
8cf36ae603
commit
c31626717f
37 changed files with 446 additions and 144 deletions
|
@ -375,3 +375,13 @@ CREATE TABLE extension_versions (
|
|||
|
||||
CREATE UNIQUE INDEX "index_extensions_external_id" ON "extensions" ("external_id");
|
||||
CREATE INDEX "index_extensions_total_download_count" ON "extensions" ("total_download_count");
|
||||
|
||||
CREATE TABLE hosted_projects (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
channel_id INTEGER NOT NULL REFERENCES channels(id),
|
||||
name TEXT NOT NULL,
|
||||
visibility TEXT NOT NULL,
|
||||
deleted_at TIMESTAMP NULL
|
||||
);
|
||||
CREATE INDEX idx_hosted_projects_on_channel_id ON hosted_projects (channel_id);
|
||||
CREATE UNIQUE INDEX uix_hosted_projects_on_channel_id_and_name ON hosted_projects (channel_id, name) WHERE (deleted_at IS NULL);
|
||||
|
|
11
crates/collab/migrations/20240226163408_hosted_projects.sql
Normal file
11
crates/collab/migrations/20240226163408_hosted_projects.sql
Normal file
|
@ -0,0 +1,11 @@
|
|||
-- Add migration script here
|
||||
|
||||
CREATE TABLE hosted_projects (
|
||||
id INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
channel_id INT NOT NULL REFERENCES channels(id),
|
||||
name TEXT NOT NULL,
|
||||
visibility TEXT NOT NULL,
|
||||
deleted_at TIMESTAMP NULL
|
||||
);
|
||||
CREATE INDEX idx_hosted_projects_on_channel_id ON hosted_projects (channel_id);
|
||||
CREATE UNIQUE INDEX uix_hosted_projects_on_channel_id_and_name ON hosted_projects (channel_id, name) WHERE (deleted_at IS NULL);
|
|
@ -587,6 +587,7 @@ pub struct ChannelsForUser {
|
|||
pub channels: Vec<Channel>,
|
||||
pub channel_memberships: Vec<channel_member::Model>,
|
||||
pub channel_participants: HashMap<ChannelId, Vec<UserId>>,
|
||||
pub hosted_projects: Vec<proto::HostedProject>,
|
||||
|
||||
pub observed_buffer_versions: Vec<proto::ChannelBufferVersion>,
|
||||
pub observed_channel_messages: Vec<proto::ChannelMessageId>,
|
||||
|
|
|
@ -88,6 +88,7 @@ id_type!(FlagId);
|
|||
id_type!(ExtensionId);
|
||||
id_type!(NotificationId);
|
||||
id_type!(NotificationKindId);
|
||||
id_type!(HostedProjectId);
|
||||
|
||||
/// ChannelRole gives you permissions for both channels and calls.
|
||||
#[derive(Eq, PartialEq, Copy, Clone, Debug, EnumIter, DeriveActiveEnum, Default, Hash)]
|
||||
|
|
|
@ -6,6 +6,7 @@ pub mod channels;
|
|||
pub mod contacts;
|
||||
pub mod contributors;
|
||||
pub mod extensions;
|
||||
pub mod hosted_projects;
|
||||
pub mod messages;
|
||||
pub mod notifications;
|
||||
pub mod projects;
|
||||
|
|
|
@ -652,9 +652,14 @@ impl Database {
|
|||
.observed_channel_messages(&channel_ids, user_id, &*tx)
|
||||
.await?;
|
||||
|
||||
let hosted_projects = self
|
||||
.get_hosted_projects(&channel_ids, &roles_by_channel_id, &*tx)
|
||||
.await?;
|
||||
|
||||
Ok(ChannelsForUser {
|
||||
channel_memberships,
|
||||
channels,
|
||||
hosted_projects,
|
||||
channel_participants,
|
||||
latest_buffer_versions,
|
||||
latest_channel_messages,
|
||||
|
|
42
crates/collab/src/db/queries/hosted_projects.rs
Normal file
42
crates/collab/src/db/queries/hosted_projects.rs
Normal file
|
@ -0,0 +1,42 @@
|
|||
use rpc::proto;
|
||||
|
||||
use super::*;
|
||||
|
||||
impl Database {
|
||||
pub async fn get_hosted_projects(
|
||||
&self,
|
||||
channel_ids: &Vec<ChannelId>,
|
||||
roles: &HashMap<ChannelId, ChannelRole>,
|
||||
tx: &DatabaseTransaction,
|
||||
) -> Result<Vec<proto::HostedProject>> {
|
||||
Ok(hosted_project::Entity::find()
|
||||
.filter(hosted_project::Column::ChannelId.is_in(channel_ids.iter().map(|id| id.0)))
|
||||
.all(&*tx)
|
||||
.await?
|
||||
.into_iter()
|
||||
.flat_map(|project| {
|
||||
if project.deleted_at.is_some() {
|
||||
return None;
|
||||
}
|
||||
match project.visibility {
|
||||
ChannelVisibility::Public => {}
|
||||
ChannelVisibility::Members => {
|
||||
let is_visible = roles
|
||||
.get(&project.channel_id)
|
||||
.map(|role| role.can_see_all_descendants())
|
||||
.unwrap_or(false);
|
||||
if !is_visible {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
};
|
||||
Some(proto::HostedProject {
|
||||
id: project.id.to_proto(),
|
||||
channel_id: project.channel_id.to_proto(),
|
||||
name: project.name.clone(),
|
||||
visibility: project.visibility.into(),
|
||||
})
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
}
|
|
@ -14,6 +14,7 @@ pub mod extension;
|
|||
pub mod extension_version;
|
||||
pub mod feature_flag;
|
||||
pub mod follower;
|
||||
pub mod hosted_project;
|
||||
pub mod language_server;
|
||||
pub mod notification;
|
||||
pub mod notification_kind;
|
||||
|
|
18
crates/collab/src/db/tables/hosted_project.rs
Normal file
18
crates/collab/src/db/tables/hosted_project.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
use crate::db::{ChannelId, ChannelVisibility, HostedProjectId};
|
||||
use sea_orm::entity::prelude::*;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
|
||||
#[sea_orm(table_name = "hosted_projects")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key)]
|
||||
pub id: HostedProjectId,
|
||||
pub channel_id: ChannelId,
|
||||
pub name: String,
|
||||
pub visibility: ChannelVisibility,
|
||||
pub deleted_at: Option<DateTime>,
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {}
|
|
@ -3396,6 +3396,9 @@ fn build_channels_update(
|
|||
for channel in channel_invites {
|
||||
update.channel_invitations.push(channel.to_proto());
|
||||
}
|
||||
for project in channels.hosted_projects {
|
||||
update.hosted_projects.push(project);
|
||||
}
|
||||
|
||||
update
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use call::Room;
|
||||
use client::ChannelId;
|
||||
use gpui::{Model, TestAppContext};
|
||||
|
||||
mod channel_buffer_tests;
|
||||
|
@ -43,6 +44,6 @@ fn room_participants(room: &Model<Room>, cx: &mut TestAppContext) -> RoomPartici
|
|||
})
|
||||
}
|
||||
|
||||
fn channel_id(room: &Model<Room>, cx: &mut TestAppContext) -> Option<u64> {
|
||||
fn channel_id(room: &Model<Room>, cx: &mut TestAppContext) -> Option<ChannelId> {
|
||||
cx.read(|cx| room.read(cx).channel_id())
|
||||
}
|
||||
|
|
|
@ -183,7 +183,7 @@ async fn test_channel_requires_zed_cla(cx_a: &mut TestAppContext, cx_b: &mut Tes
|
|||
server
|
||||
.app_state
|
||||
.db
|
||||
.set_channel_requires_zed_cla(ChannelId::from_proto(parent_channel_id), true)
|
||||
.set_channel_requires_zed_cla(ChannelId::from_proto(parent_channel_id.0), true)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
|
|
@ -100,13 +100,13 @@ async fn test_basic_channel_messages(
|
|||
Notification::ChannelMessageMention {
|
||||
message_id,
|
||||
sender_id: client_a.id(),
|
||||
channel_id,
|
||||
channel_id: channel_id.0,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
store.notification_at(1).unwrap().notification,
|
||||
Notification::ChannelInvitation {
|
||||
channel_id,
|
||||
channel_id: channel_id.0,
|
||||
channel_name: "the-channel".to_string(),
|
||||
inviter_id: client_a.id()
|
||||
}
|
||||
|
|
|
@ -4,8 +4,8 @@ use crate::{
|
|||
tests::{room_participants, RoomParticipants, TestServer},
|
||||
};
|
||||
use call::ActiveCall;
|
||||
use channel::{ChannelId, ChannelMembership, ChannelStore};
|
||||
use client::User;
|
||||
use channel::{ChannelMembership, ChannelStore};
|
||||
use client::{ChannelId, User};
|
||||
use futures::future::try_join_all;
|
||||
use gpui::{BackgroundExecutor, Model, SharedString, TestAppContext};
|
||||
use rpc::{
|
||||
|
@ -281,7 +281,7 @@ async fn test_core_channels(
|
|||
.app_state
|
||||
.db
|
||||
.rename_channel(
|
||||
db::ChannelId::from_proto(channel_a_id),
|
||||
db::ChannelId::from_proto(channel_a_id.0),
|
||||
UserId::from_proto(client_a.id()),
|
||||
"channel-a-renamed",
|
||||
)
|
||||
|
@ -1444,7 +1444,7 @@ fn assert_channels(
|
|||
fn assert_channels_list_shape(
|
||||
channel_store: &Model<ChannelStore>,
|
||||
cx: &TestAppContext,
|
||||
expected_channels: &[(u64, usize)],
|
||||
expected_channels: &[(ChannelId, usize)],
|
||||
) {
|
||||
let actual = cx.read(|cx| {
|
||||
channel_store.read_with(cx, |store, _| {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer};
|
||||
use call::{ActiveCall, ParticipantLocation};
|
||||
use client::ChannelId;
|
||||
use collab_ui::{
|
||||
channel_view::ChannelView,
|
||||
notifications::project_shared_notification::ProjectSharedNotification,
|
||||
|
@ -2000,7 +2001,7 @@ async fn test_following_to_channel_notes_without_a_shared_project(
|
|||
}
|
||||
|
||||
async fn join_channel(
|
||||
channel_id: u64,
|
||||
channel_id: ChannelId,
|
||||
client: &TestClient,
|
||||
cx: &mut TestAppContext,
|
||||
) -> anyhow::Result<()> {
|
||||
|
|
|
@ -137,7 +137,7 @@ async fn test_notifications(
|
|||
assert_eq!(
|
||||
entry.notification,
|
||||
Notification::ChannelInvitation {
|
||||
channel_id,
|
||||
channel_id: channel_id.0,
|
||||
channel_name: "the-channel".to_string(),
|
||||
inviter_id: client_a.id()
|
||||
}
|
||||
|
|
|
@ -253,7 +253,7 @@ impl RandomizedTest for RandomChannelBufferTest {
|
|||
.channel_buffers()
|
||||
.deref()
|
||||
.iter()
|
||||
.find(|b| b.read(cx).channel_id == channel_id.to_proto())
|
||||
.find(|b| b.read(cx).channel_id.0 == channel_id.to_proto())
|
||||
{
|
||||
let channel_buffer = channel_buffer.read(cx);
|
||||
|
||||
|
|
|
@ -8,7 +8,8 @@ use anyhow::anyhow;
|
|||
use call::ActiveCall;
|
||||
use channel::{ChannelBuffer, ChannelStore};
|
||||
use client::{
|
||||
self, proto::PeerId, Client, Connection, Credentials, EstablishConnectionError, UserStore,
|
||||
self, proto::PeerId, ChannelId, Client, Connection, Credentials, EstablishConnectionError,
|
||||
UserStore,
|
||||
};
|
||||
use clock::FakeSystemClock;
|
||||
use collab_ui::channel_view::ChannelView;
|
||||
|
@ -120,7 +121,7 @@ impl TestServer {
|
|||
pub async fn start2(
|
||||
cx_a: &mut TestAppContext,
|
||||
cx_b: &mut TestAppContext,
|
||||
) -> (TestServer, TestClient, TestClient, u64) {
|
||||
) -> (TestServer, TestClient, TestClient, ChannelId) {
|
||||
let mut server = Self::start(cx_a.executor()).await;
|
||||
let client_a = server.create_client(cx_a, "user_a").await;
|
||||
let client_b = server.create_client(cx_b, "user_b").await;
|
||||
|
@ -353,10 +354,10 @@ impl TestServer {
|
|||
pub async fn make_channel(
|
||||
&self,
|
||||
channel: &str,
|
||||
parent: Option<u64>,
|
||||
parent: Option<ChannelId>,
|
||||
admin: (&TestClient, &mut TestAppContext),
|
||||
members: &mut [(&TestClient, &mut TestAppContext)],
|
||||
) -> u64 {
|
||||
) -> ChannelId {
|
||||
let (_, admin_cx) = admin;
|
||||
let channel_id = admin_cx
|
||||
.read(ChannelStore::global)
|
||||
|
@ -399,7 +400,7 @@ impl TestServer {
|
|||
channel: &str,
|
||||
client: &TestClient,
|
||||
cx: &mut TestAppContext,
|
||||
) -> u64 {
|
||||
) -> ChannelId {
|
||||
let channel_id = self
|
||||
.make_channel(channel, None, (client, cx), &mut [])
|
||||
.await;
|
||||
|
@ -423,7 +424,7 @@ impl TestServer {
|
|||
&self,
|
||||
channels: &[(&str, Option<&str>)],
|
||||
creator: (&TestClient, &mut TestAppContext),
|
||||
) -> Vec<u64> {
|
||||
) -> Vec<ChannelId> {
|
||||
let mut observed_channels = HashMap::default();
|
||||
let mut result = Vec::new();
|
||||
for (channel, parent) in channels {
|
||||
|
@ -677,7 +678,7 @@ impl TestClient {
|
|||
pub async fn host_workspace(
|
||||
&self,
|
||||
workspace: &View<Workspace>,
|
||||
channel_id: u64,
|
||||
channel_id: ChannelId,
|
||||
cx: &mut VisualTestContext,
|
||||
) {
|
||||
cx.update(|cx| {
|
||||
|
@ -698,7 +699,7 @@ impl TestClient {
|
|||
|
||||
pub async fn join_workspace<'a>(
|
||||
&'a self,
|
||||
channel_id: u64,
|
||||
channel_id: ChannelId,
|
||||
cx: &'a mut TestAppContext,
|
||||
) -> (View<Workspace>, &'a mut VisualTestContext) {
|
||||
cx.update(|cx| workspace::join_channel(channel_id, self.app_state.clone(), None, cx))
|
||||
|
@ -777,7 +778,7 @@ impl TestClient {
|
|||
}
|
||||
|
||||
pub fn open_channel_notes(
|
||||
channel_id: u64,
|
||||
channel_id: ChannelId,
|
||||
cx: &mut VisualTestContext,
|
||||
) -> Task<anyhow::Result<View<ChannelView>>> {
|
||||
let window = cx.update(|cx| cx.active_window().unwrap().downcast::<Workspace>().unwrap());
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue