mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-30 01:27:31 +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_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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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::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()),
|
||||||
|
|||||||
@ -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()),
|
||||||
|
|||||||
@ -31,7 +31,7 @@ pub enum AnalyticsCollection {
|
|||||||
ConnectorEvents,
|
ConnectorEvents,
|
||||||
OutgoingWebhookEvent,
|
OutgoingWebhookEvent,
|
||||||
Dispute,
|
Dispute,
|
||||||
ConnectorEventsAnalytics,
|
ApiEventsAnalytics,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
|||||||
@ -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)]
|
||||||
|
|||||||
Reference in New Issue
Block a user