diff --git a/crates/analytics/src/query.rs b/crates/analytics/src/query.rs index a7ef0d993e..d529615207 100644 --- a/crates/analytics/src/query.rs +++ b/crates/analytics/src/query.rs @@ -388,6 +388,7 @@ impl_to_sql_for_to_string!(&DisputeDimensions, DisputeDimensions, DisputeStage); #[derive(Debug)] pub enum FilterTypes { Equal, + NotEqual, EqualBool, In, Gte, @@ -402,6 +403,7 @@ pub fn filter_type_to_sql(l: &String, op: &FilterTypes, r: &String) -> String { match op { FilterTypes::EqualBool => format!("{l} = {r}"), FilterTypes::Equal => format!("{l} = '{r}'"), + FilterTypes::NotEqual => format!("{l} != '{r}'"), FilterTypes::In => format!("{l} IN ({r})"), FilterTypes::Gte => format!("{l} >= '{r}'"), FilterTypes::Gt => format!("{l} > {r}"), diff --git a/crates/analytics/src/sdk_events/accumulator.rs b/crates/analytics/src/sdk_events/accumulator.rs index ab9e930943..fc90ae48eb 100644 --- a/crates/analytics/src/sdk_events/accumulator.rs +++ b/crates/analytics/src/sdk_events/accumulator.rs @@ -6,13 +6,19 @@ use super::metrics::SdkEventMetricRow; #[derive(Debug, Default)] pub struct SdkEventMetricsAccumulator { pub payment_attempts: CountAccumulator, - pub payment_success: CountAccumulator, pub payment_methods_call_count: CountAccumulator, pub average_payment_time: AverageAccumulator, pub sdk_initiated_count: CountAccumulator, pub sdk_rendered_count: CountAccumulator, pub payment_method_selected_count: CountAccumulator, pub payment_data_filled_count: CountAccumulator, + pub three_ds_method_invoked_count: CountAccumulator, + pub three_ds_method_skipped_count: CountAccumulator, + pub three_ds_method_successful_count: CountAccumulator, + pub three_ds_method_unsuccessful_count: CountAccumulator, + pub authentication_unsuccessful_count: CountAccumulator, + pub three_ds_challenge_flow_count: CountAccumulator, + pub three_ds_frictionless_flow_count: CountAccumulator, } #[derive(Debug, Default)] @@ -86,13 +92,19 @@ impl SdkEventMetricsAccumulator { pub fn collect(self) -> SdkEventMetricsBucketValue { SdkEventMetricsBucketValue { payment_attempts: self.payment_attempts.collect(), - payment_success_count: self.payment_success.collect(), payment_methods_call_count: self.payment_methods_call_count.collect(), average_payment_time: self.average_payment_time.collect(), sdk_initiated_count: self.sdk_initiated_count.collect(), sdk_rendered_count: self.sdk_rendered_count.collect(), payment_method_selected_count: self.payment_method_selected_count.collect(), payment_data_filled_count: self.payment_data_filled_count.collect(), + three_ds_method_invoked_count: self.three_ds_method_invoked_count.collect(), + three_ds_method_skipped_count: self.three_ds_method_skipped_count.collect(), + three_ds_method_successful_count: self.three_ds_method_successful_count.collect(), + three_ds_method_unsuccessful_count: self.three_ds_method_unsuccessful_count.collect(), + authentication_unsuccessful_count: self.authentication_unsuccessful_count.collect(), + three_ds_challenge_flow_count: self.three_ds_challenge_flow_count.collect(), + three_ds_frictionless_flow_count: self.three_ds_frictionless_flow_count.collect(), } } } diff --git a/crates/analytics/src/sdk_events/core.rs b/crates/analytics/src/sdk_events/core.rs index a5d2900e54..2ffc7f1aed 100644 --- a/crates/analytics/src/sdk_events/core.rs +++ b/crates/analytics/src/sdk_events/core.rs @@ -88,9 +88,6 @@ pub async fn get_metrics( SdkEventMetrics::PaymentAttempts => { metrics_builder.payment_attempts.add_metrics_bucket(&value) } - SdkEventMetrics::PaymentSuccessCount => { - metrics_builder.payment_success.add_metrics_bucket(&value) - } SdkEventMetrics::PaymentMethodsCallCount => metrics_builder .payment_methods_call_count .add_metrics_bucket(&value), @@ -109,6 +106,27 @@ pub async fn get_metrics( SdkEventMetrics::AveragePaymentTime => metrics_builder .average_payment_time .add_metrics_bucket(&value), + SdkEventMetrics::ThreeDsMethodInvokedCount => metrics_builder + .three_ds_method_invoked_count + .add_metrics_bucket(&value), + SdkEventMetrics::ThreeDsMethodSkippedCount => metrics_builder + .three_ds_method_skipped_count + .add_metrics_bucket(&value), + SdkEventMetrics::ThreeDsMethodSuccessfulCount => metrics_builder + .three_ds_method_successful_count + .add_metrics_bucket(&value), + SdkEventMetrics::ThreeDsMethodUnsuccessfulCount => metrics_builder + .three_ds_method_unsuccessful_count + .add_metrics_bucket(&value), + SdkEventMetrics::AuthenticationUnsuccessfulCount => metrics_builder + .authentication_unsuccessful_count + .add_metrics_bucket(&value), + SdkEventMetrics::ThreeDsChallengeFlowCount => metrics_builder + .three_ds_challenge_flow_count + .add_metrics_bucket(&value), + SdkEventMetrics::ThreeDsFrictionlessFlowCount => metrics_builder + .three_ds_frictionless_flow_count + .add_metrics_bucket(&value), } } diff --git a/crates/analytics/src/sdk_events/metrics.rs b/crates/analytics/src/sdk_events/metrics.rs index 354d2270d1..70d29a2eb6 100644 --- a/crates/analytics/src/sdk_events/metrics.rs +++ b/crates/analytics/src/sdk_events/metrics.rs @@ -11,23 +11,35 @@ use crate::{ types::{AnalyticsCollection, AnalyticsDataSource, LoadRow, MetricsResult}, }; +mod authentication_unsuccessful_count; mod average_payment_time; mod payment_attempts; mod payment_data_filled_count; mod payment_method_selected_count; mod payment_methods_call_count; -mod payment_success_count; mod sdk_initiated_count; mod sdk_rendered_count; +mod three_ds_challenge_flow_count; +mod three_ds_frictionless_flow_count; +mod three_ds_method_invoked_count; +mod three_ds_method_skipped_count; +mod three_ds_method_successful_count; +mod three_ds_method_unsuccessful_count; +use authentication_unsuccessful_count::AuthenticationUnsuccessfulCount; use average_payment_time::AveragePaymentTime; use payment_attempts::PaymentAttempts; use payment_data_filled_count::PaymentDataFilledCount; use payment_method_selected_count::PaymentMethodSelectedCount; use payment_methods_call_count::PaymentMethodsCallCount; -use payment_success_count::PaymentSuccessCount; use sdk_initiated_count::SdkInitiatedCount; use sdk_rendered_count::SdkRenderedCount; +use three_ds_challenge_flow_count::ThreeDsChallengeFlowCount; +use three_ds_frictionless_flow_count::ThreeDsFrictionlessFlowCount; +use three_ds_method_invoked_count::ThreeDsMethodInvokedCount; +use three_ds_method_skipped_count::ThreeDsMethodSkippedCount; +use three_ds_method_successful_count::ThreeDsMethodSuccessfulCount; +use three_ds_method_unsuccessful_count::ThreeDsMethodUnsuccessfulCount; #[derive(Debug, PartialEq, Eq, serde::Deserialize)] pub struct SdkEventMetricRow { @@ -92,18 +104,6 @@ where ) .await } - Self::PaymentSuccessCount => { - PaymentSuccessCount - .load_metrics( - dimensions, - publishable_key, - filters, - granularity, - time_range, - pool, - ) - .await - } Self::PaymentMethodsCallCount => { PaymentMethodsCallCount .load_metrics( @@ -176,6 +176,90 @@ where ) .await } + Self::ThreeDsMethodSkippedCount => { + ThreeDsMethodSkippedCount + .load_metrics( + dimensions, + publishable_key, + filters, + granularity, + time_range, + pool, + ) + .await + } + Self::ThreeDsMethodInvokedCount => { + ThreeDsMethodInvokedCount + .load_metrics( + dimensions, + publishable_key, + filters, + granularity, + time_range, + pool, + ) + .await + } + Self::ThreeDsMethodSuccessfulCount => { + ThreeDsMethodSuccessfulCount + .load_metrics( + dimensions, + publishable_key, + filters, + granularity, + time_range, + pool, + ) + .await + } + Self::ThreeDsMethodUnsuccessfulCount => { + ThreeDsMethodUnsuccessfulCount + .load_metrics( + dimensions, + publishable_key, + filters, + granularity, + time_range, + pool, + ) + .await + } + Self::AuthenticationUnsuccessfulCount => { + AuthenticationUnsuccessfulCount + .load_metrics( + dimensions, + publishable_key, + filters, + granularity, + time_range, + pool, + ) + .await + } + Self::ThreeDsChallengeFlowCount => { + ThreeDsChallengeFlowCount + .load_metrics( + dimensions, + publishable_key, + filters, + granularity, + time_range, + pool, + ) + .await + } + Self::ThreeDsFrictionlessFlowCount => { + ThreeDsFrictionlessFlowCount + .load_metrics( + dimensions, + publishable_key, + filters, + granularity, + time_range, + pool, + ) + .await + } } } } diff --git a/crates/analytics/src/sdk_events/metrics/authentication_unsuccessful_count.rs b/crates/analytics/src/sdk_events/metrics/authentication_unsuccessful_count.rs new file mode 100644 index 0000000000..c2fe777d82 --- /dev/null +++ b/crates/analytics/src/sdk_events/metrics/authentication_unsuccessful_count.rs @@ -0,0 +1,122 @@ +use api_models::analytics::{ + sdk_events::{ + SdkEventDimensions, SdkEventFilters, SdkEventMetricsBucketIdentifier, SdkEventNames, + }, + Granularity, TimeRange, +}; +use common_utils::errors::ReportSwitchExt; +use error_stack::ResultExt; +use time::PrimitiveDateTime; + +use super::SdkEventMetricRow; +use crate::{ + query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, ToSql, Window}, + types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult}, +}; + +#[derive(Default)] +pub(super) struct AuthenticationUnsuccessfulCount; + +#[async_trait::async_trait] +impl super::SdkEventMetric for AuthenticationUnsuccessfulCount +where + T: AnalyticsDataSource + super::SdkEventMetricAnalytics, + PrimitiveDateTime: ToSql, + AnalyticsCollection: ToSql, + Granularity: GroupByClause, + Aggregate<&'static str>: ToSql, + Window<&'static str>: ToSql, +{ + async fn load_metrics( + &self, + dimensions: &[SdkEventDimensions], + publishable_key: &str, + filters: &SdkEventFilters, + granularity: &Option, + time_range: &TimeRange, + pool: &T, + ) -> MetricsResult> { + let mut query_builder: QueryBuilder = QueryBuilder::new(AnalyticsCollection::SdkEvents); + let dimensions = dimensions.to_vec(); + + for dim in dimensions.iter() { + query_builder.add_select_column(dim).switch()?; + } + + query_builder + .add_select_column(Aggregate::Count { + field: None, + alias: Some("count"), + }) + .switch()?; + + if let Some(granularity) = granularity.as_ref() { + query_builder + .add_granularity_in_mins(granularity) + .switch()?; + } + + filters.set_filter_clause(&mut query_builder).switch()?; + + query_builder + .add_filter_clause("merchant_id", publishable_key) + .switch()?; + + query_builder + .add_filter_clause("event_name", SdkEventNames::AuthenticationCall) + .switch()?; + + query_builder + .add_filter_clause("log_type", "ERROR") + .switch()?; + + query_builder + .add_filter_clause("category", "USER_EVENT") + .switch()?; + + time_range + .set_filter_clause(&mut query_builder) + .attach_printable("Error filtering time range") + .switch()?; + + for dim in dimensions.iter() { + query_builder + .add_group_by_clause(dim) + .attach_printable("Error grouping by dimensions") + .switch()?; + } + + if let Some(_granularity) = granularity.as_ref() { + query_builder + .add_group_by_clause("time_bucket") + .attach_printable("Error adding granularity") + .switch()?; + } + + query_builder + .execute_query::(pool) + .await + .change_context(MetricsError::QueryBuildingError)? + .change_context(MetricsError::QueryExecutionFailure)? + .into_iter() + .map(|i| { + Ok(( + SdkEventMetricsBucketIdentifier::new( + i.payment_method.clone(), + i.platform.clone(), + i.browser_name.clone(), + i.source.clone(), + i.component.clone(), + i.payment_experience.clone(), + i.time_bucket.clone(), + ), + i, + )) + }) + .collect::, + crate::query::PostProcessingError, + >>() + .change_context(MetricsError::PostProcessingFailure) + } +} diff --git a/crates/analytics/src/sdk_events/metrics/sdk_initiated_count.rs b/crates/analytics/src/sdk_events/metrics/sdk_initiated_count.rs index a525e7890b..ecbca81acf 100644 --- a/crates/analytics/src/sdk_events/metrics/sdk_initiated_count.rs +++ b/crates/analytics/src/sdk_events/metrics/sdk_initiated_count.rs @@ -67,7 +67,7 @@ where .switch()?; query_builder - .add_filter_clause("event_name", SdkEventNames::StripeElementsCalled) + .add_filter_clause("event_name", SdkEventNames::OrcaElementsCalled) .switch()?; time_range diff --git a/crates/analytics/src/sdk_events/metrics/three_ds_challenge_flow_count.rs b/crates/analytics/src/sdk_events/metrics/three_ds_challenge_flow_count.rs new file mode 100644 index 0000000000..eb2eab6046 --- /dev/null +++ b/crates/analytics/src/sdk_events/metrics/three_ds_challenge_flow_count.rs @@ -0,0 +1,124 @@ +use api_models::analytics::{ + sdk_events::{ + SdkEventDimensions, SdkEventFilters, SdkEventMetricsBucketIdentifier, SdkEventNames, + }, + Granularity, TimeRange, +}; +use common_utils::errors::ReportSwitchExt; +use error_stack::ResultExt; +use time::PrimitiveDateTime; + +use super::SdkEventMetricRow; +use crate::{ + query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, ToSql, Window}, + types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult}, +}; + +#[derive(Default)] +pub(super) struct ThreeDsChallengeFlowCount; + +#[async_trait::async_trait] +impl super::SdkEventMetric for ThreeDsChallengeFlowCount +where + T: AnalyticsDataSource + super::SdkEventMetricAnalytics, + PrimitiveDateTime: ToSql, + AnalyticsCollection: ToSql, + Granularity: GroupByClause, + Aggregate<&'static str>: ToSql, + Window<&'static str>: ToSql, +{ + async fn load_metrics( + &self, + dimensions: &[SdkEventDimensions], + publishable_key: &str, + filters: &SdkEventFilters, + granularity: &Option, + time_range: &TimeRange, + pool: &T, + ) -> MetricsResult> { + let mut query_builder: QueryBuilder = QueryBuilder::new(AnalyticsCollection::SdkEvents); + let dimensions = dimensions.to_vec(); + + for dim in dimensions.iter() { + query_builder.add_select_column(dim).switch()?; + } + + query_builder + .add_select_column(Aggregate::Count { + field: None, + alias: Some("count"), + }) + .switch()?; + + if let Some(granularity) = granularity.as_ref() { + query_builder + .add_granularity_in_mins(granularity) + .switch()?; + } + + filters.set_filter_clause(&mut query_builder).switch()?; + + query_builder + .add_filter_clause("merchant_id", publishable_key) + .switch()?; + + query_builder + .add_filter_clause("event_name", SdkEventNames::DisplayThreeDsSdk) + .switch()?; + + query_builder + .add_filter_clause("log_type", "INFO") + .switch()?; + + query_builder + .add_filter_clause("category", "USER_EVENT") + .switch()?; + + query_builder.add_filter_clause("value", "C").switch()?; + + time_range + .set_filter_clause(&mut query_builder) + .attach_printable("Error filtering time range") + .switch()?; + + for dim in dimensions.iter() { + query_builder + .add_group_by_clause(dim) + .attach_printable("Error grouping by dimensions") + .switch()?; + } + + if let Some(_granularity) = granularity.as_ref() { + query_builder + .add_group_by_clause("time_bucket") + .attach_printable("Error adding granularity") + .switch()?; + } + + query_builder + .execute_query::(pool) + .await + .change_context(MetricsError::QueryBuildingError)? + .change_context(MetricsError::QueryExecutionFailure)? + .into_iter() + .map(|i| { + Ok(( + SdkEventMetricsBucketIdentifier::new( + i.payment_method.clone(), + i.platform.clone(), + i.browser_name.clone(), + i.source.clone(), + i.component.clone(), + i.payment_experience.clone(), + i.time_bucket.clone(), + ), + i, + )) + }) + .collect::, + crate::query::PostProcessingError, + >>() + .change_context(MetricsError::PostProcessingFailure) + } +} diff --git a/crates/analytics/src/sdk_events/metrics/three_ds_frictionless_flow_count.rs b/crates/analytics/src/sdk_events/metrics/three_ds_frictionless_flow_count.rs new file mode 100644 index 0000000000..ec40cf50c2 --- /dev/null +++ b/crates/analytics/src/sdk_events/metrics/three_ds_frictionless_flow_count.rs @@ -0,0 +1,126 @@ +use api_models::analytics::{ + sdk_events::{ + SdkEventDimensions, SdkEventFilters, SdkEventMetricsBucketIdentifier, SdkEventNames, + }, + Granularity, TimeRange, +}; +use common_utils::errors::ReportSwitchExt; +use error_stack::ResultExt; +use time::PrimitiveDateTime; + +use super::SdkEventMetricRow; +use crate::{ + query::{Aggregate, FilterTypes, GroupByClause, QueryBuilder, QueryFilter, ToSql, Window}, + types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult}, +}; + +#[derive(Default)] +pub(super) struct ThreeDsFrictionlessFlowCount; + +#[async_trait::async_trait] +impl super::SdkEventMetric for ThreeDsFrictionlessFlowCount +where + T: AnalyticsDataSource + super::SdkEventMetricAnalytics, + PrimitiveDateTime: ToSql, + AnalyticsCollection: ToSql, + Granularity: GroupByClause, + Aggregate<&'static str>: ToSql, + Window<&'static str>: ToSql, +{ + async fn load_metrics( + &self, + dimensions: &[SdkEventDimensions], + publishable_key: &str, + filters: &SdkEventFilters, + granularity: &Option, + time_range: &TimeRange, + pool: &T, + ) -> MetricsResult> { + let mut query_builder: QueryBuilder = QueryBuilder::new(AnalyticsCollection::SdkEvents); + let dimensions = dimensions.to_vec(); + + for dim in dimensions.iter() { + query_builder.add_select_column(dim).switch()?; + } + + query_builder + .add_select_column(Aggregate::Count { + field: None, + alias: Some("count"), + }) + .switch()?; + + if let Some(granularity) = granularity.as_ref() { + query_builder + .add_granularity_in_mins(granularity) + .switch()?; + } + + filters.set_filter_clause(&mut query_builder).switch()?; + + query_builder + .add_filter_clause("merchant_id", publishable_key) + .switch()?; + + query_builder + .add_filter_clause("event_name", SdkEventNames::DisplayThreeDsSdk) + .switch()?; + + query_builder + .add_filter_clause("log_type", "INFO") + .switch()?; + + query_builder + .add_filter_clause("category", "USER_EVENT") + .switch()?; + + query_builder + .add_custom_filter_clause("value", "C", FilterTypes::NotEqual) + .switch()?; + + time_range + .set_filter_clause(&mut query_builder) + .attach_printable("Error filtering time range") + .switch()?; + + for dim in dimensions.iter() { + query_builder + .add_group_by_clause(dim) + .attach_printable("Error grouping by dimensions") + .switch()?; + } + + if let Some(_granularity) = granularity.as_ref() { + query_builder + .add_group_by_clause("time_bucket") + .attach_printable("Error adding granularity") + .switch()?; + } + + query_builder + .execute_query::(pool) + .await + .change_context(MetricsError::QueryBuildingError)? + .change_context(MetricsError::QueryExecutionFailure)? + .into_iter() + .map(|i| { + Ok(( + SdkEventMetricsBucketIdentifier::new( + i.payment_method.clone(), + i.platform.clone(), + i.browser_name.clone(), + i.source.clone(), + i.component.clone(), + i.payment_experience.clone(), + i.time_bucket.clone(), + ), + i, + )) + }) + .collect::, + crate::query::PostProcessingError, + >>() + .change_context(MetricsError::PostProcessingFailure) + } +} diff --git a/crates/analytics/src/sdk_events/metrics/three_ds_method_invoked_count.rs b/crates/analytics/src/sdk_events/metrics/three_ds_method_invoked_count.rs new file mode 100644 index 0000000000..17368d8a74 --- /dev/null +++ b/crates/analytics/src/sdk_events/metrics/three_ds_method_invoked_count.rs @@ -0,0 +1,124 @@ +use api_models::analytics::{ + sdk_events::{ + SdkEventDimensions, SdkEventFilters, SdkEventMetricsBucketIdentifier, SdkEventNames, + }, + Granularity, TimeRange, +}; +use common_utils::errors::ReportSwitchExt; +use error_stack::ResultExt; +use time::PrimitiveDateTime; + +use super::SdkEventMetricRow; +use crate::{ + query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, ToSql, Window}, + types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult}, +}; + +#[derive(Default)] +pub(super) struct ThreeDsMethodInvokedCount; + +#[async_trait::async_trait] +impl super::SdkEventMetric for ThreeDsMethodInvokedCount +where + T: AnalyticsDataSource + super::SdkEventMetricAnalytics, + PrimitiveDateTime: ToSql, + AnalyticsCollection: ToSql, + Granularity: GroupByClause, + Aggregate<&'static str>: ToSql, + Window<&'static str>: ToSql, +{ + async fn load_metrics( + &self, + dimensions: &[SdkEventDimensions], + publishable_key: &str, + filters: &SdkEventFilters, + granularity: &Option, + time_range: &TimeRange, + pool: &T, + ) -> MetricsResult> { + let mut query_builder: QueryBuilder = QueryBuilder::new(AnalyticsCollection::SdkEvents); + let dimensions = dimensions.to_vec(); + + for dim in dimensions.iter() { + query_builder.add_select_column(dim).switch()?; + } + + query_builder + .add_select_column(Aggregate::Count { + field: None, + alias: Some("count"), + }) + .switch()?; + + if let Some(granularity) = granularity.as_ref() { + query_builder + .add_granularity_in_mins(granularity) + .switch()?; + } + + filters.set_filter_clause(&mut query_builder).switch()?; + + query_builder + .add_filter_clause("merchant_id", publishable_key) + .switch()?; + + query_builder + .add_filter_clause("event_name", SdkEventNames::ThreeDsMethod) + .switch()?; + + query_builder + .add_filter_clause("log_type", "INFO") + .switch()?; + + query_builder + .add_filter_clause("category", "USER_EVENT") + .switch()?; + + query_builder.add_filter_clause("value", "Y").switch()?; + + time_range + .set_filter_clause(&mut query_builder) + .attach_printable("Error filtering time range") + .switch()?; + + for dim in dimensions.iter() { + query_builder + .add_group_by_clause(dim) + .attach_printable("Error grouping by dimensions") + .switch()?; + } + + if let Some(_granularity) = granularity.as_ref() { + query_builder + .add_group_by_clause("time_bucket") + .attach_printable("Error adding granularity") + .switch()?; + } + + query_builder + .execute_query::(pool) + .await + .change_context(MetricsError::QueryBuildingError)? + .change_context(MetricsError::QueryExecutionFailure)? + .into_iter() + .map(|i| { + Ok(( + SdkEventMetricsBucketIdentifier::new( + i.payment_method.clone(), + i.platform.clone(), + i.browser_name.clone(), + i.source.clone(), + i.component.clone(), + i.payment_experience.clone(), + i.time_bucket.clone(), + ), + i, + )) + }) + .collect::, + crate::query::PostProcessingError, + >>() + .change_context(MetricsError::PostProcessingFailure) + } +} diff --git a/crates/analytics/src/sdk_events/metrics/three_ds_method_skipped_count.rs b/crates/analytics/src/sdk_events/metrics/three_ds_method_skipped_count.rs new file mode 100644 index 0000000000..9ffb2a1c69 --- /dev/null +++ b/crates/analytics/src/sdk_events/metrics/three_ds_method_skipped_count.rs @@ -0,0 +1,124 @@ +use api_models::analytics::{ + sdk_events::{ + SdkEventDimensions, SdkEventFilters, SdkEventMetricsBucketIdentifier, SdkEventNames, + }, + Granularity, TimeRange, +}; +use common_utils::errors::ReportSwitchExt; +use error_stack::ResultExt; +use time::PrimitiveDateTime; + +use super::SdkEventMetricRow; +use crate::{ + query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, ToSql, Window}, + types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult}, +}; + +#[derive(Default)] +pub(super) struct ThreeDsMethodSkippedCount; + +#[async_trait::async_trait] +impl super::SdkEventMetric for ThreeDsMethodSkippedCount +where + T: AnalyticsDataSource + super::SdkEventMetricAnalytics, + PrimitiveDateTime: ToSql, + AnalyticsCollection: ToSql, + Granularity: GroupByClause, + Aggregate<&'static str>: ToSql, + Window<&'static str>: ToSql, +{ + async fn load_metrics( + &self, + dimensions: &[SdkEventDimensions], + publishable_key: &str, + filters: &SdkEventFilters, + granularity: &Option, + time_range: &TimeRange, + pool: &T, + ) -> MetricsResult> { + let mut query_builder: QueryBuilder = QueryBuilder::new(AnalyticsCollection::SdkEvents); + let dimensions = dimensions.to_vec(); + + for dim in dimensions.iter() { + query_builder.add_select_column(dim).switch()?; + } + + query_builder + .add_select_column(Aggregate::Count { + field: None, + alias: Some("count"), + }) + .switch()?; + + if let Some(granularity) = granularity.as_ref() { + query_builder + .add_granularity_in_mins(granularity) + .switch()?; + } + + filters.set_filter_clause(&mut query_builder).switch()?; + + query_builder + .add_filter_clause("merchant_id", publishable_key) + .switch()?; + + query_builder + .add_filter_clause("event_name", SdkEventNames::ThreeDsMethod) + .switch()?; + + query_builder + .add_filter_clause("log_type", "INFO") + .switch()?; + + query_builder + .add_filter_clause("category", "USER_EVENT") + .switch()?; + + query_builder.add_filter_clause("value", "N").switch()?; + + time_range + .set_filter_clause(&mut query_builder) + .attach_printable("Error filtering time range") + .switch()?; + + for dim in dimensions.iter() { + query_builder + .add_group_by_clause(dim) + .attach_printable("Error grouping by dimensions") + .switch()?; + } + + if let Some(_granularity) = granularity.as_ref() { + query_builder + .add_group_by_clause("time_bucket") + .attach_printable("Error adding granularity") + .switch()?; + } + + query_builder + .execute_query::(pool) + .await + .change_context(MetricsError::QueryBuildingError)? + .change_context(MetricsError::QueryExecutionFailure)? + .into_iter() + .map(|i| { + Ok(( + SdkEventMetricsBucketIdentifier::new( + i.payment_method.clone(), + i.platform.clone(), + i.browser_name.clone(), + i.source.clone(), + i.component.clone(), + i.payment_experience.clone(), + i.time_bucket.clone(), + ), + i, + )) + }) + .collect::, + crate::query::PostProcessingError, + >>() + .change_context(MetricsError::PostProcessingFailure) + } +} diff --git a/crates/analytics/src/sdk_events/metrics/payment_success_count.rs b/crates/analytics/src/sdk_events/metrics/three_ds_method_successful_count.rs similarity index 91% rename from crates/analytics/src/sdk_events/metrics/payment_success_count.rs rename to crates/analytics/src/sdk_events/metrics/three_ds_method_successful_count.rs index 3faf821363..5a8305e8ab 100644 --- a/crates/analytics/src/sdk_events/metrics/payment_success_count.rs +++ b/crates/analytics/src/sdk_events/metrics/three_ds_method_successful_count.rs @@ -15,10 +15,10 @@ use crate::{ }; #[derive(Default)] -pub(super) struct PaymentSuccessCount; +pub(super) struct ThreeDsMethodSuccessfulCount; #[async_trait::async_trait] -impl super::SdkEventMetric for PaymentSuccessCount +impl super::SdkEventMetric for ThreeDsMethodSuccessfulCount where T: AnalyticsDataSource + super::SdkEventMetricAnalytics, PrimitiveDateTime: ToSql, @@ -63,11 +63,15 @@ where .switch()?; query_builder - .add_bool_filter_clause("first_event", 1) + .add_filter_clause("event_name", SdkEventNames::ThreeDsMethodResult) .switch()?; query_builder - .add_filter_clause("event_name", SdkEventNames::PaymentSuccess) + .add_filter_clause("log_type", "INFO") + .switch()?; + + query_builder + .add_filter_clause("category", "USER_EVENT") .switch()?; time_range diff --git a/crates/analytics/src/sdk_events/metrics/three_ds_method_unsuccessful_count.rs b/crates/analytics/src/sdk_events/metrics/three_ds_method_unsuccessful_count.rs new file mode 100644 index 0000000000..b18d025bd0 --- /dev/null +++ b/crates/analytics/src/sdk_events/metrics/three_ds_method_unsuccessful_count.rs @@ -0,0 +1,122 @@ +use api_models::analytics::{ + sdk_events::{ + SdkEventDimensions, SdkEventFilters, SdkEventMetricsBucketIdentifier, SdkEventNames, + }, + Granularity, TimeRange, +}; +use common_utils::errors::ReportSwitchExt; +use error_stack::ResultExt; +use time::PrimitiveDateTime; + +use super::SdkEventMetricRow; +use crate::{ + query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, ToSql, Window}, + types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult}, +}; + +#[derive(Default)] +pub(super) struct ThreeDsMethodUnsuccessfulCount; + +#[async_trait::async_trait] +impl super::SdkEventMetric for ThreeDsMethodUnsuccessfulCount +where + T: AnalyticsDataSource + super::SdkEventMetricAnalytics, + PrimitiveDateTime: ToSql, + AnalyticsCollection: ToSql, + Granularity: GroupByClause, + Aggregate<&'static str>: ToSql, + Window<&'static str>: ToSql, +{ + async fn load_metrics( + &self, + dimensions: &[SdkEventDimensions], + publishable_key: &str, + filters: &SdkEventFilters, + granularity: &Option, + time_range: &TimeRange, + pool: &T, + ) -> MetricsResult> { + let mut query_builder: QueryBuilder = QueryBuilder::new(AnalyticsCollection::SdkEvents); + let dimensions = dimensions.to_vec(); + + for dim in dimensions.iter() { + query_builder.add_select_column(dim).switch()?; + } + + query_builder + .add_select_column(Aggregate::Count { + field: None, + alias: Some("count"), + }) + .switch()?; + + if let Some(granularity) = granularity.as_ref() { + query_builder + .add_granularity_in_mins(granularity) + .switch()?; + } + + filters.set_filter_clause(&mut query_builder).switch()?; + + query_builder + .add_filter_clause("merchant_id", publishable_key) + .switch()?; + + query_builder + .add_filter_clause("event_name", SdkEventNames::ThreeDsMethodResult) + .switch()?; + + query_builder + .add_filter_clause("log_type", "ERROR") + .switch()?; + + query_builder + .add_filter_clause("category", "USER_EVENT") + .switch()?; + + time_range + .set_filter_clause(&mut query_builder) + .attach_printable("Error filtering time range") + .switch()?; + + for dim in dimensions.iter() { + query_builder + .add_group_by_clause(dim) + .attach_printable("Error grouping by dimensions") + .switch()?; + } + + if let Some(_granularity) = granularity.as_ref() { + query_builder + .add_group_by_clause("time_bucket") + .attach_printable("Error adding granularity") + .switch()?; + } + + query_builder + .execute_query::(pool) + .await + .change_context(MetricsError::QueryBuildingError)? + .change_context(MetricsError::QueryExecutionFailure)? + .into_iter() + .map(|i| { + Ok(( + SdkEventMetricsBucketIdentifier::new( + i.payment_method.clone(), + i.platform.clone(), + i.browser_name.clone(), + i.source.clone(), + i.component.clone(), + i.payment_experience.clone(), + i.time_bucket.clone(), + ), + i, + )) + }) + .collect::, + crate::query::PostProcessingError, + >>() + .change_context(MetricsError::PostProcessingFailure) + } +} diff --git a/crates/api_models/src/analytics/sdk_events.rs b/crates/api_models/src/analytics/sdk_events.rs index 76ccb29867..2eacaa655d 100644 --- a/crates/api_models/src/analytics/sdk_events.rs +++ b/crates/api_models/src/analytics/sdk_events.rs @@ -71,8 +71,14 @@ pub enum SdkEventDimensions { #[serde(rename_all = "snake_case")] pub enum SdkEventMetrics { PaymentAttempts, - PaymentSuccessCount, PaymentMethodsCallCount, + ThreeDsMethodInvokedCount, + ThreeDsMethodSkippedCount, + ThreeDsMethodSuccessfulCount, + ThreeDsMethodUnsuccessfulCount, + AuthenticationUnsuccessfulCount, + ThreeDsChallengeFlowCount, + ThreeDsFrictionlessFlowCount, SdkRenderedCount, SdkInitiatedCount, PaymentMethodSelectedCount, @@ -95,12 +101,11 @@ pub enum SdkEventMetrics { #[strum(serialize_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum SdkEventNames { - StripeElementsCalled, + OrcaElementsCalled, AppRendered, PaymentMethodChanged, PaymentDataFilled, PaymentAttempt, - PaymentSuccess, PaymentMethodsCall, ConfirmCall, SessionsCall, @@ -108,12 +113,24 @@ pub enum SdkEventNames { RedirectingUser, DisplayBankTransferInfoPage, DisplayQrCodeInfoPage, + AuthenticationCall, + ThreeDsMethodCall, + ThreeDsMethodResult, + ThreeDsMethod, + LoaderChanged, + DisplayThreeDsSdk, } pub mod metric_behaviour { pub struct PaymentAttempts; - pub struct PaymentSuccessCount; pub struct PaymentMethodsCallCount; + pub struct ThreeDsMethodInvokedCount; + pub struct ThreeDsMethodSkippedCount; + pub struct ThreeDsMethodSuccessfulCount; + pub struct ThreeDsMethodUnsuccessfulCount; + pub struct AuthenticationUnsuccessfulCount; + pub struct ThreeDsChallengeFlowCount; + pub struct ThreeDsFrictionlessFlowCount; pub struct SdkRenderedCount; pub struct SdkInitiatedCount; pub struct PaymentMethodSelectedCount; @@ -197,13 +214,19 @@ impl PartialEq for SdkEventMetricsBucketIdentifier { #[derive(Debug, serde::Serialize)] pub struct SdkEventMetricsBucketValue { pub payment_attempts: Option, - pub payment_success_count: Option, pub payment_methods_call_count: Option, pub average_payment_time: Option, pub sdk_rendered_count: Option, pub sdk_initiated_count: Option, pub payment_method_selected_count: Option, pub payment_data_filled_count: Option, + pub three_ds_method_invoked_count: Option, + pub three_ds_method_skipped_count: Option, + pub three_ds_method_successful_count: Option, + pub three_ds_method_unsuccessful_count: Option, + pub authentication_unsuccessful_count: Option, + pub three_ds_challenge_flow_count: Option, + pub three_ds_frictionless_flow_count: Option, } #[derive(Debug, serde::Serialize)]