diff --git a/.gitignore b/.gitignore index 1ec8d6269c2cb5d7de73211296cec3027da87d29..d3d0634a401a3fe5f8131ca5e1797289f1c01939 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,3 @@ /crates/collab/static/styles.css /vendor/bin /assets/themes/*.json -dump.rdb diff --git a/Procfile b/Procfile index 780479b95881761f774d743064f2238669b1ee56..a64b411ef3224a8b6bbf4596bff109b125462bf4 100644 --- a/Procfile +++ b/Procfile @@ -1,3 +1,2 @@ web: cd ../zed.dev && PORT=3000 npx next dev collab: cd crates/collab && cargo run -redis: redis-server diff --git a/README.md b/README.md index 5da1623b040a2f8d1a113519b8c35d8fbcdf08ba..31cfdcbd7fb5002926a47e704c76ddd464e5a7e9 100644 --- a/README.md +++ b/README.md @@ -23,12 +23,6 @@ script/sqlx migrate run script/seed-db ``` -Install Redis: - -``` -brew install redis -``` - Run the web frontend and the collaboration server. ``` diff --git a/crates/collab/src/api.rs b/crates/collab/src/api.rs index 5cc8b58cfb8305c8c5a6e4ffa6d43b60d8788186..f3209cab7312cf8c8c516e6bf6c926efd6306f5b 100644 --- a/crates/collab/src/api.rs +++ b/crates/collab/src/api.rs @@ -304,6 +304,8 @@ struct ActiveUserCountParams { #[serde(flatten)] period: TimePeriodParams, durations_in_minutes: String, + #[serde(default)] + only_collaborative: bool, } #[derive(Serialize)] @@ -329,6 +331,7 @@ async fn get_active_user_counts( .get_active_user_count( params.period.start..params.period.end, Duration::from_secs(duration * 60), + params.only_collaborative, ) .await?, }) diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index 967bd2ee996239ddf8d6692210ba08287d55e7b3..f45a54472800ddb690b4e27a72de0fc71ad3a1d0 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -75,6 +75,7 @@ pub trait Db: Send + Sync { &self, time_period: Range, min_duration: Duration, + only_collaborative: bool, ) -> Result; /// Get the users that have been most active during the given time period, @@ -605,15 +606,48 @@ impl Db for PostgresDb { &self, time_period: Range, min_duration: Duration, + only_collaborative: bool, ) -> Result { - let query = " - WITH - project_durations AS ( - SELECT user_id, project_id, SUM(duration_millis) AS project_duration - FROM project_activity_periods - WHERE $1 < ended_at AND ended_at <= $2 - GROUP BY user_id, project_id - ), + let mut with_clause = String::new(); + with_clause.push_str("WITH\n"); + with_clause.push_str( + " + project_durations AS ( + SELECT user_id, project_id, SUM(duration_millis) AS project_duration + FROM project_activity_periods + WHERE $1 < ended_at AND ended_at <= $2 + GROUP BY user_id, project_id + ), + ", + ); + with_clause.push_str( + " + project_collaborators as ( + SELECT project_id, COUNT(DISTINCT user_id) as max_collaborators + FROM project_durations + GROUP BY project_id + ), + ", + ); + + if only_collaborative { + with_clause.push_str( + " + user_durations AS ( + SELECT user_id, SUM(project_duration) as total_duration + FROM project_durations, project_collaborators + WHERE + project_durations.project_id = project_collaborators.project_id AND + max_collaborators > 1 + GROUP BY user_id + ORDER BY total_duration DESC + LIMIT $3 + ) + ", + ); + } else { + with_clause.push_str( + " user_durations AS ( SELECT user_id, SUM(project_duration) as total_duration FROM project_durations @@ -621,12 +655,20 @@ impl Db for PostgresDb { ORDER BY total_duration DESC LIMIT $3 ) + ", + ); + } + + let query = format!( + " + {with_clause} SELECT count(user_durations.user_id) FROM user_durations WHERE user_durations.total_duration >= $3 - "; + " + ); - let count: i64 = sqlx::query_scalar(query) + let count: i64 = sqlx::query_scalar(&query) .bind(time_period.start) .bind(time_period.end) .bind(min_duration.as_millis() as i64) @@ -654,16 +696,22 @@ impl Db for PostgresDb { GROUP BY user_id ORDER BY total_duration DESC LIMIT $3 + ), + project_collaborators as ( + SELECT project_id, COUNT(DISTINCT user_id) as max_collaborators + FROM project_durations + GROUP BY project_id ) - SELECT user_durations.user_id, users.github_login, project_id, project_duration - FROM user_durations, project_durations, users + SELECT user_durations.user_id, users.github_login, project_durations.project_id, project_duration, max_collaborators + FROM user_durations, project_durations, project_collaborators, users WHERE user_durations.user_id = project_durations.user_id AND - user_durations.user_id = users.id + user_durations.user_id = users.id AND + project_durations.project_id = project_collaborators.project_id ORDER BY total_duration DESC, user_id ASC "; - let mut rows = sqlx::query_as::<_, (UserId, String, ProjectId, i64)>(query) + let mut rows = sqlx::query_as::<_, (UserId, String, ProjectId, i64, i64)>(query) .bind(time_period.start) .bind(time_period.end) .bind(max_user_count as i32) @@ -671,18 +719,23 @@ impl Db for PostgresDb { let mut result = Vec::::new(); while let Some(row) = rows.next().await { - let (user_id, github_login, project_id, duration_millis) = row?; + let (user_id, github_login, project_id, duration_millis, project_collaborators) = row?; let project_id = project_id; let duration = Duration::from_millis(duration_millis as u64); + let project_activity = ProjectActivitySummary { + id: project_id, + duration, + max_collaborators: project_collaborators as usize, + }; if let Some(last_summary) = result.last_mut() { if last_summary.id == user_id { - last_summary.project_activity.push((project_id, duration)); + last_summary.project_activity.push(project_activity); continue; } } result.push(UserActivitySummary { id: user_id, - project_activity: vec![(project_id, duration)], + project_activity: vec![project_activity], github_login, }); } @@ -1314,7 +1367,14 @@ pub struct Project { pub struct UserActivitySummary { pub id: UserId, pub github_login: String, - pub project_activity: Vec<(ProjectId, Duration)>, + pub project_activity: Vec, +} + +#[derive(Clone, Debug, PartialEq, Serialize)] +pub struct ProjectActivitySummary { + id: ProjectId, + duration: Duration, + max_collaborators: usize, } #[derive(Clone, Debug, PartialEq, Serialize)] @@ -1667,47 +1727,99 @@ pub mod tests { id: user_1, github_login: "user_1".to_string(), project_activity: vec![ - (project_1, Duration::from_secs(25)), - (project_2, Duration::from_secs(30)), + ProjectActivitySummary { + id: project_1, + duration: Duration::from_secs(25), + max_collaborators: 2 + }, + ProjectActivitySummary { + id: project_2, + duration: Duration::from_secs(30), + max_collaborators: 2 + } ] }, UserActivitySummary { id: user_2, github_login: "user_2".to_string(), - project_activity: vec![(project_2, Duration::from_secs(50))] + project_activity: vec![ProjectActivitySummary { + id: project_2, + duration: Duration::from_secs(50), + max_collaborators: 2 + }] }, UserActivitySummary { id: user_3, github_login: "user_3".to_string(), - project_activity: vec![(project_1, Duration::from_secs(15))] + project_activity: vec![ProjectActivitySummary { + id: project_1, + duration: Duration::from_secs(15), + max_collaborators: 2 + }] }, ] ); assert_eq!( - db.get_active_user_count(t0..t6, Duration::from_secs(56)) + db.get_active_user_count(t0..t6, Duration::from_secs(56), false) + .await + .unwrap(), + 0 + ); + assert_eq!( + db.get_active_user_count(t0..t6, Duration::from_secs(56), true) .await .unwrap(), 0 ); assert_eq!( - db.get_active_user_count(t0..t6, Duration::from_secs(54)) + db.get_active_user_count(t0..t6, Duration::from_secs(54), false) .await .unwrap(), 1 ); assert_eq!( - db.get_active_user_count(t0..t6, Duration::from_secs(30)) + db.get_active_user_count(t0..t6, Duration::from_secs(54), true) + .await + .unwrap(), + 1 + ); + assert_eq!( + db.get_active_user_count(t0..t6, Duration::from_secs(30), false) .await .unwrap(), 2 ); assert_eq!( - db.get_active_user_count(t0..t6, Duration::from_secs(10)) + db.get_active_user_count(t0..t6, Duration::from_secs(30), true) + .await + .unwrap(), + 2 + ); + assert_eq!( + db.get_active_user_count(t0..t6, Duration::from_secs(10), false) .await .unwrap(), 3 ); + assert_eq!( + db.get_active_user_count(t0..t6, Duration::from_secs(10), true) + .await + .unwrap(), + 3 + ); + assert_eq!( + db.get_active_user_count(t0..t1, Duration::from_secs(5), false) + .await + .unwrap(), + 1 + ); + assert_eq!( + db.get_active_user_count(t0..t1, Duration::from_secs(5), true) + .await + .unwrap(), + 0 + ); assert_eq!( db.get_user_activity_timeline(t3..t6, user_1).await.unwrap(), @@ -2549,6 +2661,7 @@ pub mod tests { &self, _time_period: Range, _min_duration: Duration, + _only_collaborative: bool, ) -> Result { unimplemented!() }