feat: authentication analytics (#4684)

This commit is contained in:
Vrishab Srivatsa
2024-05-23 15:42:29 +05:30
committed by GitHub
parent 0b415dcca6
commit 5e5eb5fbae
10 changed files with 128 additions and 14 deletions

View File

@ -11,6 +11,7 @@ pub struct AuthEventMetricsAccumulator {
pub challenge_attempt_count: CountAccumulator, pub challenge_attempt_count: CountAccumulator,
pub challenge_success_count: CountAccumulator, pub challenge_success_count: CountAccumulator,
pub frictionless_flow_count: CountAccumulator, pub frictionless_flow_count: CountAccumulator,
pub frictionless_success_count: CountAccumulator,
} }
#[derive(Debug, Default)] #[derive(Debug, Default)]
@ -53,6 +54,7 @@ impl AuthEventMetricsAccumulator {
challenge_attempt_count: self.challenge_attempt_count.collect(), challenge_attempt_count: self.challenge_attempt_count.collect(),
challenge_success_count: self.challenge_success_count.collect(), challenge_success_count: self.challenge_success_count.collect(),
frictionless_flow_count: self.frictionless_flow_count.collect(), frictionless_flow_count: self.frictionless_flow_count.collect(),
frictionless_success_count: self.frictionless_success_count.collect(),
} }
} }
} }

View File

@ -78,6 +78,9 @@ pub async fn get_metrics(
AuthEventMetrics::FrictionlessFlowCount => metrics_builder AuthEventMetrics::FrictionlessFlowCount => metrics_builder
.frictionless_flow_count .frictionless_flow_count
.add_metrics_bucket(&value), .add_metrics_bucket(&value),
AuthEventMetrics::FrictionlessSuccessCount => metrics_builder
.frictionless_success_count
.add_metrics_bucket(&value),
} }
} }
} }

View File

@ -15,6 +15,7 @@ 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 three_ds_sdk_count; mod three_ds_sdk_count;
use authentication_attempt_count::AuthenticationAttemptCount; use authentication_attempt_count::AuthenticationAttemptCount;
@ -23,6 +24,7 @@ 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 three_ds_sdk_count::ThreeDsSdkCount; use three_ds_sdk_count::ThreeDsSdkCount;
#[derive(Debug, PartialEq, Eq, serde::Deserialize)] #[derive(Debug, PartialEq, Eq, serde::Deserialize)]
@ -102,6 +104,11 @@ where
.load_metrics(merchant_id, publishable_key, granularity, time_range, pool) .load_metrics(merchant_id, publishable_key, granularity, time_range, pool)
.await .await
} }
Self::FrictionlessSuccessCount => {
FrictionlessSuccessCount
.load_metrics(merchant_id, publishable_key, granularity, time_range, pool)
.await
}
} }
} }
} }

View File

@ -8,7 +8,7 @@ use time::PrimitiveDateTime;
use super::AuthEventMetricRow; use super::AuthEventMetricRow;
use crate::{ use crate::{
query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, ToSql, Window}, query::{Aggregate, FilterTypes, GroupByClause, QueryBuilder, QueryFilter, ToSql, Window},
types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult}, types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult},
}; };
@ -34,7 +34,7 @@ where
pool: &T, pool: &T,
) -> MetricsResult<Vec<(AuthEventMetricsBucketIdentifier, AuthEventMetricRow)>> { ) -> MetricsResult<Vec<(AuthEventMetricsBucketIdentifier, AuthEventMetricRow)>> {
let mut query_builder: QueryBuilder<T> = let mut query_builder: QueryBuilder<T> =
QueryBuilder::new(AnalyticsCollection::ConnectorEventsAnalytics); QueryBuilder::new(AnalyticsCollection::ApiEventsAnalytics);
query_builder query_builder
.add_select_column(Aggregate::Count { .add_select_column(Aggregate::Count {
@ -54,7 +54,11 @@ where
.switch()?; .switch()?;
query_builder query_builder
.add_filter_clause("flow", AuthEventFlows::PostAuthentication) .add_filter_clause("api_flow", AuthEventFlows::IncomingWebhookReceive)
.switch()?;
query_builder
.add_custom_filter_clause("request", "threeDSServerTransID", FilterTypes::Like)
.switch()?; .switch()?;
time_range time_range

View File

@ -34,7 +34,7 @@ where
pool: &T, pool: &T,
) -> MetricsResult<Vec<(AuthEventMetricsBucketIdentifier, AuthEventMetricRow)>> { ) -> MetricsResult<Vec<(AuthEventMetricsBucketIdentifier, AuthEventMetricRow)>> {
let mut query_builder: QueryBuilder<T> = let mut query_builder: QueryBuilder<T> =
QueryBuilder::new(AnalyticsCollection::ConnectorEventsAnalytics); QueryBuilder::new(AnalyticsCollection::ApiEventsAnalytics);
query_builder query_builder
.add_select_column(Aggregate::Count { .add_select_column(Aggregate::Count {
@ -54,11 +54,11 @@ where
.switch()?; .switch()?;
query_builder query_builder
.add_filter_clause("flow", AuthEventFlows::PostAuthentication) .add_filter_clause("api_flow", AuthEventFlows::IncomingWebhookReceive)
.switch()?; .switch()?;
query_builder query_builder
.add_filter_clause("visitParamExtractRaw(response, 'transStatus')", "\"Y\"") .add_filter_clause("visitParamExtractRaw(request, 'transStatus')", "\"Y\"")
.switch()?; .switch()?;
time_range time_range

View File

@ -0,0 +1,94 @@
use api_models::analytics::{
auth_events::{AuthEventFlows, AuthEventMetricsBucketIdentifier},
Granularity, TimeRange,
};
use common_utils::errors::ReportSwitchExt;
use error_stack::ResultExt;
use time::PrimitiveDateTime;
use super::AuthEventMetricRow;
use crate::{
query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, ToSql, Window},
types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult},
};
#[derive(Default)]
pub(super) struct FrictionlessSuccessCount;
#[async_trait::async_trait]
impl<T> super::AuthEventMetric<T> for FrictionlessSuccessCount
where
T: AnalyticsDataSource + super::AuthEventMetricAnalytics,
PrimitiveDateTime: ToSql<T>,
AnalyticsCollection: ToSql<T>,
Granularity: GroupByClause<T>,
Aggregate<&'static str>: ToSql<T>,
Window<&'static str>: ToSql<T>,
{
async fn load_metrics(
&self,
merchant_id: &str,
_publishable_key: &str,
granularity: &Option<Granularity>,
time_range: &TimeRange,
pool: &T,
) -> MetricsResult<Vec<(AuthEventMetricsBucketIdentifier, AuthEventMetricRow)>> {
let mut query_builder: QueryBuilder<T> =
QueryBuilder::new(AnalyticsCollection::ApiEventsAnalytics);
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()?;
}
query_builder
.add_filter_clause("merchant_id", merchant_id)
.switch()?;
query_builder
.add_filter_clause("api_flow", AuthEventFlows::PaymentsExternalAuthentication)
.switch()?;
query_builder
.add_filter_clause("visitParamExtractRaw(response, 'transStatus')", "\"Y\"")
.switch()?;
time_range
.set_filter_clause(&mut query_builder)
.attach_printable("Error filtering time range")
.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::<AuthEventMetricRow, _>(pool)
.await
.change_context(MetricsError::QueryBuildingError)?
.change_context(MetricsError::QueryExecutionFailure)?
.into_iter()
.map(|i| {
Ok((
AuthEventMetricsBucketIdentifier::new(i.time_bucket.clone()),
i,
))
})
.collect::<error_stack::Result<
Vec<(AuthEventMetricsBucketIdentifier, AuthEventMetricRow)>,
crate::query::PostProcessingError,
>>()
.change_context(MetricsError::PostProcessingFailure)
}
}

View File

@ -136,7 +136,7 @@ impl AnalyticsDataSource for ClickhouseClient {
AnalyticsCollection::SdkEvents AnalyticsCollection::SdkEvents
| AnalyticsCollection::ApiEvents | AnalyticsCollection::ApiEvents
| AnalyticsCollection::ConnectorEvents | AnalyticsCollection::ConnectorEvents
| AnalyticsCollection::ConnectorEventsAnalytics | AnalyticsCollection::ApiEventsAnalytics
| AnalyticsCollection::OutgoingWebhookEvent => TableEngine::BasicTree, | AnalyticsCollection::OutgoingWebhookEvent => TableEngine::BasicTree,
} }
} }
@ -374,7 +374,7 @@ impl ToSql<ClickhouseClient> for AnalyticsCollection {
Self::Refund => Ok("refunds".to_string()), Self::Refund => Ok("refunds".to_string()),
Self::SdkEvents => Ok("sdk_events_audit".to_string()), Self::SdkEvents => Ok("sdk_events_audit".to_string()),
Self::ApiEvents => Ok("api_events_audit".to_string()), Self::ApiEvents => Ok("api_events_audit".to_string()),
Self::ConnectorEventsAnalytics => Ok("connector_events".to_string()), Self::ApiEventsAnalytics => Ok("api_events".to_string()),
Self::PaymentIntent => Ok("payment_intents".to_string()), Self::PaymentIntent => Ok("payment_intents".to_string()),
Self::ConnectorEvents => Ok("connector_events_audit".to_string()), Self::ConnectorEvents => Ok("connector_events_audit".to_string()),
Self::OutgoingWebhookEvent => Ok("outgoing_webhook_events_audit".to_string()), Self::OutgoingWebhookEvent => Ok("outgoing_webhook_events_audit".to_string()),

View File

@ -548,10 +548,10 @@ impl ToSql<SqlxClient> for AnalyticsCollection {
Self::ApiEvents => Err(error_stack::report!(ParsingError::UnknownError) Self::ApiEvents => Err(error_stack::report!(ParsingError::UnknownError)
.attach_printable("ApiEvents table is not implemented for Sqlx"))?, .attach_printable("ApiEvents table is not implemented for Sqlx"))?,
Self::PaymentIntent => Ok("payment_intent".to_string()), Self::PaymentIntent => Ok("payment_intent".to_string()),
Self::ConnectorEvents | Self::ConnectorEventsAnalytics => { Self::ConnectorEvents => Err(error_stack::report!(ParsingError::UnknownError)
Err(error_stack::report!(ParsingError::UnknownError) .attach_printable("ConnectorEvents table is not implemented for Sqlx"))?,
.attach_printable("ConnectorEvents table is not implemented for Sqlx"))? Self::ApiEventsAnalytics => Err(error_stack::report!(ParsingError::UnknownError)
} .attach_printable("ApiEvents table is not implemented for Sqlx"))?,
Self::OutgoingWebhookEvent => Err(error_stack::report!(ParsingError::UnknownError) Self::OutgoingWebhookEvent => Err(error_stack::report!(ParsingError::UnknownError)
.attach_printable("OutgoingWebhookEvents table is not implemented for Sqlx"))?, .attach_printable("OutgoingWebhookEvents table is not implemented for Sqlx"))?,
Self::Dispute => Ok("dispute".to_string()), Self::Dispute => Ok("dispute".to_string()),

View File

@ -31,7 +31,7 @@ pub enum AnalyticsCollection {
ConnectorEvents, ConnectorEvents,
OutgoingWebhookEvent, OutgoingWebhookEvent,
Dispute, Dispute,
ConnectorEventsAnalytics, ApiEventsAnalytics,
} }
#[allow(dead_code)] #[allow(dead_code)]

View File

@ -25,6 +25,7 @@ pub enum AuthEventMetrics {
AuthenticationSuccessCount, AuthenticationSuccessCount,
ChallengeFlowCount, ChallengeFlowCount,
FrictionlessFlowCount, FrictionlessFlowCount,
FrictionlessSuccessCount,
ChallengeAttemptCount, ChallengeAttemptCount,
ChallengeSuccessCount, ChallengeSuccessCount,
} }
@ -42,7 +43,8 @@ pub enum AuthEventMetrics {
strum::AsRefStr, strum::AsRefStr,
)] )]
pub enum AuthEventFlows { pub enum AuthEventFlows {
PostAuthentication, IncomingWebhookReceive,
PaymentsExternalAuthentication,
} }
pub mod metric_behaviour { pub mod metric_behaviour {
@ -51,6 +53,7 @@ pub mod metric_behaviour {
pub struct AuthenticationSuccessCount; pub struct AuthenticationSuccessCount;
pub struct ChallengeFlowCount; pub struct ChallengeFlowCount;
pub struct FrictionlessFlowCount; pub struct FrictionlessFlowCount;
pub struct FrictionlessSuccessCount;
pub struct ChallengeAttemptCount; pub struct ChallengeAttemptCount;
pub struct ChallengeSuccessCount; pub struct ChallengeSuccessCount;
} }
@ -100,6 +103,7 @@ pub struct AuthEventMetricsBucketValue {
pub challenge_attempt_count: Option<u64>, pub challenge_attempt_count: Option<u64>,
pub challenge_success_count: Option<u64>, pub challenge_success_count: Option<u64>,
pub frictionless_flow_count: Option<u64>, pub frictionless_flow_count: Option<u64>,
pub frictionless_success_count: Option<u64>,
} }
#[derive(Debug, serde::Serialize)] #[derive(Debug, serde::Serialize)]