Merge pull request #1384 from zed-industries/expose-collaboration-metrics

Antonio Scandurra created

Expose collaboration metrics

Change summary

.gitignore               |   1 
Procfile                 |   1 
README.md                |   6 -
crates/collab/src/api.rs |   3 
crates/collab/src/db.rs  | 165 +++++++++++++++++++++++++++++++++++------
5 files changed, 142 insertions(+), 34 deletions(-)

Detailed changes

.gitignore 🔗

@@ -8,4 +8,3 @@
 /crates/collab/static/styles.css
 /vendor/bin
 /assets/themes/*.json
-dump.rdb

Procfile 🔗

@@ -1,3 +1,2 @@
 web: cd ../zed.dev && PORT=3000 npx next dev
 collab: cd crates/collab && cargo run
-redis: redis-server

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.
 
 ```

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?,
         })

crates/collab/src/db.rs 🔗

@@ -75,6 +75,7 @@ pub trait Db: Send + Sync {
         &self,
         time_period: Range<OffsetDateTime>,
         min_duration: Duration,
+        only_collaborative: bool,
     ) -> Result<usize>;
 
     /// 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<OffsetDateTime>,
         min_duration: Duration,
+        only_collaborative: bool,
     ) -> Result<usize> {
-        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::<UserActivitySummary>::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<ProjectActivitySummary>,
+}
+
+#[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<OffsetDateTime>,
             _min_duration: Duration,
+            _only_collaborative: bool,
         ) -> Result<usize> {
             unimplemented!()
         }