Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(analytics): authentication analytics #4429

Merged
merged 23 commits into from
May 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
a5084a0
feat(analytics): Added load time metric for sdk, added percentile agg…
Apr 5, 2024
78b7ab1
fix: address clippy lints
Apr 8, 2024
5f19cfd
fix: modify percentile type to u8
Apr 8, 2024
71576d6
fix: modified alias in queries
Apr 9, 2024
361e0ba
Removed authentication metrics from sdk_events
Apr 16, 2024
3e58ffb
feat:authentication analytics - separated from sdk events
Apr 22, 2024
5db9032
Added common filters to the authentication metrics queries
Apr 22, 2024
89b407d
fix: compatibility with main branch changes
Apr 22, 2024
26f2c05
Merge branch 'main' into feat/authentication_analytics_2
vsrivatsa-edinburgh Apr 23, 2024
2376dcb
refactor: Update crates/analytics/src/query.rs
vsrivatsa-edinburgh Apr 26, 2024
1c96e03
chore: run formatter
hyperswitch-bot[bot] Apr 26, 2024
70f2a9f
chore: resolve comments
Apr 26, 2024
7d7c6b5
Merge branch 'main' of https://github.com/juspay/hyperswitch into fea…
Apr 26, 2024
96a15b1
Merge branch 'feat/authentication_analytics_2' of https://github.com/…
Apr 26, 2024
883c165
Merge branch 'main' into feat/authentication_analytics_2
vsrivatsa-edinburgh Apr 28, 2024
dc2c26d
Merge branch 'main' into feat/authentication_analytics_2
vsrivatsa-edinburgh May 2, 2024
d3c1954
chore: address clippy lints
May 3, 2024
fa54dd0
Merge branch 'feat/authentication_analytics_2' of https://github.com/…
May 3, 2024
0872ae2
fix: updated AnalyticsCollection for connector event metrics
May 6, 2024
fdfaebe
Merge branch 'main' of https://github.com/juspay/hyperswitch into fea…
May 7, 2024
3389032
Merge branch 'main' into feat/authentication_analytics_2
vsrivatsa-edinburgh May 9, 2024
9771d47
Merge branch 'main' into feat/authentication_analytics_2
vsrivatsa-edinburgh May 9, 2024
ed8eea3
Merge branch 'main' into feat/authentication_analytics_2
vsrivatsa-edinburgh May 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions crates/analytics/src/auth_events.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pub mod accumulator;
mod core;
pub mod metrics;
pub use accumulator::{AuthEventMetricAccumulator, AuthEventMetricsAccumulator};

pub use self::core::get_metrics;
58 changes: 58 additions & 0 deletions crates/analytics/src/auth_events/accumulator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use api_models::analytics::auth_events::AuthEventMetricsBucketValue;

use super::metrics::AuthEventMetricRow;

#[derive(Debug, Default)]
pub struct AuthEventMetricsAccumulator {
pub three_ds_sdk_count: CountAccumulator,
pub authentication_attempt_count: CountAccumulator,
pub authentication_success_count: CountAccumulator,
pub challenge_flow_count: CountAccumulator,
pub challenge_attempt_count: CountAccumulator,
pub challenge_success_count: CountAccumulator,
pub frictionless_flow_count: CountAccumulator,
}

#[derive(Debug, Default)]
#[repr(transparent)]
pub struct CountAccumulator {
pub count: Option<i64>,
}

pub trait AuthEventMetricAccumulator {
type MetricOutput;

fn add_metrics_bucket(&mut self, metrics: &AuthEventMetricRow);

fn collect(self) -> Self::MetricOutput;
}

impl AuthEventMetricAccumulator for CountAccumulator {
type MetricOutput = Option<u64>;
#[inline]
fn add_metrics_bucket(&mut self, metrics: &AuthEventMetricRow) {
self.count = match (self.count, metrics.count) {
(None, None) => None,
(None, i @ Some(_)) | (i @ Some(_), None) => i,
(Some(a), Some(b)) => Some(a + b),
}
}
#[inline]
fn collect(self) -> Self::MetricOutput {
self.count.and_then(|i| u64::try_from(i).ok())
}
}

impl AuthEventMetricsAccumulator {
pub fn collect(self) -> AuthEventMetricsBucketValue {
AuthEventMetricsBucketValue {
three_ds_sdk_count: self.three_ds_sdk_count.collect(),
authentication_attempt_count: self.authentication_attempt_count.collect(),
authentication_success_count: self.authentication_success_count.collect(),
challenge_flow_count: self.challenge_flow_count.collect(),
challenge_attempt_count: self.challenge_attempt_count.collect(),
challenge_success_count: self.challenge_success_count.collect(),
frictionless_flow_count: self.frictionless_flow_count.collect(),
}
}
}
108 changes: 108 additions & 0 deletions crates/analytics/src/auth_events/core.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
use std::collections::HashMap;

use api_models::analytics::{
auth_events::{AuthEventMetrics, AuthEventMetricsBucketIdentifier, MetricsBucketResponse},
AnalyticsMetadata, GetAuthEventMetricRequest, MetricsResponse,
};
use error_stack::ResultExt;
use router_env::{instrument, logger, tracing};

use super::AuthEventMetricsAccumulator;
use crate::{
auth_events::AuthEventMetricAccumulator,
errors::{AnalyticsError, AnalyticsResult},
AnalyticsProvider,
};

#[instrument(skip_all)]
pub async fn get_metrics(
pool: &AnalyticsProvider,
merchant_id: &String,
publishable_key: Option<&String>,
req: GetAuthEventMetricRequest,
) -> AnalyticsResult<MetricsResponse<MetricsBucketResponse>> {
let mut metrics_accumulator: HashMap<
AuthEventMetricsBucketIdentifier,
AuthEventMetricsAccumulator,
> = HashMap::new();

if let Some(publishable_key) = publishable_key {
let mut set = tokio::task::JoinSet::new();
for metric_type in req.metrics.iter().cloned() {
let req = req.clone();
let merchant_id_scoped = merchant_id.to_owned();
let publishable_key_scoped = publishable_key.to_owned();
let pool = pool.clone();
set.spawn(async move {
let data = pool
.get_auth_event_metrics(
&metric_type,
&merchant_id_scoped,
&publishable_key_scoped,
&req.time_series.map(|t| t.granularity),
&req.time_range,
)
.await
.change_context(AnalyticsError::UnknownError);
(metric_type, data)
});
}

while let Some((metric, data)) = set
.join_next()
.await
.transpose()
.change_context(AnalyticsError::UnknownError)?
{
for (id, value) in data? {
let metrics_builder = metrics_accumulator.entry(id).or_default();
match metric {
AuthEventMetrics::ThreeDsSdkCount => metrics_builder
.three_ds_sdk_count
.add_metrics_bucket(&value),
AuthEventMetrics::AuthenticationAttemptCount => metrics_builder
.authentication_attempt_count
.add_metrics_bucket(&value),
AuthEventMetrics::AuthenticationSuccessCount => metrics_builder
.authentication_success_count
.add_metrics_bucket(&value),
AuthEventMetrics::ChallengeFlowCount => metrics_builder
.challenge_flow_count
.add_metrics_bucket(&value),
AuthEventMetrics::ChallengeAttemptCount => metrics_builder
.challenge_attempt_count
.add_metrics_bucket(&value),
AuthEventMetrics::ChallengeSuccessCount => metrics_builder
.challenge_success_count
.add_metrics_bucket(&value),
AuthEventMetrics::FrictionlessFlowCount => metrics_builder
.frictionless_flow_count
.add_metrics_bucket(&value),
}
}
}

let query_data: Vec<MetricsBucketResponse> = metrics_accumulator
.into_iter()
.map(|(id, val)| MetricsBucketResponse {
values: val.collect(),
dimensions: id,
})
.collect();

Ok(MetricsResponse {
query_data,
meta_data: [AnalyticsMetadata {
current_time_range: req.time_range,
}],
})
} else {
logger::error!("Publishable key not present for merchant ID");
Ok(MetricsResponse {
query_data: vec![],
meta_data: [AnalyticsMetadata {
current_time_range: req.time_range,
}],
})
}
}
107 changes: 107 additions & 0 deletions crates/analytics/src/auth_events/metrics.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
use api_models::analytics::{
auth_events::{AuthEventMetrics, AuthEventMetricsBucketIdentifier},
Granularity, TimeRange,
};
use time::PrimitiveDateTime;

use crate::{
query::{Aggregate, GroupByClause, ToSql, Window},
types::{AnalyticsCollection, AnalyticsDataSource, LoadRow, MetricsResult},
};

mod authentication_attempt_count;
mod authentication_success_count;
mod challenge_attempt_count;
mod challenge_flow_count;
mod challenge_success_count;
mod frictionless_flow_count;
mod three_ds_sdk_count;

use authentication_attempt_count::AuthenticationAttemptCount;
use authentication_success_count::AuthenticationSuccessCount;
use challenge_attempt_count::ChallengeAttemptCount;
use challenge_flow_count::ChallengeFlowCount;
use challenge_success_count::ChallengeSuccessCount;
use frictionless_flow_count::FrictionlessFlowCount;
use three_ds_sdk_count::ThreeDsSdkCount;

#[derive(Debug, PartialEq, Eq, serde::Deserialize)]
pub struct AuthEventMetricRow {
pub count: Option<i64>,
pub time_bucket: Option<String>,
}

pub trait AuthEventMetricAnalytics: LoadRow<AuthEventMetricRow> {}

#[async_trait::async_trait]
pub trait AuthEventMetric<T>
where
T: AnalyticsDataSource + AuthEventMetricAnalytics,
{
async fn load_metrics(
&self,
merchant_id: &str,
publishable_key: &str,
granularity: &Option<Granularity>,
time_range: &TimeRange,
pool: &T,
) -> MetricsResult<Vec<(AuthEventMetricsBucketIdentifier, AuthEventMetricRow)>>;
}

#[async_trait::async_trait]
impl<T> AuthEventMetric<T> for AuthEventMetrics
where
T: AnalyticsDataSource + 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)>> {
match self {
Self::ThreeDsSdkCount => {
ThreeDsSdkCount
.load_metrics(merchant_id, publishable_key, granularity, time_range, pool)
.await
}
Self::AuthenticationAttemptCount => {
AuthenticationAttemptCount
.load_metrics(merchant_id, publishable_key, granularity, time_range, pool)
.await
}
Self::AuthenticationSuccessCount => {
AuthenticationSuccessCount
.load_metrics(merchant_id, publishable_key, granularity, time_range, pool)
.await
}
Self::ChallengeFlowCount => {
ChallengeFlowCount
.load_metrics(merchant_id, publishable_key, granularity, time_range, pool)
.await
}
Self::ChallengeAttemptCount => {
ChallengeAttemptCount
.load_metrics(merchant_id, publishable_key, granularity, time_range, pool)
.await
}
Self::ChallengeSuccessCount => {
ChallengeSuccessCount
.load_metrics(merchant_id, publishable_key, granularity, time_range, pool)
.await
}
Self::FrictionlessFlowCount => {
FrictionlessFlowCount
.load_metrics(merchant_id, publishable_key, granularity, time_range, pool)
.await
}
}
}
}
Loading
Loading