diff --git a/defs/api/app-analytics/GET/Output.schema.json b/defs/api/app-analytics/GET/Output.schema.json index d7695e78..27f19edb 100644 --- a/defs/api/app-analytics/GET/Output.schema.json +++ b/defs/api/app-analytics/GET/Output.schema.json @@ -21,6 +21,7 @@ "stations", "total_duration_ms", "until", + "users", "utc_offset_minutes" ], "properties": { @@ -123,6 +124,11 @@ "format": "uint64", "minimum": 0.0 }, + "users": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, "total_duration_ms": { "type": "integer", "format": "uint64", @@ -148,7 +154,8 @@ "max_concurrent_listeners", "sessions", "total_duration_ms", - "total_transfer_bytes" + "total_transfer_bytes", + "users" ], "properties": { "key": { @@ -186,6 +193,11 @@ "format": "uint64", "minimum": 0.0 }, + "users": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, "total_duration_ms": { "type": "integer", "format": "uint64", @@ -219,7 +231,8 @@ "max_concurrent_listeners", "sessions", "total_duration_ms", - "total_transfer_bytes" + "total_transfer_bytes", + "users" ], "properties": { "key": { @@ -263,6 +276,11 @@ "format": "uint64", "minimum": 0.0 }, + "users": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, "total_duration_ms": { "type": "integer", "format": "uint64", @@ -296,7 +314,8 @@ "max_concurrent_listeners", "sessions", "total_duration_ms", - "total_transfer_bytes" + "total_transfer_bytes", + "users" ], "properties": { "key": { @@ -565,6 +584,11 @@ "format": "uint64", "minimum": 0.0 }, + "users": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, "total_duration_ms": { "type": "integer", "format": "uint64", @@ -598,7 +622,8 @@ "max_concurrent_listeners", "sessions", "total_duration_ms", - "total_transfer_bytes" + "total_transfer_bytes", + "users" ], "properties": { "key": { @@ -614,6 +639,11 @@ "format": "uint64", "minimum": 0.0 }, + "users": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, "total_duration_ms": { "type": "integer", "format": "uint64", @@ -646,7 +676,8 @@ "max_concurrent_listeners", "sessions", "total_duration_ms", - "total_transfer_bytes" + "total_transfer_bytes", + "users" ], "properties": { "key": { @@ -663,6 +694,11 @@ "format": "uint64", "minimum": 0.0 }, + "users": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, "total_duration_ms": { "type": "integer", "format": "uint64", @@ -696,7 +732,8 @@ "max_concurrent_listeners", "sessions", "total_duration_ms", - "total_transfer_bytes" + "total_transfer_bytes", + "users" ], "properties": { "key": { @@ -724,6 +761,11 @@ "format": "uint64", "minimum": 0.0 }, + "users": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, "total_duration_ms": { "type": "integer", "format": "uint64", diff --git a/defs/app-analytics/Analytics.ts b/defs/app-analytics/Analytics.ts index 404a01ff..8774b084 100644 --- a/defs/app-analytics/Analytics.ts +++ b/defs/app-analytics/Analytics.ts @@ -18,6 +18,7 @@ export type Analytics = { utc_offset_minutes: number; sessions: number; ips: number; + users: number; total_duration_ms: number; max_concurrent_listeners?: number; max_concurrent_listeners_date?: DateTime; diff --git a/defs/app-analytics/AnalyticsItem.ts b/defs/app-analytics/AnalyticsItem.ts index a14c3a83..a2b9c2d4 100644 --- a/defs/app-analytics/AnalyticsItem.ts +++ b/defs/app-analytics/AnalyticsItem.ts @@ -5,6 +5,7 @@ export type AnalyticsItem = { key: K; sessions: number; ips: number; + users: number; total_duration_ms: number; total_transfer_bytes: number; max_concurrent_listeners?: number; diff --git a/defs/db/WsStatsConnection.ts b/defs/db/WsStatsConnection.ts index 1e73bfa6..7e49470b 100644 --- a/defs/db/WsStatsConnection.ts +++ b/defs/db/WsStatsConnection.ts @@ -12,6 +12,7 @@ export type WsStatsConnection = { ip: string; ap: string | null | undefined; av: number | null | undefined; + us: string | null | undefined; re: number; ca: DateTime; cl: DateTime | null | undefined; diff --git a/defs/ws-stats/api/ws/stats/connection/WS/Query.ts b/defs/ws-stats/api/ws/stats/connection/WS/Query.ts index dc620975..850f4934 100644 --- a/defs/ws-stats/api/ws/stats/connection/WS/Query.ts +++ b/defs/ws-stats/api/ws/stats/connection/WS/Query.ts @@ -3,6 +3,7 @@ export type Query = { connection_id?: string; station_id: string; + user_id?: string; app_kind?: string; app_version?: number; }; diff --git a/front/share/src/analytics/AnalyticsData.svelte b/front/share/src/analytics/AnalyticsData.svelte index c5bbfcb2..07bcfa7d 100644 --- a/front/share/src/analytics/AnalyticsData.svelte +++ b/front/share/src/analytics/AnalyticsData.svelte @@ -1089,12 +1089,12 @@ key: item_key, sessions: 0, ips: 0, + users: 0, total_duration_ms: 0, - total_transfer_bytes: 0, - // max_concurrent_listeners: undefined, - // max_concurrent_listeners_date: undefined, + total_transfer_bytes: 0 }) } else { + // @ts-expect-error items.push(item) } @@ -1131,12 +1131,12 @@ key: item_key, sessions: 0, ips: 0, + users: 0, total_duration_ms: 0, total_transfer_bytes: 0, - // max_concurrent_listeners: undefined, - // max_concurrent_listeners_date: undefined, }) } else { + // @ts-expect-error items.push(item) } diff --git a/rs/packages/api/src/ws_stats/routes/connection.rs b/rs/packages/api/src/ws_stats/routes/connection.rs index 5bda2e96..b4cec761 100644 --- a/rs/packages/api/src/ws_stats/routes/connection.rs +++ b/rs/packages/api/src/ws_stats/routes/connection.rs @@ -80,6 +80,10 @@ pub struct Query { station_id: String, + #[ts(optional)] + #[serde(skip_serializing_if = "Option::is_none")] + user_id: Option, + #[ts(optional)] #[serde(skip_serializing_if = "Option::is_none")] app_kind: Option, @@ -140,6 +144,7 @@ impl WsConnectionHandler { let Query { connection_id: prev_id, station_id, + user_id, app_kind, app_version, } = qs; @@ -179,6 +184,7 @@ impl WsConnectionHandler { ip, app_kind: app_kind.clone(), app_version, + user_id, reconnections, created_at, closed_at: None, diff --git a/rs/packages/db/src/models/stream_connection/app_analytics/mod.rs b/rs/packages/db/src/models/stream_connection/app_analytics/mod.rs index ae560573..9bc4c180 100644 --- a/rs/packages/db/src/models/stream_connection/app_analytics/mod.rs +++ b/rs/packages/db/src/models/stream_connection/app_analytics/mod.rs @@ -49,6 +49,9 @@ pub struct Item { #[serde(with = "serde_util::as_f64::option")] pub app_version: Option, + #[serde(rename = "us")] + pub user_id: Option, + // #[serde(rename = "re")] // #[serde(with = "serde_util::as_f64")] // pub reconnections: u16, @@ -71,6 +74,7 @@ impl Item { WsStatsConnection::KEY_IP: 1, WsStatsConnection::KEY_APP_KIND: 1, WsStatsConnection::KEY_APP_VERSION: 1, + WsStatsConnection::KEY_USER_ID: 1, WsStatsConnection::KEY_CREATED_AT: 1, } } @@ -85,6 +89,7 @@ fn ws_stat_item_keys() { assert_eq!(Item::KEY_IP, WsStatsConnection::KEY_IP); assert_eq!(Item::KEY_APP_KIND, WsStatsConnection::KEY_APP_KIND); assert_eq!(Item::KEY_APP_VERSION, WsStatsConnection::KEY_APP_VERSION); + assert_eq!(Item::KEY_USER_ID, WsStatsConnection::KEY_USER_ID); assert_eq!(Item::KEY_CREATED_AT, WsStatsConnection::KEY_CREATED_AT); } @@ -111,6 +116,10 @@ pub struct Analytics { #[serde(deserialize_with = "serde_util::as_f64::deserialize")] pub ips: u64, + #[serde(serialize_with = "serde_util::as_f64::serialize")] + #[serde(deserialize_with = "serde_util::as_f64::deserialize")] + pub users: u64, + #[serde(serialize_with = "serde_util::as_f64::serialize")] #[serde(deserialize_with = "serde_util::as_f64::deserialize")] pub total_duration_ms: u64, @@ -142,12 +151,19 @@ pub struct Analytics { #[ts(export, export_to = "../../../defs/app-analytics/")] pub struct AnalyticsItem { pub key: K, + #[serde(serialize_with = "serde_util::as_f64::serialize")] #[serde(deserialize_with = "serde_util::as_f64::deserialize")] pub sessions: u64, + #[serde(serialize_with = "serde_util::as_f64::serialize")] #[serde(deserialize_with = "serde_util::as_f64::deserialize")] pub ips: u64, + + #[serde(serialize_with = "serde_util::as_f64::serialize")] + #[serde(deserialize_with = "serde_util::as_f64::deserialize")] + pub users: u64, + #[serde(serialize_with = "serde_util::as_f64::serialize")] #[serde(deserialize_with = "serde_util::as_f64::deserialize")] pub total_duration_ms: u64, @@ -245,6 +261,7 @@ type KeyedAccumulatorMap = HashMap; struct AccumulatorItem { sessions: u64, ips: HashSet, + users: HashSet, total_duration_ms: u64, total_transfer_bytes: u64, #[cfg(feature = "analytics-max-concurrent")] @@ -262,6 +279,7 @@ impl AccumulatorItem { fn merge(&mut self, dst: Self) { self.sessions += dst.sessions; self.ips.extend(dst.ips); + self.users.extend(dst.users); self.total_duration_ms += dst.total_duration_ms; self.total_transfer_bytes += dst.total_transfer_bytes; @@ -317,16 +335,17 @@ struct Batch { pub now_ms: u64, pub sessions: u64, + pub ips: HashSet, + pub users: HashSet, + pub total_duration_ms: u64, pub total_transfer_bytes: u64, - pub ips: HashSet, + pub by_day: KeyedAccumulatorMap, pub by_hour: KeyedAccumulatorMap, pub by_app_kind: KeyedAccumulatorMap>, pub by_app_version: KeyedAccumulatorMap, - // pub by_browser: KeyedAccumulatorMap>, - // pub by_os: KeyedAccumulatorMap>, - // pub by_domain: KeyedAccumulatorMap>, + pub by_country: KeyedAccumulatorMap>, pub by_station: KeyedAccumulatorMap, #[cfg(feature = "analytics-max-concurrent")] @@ -341,9 +360,11 @@ impl Batch { now_ms: OffsetDateTime::now_utc().unix_timestamp() as u64 * 1000, sessions: 0, + ips: Default::default(), + users: Default::default(), + total_duration_ms: 0, total_transfer_bytes: 0, - ips: Default::default(), by_day: Default::default(), by_hour: Default::default(), @@ -367,13 +388,11 @@ impl Batch { .duration_ms .unwrap_or_else(|| (self.now_ms - created_at.unix_timestamp() as u64 * 1000)); - // let conn_transfer_bytes = conn.transfer_bytes.unwrap_or(0); let conn_year = created_at.year() as u16; let conn_month = created_at.month() as u8; let conn_day = created_at.day(); let conn_hour = created_at.hour(); - // let conn_browser = conn.browser; - // let conn_os = conn.os; + let conn_kind_version = AppKindVersion { kind: conn.app_kind.clone(), version: conn.app_version, @@ -381,9 +400,12 @@ impl Batch { self.sessions += 1; self.total_duration_ms += conn_duration_ms; - // self.total_transfer_bytes += conn_transfer_bytes; self.ips.insert(conn.ip); + if let Some(id) = conn.user_id { + self.users.insert(id); + } + #[cfg(feature = "analytics-max-concurrent")] let start_s = conn.created_at.unix_timestamp() as u32; @@ -657,6 +679,7 @@ pub async fn get_analytics(query: AnalyticsQuery) -> Result Result Result, #[serde(rename = "du")] @@ -45,6 +46,10 @@ pub struct WsStatsConnection { #[serde(with = "serde_util::as_f64::option")] pub app_version: Option, + /// The user id is a client generated UID, its mainly used to count the number of different users in the stats + #[serde(rename = "us")] + pub user_id: Option, + #[serde(rename = "re")] #[serde(with = "serde_util::as_f64")] pub reconnections: u16, @@ -70,6 +75,10 @@ impl Model for WsStatsConnection { .keys(doc! { Self::KEY_STATION_ID: 1 }) .build(); + let user_id: IndexModel = IndexModel::builder() + .keys(doc! { Self::KEY_USER_ID: 1 }) + .build(); + let created_at = IndexModel::builder() .keys(doc! { Self::KEY_CREATED_AT: 1 }) .build(); @@ -82,7 +91,13 @@ impl Model for WsStatsConnection { .keys(doc! { Self::KEY_IS_OPEN: 1 }) .build(); - vec![station_id, created_at, station_id_created_at, is_open] + vec![ + station_id, + user_id, + created_at, + station_id_created_at, + is_open, + ] } }