mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 17:19:15 +08:00
feat: authentication analytics (#4684)
This commit is contained in:
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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()),
|
||||
|
||||
@ -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()),
|
||||
|
||||
@ -31,7 +31,7 @@ pub enum AnalyticsCollection {
|
||||
ConnectorEvents,
|
||||
OutgoingWebhookEvent,
|
||||
Dispute,
|
||||
ConnectorEventsAnalytics,
|
||||
ApiEventsAnalytics,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
|
||||
@ -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)]
|
||||
|
||||
Reference in New Issue
Block a user