feat(analytics): refactor and rewrite authentication related analytics (#7433)

Co-authored-by: Sandeep Kumar <sandeep.kumar@Sandeep-Kumar-LVF93XQXPC.local>
This commit is contained in:
Sandeep Kumar
2025-03-06 16:31:31 +05:30
committed by GitHub
parent ea817060cf
commit 1ff273e137
18 changed files with 199 additions and 194 deletions

View File

@ -4,7 +4,7 @@ use super::metrics::AuthEventMetricRow;
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct AuthEventMetricsAccumulator { pub struct AuthEventMetricsAccumulator {
pub three_ds_sdk_count: CountAccumulator, pub authentication_count: CountAccumulator,
pub authentication_attempt_count: CountAccumulator, pub authentication_attempt_count: CountAccumulator,
pub authentication_success_count: CountAccumulator, pub authentication_success_count: CountAccumulator,
pub challenge_flow_count: CountAccumulator, pub challenge_flow_count: CountAccumulator,
@ -47,7 +47,7 @@ impl AuthEventMetricAccumulator for CountAccumulator {
impl AuthEventMetricsAccumulator { impl AuthEventMetricsAccumulator {
pub fn collect(self) -> AuthEventMetricsBucketValue { pub fn collect(self) -> AuthEventMetricsBucketValue {
AuthEventMetricsBucketValue { AuthEventMetricsBucketValue {
three_ds_sdk_count: self.three_ds_sdk_count.collect(), authentication_count: self.authentication_count.collect(),
authentication_attempt_count: self.authentication_attempt_count.collect(), authentication_attempt_count: self.authentication_attempt_count.collect(),
authentication_success_count: self.authentication_success_count.collect(), authentication_success_count: self.authentication_success_count.collect(),
challenge_flow_count: self.challenge_flow_count.collect(), challenge_flow_count: self.challenge_flow_count.collect(),

View File

@ -18,7 +18,6 @@ use crate::{
pub async fn get_metrics( pub async fn get_metrics(
pool: &AnalyticsProvider, pool: &AnalyticsProvider,
merchant_id: &common_utils::id_type::MerchantId, merchant_id: &common_utils::id_type::MerchantId,
publishable_key: &String,
req: GetAuthEventMetricRequest, req: GetAuthEventMetricRequest,
) -> AnalyticsResult<MetricsResponse<MetricsBucketResponse>> { ) -> AnalyticsResult<MetricsResponse<MetricsBucketResponse>> {
let mut metrics_accumulator: HashMap< let mut metrics_accumulator: HashMap<
@ -30,14 +29,12 @@ pub async fn get_metrics(
for metric_type in req.metrics.iter().cloned() { for metric_type in req.metrics.iter().cloned() {
let req = req.clone(); let req = req.clone();
let merchant_id_scoped = merchant_id.to_owned(); let merchant_id_scoped = merchant_id.to_owned();
let publishable_key_scoped = publishable_key.to_owned();
let pool = pool.clone(); let pool = pool.clone();
set.spawn(async move { set.spawn(async move {
let data = pool let data = pool
.get_auth_event_metrics( .get_auth_event_metrics(
&metric_type, &metric_type,
&merchant_id_scoped, &merchant_id_scoped,
&publishable_key_scoped,
req.time_series.map(|t| t.granularity), req.time_series.map(|t| t.granularity),
&req.time_range, &req.time_range,
) )
@ -56,8 +53,8 @@ pub async fn get_metrics(
for (id, value) in data? { for (id, value) in data? {
let metrics_builder = metrics_accumulator.entry(id).or_default(); let metrics_builder = metrics_accumulator.entry(id).or_default();
match metric { match metric {
AuthEventMetrics::ThreeDsSdkCount => metrics_builder AuthEventMetrics::AuthenticationCount => metrics_builder
.three_ds_sdk_count .authentication_count
.add_metrics_bucket(&value), .add_metrics_bucket(&value),
AuthEventMetrics::AuthenticationAttemptCount => metrics_builder AuthEventMetrics::AuthenticationAttemptCount => metrics_builder
.authentication_attempt_count .authentication_attempt_count

View File

@ -12,22 +12,22 @@ use crate::{
}; };
mod authentication_attempt_count; mod authentication_attempt_count;
mod authentication_count;
mod authentication_success_count; mod authentication_success_count;
mod challenge_attempt_count; mod challenge_attempt_count;
mod challenge_flow_count; mod challenge_flow_count;
mod challenge_success_count; mod challenge_success_count;
mod frictionless_flow_count; mod frictionless_flow_count;
mod frictionless_success_count; mod frictionless_success_count;
mod three_ds_sdk_count;
use authentication_attempt_count::AuthenticationAttemptCount; use authentication_attempt_count::AuthenticationAttemptCount;
use authentication_count::AuthenticationCount;
use authentication_success_count::AuthenticationSuccessCount; use authentication_success_count::AuthenticationSuccessCount;
use challenge_attempt_count::ChallengeAttemptCount; use challenge_attempt_count::ChallengeAttemptCount;
use challenge_flow_count::ChallengeFlowCount; use challenge_flow_count::ChallengeFlowCount;
use challenge_success_count::ChallengeSuccessCount; use challenge_success_count::ChallengeSuccessCount;
use frictionless_flow_count::FrictionlessFlowCount; use frictionless_flow_count::FrictionlessFlowCount;
use frictionless_success_count::FrictionlessSuccessCount; use frictionless_success_count::FrictionlessSuccessCount;
use three_ds_sdk_count::ThreeDsSdkCount;
#[derive(Debug, PartialEq, Eq, serde::Deserialize, Hash)] #[derive(Debug, PartialEq, Eq, serde::Deserialize, Hash)]
pub struct AuthEventMetricRow { pub struct AuthEventMetricRow {
@ -45,7 +45,6 @@ where
async fn load_metrics( async fn load_metrics(
&self, &self,
merchant_id: &common_utils::id_type::MerchantId, merchant_id: &common_utils::id_type::MerchantId,
publishable_key: &str,
granularity: Option<Granularity>, granularity: Option<Granularity>,
time_range: &TimeRange, time_range: &TimeRange,
pool: &T, pool: &T,
@ -65,50 +64,49 @@ where
async fn load_metrics( async fn load_metrics(
&self, &self,
merchant_id: &common_utils::id_type::MerchantId, merchant_id: &common_utils::id_type::MerchantId,
publishable_key: &str,
granularity: Option<Granularity>, granularity: Option<Granularity>,
time_range: &TimeRange, time_range: &TimeRange,
pool: &T, pool: &T,
) -> MetricsResult<HashSet<(AuthEventMetricsBucketIdentifier, AuthEventMetricRow)>> { ) -> MetricsResult<HashSet<(AuthEventMetricsBucketIdentifier, AuthEventMetricRow)>> {
match self { match self {
Self::ThreeDsSdkCount => { Self::AuthenticationCount => {
ThreeDsSdkCount AuthenticationCount
.load_metrics(merchant_id, publishable_key, granularity, time_range, pool) .load_metrics(merchant_id, granularity, time_range, pool)
.await .await
} }
Self::AuthenticationAttemptCount => { Self::AuthenticationAttemptCount => {
AuthenticationAttemptCount AuthenticationAttemptCount
.load_metrics(merchant_id, publishable_key, granularity, time_range, pool) .load_metrics(merchant_id, granularity, time_range, pool)
.await .await
} }
Self::AuthenticationSuccessCount => { Self::AuthenticationSuccessCount => {
AuthenticationSuccessCount AuthenticationSuccessCount
.load_metrics(merchant_id, publishable_key, granularity, time_range, pool) .load_metrics(merchant_id, granularity, time_range, pool)
.await .await
} }
Self::ChallengeFlowCount => { Self::ChallengeFlowCount => {
ChallengeFlowCount ChallengeFlowCount
.load_metrics(merchant_id, publishable_key, granularity, time_range, pool) .load_metrics(merchant_id, granularity, time_range, pool)
.await .await
} }
Self::ChallengeAttemptCount => { Self::ChallengeAttemptCount => {
ChallengeAttemptCount ChallengeAttemptCount
.load_metrics(merchant_id, publishable_key, granularity, time_range, pool) .load_metrics(merchant_id, granularity, time_range, pool)
.await .await
} }
Self::ChallengeSuccessCount => { Self::ChallengeSuccessCount => {
ChallengeSuccessCount ChallengeSuccessCount
.load_metrics(merchant_id, publishable_key, granularity, time_range, pool) .load_metrics(merchant_id, granularity, time_range, pool)
.await .await
} }
Self::FrictionlessFlowCount => { Self::FrictionlessFlowCount => {
FrictionlessFlowCount FrictionlessFlowCount
.load_metrics(merchant_id, publishable_key, granularity, time_range, pool) .load_metrics(merchant_id, granularity, time_range, pool)
.await .await
} }
Self::FrictionlessSuccessCount => { Self::FrictionlessSuccessCount => {
FrictionlessSuccessCount FrictionlessSuccessCount
.load_metrics(merchant_id, publishable_key, granularity, time_range, pool) .load_metrics(merchant_id, granularity, time_range, pool)
.await .await
} }
} }

View File

@ -1,9 +1,9 @@
use std::collections::HashSet; use std::collections::HashSet;
use api_models::analytics::{ use api_models::analytics::{
auth_events::AuthEventMetricsBucketIdentifier, sdk_events::SdkEventNames, Granularity, auth_events::AuthEventMetricsBucketIdentifier, Granularity, TimeRange,
TimeRange,
}; };
use common_enums::AuthenticationStatus;
use common_utils::errors::ReportSwitchExt; use common_utils::errors::ReportSwitchExt;
use error_stack::ResultExt; use error_stack::ResultExt;
use time::PrimitiveDateTime; use time::PrimitiveDateTime;
@ -29,14 +29,13 @@ where
{ {
async fn load_metrics( async fn load_metrics(
&self, &self,
_merchant_id: &common_utils::id_type::MerchantId, merchant_id: &common_utils::id_type::MerchantId,
publishable_key: &str,
granularity: Option<Granularity>, granularity: Option<Granularity>,
time_range: &TimeRange, time_range: &TimeRange,
pool: &T, pool: &T,
) -> MetricsResult<HashSet<(AuthEventMetricsBucketIdentifier, AuthEventMetricRow)>> { ) -> MetricsResult<HashSet<(AuthEventMetricsBucketIdentifier, AuthEventMetricRow)>> {
let mut query_builder: QueryBuilder<T> = let mut query_builder: QueryBuilder<T> =
QueryBuilder::new(AnalyticsCollection::SdkEventsAnalytics); QueryBuilder::new(AnalyticsCollection::Authentications);
query_builder query_builder
.add_select_column(Aggregate::Count { .add_select_column(Aggregate::Count {
@ -45,30 +44,26 @@ where
}) })
.switch()?; .switch()?;
if let Some(granularity) = granularity {
query_builder
.add_granularity_in_mins(granularity)
.switch()?;
}
query_builder query_builder
.add_filter_clause("merchant_id", publishable_key) .add_select_column(Aggregate::Min {
field: "created_at",
alias: Some("start_bucket"),
})
.switch()?; .switch()?;
query_builder query_builder
.add_bool_filter_clause("first_event", 1) .add_select_column(Aggregate::Max {
field: "created_at",
alias: Some("end_bucket"),
})
.switch()?; .switch()?;
query_builder query_builder
.add_filter_clause("event_name", SdkEventNames::AuthenticationCallInit) .add_filter_clause("merchant_id", merchant_id)
.switch()?; .switch()?;
query_builder query_builder
.add_filter_clause("log_type", "INFO") .add_negative_filter_clause("authentication_status", AuthenticationStatus::Pending)
.switch()?;
query_builder
.add_filter_clause("category", "API")
.switch()?; .switch()?;
time_range time_range
@ -76,9 +71,9 @@ where
.attach_printable("Error filtering time range") .attach_printable("Error filtering time range")
.switch()?; .switch()?;
if let Some(_granularity) = granularity.as_ref() { if let Some(granularity) = granularity {
query_builder granularity
.add_group_by_clause("time_bucket") .set_group_by_clause(&mut query_builder)
.attach_printable("Error adding granularity") .attach_printable("Error adding granularity")
.switch()?; .switch()?;
} }

View File

@ -1,8 +1,7 @@
use std::collections::HashSet; use std::collections::HashSet;
use api_models::analytics::{ use api_models::analytics::{
auth_events::AuthEventMetricsBucketIdentifier, sdk_events::SdkEventNames, Granularity, auth_events::AuthEventMetricsBucketIdentifier, Granularity, TimeRange,
TimeRange,
}; };
use common_utils::errors::ReportSwitchExt; use common_utils::errors::ReportSwitchExt;
use error_stack::ResultExt; use error_stack::ResultExt;
@ -15,10 +14,10 @@ use crate::{
}; };
#[derive(Default)] #[derive(Default)]
pub(super) struct ThreeDsSdkCount; pub(super) struct AuthenticationCount;
#[async_trait::async_trait] #[async_trait::async_trait]
impl<T> super::AuthEventMetric<T> for ThreeDsSdkCount impl<T> super::AuthEventMetric<T> for AuthenticationCount
where where
T: AnalyticsDataSource + super::AuthEventMetricAnalytics, T: AnalyticsDataSource + super::AuthEventMetricAnalytics,
PrimitiveDateTime: ToSql<T>, PrimitiveDateTime: ToSql<T>,
@ -29,14 +28,13 @@ where
{ {
async fn load_metrics( async fn load_metrics(
&self, &self,
_merchant_id: &common_utils::id_type::MerchantId, merchant_id: &common_utils::id_type::MerchantId,
publishable_key: &str,
granularity: Option<Granularity>, granularity: Option<Granularity>,
time_range: &TimeRange, time_range: &TimeRange,
pool: &T, pool: &T,
) -> MetricsResult<HashSet<(AuthEventMetricsBucketIdentifier, AuthEventMetricRow)>> { ) -> MetricsResult<HashSet<(AuthEventMetricsBucketIdentifier, AuthEventMetricRow)>> {
let mut query_builder: QueryBuilder<T> = let mut query_builder: QueryBuilder<T> =
QueryBuilder::new(AnalyticsCollection::SdkEventsAnalytics); QueryBuilder::new(AnalyticsCollection::Authentications);
query_builder query_builder
.add_select_column(Aggregate::Count { .add_select_column(Aggregate::Count {
@ -45,30 +43,22 @@ where
}) })
.switch()?; .switch()?;
if let Some(granularity) = granularity {
query_builder
.add_granularity_in_mins(granularity)
.switch()?;
}
query_builder query_builder
.add_filter_clause("merchant_id", publishable_key) .add_select_column(Aggregate::Min {
field: "created_at",
alias: Some("start_bucket"),
})
.switch()?; .switch()?;
query_builder query_builder
.add_bool_filter_clause("first_event", 1) .add_select_column(Aggregate::Max {
field: "created_at",
alias: Some("end_bucket"),
})
.switch()?; .switch()?;
query_builder query_builder
.add_filter_clause("category", "USER_EVENT") .add_filter_clause("merchant_id", merchant_id)
.switch()?;
query_builder
.add_filter_clause("log_type", "INFO")
.switch()?;
query_builder
.add_filter_clause("event_name", SdkEventNames::ThreeDsMethod)
.switch()?; .switch()?;
time_range time_range
@ -76,9 +66,9 @@ where
.attach_printable("Error filtering time range") .attach_printable("Error filtering time range")
.switch()?; .switch()?;
if let Some(_granularity) = granularity.as_ref() { if let Some(granularity) = granularity {
query_builder granularity
.add_group_by_clause("time_bucket") .set_group_by_clause(&mut query_builder)
.attach_printable("Error adding granularity") .attach_printable("Error adding granularity")
.switch()?; .switch()?;
} }

View File

@ -1,9 +1,9 @@
use std::collections::HashSet; use std::collections::HashSet;
use api_models::analytics::{ use api_models::analytics::{
auth_events::AuthEventMetricsBucketIdentifier, sdk_events::SdkEventNames, Granularity, auth_events::AuthEventMetricsBucketIdentifier, Granularity, TimeRange,
TimeRange,
}; };
use common_enums::AuthenticationStatus;
use common_utils::errors::ReportSwitchExt; use common_utils::errors::ReportSwitchExt;
use error_stack::ResultExt; use error_stack::ResultExt;
use time::PrimitiveDateTime; use time::PrimitiveDateTime;
@ -29,14 +29,13 @@ where
{ {
async fn load_metrics( async fn load_metrics(
&self, &self,
_merchant_id: &common_utils::id_type::MerchantId, merchant_id: &common_utils::id_type::MerchantId,
publishable_key: &str,
granularity: Option<Granularity>, granularity: Option<Granularity>,
time_range: &TimeRange, time_range: &TimeRange,
pool: &T, pool: &T,
) -> MetricsResult<HashSet<(AuthEventMetricsBucketIdentifier, AuthEventMetricRow)>> { ) -> MetricsResult<HashSet<(AuthEventMetricsBucketIdentifier, AuthEventMetricRow)>> {
let mut query_builder: QueryBuilder<T> = let mut query_builder: QueryBuilder<T> =
QueryBuilder::new(AnalyticsCollection::SdkEventsAnalytics); QueryBuilder::new(AnalyticsCollection::Authentications);
query_builder query_builder
.add_select_column(Aggregate::Count { .add_select_column(Aggregate::Count {
@ -45,30 +44,26 @@ where
}) })
.switch()?; .switch()?;
if let Some(granularity) = granularity {
query_builder
.add_granularity_in_mins(granularity)
.switch()?;
}
query_builder query_builder
.add_filter_clause("merchant_id", publishable_key) .add_select_column(Aggregate::Min {
field: "created_at",
alias: Some("start_bucket"),
})
.switch()?; .switch()?;
query_builder query_builder
.add_bool_filter_clause("first_event", 1) .add_select_column(Aggregate::Max {
field: "created_at",
alias: Some("end_bucket"),
})
.switch()?; .switch()?;
query_builder query_builder
.add_filter_clause("event_name", SdkEventNames::AuthenticationCall) .add_filter_clause("merchant_id", merchant_id)
.switch()?; .switch()?;
query_builder query_builder
.add_filter_clause("log_type", "INFO") .add_filter_clause("authentication_status", AuthenticationStatus::Success)
.switch()?;
query_builder
.add_filter_clause("category", "API")
.switch()?; .switch()?;
time_range time_range
@ -76,9 +71,9 @@ where
.attach_printable("Error filtering time range") .attach_printable("Error filtering time range")
.switch()?; .switch()?;
if let Some(_granularity) = granularity.as_ref() { if let Some(granularity) = granularity {
query_builder granularity
.add_group_by_clause("time_bucket") .set_group_by_clause(&mut query_builder)
.attach_printable("Error adding granularity") .attach_printable("Error adding granularity")
.switch()?; .switch()?;
} }

View File

@ -1,8 +1,7 @@
use std::collections::HashSet; use std::collections::HashSet;
use api_models::analytics::{ use api_models::analytics::{
auth_events::{AuthEventFlows, AuthEventMetricsBucketIdentifier}, auth_events::AuthEventMetricsBucketIdentifier, Granularity, TimeRange,
Granularity, TimeRange,
}; };
use common_utils::errors::ReportSwitchExt; use common_utils::errors::ReportSwitchExt;
use error_stack::ResultExt; use error_stack::ResultExt;
@ -10,7 +9,7 @@ use time::PrimitiveDateTime;
use super::AuthEventMetricRow; use super::AuthEventMetricRow;
use crate::{ use crate::{
query::{Aggregate, FilterTypes, GroupByClause, QueryBuilder, QueryFilter, ToSql, Window}, query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, ToSql, Window},
types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult}, types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult},
}; };
@ -30,13 +29,12 @@ where
async fn load_metrics( async fn load_metrics(
&self, &self,
merchant_id: &common_utils::id_type::MerchantId, merchant_id: &common_utils::id_type::MerchantId,
_publishable_key: &str,
granularity: Option<Granularity>, granularity: Option<Granularity>,
time_range: &TimeRange, time_range: &TimeRange,
pool: &T, pool: &T,
) -> MetricsResult<HashSet<(AuthEventMetricsBucketIdentifier, AuthEventMetricRow)>> { ) -> MetricsResult<HashSet<(AuthEventMetricsBucketIdentifier, AuthEventMetricRow)>> {
let mut query_builder: QueryBuilder<T> = let mut query_builder: QueryBuilder<T> =
QueryBuilder::new(AnalyticsCollection::ApiEventsAnalytics); QueryBuilder::new(AnalyticsCollection::Authentications);
query_builder query_builder
.add_select_column(Aggregate::Count { .add_select_column(Aggregate::Count {
@ -45,22 +43,30 @@ where
}) })
.switch()?; .switch()?;
if let Some(granularity) = granularity { query_builder
query_builder .add_select_column(Aggregate::Min {
.add_granularity_in_mins(granularity) field: "created_at",
.switch()?; alias: Some("start_bucket"),
} })
.switch()?;
query_builder
.add_select_column(Aggregate::Max {
field: "created_at",
alias: Some("end_bucket"),
})
.switch()?;
query_builder query_builder
.add_filter_clause("merchant_id", merchant_id) .add_filter_clause("merchant_id", merchant_id)
.switch()?; .switch()?;
query_builder query_builder
.add_filter_clause("api_flow", AuthEventFlows::IncomingWebhookReceive) .add_filter_clause("trans_status", "C".to_string())
.switch()?; .switch()?;
query_builder query_builder
.add_custom_filter_clause("request", "threeDSServerTransID", FilterTypes::Like) .add_negative_filter_clause("authentication_status", "pending")
.switch()?; .switch()?;
time_range time_range
@ -68,9 +74,9 @@ where
.attach_printable("Error filtering time range") .attach_printable("Error filtering time range")
.switch()?; .switch()?;
if let Some(_granularity) = granularity.as_ref() { if let Some(granularity) = granularity {
query_builder granularity
.add_group_by_clause("time_bucket") .set_group_by_clause(&mut query_builder)
.attach_printable("Error adding granularity") .attach_printable("Error adding granularity")
.switch()?; .switch()?;
} }

View File

@ -1,8 +1,7 @@
use std::collections::HashSet; use std::collections::HashSet;
use api_models::analytics::{ use api_models::analytics::{
auth_events::AuthEventMetricsBucketIdentifier, sdk_events::SdkEventNames, Granularity, auth_events::AuthEventMetricsBucketIdentifier, Granularity, TimeRange,
TimeRange,
}; };
use common_utils::errors::ReportSwitchExt; use common_utils::errors::ReportSwitchExt;
use error_stack::ResultExt; use error_stack::ResultExt;
@ -29,14 +28,13 @@ where
{ {
async fn load_metrics( async fn load_metrics(
&self, &self,
_merchant_id: &common_utils::id_type::MerchantId, merchant_id: &common_utils::id_type::MerchantId,
publishable_key: &str,
granularity: Option<Granularity>, granularity: Option<Granularity>,
time_range: &TimeRange, time_range: &TimeRange,
pool: &T, pool: &T,
) -> MetricsResult<HashSet<(AuthEventMetricsBucketIdentifier, AuthEventMetricRow)>> { ) -> MetricsResult<HashSet<(AuthEventMetricsBucketIdentifier, AuthEventMetricRow)>> {
let mut query_builder: QueryBuilder<T> = let mut query_builder: QueryBuilder<T> =
QueryBuilder::new(AnalyticsCollection::SdkEventsAnalytics); QueryBuilder::new(AnalyticsCollection::Authentications);
query_builder query_builder
.add_select_column(Aggregate::Count { .add_select_column(Aggregate::Count {
@ -45,42 +43,36 @@ where
}) })
.switch()?; .switch()?;
if let Some(granularity) = granularity {
query_builder
.add_granularity_in_mins(granularity)
.switch()?;
}
query_builder query_builder
.add_filter_clause("merchant_id", publishable_key) .add_select_column(Aggregate::Min {
field: "created_at",
alias: Some("start_bucket"),
})
.switch()?; .switch()?;
query_builder query_builder
.add_bool_filter_clause("first_event", 1) .add_select_column(Aggregate::Max {
field: "created_at",
alias: Some("end_bucket"),
})
.switch()?; .switch()?;
query_builder query_builder
.add_filter_clause("category", "USER_EVENT") .add_filter_clause("merchant_id", merchant_id)
.switch()?; .switch()?;
query_builder query_builder
.add_filter_clause("log_type", "INFO") .add_filter_clause("trans_status", "C".to_string())
.switch()?; .switch()?;
query_builder
.add_filter_clause("event_name", SdkEventNames::DisplayThreeDsSdk)
.switch()?;
query_builder.add_filter_clause("value", "C").switch()?;
time_range time_range
.set_filter_clause(&mut query_builder) .set_filter_clause(&mut query_builder)
.attach_printable("Error filtering time range") .attach_printable("Error filtering time range")
.switch()?; .switch()?;
if let Some(_granularity) = granularity.as_ref() { if let Some(granularity) = granularity {
query_builder granularity
.add_group_by_clause("time_bucket") .set_group_by_clause(&mut query_builder)
.attach_printable("Error adding granularity") .attach_printable("Error adding granularity")
.switch()?; .switch()?;
} }

View File

@ -1,9 +1,9 @@
use std::collections::HashSet; use std::collections::HashSet;
use api_models::analytics::{ use api_models::analytics::{
auth_events::{AuthEventFlows, AuthEventMetricsBucketIdentifier}, auth_events::AuthEventMetricsBucketIdentifier, Granularity, TimeRange,
Granularity, TimeRange,
}; };
use common_enums::AuthenticationStatus;
use common_utils::errors::ReportSwitchExt; use common_utils::errors::ReportSwitchExt;
use error_stack::ResultExt; use error_stack::ResultExt;
use time::PrimitiveDateTime; use time::PrimitiveDateTime;
@ -30,13 +30,12 @@ where
async fn load_metrics( async fn load_metrics(
&self, &self,
merchant_id: &common_utils::id_type::MerchantId, merchant_id: &common_utils::id_type::MerchantId,
_publishable_key: &str,
granularity: Option<Granularity>, granularity: Option<Granularity>,
time_range: &TimeRange, time_range: &TimeRange,
pool: &T, pool: &T,
) -> MetricsResult<HashSet<(AuthEventMetricsBucketIdentifier, AuthEventMetricRow)>> { ) -> MetricsResult<HashSet<(AuthEventMetricsBucketIdentifier, AuthEventMetricRow)>> {
let mut query_builder: QueryBuilder<T> = let mut query_builder: QueryBuilder<T> =
QueryBuilder::new(AnalyticsCollection::ApiEventsAnalytics); QueryBuilder::new(AnalyticsCollection::Authentications);
query_builder query_builder
.add_select_column(Aggregate::Count { .add_select_column(Aggregate::Count {
@ -45,22 +44,30 @@ where
}) })
.switch()?; .switch()?;
if let Some(granularity) = granularity { query_builder
query_builder .add_select_column(Aggregate::Min {
.add_granularity_in_mins(granularity) field: "created_at",
.switch()?; alias: Some("start_bucket"),
} })
.switch()?;
query_builder
.add_select_column(Aggregate::Max {
field: "created_at",
alias: Some("end_bucket"),
})
.switch()?;
query_builder query_builder
.add_filter_clause("merchant_id", merchant_id) .add_filter_clause("merchant_id", merchant_id)
.switch()?; .switch()?;
query_builder query_builder
.add_filter_clause("api_flow", AuthEventFlows::IncomingWebhookReceive) .add_filter_clause("authentication_status", AuthenticationStatus::Success)
.switch()?; .switch()?;
query_builder query_builder
.add_filter_clause("visitParamExtractRaw(request, 'transStatus')", "\"Y\"") .add_filter_clause("trans_status", "C".to_string())
.switch()?; .switch()?;
time_range time_range
@ -68,9 +75,9 @@ where
.attach_printable("Error filtering time range") .attach_printable("Error filtering time range")
.switch()?; .switch()?;
if let Some(_granularity) = granularity.as_ref() { if let Some(granularity) = granularity {
query_builder granularity
.add_group_by_clause("time_bucket") .set_group_by_clause(&mut query_builder)
.attach_printable("Error adding granularity") .attach_printable("Error adding granularity")
.switch()?; .switch()?;
} }

View File

@ -1,8 +1,7 @@
use std::collections::HashSet; use std::collections::HashSet;
use api_models::analytics::{ use api_models::analytics::{
auth_events::AuthEventMetricsBucketIdentifier, sdk_events::SdkEventNames, Granularity, auth_events::AuthEventMetricsBucketIdentifier, Granularity, TimeRange,
TimeRange,
}; };
use common_utils::errors::ReportSwitchExt; use common_utils::errors::ReportSwitchExt;
use error_stack::ResultExt; use error_stack::ResultExt;
@ -29,14 +28,13 @@ where
{ {
async fn load_metrics( async fn load_metrics(
&self, &self,
_merchant_id: &common_utils::id_type::MerchantId, merchant_id: &common_utils::id_type::MerchantId,
publishable_key: &str,
granularity: Option<Granularity>, granularity: Option<Granularity>,
time_range: &TimeRange, time_range: &TimeRange,
pool: &T, pool: &T,
) -> MetricsResult<HashSet<(AuthEventMetricsBucketIdentifier, AuthEventMetricRow)>> { ) -> MetricsResult<HashSet<(AuthEventMetricsBucketIdentifier, AuthEventMetricRow)>> {
let mut query_builder: QueryBuilder<T> = let mut query_builder: QueryBuilder<T> =
QueryBuilder::new(AnalyticsCollection::SdkEventsAnalytics); QueryBuilder::new(AnalyticsCollection::Authentications);
query_builder query_builder
.add_select_column(Aggregate::Count { .add_select_column(Aggregate::Count {
@ -45,34 +43,26 @@ where
}) })
.switch()?; .switch()?;
if let Some(granularity) = granularity {
query_builder
.add_granularity_in_mins(granularity)
.switch()?;
}
query_builder query_builder
.add_filter_clause("merchant_id", publishable_key) .add_select_column(Aggregate::Min {
field: "created_at",
alias: Some("start_bucket"),
})
.switch()?; .switch()?;
query_builder query_builder
.add_bool_filter_clause("first_event", 1) .add_select_column(Aggregate::Max {
field: "created_at",
alias: Some("end_bucket"),
})
.switch()?; .switch()?;
query_builder query_builder
.add_filter_clause("category", "USER_EVENT") .add_filter_clause("merchant_id", merchant_id)
.switch()?; .switch()?;
query_builder query_builder
.add_filter_clause("log_type", "INFO") .add_filter_clause("trans_status", "Y".to_string())
.switch()?;
query_builder
.add_filter_clause("event_name", SdkEventNames::DisplayThreeDsSdk)
.switch()?;
query_builder
.add_negative_filter_clause("value", "C")
.switch()?; .switch()?;
time_range time_range
@ -80,9 +70,9 @@ where
.attach_printable("Error filtering time range") .attach_printable("Error filtering time range")
.switch()?; .switch()?;
if let Some(_granularity) = granularity.as_ref() { if let Some(granularity) = granularity {
query_builder granularity
.add_group_by_clause("time_bucket") .set_group_by_clause(&mut query_builder)
.attach_printable("Error adding granularity") .attach_printable("Error adding granularity")
.switch()?; .switch()?;
} }

View File

@ -1,9 +1,9 @@
use std::collections::HashSet; use std::collections::HashSet;
use api_models::analytics::{ use api_models::analytics::{
auth_events::{AuthEventFlows, AuthEventMetricsBucketIdentifier}, auth_events::AuthEventMetricsBucketIdentifier, Granularity, TimeRange,
Granularity, TimeRange,
}; };
use common_enums::AuthenticationStatus;
use common_utils::errors::ReportSwitchExt; use common_utils::errors::ReportSwitchExt;
use error_stack::ResultExt; use error_stack::ResultExt;
use time::PrimitiveDateTime; use time::PrimitiveDateTime;
@ -30,13 +30,12 @@ where
async fn load_metrics( async fn load_metrics(
&self, &self,
merchant_id: &common_utils::id_type::MerchantId, merchant_id: &common_utils::id_type::MerchantId,
_publishable_key: &str,
granularity: Option<Granularity>, granularity: Option<Granularity>,
time_range: &TimeRange, time_range: &TimeRange,
pool: &T, pool: &T,
) -> MetricsResult<HashSet<(AuthEventMetricsBucketIdentifier, AuthEventMetricRow)>> { ) -> MetricsResult<HashSet<(AuthEventMetricsBucketIdentifier, AuthEventMetricRow)>> {
let mut query_builder: QueryBuilder<T> = let mut query_builder: QueryBuilder<T> =
QueryBuilder::new(AnalyticsCollection::ApiEventsAnalytics); QueryBuilder::new(AnalyticsCollection::Authentications);
query_builder query_builder
.add_select_column(Aggregate::Count { .add_select_column(Aggregate::Count {
@ -45,22 +44,30 @@ where
}) })
.switch()?; .switch()?;
if let Some(granularity) = granularity {
query_builder
.add_granularity_in_mins(granularity)
.switch()?;
}
query_builder query_builder
.add_filter_clause("merchant_id", merchant_id.get_string_repr()) .add_select_column(Aggregate::Min {
field: "created_at",
alias: Some("start_bucket"),
})
.switch()?; .switch()?;
query_builder query_builder
.add_filter_clause("api_flow", AuthEventFlows::PaymentsExternalAuthentication) .add_select_column(Aggregate::Max {
field: "created_at",
alias: Some("end_bucket"),
})
.switch()?; .switch()?;
query_builder query_builder
.add_filter_clause("visitParamExtractRaw(response, 'transStatus')", "\"Y\"") .add_filter_clause("merchant_id", merchant_id)
.switch()?;
query_builder
.add_filter_clause("trans_status", "Y".to_string())
.switch()?;
query_builder
.add_filter_clause("authentication_status", AuthenticationStatus::Success)
.switch()?; .switch()?;
time_range time_range
@ -68,9 +75,9 @@ where
.attach_printable("Error filtering time range") .attach_printable("Error filtering time range")
.switch()?; .switch()?;
if let Some(_granularity) = granularity.as_ref() { if let Some(granularity) = granularity {
query_builder granularity
.add_group_by_clause("time_bucket") .set_group_by_clause(&mut query_builder)
.attach_printable("Error adding granularity") .attach_printable("Error adding granularity")
.switch()?; .switch()?;
} }

View File

@ -138,6 +138,7 @@ impl AnalyticsDataSource for ClickhouseClient {
| AnalyticsCollection::FraudCheck | AnalyticsCollection::FraudCheck
| AnalyticsCollection::PaymentIntent | AnalyticsCollection::PaymentIntent
| AnalyticsCollection::PaymentIntentSessionized | AnalyticsCollection::PaymentIntentSessionized
| AnalyticsCollection::Authentications
| AnalyticsCollection::Dispute => { | AnalyticsCollection::Dispute => {
TableEngine::CollapsingMergeTree { sign: "sign_flag" } TableEngine::CollapsingMergeTree { sign: "sign_flag" }
} }
@ -457,6 +458,7 @@ impl ToSql<ClickhouseClient> for AnalyticsCollection {
Self::Dispute => Ok("dispute".to_string()), Self::Dispute => Ok("dispute".to_string()),
Self::DisputeSessionized => Ok("sessionizer_dispute".to_string()), Self::DisputeSessionized => Ok("sessionizer_dispute".to_string()),
Self::ActivePaymentsAnalytics => Ok("active_payments".to_string()), Self::ActivePaymentsAnalytics => Ok("active_payments".to_string()),
Self::Authentications => Ok("authentications".to_string()),
} }
} }
} }

View File

@ -909,7 +909,6 @@ impl AnalyticsProvider {
&self, &self,
metric: &AuthEventMetrics, metric: &AuthEventMetrics,
merchant_id: &common_utils::id_type::MerchantId, merchant_id: &common_utils::id_type::MerchantId,
publishable_key: &str,
granularity: Option<Granularity>, granularity: Option<Granularity>,
time_range: &TimeRange, time_range: &TimeRange,
) -> types::MetricsResult<HashSet<(AuthEventMetricsBucketIdentifier, AuthEventMetricRow)>> { ) -> types::MetricsResult<HashSet<(AuthEventMetricsBucketIdentifier, AuthEventMetricRow)>> {
@ -917,14 +916,13 @@ impl AnalyticsProvider {
Self::Sqlx(_pool) => Err(report!(MetricsError::NotImplemented)), Self::Sqlx(_pool) => Err(report!(MetricsError::NotImplemented)),
Self::Clickhouse(pool) => { Self::Clickhouse(pool) => {
metric metric
.load_metrics(merchant_id, publishable_key, granularity, time_range, pool) .load_metrics(merchant_id, granularity, time_range, pool)
.await .await
} }
Self::CombinedCkh(_sqlx_pool, ckh_pool) | Self::CombinedSqlx(_sqlx_pool, ckh_pool) => { Self::CombinedCkh(_sqlx_pool, ckh_pool) | Self::CombinedSqlx(_sqlx_pool, ckh_pool) => {
metric metric
.load_metrics( .load_metrics(
merchant_id, merchant_id,
publishable_key,
granularity, granularity,
// Since API events are ckh only use ckh here // Since API events are ckh only use ckh here
time_range, time_range,

View File

@ -19,6 +19,7 @@ use api_models::{
}, },
refunds::RefundStatus, refunds::RefundStatus,
}; };
use common_enums::{AuthenticationStatus, TransactionStatus};
use common_utils::{ use common_utils::{
errors::{CustomResult, ParsingError}, errors::{CustomResult, ParsingError},
id_type::{MerchantId, OrganizationId, ProfileId}, id_type::{MerchantId, OrganizationId, ProfileId},
@ -502,6 +503,8 @@ impl_to_sql_for_to_string!(
Currency, Currency,
RefundType, RefundType,
FrmTransactionType, FrmTransactionType,
TransactionStatus,
AuthenticationStatus,
Flow, Flow,
&String, &String,
&bool, &bool,

View File

@ -1034,6 +1034,8 @@ impl ToSql<SqlxClient> for AnalyticsCollection {
Self::Dispute => Ok("dispute".to_string()), Self::Dispute => Ok("dispute".to_string()),
Self::DisputeSessionized => Err(error_stack::report!(ParsingError::UnknownError) Self::DisputeSessionized => Err(error_stack::report!(ParsingError::UnknownError)
.attach_printable("DisputeSessionized table is not implemented for Sqlx"))?, .attach_printable("DisputeSessionized table is not implemented for Sqlx"))?,
Self::Authentications => Err(error_stack::report!(ParsingError::UnknownError)
.attach_printable("Authentications table is not implemented for Sqlx"))?,
} }
} }
} }

View File

@ -37,6 +37,7 @@ pub enum AnalyticsCollection {
PaymentIntentSessionized, PaymentIntentSessionized,
ConnectorEvents, ConnectorEvents,
OutgoingWebhookEvent, OutgoingWebhookEvent,
Authentications,
Dispute, Dispute,
DisputeSessionized, DisputeSessionized,
ApiEventsAnalytics, ApiEventsAnalytics,

View File

@ -5,6 +5,29 @@ use std::{
use super::NameDescription; use super::NameDescription;
#[derive(
Debug,
serde::Serialize,
serde::Deserialize,
strum::AsRefStr,
PartialEq,
PartialOrd,
Eq,
Ord,
strum::Display,
strum::EnumIter,
Clone,
Copy,
)]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum AuthEventDimensions {
#[serde(rename = "authentication_status")]
AuthenticationStatus,
#[serde(rename = "trans_status")]
TransactionStatus,
}
#[derive( #[derive(
Clone, Clone,
Debug, Debug,
@ -20,7 +43,7 @@ use super::NameDescription;
#[strum(serialize_all = "snake_case")] #[strum(serialize_all = "snake_case")]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub enum AuthEventMetrics { pub enum AuthEventMetrics {
ThreeDsSdkCount, AuthenticationCount,
AuthenticationAttemptCount, AuthenticationAttemptCount,
AuthenticationSuccessCount, AuthenticationSuccessCount,
ChallengeFlowCount, ChallengeFlowCount,
@ -48,7 +71,7 @@ pub enum AuthEventFlows {
} }
pub mod metric_behaviour { pub mod metric_behaviour {
pub struct ThreeDsSdkCount; pub struct AuthenticationCount;
pub struct AuthenticationAttemptCount; pub struct AuthenticationAttemptCount;
pub struct AuthenticationSuccessCount; pub struct AuthenticationSuccessCount;
pub struct ChallengeFlowCount; pub struct ChallengeFlowCount;
@ -96,7 +119,7 @@ impl PartialEq for AuthEventMetricsBucketIdentifier {
#[derive(Debug, serde::Serialize)] #[derive(Debug, serde::Serialize)]
pub struct AuthEventMetricsBucketValue { pub struct AuthEventMetricsBucketValue {
pub three_ds_sdk_count: Option<u64>, pub authentication_count: Option<u64>,
pub authentication_attempt_count: Option<u64>, pub authentication_attempt_count: Option<u64>,
pub authentication_success_count: Option<u64>, pub authentication_success_count: Option<u64>,
pub challenge_flow_count: Option<u64>, pub challenge_flow_count: Option<u64>,

View File

@ -975,7 +975,6 @@ pub mod routes {
analytics::auth_events::get_metrics( analytics::auth_events::get_metrics(
&state.pool, &state.pool,
auth.merchant_account.get_id(), auth.merchant_account.get_id(),
&auth.merchant_account.publishable_key,
req, req,
) )
.await .await