diff --git a/src/declarations/orbiter/orbiter.did.d.ts b/src/declarations/orbiter/orbiter.did.d.ts index 5a8d1f6bc..017fc1d78 100644 --- a/src/declarations/orbiter/orbiter.did.d.ts +++ b/src/declarations/orbiter/orbiter.did.d.ts @@ -37,6 +37,13 @@ export interface AnalyticsTop10PageViews { export interface AnalyticsTrackEvents { total: Array<[string, number]>; } +export interface AnalyticsWebVitalsPerformanceMetrics { + cls: number; + fcp: number; + inp: number; + lcp: number; + ttfb: number; +} export interface CalendarDate { day: number; month: number; @@ -187,6 +194,10 @@ export interface _SERVICE { get_page_views_analytics_metrics: ActorMethod<[GetAnalytics], AnalyticsMetricsPageViews>; get_page_views_analytics_top_10: ActorMethod<[GetAnalytics], AnalyticsTop10PageViews>; get_performance_metrics: ActorMethod<[GetAnalytics], Array<[AnalyticKey, PerformanceMetric]>>; + get_performance_metrics_web_vitals: ActorMethod< + [GetAnalytics], + AnalyticsWebVitalsPerformanceMetrics + >; get_track_events: ActorMethod<[GetAnalytics], Array<[AnalyticKey, TrackEvent]>>; get_track_events_analytics: ActorMethod<[GetAnalytics], AnalyticsTrackEvents>; list_controllers: ActorMethod<[], Array<[Principal, Controller]>>; diff --git a/src/declarations/orbiter/orbiter.factory.did.js b/src/declarations/orbiter/orbiter.factory.did.js index 16b254732..5bde587be 100644 --- a/src/declarations/orbiter/orbiter.factory.did.js +++ b/src/declarations/orbiter/orbiter.factory.did.js @@ -110,6 +110,13 @@ export const idlFactory = ({ IDL }) => { satellite_id: IDL.Principal, version: IDL.Opt(IDL.Nat64) }); + const AnalyticsWebVitalsPerformanceMetrics = IDL.Record({ + cls: IDL.Float64, + fcp: IDL.Float64, + inp: IDL.Float64, + lcp: IDL.Float64, + ttfb: IDL.Float64 + }); const TrackEvent = IDL.Record({ updated_at: IDL.Nat64, session_id: IDL.Text, @@ -208,6 +215,11 @@ export const idlFactory = ({ IDL }) => { [IDL.Vec(IDL.Tuple(AnalyticKey, PerformanceMetric))], ['query'] ), + get_performance_metrics_web_vitals: IDL.Func( + [GetAnalytics], + [AnalyticsWebVitalsPerformanceMetrics], + ['query'] + ), get_track_events: IDL.Func( [GetAnalytics], [IDL.Vec(IDL.Tuple(AnalyticKey, TrackEvent))], diff --git a/src/declarations/orbiter/orbiter.factory.did.mjs b/src/declarations/orbiter/orbiter.factory.did.mjs index 16b254732..5bde587be 100644 --- a/src/declarations/orbiter/orbiter.factory.did.mjs +++ b/src/declarations/orbiter/orbiter.factory.did.mjs @@ -110,6 +110,13 @@ export const idlFactory = ({ IDL }) => { satellite_id: IDL.Principal, version: IDL.Opt(IDL.Nat64) }); + const AnalyticsWebVitalsPerformanceMetrics = IDL.Record({ + cls: IDL.Float64, + fcp: IDL.Float64, + inp: IDL.Float64, + lcp: IDL.Float64, + ttfb: IDL.Float64 + }); const TrackEvent = IDL.Record({ updated_at: IDL.Nat64, session_id: IDL.Text, @@ -208,6 +215,11 @@ export const idlFactory = ({ IDL }) => { [IDL.Vec(IDL.Tuple(AnalyticKey, PerformanceMetric))], ['query'] ), + get_performance_metrics_web_vitals: IDL.Func( + [GetAnalytics], + [AnalyticsWebVitalsPerformanceMetrics], + ['query'] + ), get_track_events: IDL.Func( [GetAnalytics], [IDL.Vec(IDL.Tuple(AnalyticKey, TrackEvent))], diff --git a/src/orbiter/orbiter.did b/src/orbiter/orbiter.did index bfbce4db8..cabce641a 100644 --- a/src/orbiter/orbiter.did +++ b/src/orbiter/orbiter.did @@ -28,6 +28,13 @@ type AnalyticsTop10PageViews = record { pages : vec record { text; nat32 }; }; type AnalyticsTrackEvents = record { total : vec record { text; nat32 } }; +type AnalyticsWebVitalsPerformanceMetrics = record { + cls : float64; + fcp : float64; + inp : float64; + lcp : float64; + ttfb : float64; +}; type CalendarDate = record { day : nat8; month : nat8; year : int32 }; type Controller = record { updated_at : nat64; @@ -166,6 +173,9 @@ service : () -> { get_performance_metrics : (GetAnalytics) -> ( vec record { AnalyticKey; PerformanceMetric }, ) query; + get_performance_metrics_web_vitals : (GetAnalytics) -> ( + AnalyticsWebVitalsPerformanceMetrics, + ) query; get_track_events : (GetAnalytics) -> ( vec record { AnalyticKey; TrackEvent }, ) query; diff --git a/src/orbiter/src/analytics.rs b/src/orbiter/src/analytics.rs index ef47791b0..712602ea8 100644 --- a/src/orbiter/src/analytics.rs +++ b/src/orbiter/src/analytics.rs @@ -1,8 +1,12 @@ use crate::types::interface::{ AnalyticsBrowsersPageViews, AnalyticsClientsPageViews, AnalyticsDevicesPageViews, AnalyticsMetricsPageViews, AnalyticsTop10PageViews, AnalyticsTrackEvents, + AnalyticsWebVitalsPerformanceMetrics, +}; +use crate::types::state::{ + AnalyticKey, PageView, PerformanceData, PerformanceMetric, PerformanceMetricName, TrackEvent, + WebVitalsMetric, }; -use crate::types::state::{AnalyticKey, PageView, TrackEvent}; use junobuild_shared::day::calendar_date; use junobuild_shared::types::utils::CalendarDate; use regex::Regex; @@ -218,6 +222,64 @@ pub fn analytics_track_events( } } +#[derive(Default)] +struct PerformanceMetricAccumulator { + sum: f64, + count: u32, +} + +impl PerformanceMetricAccumulator { + fn add(&mut self, value: &f64) { + self.sum += value; + self.count += 1; + } + + fn average(&self) -> f64 { + if self.count > 0 { + self.sum / self.count as f64 + } else { + 0.0 + } + } +} + +pub fn analytics_performance_metrics_web_vitals( + metrics: &Vec<(AnalyticKey, PerformanceMetric)>, +) -> AnalyticsWebVitalsPerformanceMetrics { + let mut cls = PerformanceMetricAccumulator::default(); + let mut fcp = PerformanceMetricAccumulator::default(); + let mut inp = PerformanceMetricAccumulator::default(); + let mut lcp = PerformanceMetricAccumulator::default(); + let mut ttfb = PerformanceMetricAccumulator::default(); + + for ( + _, + PerformanceMetric { + data, metric_name, .. + }, + ) in metrics + { + #[allow(irrefutable_let_patterns)] + if let PerformanceData::WebVitalsMetric(WebVitalsMetric { value, .. }) = &data { + match metric_name { + PerformanceMetricName::CLS => cls.add(value), + PerformanceMetricName::FCP => fcp.add(value), + PerformanceMetricName::INP => inp.add(value), + PerformanceMetricName::LCP => lcp.add(value), + PerformanceMetricName::TTFB => ttfb.add(value), + } + } + } + + AnalyticsWebVitalsPerformanceMetrics { + cls: cls.average(), + fcp: fcp.average(), + inp: inp.average(), + lcp: lcp.average(), + ttfb: ttfb.average(), + } +} + fn analytics_metrics( collected_at: &u64, session_id: &str, diff --git a/src/orbiter/src/lib.rs b/src/orbiter/src/lib.rs index 55e83af98..ff849d214 100644 --- a/src/orbiter/src/lib.rs +++ b/src/orbiter/src/lib.rs @@ -14,7 +14,7 @@ mod types; use crate::analytics::{ analytics_page_views_clients, analytics_page_views_metrics, analytics_page_views_top_10, - analytics_track_events, + analytics_performance_metrics_web_vitals, analytics_track_events, }; use crate::assert::assert_enabled; use crate::config::store::{ @@ -35,8 +35,8 @@ use crate::store::{ }; use crate::types::interface::{ AnalyticsClientsPageViews, AnalyticsMetricsPageViews, AnalyticsTop10PageViews, - AnalyticsTrackEvents, DelSatelliteConfig, GetAnalytics, SetPageView, SetPerformanceMetric, - SetSatelliteConfig, SetTrackEvent, + AnalyticsTrackEvents, AnalyticsWebVitalsPerformanceMetrics, DelSatelliteConfig, GetAnalytics, + SetPageView, SetPerformanceMetric, SetSatelliteConfig, SetTrackEvent, }; use crate::types::state::{ AnalyticKey, HeapState, PageView, PerformanceMetric, SatelliteConfigs, State, TrackEvent, @@ -253,6 +253,14 @@ fn get_performance_metrics(filter: GetAnalytics) -> Vec<(AnalyticKey, Performanc get_performance_metrics_store(&filter) } +#[query(guard = "caller_is_controller")] +fn get_performance_metrics_web_vitals( + filter: GetAnalytics, +) -> AnalyticsWebVitalsPerformanceMetrics { + let metrics = get_performance_metrics_store(&filter); + analytics_performance_metrics_web_vitals(&metrics) +} + /// /// Controllers /// diff --git a/src/orbiter/src/types.rs b/src/orbiter/src/types.rs index f55728282..a23d0f371 100644 --- a/src/orbiter/src/types.rs +++ b/src/orbiter/src/types.rs @@ -271,4 +271,13 @@ pub mod interface { pub struct AnalyticsTrackEvents { pub total: HashMap, } + + #[derive(CandidType, Deserialize, Clone)] + pub struct AnalyticsWebVitalsPerformanceMetrics { + pub cls: f64, + pub fcp: f64, + pub inp: f64, + pub lcp: f64, + pub ttfb: f64, + } }