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_success_count: CountAccumulator,
pub frictionless_flow_count: CountAccumulator,
pub frictionless_success_count: CountAccumulator,
}
#[derive(Debug, Default)]
@ -53,6 +54,7 @@ impl AuthEventMetricsAccumulator {
challenge_attempt_count: self.challenge_attempt_count.collect(),
challenge_success_count: self.challenge_success_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
.frictionless_flow_count
.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_success_count;
mod frictionless_flow_count;
mod frictionless_success_count;
mod three_ds_sdk_count;
use authentication_attempt_count::AuthenticationAttemptCount;
@ -23,6 +24,7 @@ use challenge_attempt_count::ChallengeAttemptCount;
use challenge_flow_count::ChallengeFlowCount;
use challenge_success_count::ChallengeSuccessCount;
use frictionless_flow_count::FrictionlessFlowCount;
use frictionless_success_count::FrictionlessSuccessCount;
use three_ds_sdk_count::ThreeDsSdkCount;
#[derive(Debug, PartialEq, Eq, serde::Deserialize)]
@ -102,6 +104,11 @@ where
.load_metrics(merchant_id, publishable_key, granularity, time_range, pool)
.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 crate::{
query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, ToSql, Window},
query::{Aggregate, FilterTypes, GroupByClause, QueryBuilder, QueryFilter, ToSql, Window},
types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult},
};
@ -34,7 +34,7 @@ where
pool: &T,
) -> MetricsResult<Vec<(AuthEventMetricsBucketIdentifier, AuthEventMetricRow)>> {
let mut query_builder: QueryBuilder<T> =
QueryBuilder::new(AnalyticsCollection::ConnectorEventsAnalytics);
QueryBuilder::new(AnalyticsCollection::ApiEventsAnalytics);
query_builder
.add_select_column(Aggregate::Count {
@ -54,7 +54,11 @@ where
.switch()?;
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()?;
time_range

View File

@ -34,7 +34,7 @@ where
pool: &T,
) -> MetricsResult<Vec<(AuthEventMetricsBucketIdentifier, AuthEventMetricRow)>> {
let mut query_builder: QueryBuilder<T> =
QueryBuilder::new(AnalyticsCollection::ConnectorEventsAnalytics);
QueryBuilder::new(AnalyticsCollection::ApiEventsAnalytics);
query_builder
.add_select_column(Aggregate::Count {
@ -54,11 +54,11 @@ where
.switch()?;
query_builder
.add_filter_clause("flow", AuthEventFlows::PostAuthentication)
.add_filter_clause("api_flow", AuthEventFlows::IncomingWebhookReceive)
.switch()?;
query_builder
.add_filter_clause("visitParamExtractRaw(response, 'transStatus')", "\"Y\"")
.add_filter_clause("visitParamExtractRaw(request, 'transStatus')", "\"Y\"")
.switch()?;
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::ApiEvents
| AnalyticsCollection::ConnectorEvents
| AnalyticsCollection::ConnectorEventsAnalytics
| AnalyticsCollection::ApiEventsAnalytics
| AnalyticsCollection::OutgoingWebhookEvent => TableEngine::BasicTree,
}
}
@ -374,7 +374,7 @@ impl ToSql<ClickhouseClient> for AnalyticsCollection {
Self::Refund => Ok("refunds".to_string()),
Self::SdkEvents => Ok("sdk_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::ConnectorEvents => Ok("connector_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)
.attach_printable("ApiEvents table is not implemented for Sqlx"))?,
Self::PaymentIntent => Ok("payment_intent".to_string()),
Self::ConnectorEvents | Self::ConnectorEventsAnalytics => {
Err(error_stack::report!(ParsingError::UnknownError)
.attach_printable("ConnectorEvents table is not implemented for Sqlx"))?
}
Self::ConnectorEvents => Err(error_stack::report!(ParsingError::UnknownError)
.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)
.attach_printable("OutgoingWebhookEvents table is not implemented for Sqlx"))?,
Self::Dispute => Ok("dispute".to_string()),

View File

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

View File

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