🎨 Tweak some names dealing with user activity

* Rename `project_activity_summary` to `top_users_activity_summary`
to make clearer the distinction between it and the per-user summary.
* Rename `user_activity_summary` to `user_activity_timeline`, since
its output is structured a bit differently than the courser-grained
"summary" returned by the top-user query.
* Rename `ActivityDuration` -> `ActivityPeriod`
This commit is contained in:
Max Brunsfeld 2022-06-28 15:37:56 -07:00
parent 5cc5e15f4d
commit 1d10e45282
3 changed files with 60 additions and 49 deletions

View file

@ -36,8 +36,8 @@ pub fn routes(rpc_server: &Arc<rpc::Server>, state: Arc<AppState>) -> Router<Bod
.route("/panic", post(trace_panic))
.route("/rpc_server_snapshot", get(get_rpc_server_snapshot))
.route(
"/project_activity_summary",
get(get_project_activity_summary),
"/user_activity/summary",
get(get_top_users_activity_summary),
)
.route("/project_metadata", get(get_project_metadata))
.layer(
@ -264,20 +264,20 @@ async fn get_rpc_server_snapshot(
}
#[derive(Deserialize)]
struct GetProjectActivityParams {
struct TimePeriodParams {
#[serde(with = "time::serde::iso8601")]
start: OffsetDateTime,
#[serde(with = "time::serde::iso8601")]
end: OffsetDateTime,
}
async fn get_project_activity_summary(
Query(params): Query<GetProjectActivityParams>,
async fn get_top_users_activity_summary(
Query(params): Query<TimePeriodParams>,
Extension(app): Extension<Arc<AppState>>,
) -> Result<ErasedJson> {
let summary = app
.db
.summarize_project_activity(params.start..params.end, 100)
.get_top_users_activity_summary(params.start..params.end, 100)
.await?;
Ok(ErasedJson::pretty(summary))
}

View file

@ -7,7 +7,7 @@ use axum::http::StatusCode;
use collections::HashMap;
use futures::StreamExt;
use nanoid::nanoid;
use serde::Serialize;
use serde::{Deserialize, Serialize};
pub use sqlx::postgres::PgPoolOptions as DbOptions;
use sqlx::{types::Uuid, FromRow, QueryBuilder, Row};
use time::{OffsetDateTime, PrimitiveDateTime};
@ -63,7 +63,7 @@ pub trait Db: Send + Sync {
/// Record which users have been active in which projects during
/// a given period of time.
async fn record_project_activity(
async fn record_user_activity(
&self,
time_period: Range<OffsetDateTime>,
active_projects: &[(UserId, ProjectId)],
@ -71,18 +71,18 @@ pub trait Db: Send + Sync {
/// Get the users that have been most active during the given time period,
/// along with the amount of time they have been active in each project.
async fn summarize_project_activity(
async fn get_top_users_activity_summary(
&self,
time_period: Range<OffsetDateTime>,
max_user_count: usize,
) -> Result<Vec<UserActivitySummary>>;
/// Get the project activity for the given user and time period.
async fn summarize_user_activity(
async fn get_user_activity_timeline(
&self,
user_id: UserId,
time_period: Range<OffsetDateTime>,
) -> Result<Vec<UserActivityDuration>>;
user_id: UserId,
) -> Result<Vec<UserActivityPeriod>>;
async fn get_contacts(&self, id: UserId) -> Result<Vec<Contact>>;
async fn has_contact(&self, user_id_a: UserId, user_id_b: UserId) -> Result<bool>;
@ -564,7 +564,7 @@ impl Db for PostgresDb {
Ok(extension_counts)
}
async fn record_project_activity(
async fn record_user_activity(
&self,
time_period: Range<OffsetDateTime>,
projects: &[(UserId, ProjectId)],
@ -593,7 +593,7 @@ impl Db for PostgresDb {
Ok(())
}
async fn summarize_project_activity(
async fn get_top_users_activity_summary(
&self,
time_period: Range<OffsetDateTime>,
max_user_count: usize,
@ -648,11 +648,11 @@ impl Db for PostgresDb {
Ok(result)
}
async fn summarize_user_activity(
async fn get_user_activity_timeline(
&self,
user_id: UserId,
time_period: Range<OffsetDateTime>,
) -> Result<Vec<UserActivityDuration>> {
user_id: UserId,
) -> Result<Vec<UserActivityPeriod>> {
const COALESCE_THRESHOLD: Duration = Duration::from_secs(5);
let query = "
@ -689,19 +689,19 @@ impl Db for PostgresDb {
.bind(time_period.end)
.fetch(&self.pool);
let mut durations: HashMap<ProjectId, Vec<UserActivityDuration>> = Default::default();
let mut time_periods: HashMap<ProjectId, Vec<UserActivityPeriod>> = Default::default();
while let Some(row) = rows.next().await {
let (ended_at, duration_millis, project_id, extension, extension_count) = row?;
let ended_at = ended_at.assume_utc();
let duration = Duration::from_millis(duration_millis as u64);
let started_at = ended_at - duration;
let project_durations = durations.entry(project_id).or_default();
let project_time_periods = time_periods.entry(project_id).or_default();
if let Some(prev_duration) = project_durations.last_mut() {
if let Some(prev_duration) = project_time_periods.last_mut() {
if started_at - prev_duration.end <= COALESCE_THRESHOLD {
prev_duration.end = ended_at;
} else {
project_durations.push(UserActivityDuration {
project_time_periods.push(UserActivityPeriod {
project_id,
start: started_at,
end: ended_at,
@ -709,7 +709,7 @@ impl Db for PostgresDb {
});
}
} else {
project_durations.push(UserActivityDuration {
project_time_periods.push(UserActivityPeriod {
project_id,
start: started_at,
end: ended_at,
@ -718,7 +718,7 @@ impl Db for PostgresDb {
}
if let Some((extension, extension_count)) = extension.zip(extension_count) {
project_durations
project_time_periods
.last_mut()
.unwrap()
.extensions
@ -726,7 +726,7 @@ impl Db for PostgresDb {
}
}
let mut durations = durations.into_values().flatten().collect::<Vec<_>>();
let mut durations = time_periods.into_values().flatten().collect::<Vec<_>>();
durations.sort_unstable_by_key(|duration| duration.start);
Ok(durations)
}
@ -1206,7 +1206,18 @@ impl Db for PostgresDb {
macro_rules! id_type {
($name:ident) => {
#[derive(
Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, sqlx::Type, Serialize,
Clone,
Copy,
Debug,
Default,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
sqlx::Type,
Serialize,
Deserialize,
)]
#[sqlx(transparent)]
#[serde(transparent)]
@ -1263,7 +1274,7 @@ pub struct UserActivitySummary {
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct UserActivityDuration {
pub struct UserActivityPeriod {
project_id: ProjectId,
start: OffsetDateTime,
end: OffsetDateTime,
@ -1549,24 +1560,24 @@ pub mod tests {
// User 2 opens a project
let t1 = t0 + Duration::from_secs(10);
db.record_project_activity(t0..t1, &[(user_2, project_2)])
db.record_user_activity(t0..t1, &[(user_2, project_2)])
.await
.unwrap();
let t2 = t1 + Duration::from_secs(10);
db.record_project_activity(t1..t2, &[(user_2, project_2)])
db.record_user_activity(t1..t2, &[(user_2, project_2)])
.await
.unwrap();
// User 1 joins the project
let t3 = t2 + Duration::from_secs(10);
db.record_project_activity(t2..t3, &[(user_2, project_2), (user_1, project_2)])
db.record_user_activity(t2..t3, &[(user_2, project_2), (user_1, project_2)])
.await
.unwrap();
// User 1 opens another project
let t4 = t3 + Duration::from_secs(10);
db.record_project_activity(
db.record_user_activity(
t3..t4,
&[
(user_2, project_2),
@ -1579,7 +1590,7 @@ pub mod tests {
// User 3 joins that project
let t5 = t4 + Duration::from_secs(10);
db.record_project_activity(
db.record_user_activity(
t4..t5,
&[
(user_2, project_2),
@ -1593,18 +1604,18 @@ pub mod tests {
// User 2 leaves
let t6 = t5 + Duration::from_secs(5);
db.record_project_activity(t5..t6, &[(user_1, project_1), (user_3, project_1)])
db.record_user_activity(t5..t6, &[(user_1, project_1), (user_3, project_1)])
.await
.unwrap();
let t7 = t6 + Duration::from_secs(60);
let t8 = t7 + Duration::from_secs(10);
db.record_project_activity(t7..t8, &[(user_1, project_1)])
db.record_user_activity(t7..t8, &[(user_1, project_1)])
.await
.unwrap();
assert_eq!(
db.summarize_project_activity(t0..t6, 10).await.unwrap(),
db.get_top_users_activity_summary(t0..t6, 10).await.unwrap(),
&[
UserActivitySummary {
id: user_1,
@ -1627,15 +1638,15 @@ pub mod tests {
]
);
assert_eq!(
db.summarize_user_activity(user_1, t3..t6).await.unwrap(),
db.get_user_activity_timeline(t3..t6, user_1).await.unwrap(),
&[
UserActivityDuration {
UserActivityPeriod {
project_id: project_1,
start: t3,
end: t6,
extensions: HashMap::from_iter([("rs".to_string(), 5), ("md".to_string(), 7)]),
},
UserActivityDuration {
UserActivityPeriod {
project_id: project_2,
start: t3,
end: t5,
@ -1644,21 +1655,21 @@ pub mod tests {
]
);
assert_eq!(
db.summarize_user_activity(user_1, t0..t8).await.unwrap(),
db.get_user_activity_timeline(t0..t8, user_1).await.unwrap(),
&[
UserActivityDuration {
UserActivityPeriod {
project_id: project_2,
start: t2,
end: t5,
extensions: Default::default(),
},
UserActivityDuration {
UserActivityPeriod {
project_id: project_1,
start: t3,
end: t6,
extensions: HashMap::from_iter([("rs".to_string(), 5), ("md".to_string(), 7)]),
},
UserActivityDuration {
UserActivityPeriod {
project_id: project_1,
start: t7,
end: t8,
@ -2450,27 +2461,27 @@ pub mod tests {
unimplemented!()
}
async fn record_project_activity(
async fn record_user_activity(
&self,
_period: Range<OffsetDateTime>,
_time_period: Range<OffsetDateTime>,
_active_projects: &[(UserId, ProjectId)],
) -> Result<()> {
unimplemented!()
}
async fn summarize_project_activity(
async fn get_top_users_activity_summary(
&self,
_period: Range<OffsetDateTime>,
_time_period: Range<OffsetDateTime>,
_limit: usize,
) -> Result<Vec<UserActivitySummary>> {
unimplemented!()
}
async fn summarize_user_activity(
async fn get_user_activity_timeline(
&self,
_user_id: UserId,
_time_period: Range<OffsetDateTime>,
) -> Result<Vec<UserActivityDuration>> {
_user_id: UserId,
) -> Result<Vec<UserActivityPeriod>> {
unimplemented!()
}

View file

@ -332,7 +332,7 @@ impl Server {
let period_end = OffsetDateTime::now_utc();
this.app_state
.db
.record_project_activity(period_start..period_end, &active_projects)
.record_user_activity(period_start..period_end, &active_projects)
.await
.trace_err();
period_start = period_end;