diff --git a/src/libs/shared/src/day.rs b/src/libs/shared/src/day.rs index a0cc2e105..ed4abe071 100644 --- a/src/libs/shared/src/day.rs +++ b/src/libs/shared/src/day.rs @@ -1,5 +1,5 @@ use crate::types::utils::CalendarDate; -use time::{Duration, OffsetDateTime}; +use time::{Duration, OffsetDateTime, Time}; /// Converts a Unix timestamp (in nanoseconds) to an `OffsetDateTime`. /// @@ -43,3 +43,28 @@ pub fn day_of_the_year(timestamp: &u64) -> usize { pub fn calendar_date(timestamp: &u64) -> CalendarDate { CalendarDate::from(&to_date(timestamp).to_calendar_date()) } + +/// Converts a Unix timestamp (in nanoseconds) to the start of its day (00:00:00 UTC). +/// +/// # Arguments +/// - `timestamp`: A reference to a `u64` Unix timestamp in nanoseconds. +/// +/// # Returns +/// - `Ok(u64)` containing the nanosecond timestamp at the start of the day. +/// - `Err(String)` if the conversion to midnight fails. +/// +/// # Errors +/// This function returns an error if constructing the midnight time fails, +/// or if the resulting timestamp cannot be safely converted to `u64`. +pub fn start_of_day(timestamp: &u64) -> Result { + let datetime = to_date(timestamp); + let date_only = datetime.date(); + + let midnight = Time::from_hms(0, 0, 0) + .map_err(|e| format!("Failed to create midnight time: {e}"))?; + + let start_of_day = date_only.with_time(midnight); + let nanos = start_of_day.assume_utc().unix_timestamp_nanos(); + + nanos.try_into().map_err(|_| "Timestamp too large for u64".to_string()) +} \ No newline at end of file diff --git a/src/orbiter/src/analytics/mod.rs b/src/orbiter/src/analytics/mod.rs new file mode 100644 index 000000000..018452802 --- /dev/null +++ b/src/orbiter/src/analytics/mod.rs @@ -0,0 +1 @@ +pub mod raw; \ No newline at end of file diff --git a/src/orbiter/src/analytics.rs b/src/orbiter/src/analytics/raw.rs similarity index 100% rename from src/orbiter/src/analytics.rs rename to src/orbiter/src/analytics/raw.rs diff --git a/src/orbiter/src/events/analytics/filters.rs b/src/orbiter/src/events/analytics/filters.rs new file mode 100644 index 000000000..10c78e766 --- /dev/null +++ b/src/orbiter/src/events/analytics/filters.rs @@ -0,0 +1,108 @@ +use crate::state::types::state::{AnalyticKey, AnalyticSatelliteKey}; +use crate::types::interface::GetAnalytics; +use junobuild_shared::types::state::SatelliteId; +use std::ops::RangeBounds; +use junobuild_shared::day::start_of_day; +use crate::state::types::state::analytics::{DailyAnalyticKey, DailyAnalyticSatelliteKey, DailySessionsAnalyticKey, DailySessionsAnalyticSatelliteKey}; + +pub fn filter_daily_analytics( + GetAnalytics { + from, + to, + satellite_id: _, + method: _ + }: &GetAnalytics, +) -> impl RangeBounds { + let start_key = DailyAnalyticKey { + start_of_day: from + .map(|from| start_of_day(&from).unwrap_or(from)) // We fallback for simplicity reasons + .unwrap_or(u64::MIN), + }; + + let end_key = DailyAnalyticKey { + start_of_day: to + .map(|to| start_of_day(&to).unwrap_or(to)) + .unwrap_or(u64::MAX), + }; + + start_key..end_key +} + +pub fn filter_daily_satellites_analytics( + GetAnalytics { + from, + to, + satellite_id: _, + method: _ + }: &GetAnalytics, + satellite_id: SatelliteId, +) -> impl RangeBounds { + let start_key = DailyAnalyticSatelliteKey { + satellite_id, + start_of_day: from + .map(|from| start_of_day(&from).unwrap_or(from)) // We fallback for simplicity reasons + .unwrap_or(u64::MIN), + }; + + let end_key = DailyAnalyticSatelliteKey { + satellite_id, + start_of_day: to + .map(|to| start_of_day(&to).unwrap_or(to)) + .unwrap_or(u64::MAX), + }; + + start_key..end_key +} + +pub fn filter_daily_sessions_analytics( + GetAnalytics { + from, + to, + satellite_id: _, + method: _ + }: &GetAnalytics, +) -> impl RangeBounds { + let start_key = DailySessionsAnalyticKey { + start_of_day: from + .map(|from| start_of_day(&from).unwrap_or(from)) // We fallback for simplicity reasons + .unwrap_or(u64::MIN), + session_id: "".to_string() + }; + + let end_key = DailySessionsAnalyticKey { + start_of_day: to + .map(|to| start_of_day(&to).unwrap_or(to)) + .unwrap_or(u64::MAX), + session_id: "".to_string() + }; + + start_key..end_key +} + +pub fn filter_daily_sessions_satellites_analytics( + GetAnalytics { + from, + to, + satellite_id: _, + method: _ + }: &GetAnalytics, + satellite_id: SatelliteId, +) -> impl RangeBounds { + let start_key = DailySessionsAnalyticSatelliteKey { + satellite_id, + start_of_day: from + .map(|from| start_of_day(&from).unwrap_or(from)) // We fallback for simplicity reasons + .unwrap_or(u64::MIN), + session_id: "".to_string() + }; + + let end_key = DailySessionsAnalyticSatelliteKey { + satellite_id, + start_of_day: to + .map(|to| start_of_day(&to).unwrap_or(to)) + .unwrap_or(u64::MAX), + session_id: "".to_string() + }; + + start_key..end_key +} diff --git a/src/orbiter/src/events/analytics/mod.rs b/src/orbiter/src/events/analytics/mod.rs new file mode 100644 index 000000000..b2a5e6f6e --- /dev/null +++ b/src/orbiter/src/events/analytics/mod.rs @@ -0,0 +1,3 @@ +pub mod precompute; +mod store; +mod filters; \ No newline at end of file diff --git a/src/orbiter/src/events/analytics/precompute.rs b/src/orbiter/src/events/analytics/precompute.rs new file mode 100644 index 000000000..de3d43a9a --- /dev/null +++ b/src/orbiter/src/events/analytics/precompute.rs @@ -0,0 +1,14 @@ +use crate::events::analytics::store::{increment_daily_page_views, increment_daily_session_views}; +use crate::state::types::state::{AnalyticKey, PageView}; +use crate::state::types::state::analytics::{DailyAnalyticKey, DailySessionsAnalyticKey}; + +pub fn update_page_view_analytics(key: &AnalyticKey, page_view: &PageView) -> Result<(), String> { + let daily_key = DailyAnalyticKey::from_analytic_key(key)?; + increment_daily_page_views(&daily_key, page_view)?; + + let daily_session_key = DailySessionsAnalyticKey::from_analytic_key_and_page_view(key, page_view)?; + increment_daily_session_views(&daily_session_key, page_view)?; + + Ok(()) +} + diff --git a/src/orbiter/src/events/analytics/store.rs b/src/orbiter/src/events/analytics/store.rs new file mode 100644 index 000000000..1914da8c7 --- /dev/null +++ b/src/orbiter/src/events/analytics/store.rs @@ -0,0 +1,139 @@ +use std::collections::HashSet; +use junobuild_shared::version::next_version; +use crate::events::analytics::filters::{filter_daily_analytics, filter_daily_satellites_analytics, filter_daily_sessions_analytics, filter_daily_sessions_satellites_analytics}; +use crate::events::filters::{filter_analytics, filter_satellites_analytics}; +use crate::state::memory::STATE; +use crate::state::types::state::{AnalyticKey, AnalyticSatelliteKey, PageView, StableState}; +use crate::state::types::state::analytics::{DailyPageViews, DailyAnalyticKey, DailyAnalyticSatelliteKey, DailySessionsAnalyticKey, DailySessionViews, DailySessionsAnalyticSatelliteKey}; +use crate::types::interface::GetAnalytics; +// --------------------------------------------------------- +// Read +// --------------------------------------------------------- + +pub fn get_daily_page_views(filter: &GetAnalytics) -> Vec<(DailyAnalyticKey, DailyPageViews)> { + STATE.with(|state| get_daily_page_views_impl(filter, &state.borrow_mut().stable)) +} + +fn get_daily_page_views_impl(filter: &GetAnalytics, state: &StableState) -> Vec<(DailyAnalyticKey, DailyPageViews)> { + match filter.satellite_id { + None => state + .daily_page_views + .range(filter_daily_analytics(filter)) + .map(|(key, daily_page_view)| (key, daily_page_view)) + .collect(), + Some(satellite_id) => { + let satellites_keys: Vec<(DailyAnalyticSatelliteKey, DailyAnalyticKey)> = state + .satellites_daily_page_views + .range(filter_daily_satellites_analytics(filter, satellite_id)) + .collect(); + satellites_keys + .iter() + .filter_map(|(_, key)| { + let daily_page_view = state.daily_page_views.get(key); + daily_page_view.map(|daily_page_view| (key.clone(), daily_page_view)) + }) + .collect() + } + } +} + +pub fn get_daily_sessions(filter: &GetAnalytics) -> Vec<(DailySessionsAnalyticKey, DailySessionViews)> { + STATE.with(|state| get_daily_sessions_impl(filter, &state.borrow_mut().stable)) +} + +fn get_daily_sessions_impl(filter: &GetAnalytics, state: &StableState) -> Vec<(DailySessionsAnalyticKey, DailySessionViews)> { + match filter.satellite_id { + None => state + .daily_session_views + .range(filter_daily_sessions_analytics(filter)) + .map(|(key, daily_session)| (key, daily_session)) + .collect(), + Some(satellite_id) => { + let satellites_keys: Vec<(DailySessionsAnalyticSatelliteKey, DailySessionsAnalyticKey)> = state + .satellites_daily_session_views + .range(filter_daily_sessions_satellites_analytics(filter, satellite_id)) + .collect(); + satellites_keys + .iter() + .filter_map(|(_, key)| { + let daily_session_views = state.daily_session_views.get(key); + daily_session_views.map(|daily_session_view| (key.clone(), daily_session_view)) + }) + .collect() + } + } +} + +// --------------------------------------------------------- +// Save +// --------------------------------------------------------- + +pub fn increment_daily_page_views(daily_key: &DailyAnalyticKey, page_view: &PageView) -> Result<(), String> { + STATE.with(|state| increment_daily_page_view_analytics_impl(daily_key, page_view, &mut state.borrow_mut().stable)) +} + +pub fn increment_daily_session_views(daily_session_key: &DailySessionsAnalyticKey, page_view: &PageView) -> Result<(), String> { + STATE.with(|state| increment_daily_session_view_analytics_impl(daily_session_key, page_view, &mut state.borrow_mut().stable)) +} + +fn increment_daily_page_view_analytics_impl( + daily_key: &DailyAnalyticKey, page_view: &PageView, + state: &mut StableState, +) -> Result<(), String> { + let current_daily_total_page_views = state.daily_page_views.get(&daily_key); + + let version = next_version(¤t_daily_total_page_views); + + let count = current_daily_total_page_views + .map(|v| v.count.saturating_add(1)) + .unwrap_or(1); + + let new_daily_page_views: DailyPageViews = DailyPageViews { + count, + version: Some(version), + }; + + state.daily_page_views.insert(daily_key.clone(), new_daily_page_views); + + state.satellites_daily_page_views.insert( + DailyAnalyticSatelliteKey::from_key(&daily_key, &page_view.satellite_id), + daily_key.clone(), + ); + + Ok(()) +} + +fn increment_daily_session_view_analytics_impl( + daily_session_key: &DailySessionsAnalyticKey, page_view: &PageView, + state: &mut StableState, +) -> Result<(), String> { + let mut current_daily_session_views = state.daily_session_views.get(&daily_session_key); + + let version = next_version(¤t_daily_session_views); + + let count = current_daily_session_views + .as_ref() + .map(|v| v.count.saturating_add(1)) + .unwrap_or(1); + + let mut hrefs = current_daily_session_views + .map(|v| v.hrefs) + .unwrap_or_else(HashSet::new); + + hrefs.insert(page_view.href.clone()); + + let new_daily_session_views: DailySessionViews = DailySessionViews { + count, + version: Some(version), + hrefs, + }; + + state.daily_session_views.insert(daily_session_key.clone(), new_daily_session_views); + + state.satellites_daily_session_views.insert( + DailySessionsAnalyticSatelliteKey::from_key(&daily_session_key, &page_view.satellite_id, &page_view.session_id), + daily_session_key.clone(), + ); + + Ok(()) +} \ No newline at end of file diff --git a/src/orbiter/src/events/filters.rs b/src/orbiter/src/events/filters.rs index 4ce29c95a..781f42b4f 100644 --- a/src/orbiter/src/events/filters.rs +++ b/src/orbiter/src/events/filters.rs @@ -8,6 +8,7 @@ pub fn filter_analytics( from, to, satellite_id: _, + method: _ }: &GetAnalytics, ) -> impl RangeBounds { let start_key = AnalyticKey { @@ -28,6 +29,7 @@ pub fn filter_satellites_analytics( from, to, satellite_id: _, + method: _ }: &GetAnalytics, satellite_id: SatelliteId, ) -> impl RangeBounds { diff --git a/src/orbiter/src/events/helpers.rs b/src/orbiter/src/events/helpers.rs index bb1710b27..9b95793e6 100644 --- a/src/orbiter/src/events/helpers.rs +++ b/src/orbiter/src/events/helpers.rs @@ -2,6 +2,7 @@ use crate::assert::config::{ assert_page_views_enabled, assert_performance_metrics_enabled, assert_track_events_enabled, }; use crate::config::store::get_satellite_config; +use crate::events::analytics::precompute::update_page_view_analytics; use crate::events::store::{insert_page_view, insert_performance_metric, insert_track_event}; use crate::state::types::state::{AnalyticKey, PageView, PerformanceMetric, TrackEvent}; use crate::types::interface::{SetPageView, SetPerformanceMetric, SetTrackEvent}; @@ -12,7 +13,11 @@ pub fn assert_and_insert_page_view( ) -> Result { assert_page_views_enabled(&get_satellite_config(&page_view.satellite_id))?; - insert_page_view(key, page_view) + let saved_page_view = insert_page_view(key.clone(), page_view)?; + + update_page_view_analytics(&key, &saved_page_view)?; + + Ok(saved_page_view) } pub fn assert_and_insert_track_event( diff --git a/src/orbiter/src/events/mod.rs b/src/orbiter/src/events/mod.rs index 6346e8108..28eaf634f 100644 --- a/src/orbiter/src/events/mod.rs +++ b/src/orbiter/src/events/mod.rs @@ -1,3 +1,4 @@ mod filters; pub mod helpers; pub mod store; +mod analytics; diff --git a/src/orbiter/src/lib.rs b/src/orbiter/src/lib.rs index 3d5339718..1b8117db3 100644 --- a/src/orbiter/src/lib.rs +++ b/src/orbiter/src/lib.rs @@ -1,4 +1,3 @@ -mod analytics; mod assert; mod config; mod constants; @@ -12,8 +11,9 @@ mod msg; mod serializers; mod state; mod types; +mod analytics; -use crate::analytics::{ +use analytics::raw::{ analytics_page_views_clients, analytics_page_views_metrics, analytics_page_views_top_10, analytics_performance_metrics_web_vitals, analytics_track_events, }; diff --git a/src/orbiter/src/state/impls.rs b/src/orbiter/src/state/impls.rs index b7e963a64..0bb955b28 100644 --- a/src/orbiter/src/state/impls.rs +++ b/src/orbiter/src/state/impls.rs @@ -7,16 +7,15 @@ use crate::serializers::bounded::{ use crate::serializers::constants::{ANALYTIC_KEY_MAX_SIZE, ANALYTIC_SATELLITE_KEY_MAX_SIZE}; use crate::state::memory::init_stable_state; use crate::state::types::memory::{StoredPageView, StoredTrackEvent}; -use crate::state::types::state::{ - AnalyticKey, AnalyticSatelliteKey, HeapState, PageView, PerformanceMetric, SatelliteConfigs, - State, TrackEvent, -}; +use crate::state::types::state::{AnalyticKey, AnalyticSatelliteKey, HeapState, PageView, PerformanceMetric, SatelliteConfigs, SessionId, State, TrackEvent}; use ciborium::from_reader; use ic_stable_structures::storable::Bound; use ic_stable_structures::Storable; use junobuild_shared::serializers::{deserialize_from_bytes, serialize_to_bytes}; use junobuild_shared::types::state::{Controllers, SatelliteId, Version, Versioned}; use std::borrow::Cow; +use junobuild_shared::day::start_of_day; +use crate::state::types::state::analytics::{DailyPageViews, DailyAnalyticKey, DailyAnalyticSatelliteKey, DailySessionViews, DailySessionsAnalyticKey, DailySessionsAnalyticSatelliteKey}; impl Default for State { fn default() -> Self { @@ -164,6 +163,90 @@ impl Storable for AnalyticSatelliteKey { }; } +impl Storable for DailyPageViews { + fn to_bytes(&self) -> Cow<[u8]> { + serialize_to_bytes(self) + } + + fn from_bytes(bytes: Cow<[u8]>) -> Self { + deserialize_from_bytes(bytes) + } + + const BOUND: Bound = Bound::Unbounded; +} + +impl Storable for DailySessionViews { + fn to_bytes(&self) -> Cow<[u8]> { + serialize_to_bytes(self) + } + + fn from_bytes(bytes: Cow<[u8]>) -> Self { + deserialize_from_bytes(bytes) + } + + const BOUND: Bound = Bound::Unbounded; +} + +impl Storable for DailyAnalyticKey { + fn to_bytes(&self) -> Cow<[u8]> { + serialize_to_bytes(self) + } + + fn from_bytes(bytes: Cow<[u8]>) -> Self { + deserialize_from_bytes(bytes) + } + + const BOUND: Bound = Bound::Unbounded; +} + +impl Storable for DailyAnalyticSatelliteKey { + fn to_bytes(&self) -> Cow<[u8]> { + serialize_to_bytes(self) + } + + fn from_bytes(bytes: Cow<[u8]>) -> Self { + deserialize_from_bytes(bytes) + } + + const BOUND: Bound = Bound::Unbounded; +} + +impl Storable for DailySessionsAnalyticKey { + fn to_bytes(&self) -> Cow<[u8]> { + serialize_to_bytes(self) + } + + fn from_bytes(bytes: Cow<[u8]>) -> Self { + deserialize_from_bytes(bytes) + } + + const BOUND: Bound = Bound::Unbounded; +} + +impl Storable for DailySessionsAnalyticSatelliteKey { + fn to_bytes(&self) -> Cow<[u8]> { + serialize_to_bytes(self) + } + + fn from_bytes(bytes: Cow<[u8]>) -> Self { + deserialize_from_bytes(bytes) + } + + const BOUND: Bound = Bound::Unbounded; +} + +impl Versioned for DailyPageViews { + fn version(&self) -> Option { + self.version + } +} + +impl Versioned for DailySessionViews { + fn version(&self) -> Option { + self.version + } +} + // --------------------------------------------------------- // Key conversion // --------------------------------------------------------- @@ -177,3 +260,39 @@ impl AnalyticSatelliteKey { } } } + +impl DailyAnalyticKey { + pub fn from_analytic_key(key: &AnalyticKey) -> Result { + Ok(Self { + start_of_day: start_of_day(&key.collected_at)?, + }) + } +} + +impl DailySessionsAnalyticKey { + pub fn from_analytic_key_and_page_view(key: &AnalyticKey, page_view: &PageView) -> Result { + Ok(Self { + start_of_day: start_of_day(&key.collected_at)?, + session_id: page_view.session_id.clone(), + }) + } +} + +impl DailyAnalyticSatelliteKey { + pub fn from_key(key: &DailyAnalyticKey, satellite_id: &SatelliteId) -> Self { + DailyAnalyticSatelliteKey { + satellite_id: *satellite_id, + start_of_day: key.start_of_day, + } + } +} + +impl DailySessionsAnalyticSatelliteKey { + pub fn from_key(key: &DailySessionsAnalyticKey, satellite_id: &SatelliteId, session_id: &SessionId) -> Self { + DailySessionsAnalyticSatelliteKey { + satellite_id: *satellite_id, + start_of_day: key.start_of_day, + session_id: session_id.clone(), + } + } +} diff --git a/src/orbiter/src/state/memory.rs b/src/orbiter/src/state/memory.rs index c11b92706..65e0bea89 100644 --- a/src/orbiter/src/state/memory.rs +++ b/src/orbiter/src/state/memory.rs @@ -6,6 +6,7 @@ use junobuild_shared::types::memory::Memory; use std::cell::RefCell; const UPGRADES: MemoryId = MemoryId::new(0); + const PAGE_VIEWS: MemoryId = MemoryId::new(1); const TRACK_EVENTS: MemoryId = MemoryId::new(2); const SATELLITES_PAGE_VIEWS: MemoryId = MemoryId::new(3); @@ -13,6 +14,11 @@ const SATELLITES_TRACK_EVENTS: MemoryId = MemoryId::new(4); const PERFORMANCE_METRICS: MemoryId = MemoryId::new(5); const SATELLITES_PERFORMANCE_METRICS: MemoryId = MemoryId::new(6); +const DAILY_TOTAL_PAGE_VIEWS: MemoryId = MemoryId::new(100); +const SATELLITES_DAILY_TOTAL_PAGE_VIEWS: MemoryId = MemoryId::new(101); +const DAILY_SESSIONS: MemoryId = MemoryId::new(102); +const SATELLITES_DAILY_SESSIONS: MemoryId = MemoryId::new(103); + thread_local! { pub static STATE: RefCell = RefCell::default(); @@ -38,5 +44,9 @@ pub fn init_stable_state() -> StableState { satellites_performance_metrics: StableBTreeMap::init(get_memory( SATELLITES_PERFORMANCE_METRICS, )), + daily_page_views: StableBTreeMap::init(get_memory(DAILY_TOTAL_PAGE_VIEWS)), + satellites_daily_page_views: StableBTreeMap::init(get_memory(SATELLITES_DAILY_TOTAL_PAGE_VIEWS)), + daily_session_views: StableBTreeMap::init(get_memory(DAILY_SESSIONS)), + satellites_daily_session_views: StableBTreeMap::init(get_memory(SATELLITES_DAILY_SESSIONS)), } } diff --git a/src/orbiter/src/state/types.rs b/src/orbiter/src/state/types.rs index af8984be1..825e78e1c 100644 --- a/src/orbiter/src/state/types.rs +++ b/src/orbiter/src/state/types.rs @@ -9,6 +9,7 @@ pub mod state { }; use serde::{Deserialize, Serialize}; use std::collections::HashMap; + use crate::state::types::state::analytics::{DailyPageViewsStable, DailySessionViewsStable, SatellitesDailyPageViewsStable, SatellitesDailySessionViewsStable}; #[derive(Serialize, Deserialize)] pub struct State { @@ -40,6 +41,11 @@ pub mod state { pub satellites_page_views: SatellitesPageViewsStable, pub satellites_track_events: SatellitesTrackEventsStable, pub satellites_performance_metrics: SatellitesPerformanceMetricsStable, + + pub daily_page_views: DailyPageViewsStable, + pub satellites_daily_page_views: SatellitesDailyPageViewsStable, + pub daily_session_views: DailySessionViewsStable, + pub satellites_daily_session_views: SatellitesDailySessionViewsStable, } pub type SatelliteConfig = OrbiterSatelliteConfig; @@ -140,6 +146,61 @@ pub mod state { Prerender, Restore, } + + pub mod analytics { + use std::collections::HashSet; + use candid::{CandidType, Deserialize}; + use ic_stable_structures::StableBTreeMap; + use serde::Serialize; + use junobuild_shared::types::memory::Memory; + use junobuild_shared::types::state::{SatelliteId, Timestamp, Version}; + use crate::state::types::state::SessionId; + + pub type DailyPageViewsStable = StableBTreeMap; + pub type SatellitesDailyPageViewsStable = StableBTreeMap; + pub type DailySessionViewsStable = StableBTreeMap; + pub type SatellitesDailySessionViewsStable = StableBTreeMap; + + pub type StartOfDayKey = Timestamp; + + #[derive(CandidType, Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub struct DailyAnalyticKey { + pub start_of_day: Timestamp, + } + + #[derive(CandidType, Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub struct DailyAnalyticSatelliteKey { + pub satellite_id: SatelliteId, + pub start_of_day: Timestamp, + } + + #[derive(CandidType, Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub struct DailySessionsAnalyticKey { + pub start_of_day: Timestamp, + pub session_id: SessionId, + } + + #[derive(CandidType, Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub struct DailySessionsAnalyticSatelliteKey { + pub satellite_id: SatelliteId, + pub start_of_day: Timestamp, + pub session_id: SessionId, + } + + #[derive(CandidType, Serialize, Deserialize, Clone)] + pub struct DailyPageViews { + pub count: u32, + pub version: Option, + } + + #[derive(CandidType, Serialize, Deserialize, Clone)] + pub struct DailySessionViews { + pub count: u32, + pub hrefs: HashSet, + pub version: Option, + } + + } } pub mod memory { diff --git a/src/orbiter/src/types.rs b/src/orbiter/src/types.rs index e0b7bdb38..95b5446c8 100644 --- a/src/orbiter/src/types.rs +++ b/src/orbiter/src/types.rs @@ -60,6 +60,13 @@ pub mod interface { pub satellite_id: Option, pub from: Option, pub to: Option, + pub method: Option, + } + + #[derive(CandidType, Deserialize, Clone)] + pub enum AnalyticsMethod { + Precomputed, + Raw, } #[derive(CandidType, Deserialize, Clone)]