diff --git a/crates/channel/Cargo.toml b/crates/channel/Cargo.toml index c2191fdfa3..00e9135bc1 100644 --- a/crates/channel/Cargo.toml +++ b/crates/channel/Cargo.toml @@ -47,5 +47,6 @@ tempfile = "3" collections = { path = "../collections", features = ["test-support"] } gpui = { path = "../gpui", features = ["test-support"] } rpc = { path = "../rpc", features = ["test-support"] } +client = { path = "../client", features = ["test-support"] } settings = { path = "../settings", features = ["test-support"] } util = { path = "../util", features = ["test-support"] } diff --git a/crates/channel/src/channel_store.rs b/crates/channel/src/channel_store.rs index 6096692ccb..48228041ec 100644 --- a/crates/channel/src/channel_store.rs +++ b/crates/channel/src/channel_store.rs @@ -111,6 +111,10 @@ impl ChannelStore { } } + pub fn client(&self) -> Arc { + self.client.clone() + } + pub fn has_children(&self, channel_id: ChannelId) -> bool { self.channel_paths.iter().any(|path| { if let Some(ix) = path.iter().position(|id| *id == channel_id) { diff --git a/crates/channel/src/channel_store_tests.rs b/crates/channel/src/channel_store_tests.rs index 3ae899ecde..22174f161b 100644 --- a/crates/channel/src/channel_store_tests.rs +++ b/crates/channel/src/channel_store_tests.rs @@ -4,15 +4,12 @@ use super::*; use client::{test::FakeServer, Client, UserStore}; use gpui::{AppContext, ModelHandle, TestAppContext}; use rpc::proto; +use settings::SettingsStore; use util::http::FakeHttpClient; #[gpui::test] fn test_update_channels(cx: &mut AppContext) { - let http = FakeHttpClient::with_404_response(); - let client = Client::new(http.clone(), cx); - let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http, cx)); - - let channel_store = cx.add_model(|cx| ChannelStore::new(client, user_store, cx)); + let channel_store = init_test(cx); update_channels( &channel_store, @@ -80,11 +77,7 @@ fn test_update_channels(cx: &mut AppContext) { #[gpui::test] fn test_dangling_channel_paths(cx: &mut AppContext) { - let http = FakeHttpClient::with_404_response(); - let client = Client::new(http.clone(), cx); - let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http, cx)); - - let channel_store = cx.add_model(|cx| ChannelStore::new(client, user_store, cx)); + let channel_store = init_test(cx); update_channels( &channel_store, @@ -141,18 +134,11 @@ fn test_dangling_channel_paths(cx: &mut AppContext) { #[gpui::test] async fn test_channel_messages(cx: &mut TestAppContext) { - cx.foreground().forbid_parking(); - let user_id = 5; - let http_client = FakeHttpClient::with_404_response(); - let client = cx.update(|cx| Client::new(http_client.clone(), cx)); - let server = FakeServer::for_client(user_id, &client, cx).await; - let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); - crate::init(&client); - - let channel_store = cx.add_model(|cx| ChannelStore::new(client, user_store, cx)); - let channel_id = 5; + let channel_store = cx.update(init_test); + let client = channel_store.read_with(cx, |s, _| s.client()); + let server = FakeServer::for_client(user_id, &client, cx).await; // Get the available channels. server.send(proto::UpdateChannels { @@ -163,85 +149,71 @@ async fn test_channel_messages(cx: &mut TestAppContext) { }], ..Default::default() }); - channel_store.next_notification(cx).await; + cx.foreground().run_until_parked(); cx.read(|cx| { assert_channels(&channel_store, &[(0, "the-channel".to_string(), false)], cx); }); let get_users = server.receive::().await.unwrap(); assert_eq!(get_users.payload.user_ids, vec![5]); - server - .respond( - get_users.receipt(), - proto::UsersResponse { - users: vec![proto::User { - id: 5, - github_login: "nathansobo".into(), - avatar_url: "http://avatar.com/nathansobo".into(), - }], - }, - ) - .await; + server.respond( + get_users.receipt(), + proto::UsersResponse { + users: vec![proto::User { + id: 5, + github_login: "nathansobo".into(), + avatar_url: "http://avatar.com/nathansobo".into(), + }], + }, + ); // Join a channel and populate its existing messages. - let channel = channel_store - .update(cx, |store, cx| { - let channel_id = store.channels().next().unwrap().1.id; - store.open_channel_chat(channel_id, cx) - }) - .await - .unwrap(); - channel.read_with(cx, |channel, _| assert!(channel.messages().is_empty())); + let channel = channel_store.update(cx, |store, cx| { + let channel_id = store.channels().next().unwrap().1.id; + store.open_channel_chat(channel_id, cx) + }); let join_channel = server.receive::().await.unwrap(); - server - .respond( - join_channel.receipt(), - proto::JoinChannelChatResponse { - messages: vec![ - proto::ChannelMessage { - id: 10, - body: "a".into(), - timestamp: 1000, - sender_id: 5, - nonce: Some(1.into()), - }, - proto::ChannelMessage { - id: 11, - body: "b".into(), - timestamp: 1001, - sender_id: 6, - nonce: Some(2.into()), - }, - ], - done: false, - }, - ) - .await; + server.respond( + join_channel.receipt(), + proto::JoinChannelChatResponse { + messages: vec![ + proto::ChannelMessage { + id: 10, + body: "a".into(), + timestamp: 1000, + sender_id: 5, + nonce: Some(1.into()), + }, + proto::ChannelMessage { + id: 11, + body: "b".into(), + timestamp: 1001, + sender_id: 6, + nonce: Some(2.into()), + }, + ], + done: false, + }, + ); + + cx.foreground().start_waiting(); // Client requests all users for the received messages let mut get_users = server.receive::().await.unwrap(); get_users.payload.user_ids.sort(); assert_eq!(get_users.payload.user_ids, vec![6]); - server - .respond( - get_users.receipt(), - proto::UsersResponse { - users: vec![proto::User { - id: 6, - github_login: "maxbrunsfeld".into(), - avatar_url: "http://avatar.com/maxbrunsfeld".into(), - }], - }, - ) - .await; - - assert_eq!( - channel.next_event(cx).await, - ChannelChatEvent::MessagesUpdated { - old_range: 0..0, - new_count: 2, - } + server.respond( + get_users.receipt(), + proto::UsersResponse { + users: vec![proto::User { + id: 6, + github_login: "maxbrunsfeld".into(), + avatar_url: "http://avatar.com/maxbrunsfeld".into(), + }], + }, ); + + let channel = channel.await.unwrap(); channel.read_with(cx, |channel, _| { assert_eq!( channel @@ -270,18 +242,16 @@ async fn test_channel_messages(cx: &mut TestAppContext) { // Client requests user for message since they haven't seen them yet let get_users = server.receive::().await.unwrap(); assert_eq!(get_users.payload.user_ids, vec![7]); - server - .respond( - get_users.receipt(), - proto::UsersResponse { - users: vec![proto::User { - id: 7, - github_login: "as-cii".into(), - avatar_url: "http://avatar.com/as-cii".into(), - }], - }, - ) - .await; + server.respond( + get_users.receipt(), + proto::UsersResponse { + users: vec![proto::User { + id: 7, + github_login: "as-cii".into(), + avatar_url: "http://avatar.com/as-cii".into(), + }], + }, + ); assert_eq!( channel.next_event(cx).await, @@ -307,30 +277,28 @@ async fn test_channel_messages(cx: &mut TestAppContext) { let get_messages = server.receive::().await.unwrap(); assert_eq!(get_messages.payload.channel_id, 5); assert_eq!(get_messages.payload.before_message_id, 10); - server - .respond( - get_messages.receipt(), - proto::GetChannelMessagesResponse { - done: true, - messages: vec![ - proto::ChannelMessage { - id: 8, - body: "y".into(), - timestamp: 998, - sender_id: 5, - nonce: Some(4.into()), - }, - proto::ChannelMessage { - id: 9, - body: "z".into(), - timestamp: 999, - sender_id: 6, - nonce: Some(5.into()), - }, - ], - }, - ) - .await; + server.respond( + get_messages.receipt(), + proto::GetChannelMessagesResponse { + done: true, + messages: vec![ + proto::ChannelMessage { + id: 8, + body: "y".into(), + timestamp: 998, + sender_id: 5, + nonce: Some(4.into()), + }, + proto::ChannelMessage { + id: 9, + body: "z".into(), + timestamp: 999, + sender_id: 6, + nonce: Some(5.into()), + }, + ], + }, + ); assert_eq!( channel.next_event(cx).await, @@ -353,6 +321,19 @@ async fn test_channel_messages(cx: &mut TestAppContext) { }); } +fn init_test(cx: &mut AppContext) -> ModelHandle { + let http = FakeHttpClient::with_404_response(); + let client = Client::new(http.clone(), cx); + let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http, cx)); + + cx.foreground().forbid_parking(); + cx.set_global(SettingsStore::test(cx)); + crate::init(&client); + client::init(&client, cx); + + cx.add_model(|cx| ChannelStore::new(client, user_store, cx)) +} + fn update_channels( channel_store: &ModelHandle, message: proto::UpdateChannels, diff --git a/crates/client/src/test.rs b/crates/client/src/test.rs index 00e7cd1508..38cd12f21c 100644 --- a/crates/client/src/test.rs +++ b/crates/client/src/test.rs @@ -170,8 +170,7 @@ impl FakeServer { staff: false, flags: Default::default(), }, - ) - .await; + ); continue; } @@ -182,11 +181,7 @@ impl FakeServer { } } - pub async fn respond( - &self, - receipt: Receipt, - response: T::Response, - ) { + pub fn respond(&self, receipt: Receipt, response: T::Response) { self.peer.respond(receipt, response).unwrap() } diff --git a/crates/collab/src/db/queries/messages.rs b/crates/collab/src/db/queries/messages.rs index e8b9ec7416..4593d8f2f3 100644 --- a/crates/collab/src/db/queries/messages.rs +++ b/crates/collab/src/db/queries/messages.rs @@ -82,7 +82,7 @@ impl Database { id: row.id.to_proto(), sender_id: row.sender_id.to_proto(), body: row.body, - timestamp: row.sent_at.unix_timestamp() as u64, + timestamp: row.sent_at.assume_utc().unix_timestamp() as u64, nonce: Some(proto::Nonce { upper_half: nonce.0, lower_half: nonce.1, @@ -124,6 +124,9 @@ impl Database { Err(anyhow!("not a chat participant"))?; } + let timestamp = timestamp.to_offset(time::UtcOffset::UTC); + let timestamp = time::PrimitiveDateTime::new(timestamp.date(), timestamp.time()); + let message = channel_message::Entity::insert(channel_message::ActiveModel { channel_id: ActiveValue::Set(channel_id), sender_id: ActiveValue::Set(user_id), diff --git a/crates/collab/src/db/tables/channel_message.rs b/crates/collab/src/db/tables/channel_message.rs index d043d5b668..ff49c63ba7 100644 --- a/crates/collab/src/db/tables/channel_message.rs +++ b/crates/collab/src/db/tables/channel_message.rs @@ -1,6 +1,6 @@ use crate::db::{ChannelId, MessageId, UserId}; use sea_orm::entity::prelude::*; -use time::OffsetDateTime; +use time::PrimitiveDateTime; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "channel_messages")] @@ -10,7 +10,7 @@ pub struct Model { pub channel_id: ChannelId, pub sender_id: UserId, pub body: String, - pub sent_at: OffsetDateTime, + pub sent_at: PrimitiveDateTime, pub nonce: Uuid, } diff --git a/crates/collab/src/db/tests/message_tests.rs b/crates/collab/src/db/tests/message_tests.rs index 1dd686a1d2..c40e53d355 100644 --- a/crates/collab/src/db/tests/message_tests.rs +++ b/crates/collab/src/db/tests/message_tests.rs @@ -30,6 +30,12 @@ async fn test_channel_message_nonces(db: &Arc) { .await .unwrap(); + let owner_id = db.create_server("test").await.unwrap().0 as u32; + + db.join_channel_chat(channel, rpc::ConnectionId { owner_id, id: 0 }, user) + .await + .unwrap(); + let msg1_id = db .create_channel_message(channel, user, "1", OffsetDateTime::now_utc(), 1) .await diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index 0105a9d27b..1cbd8574eb 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -1,21 +1,33 @@ +use crate::collab_panel::{CollaborationPanelDockPosition, CollaborationPanelSettings}; +use anyhow::Result; use channel::{ChannelChat, ChannelChatEvent, ChannelMessage, ChannelStore}; use client::Client; +use db::kvp::KEY_VALUE_STORE; use editor::Editor; use gpui::{ actions, elements::*, platform::{CursorStyle, MouseButton}, + serde_json, views::{ItemType, Select, SelectStyle}, - AnyViewHandle, AppContext, Entity, ModelHandle, Subscription, View, ViewContext, ViewHandle, + AnyViewHandle, AppContext, AsyncAppContext, Entity, ModelHandle, Subscription, Task, View, + ViewContext, ViewHandle, WeakViewHandle, }; use language::language_settings::SoftWrap; use menu::Confirm; +use project::Fs; +use serde::{Deserialize, Serialize}; use std::sync::Arc; use theme::Theme; use time::{OffsetDateTime, UtcOffset}; use util::{ResultExt, TryFutureExt}; +use workspace::{ + dock::{DockPosition, Panel}, + Workspace, +}; const MESSAGE_LOADING_THRESHOLD: usize = 50; +const CHAT_PANEL_KEY: &'static str = "ChatPanel"; pub struct ChatPanel { client: Arc, @@ -25,11 +37,25 @@ pub struct ChatPanel { input_editor: ViewHandle, channel_select: ViewHandle