refactor: exposed auth analytics at merchant,org and profile levels (#8335)

This commit is contained in:
Sanskar Atrey
2025-07-01 16:51:35 +05:30
committed by GitHub
parent 69ab255394
commit e638f239d3
17 changed files with 368 additions and 200 deletions

View File

@ -27,7 +27,7 @@ use crate::{
#[instrument(skip_all)]
pub async fn get_metrics(
pool: &AnalyticsProvider,
merchant_id: &common_utils::id_type::MerchantId,
auth: &AuthInfo,
req: GetAuthEventMetricRequest,
) -> AnalyticsResult<AuthEventMetricsResponse<MetricsBucketResponse>> {
let mut metrics_accumulator: HashMap<
@ -38,14 +38,14 @@ pub async fn get_metrics(
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 auth_scoped = auth.to_owned();
let pool = pool.clone();
set.spawn(async move {
let data = pool
.get_auth_event_metrics(
&metric_type,
&req.group_by_names.clone(),
&merchant_id_scoped,
&auth_scoped,
&req.filters,
req.time_series.map(|t| t.granularity),
&req.time_range,
@ -130,7 +130,7 @@ pub async fn get_metrics(
pub async fn get_filters(
pool: &AnalyticsProvider,
req: GetAuthEventFilterRequest,
merchant_id: &common_utils::id_type::MerchantId,
auth: &AuthInfo,
) -> AnalyticsResult<AuthEventFiltersResponse> {
let mut res = AuthEventFiltersResponse::default();
for dim in req.group_by_names {
@ -139,14 +139,14 @@ pub async fn get_filters(
Err(report!(AnalyticsError::UnknownError))
}
AnalyticsProvider::Clickhouse(pool) => {
get_auth_events_filter_for_dimension(dim, merchant_id, &req.time_range, pool)
get_auth_events_filter_for_dimension(dim, auth, &req.time_range, pool)
.await
.map_err(|e| e.change_context(AnalyticsError::UnknownError))
}
AnalyticsProvider::CombinedCkh(sqlx_pool, ckh_pool) | AnalyticsProvider::CombinedSqlx(sqlx_pool, ckh_pool) => {
let ckh_result = get_auth_events_filter_for_dimension(
dim,
merchant_id,
auth,
&req.time_range,
ckh_pool,
)
@ -154,7 +154,7 @@ pub async fn get_filters(
.map_err(|e| e.change_context(AnalyticsError::UnknownError));
let sqlx_result = get_auth_events_filter_for_dimension(
dim,
merchant_id,
auth,
&req.time_range,
sqlx_pool,
)

View File

@ -6,6 +6,7 @@ use error_stack::ResultExt;
use time::PrimitiveDateTime;
use crate::{
enums::AuthInfo,
query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, ToSql, Window},
types::{
AnalyticsCollection, AnalyticsDataSource, DBEnumWrapper, FiltersError, FiltersResult,
@ -17,7 +18,7 @@ pub trait AuthEventFilterAnalytics: LoadRow<AuthEventFilterRow> {}
pub async fn get_auth_events_filter_for_dimension<T>(
dimension: AuthEventDimensions,
merchant_id: &common_utils::id_type::MerchantId,
auth: &AuthInfo,
time_range: &TimeRange,
pool: &T,
) -> FiltersResult<Vec<AuthEventFilterRow>>
@ -38,12 +39,10 @@ where
.attach_printable("Error filtering time range")
.switch()?;
query_builder
.add_filter_clause("merchant_id", merchant_id)
.switch()?;
query_builder.set_distinct();
auth.set_filter_clause(&mut query_builder).switch()?;
query_builder
.execute_query::<AuthEventFilterRow, _>(pool)
.await

View File

@ -12,6 +12,7 @@ use time::PrimitiveDateTime;
use crate::{
query::{Aggregate, GroupByClause, ToSql, Window},
types::{AnalyticsCollection, AnalyticsDataSource, DBEnumWrapper, LoadRow, MetricsResult},
AuthInfo,
};
mod authentication_attempt_count;
@ -86,7 +87,7 @@ where
{
async fn load_metrics(
&self,
merchant_id: &common_utils::id_type::MerchantId,
auth: &AuthInfo,
dimensions: &[AuthEventDimensions],
filters: &AuthEventFilters,
granularity: Option<Granularity>,
@ -107,7 +108,7 @@ where
{
async fn load_metrics(
&self,
merchant_id: &common_utils::id_type::MerchantId,
auth: &AuthInfo,
dimensions: &[AuthEventDimensions],
filters: &AuthEventFilters,
granularity: Option<Granularity>,
@ -117,146 +118,62 @@ where
match self {
Self::AuthenticationCount => {
AuthenticationCount
.load_metrics(
merchant_id,
dimensions,
filters,
granularity,
time_range,
pool,
)
.load_metrics(auth, dimensions, filters, granularity, time_range, pool)
.await
}
Self::AuthenticationAttemptCount => {
AuthenticationAttemptCount
.load_metrics(
merchant_id,
dimensions,
filters,
granularity,
time_range,
pool,
)
.load_metrics(auth, dimensions, filters, granularity, time_range, pool)
.await
}
Self::AuthenticationSuccessCount => {
AuthenticationSuccessCount
.load_metrics(
merchant_id,
dimensions,
filters,
granularity,
time_range,
pool,
)
.load_metrics(auth, dimensions, filters, granularity, time_range, pool)
.await
}
Self::ChallengeFlowCount => {
ChallengeFlowCount
.load_metrics(
merchant_id,
dimensions,
filters,
granularity,
time_range,
pool,
)
.load_metrics(auth, dimensions, filters, granularity, time_range, pool)
.await
}
Self::ChallengeAttemptCount => {
ChallengeAttemptCount
.load_metrics(
merchant_id,
dimensions,
filters,
granularity,
time_range,
pool,
)
.load_metrics(auth, dimensions, filters, granularity, time_range, pool)
.await
}
Self::ChallengeSuccessCount => {
ChallengeSuccessCount
.load_metrics(
merchant_id,
dimensions,
filters,
granularity,
time_range,
pool,
)
.load_metrics(auth, dimensions, filters, granularity, time_range, pool)
.await
}
Self::FrictionlessFlowCount => {
FrictionlessFlowCount
.load_metrics(
merchant_id,
dimensions,
filters,
granularity,
time_range,
pool,
)
.load_metrics(auth, dimensions, filters, granularity, time_range, pool)
.await
}
Self::FrictionlessSuccessCount => {
FrictionlessSuccessCount
.load_metrics(
merchant_id,
dimensions,
filters,
granularity,
time_range,
pool,
)
.load_metrics(auth, dimensions, filters, granularity, time_range, pool)
.await
}
Self::AuthenticationErrorMessage => {
AuthenticationErrorMessage
.load_metrics(
merchant_id,
dimensions,
filters,
granularity,
time_range,
pool,
)
.load_metrics(auth, dimensions, filters, granularity, time_range, pool)
.await
}
Self::AuthenticationFunnel => {
AuthenticationFunnel
.load_metrics(
merchant_id,
dimensions,
filters,
granularity,
time_range,
pool,
)
.load_metrics(auth, dimensions, filters, granularity, time_range, pool)
.await
}
Self::AuthenticationExemptionApprovedCount => {
AuthenticationExemptionApprovedCount
.load_metrics(
merchant_id,
dimensions,
filters,
granularity,
time_range,
pool,
)
.load_metrics(auth, dimensions, filters, granularity, time_range, pool)
.await
}
Self::AuthenticationExemptionRequestedCount => {
AuthenticationExemptionRequestedCount
.load_metrics(
merchant_id,
dimensions,
filters,
granularity,
time_range,
pool,
)
.load_metrics(auth, dimensions, filters, granularity, time_range, pool)
.await
}
}

View File

@ -13,6 +13,7 @@ use super::AuthEventMetricRow;
use crate::{
query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window},
types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult},
AuthInfo,
};
#[derive(Default)]
@ -30,7 +31,7 @@ where
{
async fn load_metrics(
&self,
merchant_id: &common_utils::id_type::MerchantId,
auth: &AuthInfo,
dimensions: &[AuthEventDimensions],
filters: &AuthEventFilters,
granularity: Option<Granularity>,
@ -65,10 +66,6 @@ where
})
.switch()?;
query_builder
.add_filter_clause("merchant_id", merchant_id)
.switch()?;
query_builder
.add_filter_in_range_clause(
"authentication_status",
@ -80,6 +77,7 @@ where
.set_filter_clause(&mut query_builder)
.attach_printable("Error filtering time range")
.switch()?;
auth.set_filter_clause(&mut query_builder).switch()?;
for dim in dimensions.iter() {
query_builder

View File

@ -12,6 +12,7 @@ use super::AuthEventMetricRow;
use crate::{
query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window},
types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult},
AuthInfo,
};
#[derive(Default)]
@ -29,7 +30,7 @@ where
{
async fn load_metrics(
&self,
merchant_id: &common_utils::id_type::MerchantId,
auth: &AuthInfo,
dimensions: &[AuthEventDimensions],
filters: &AuthEventFilters,
granularity: Option<Granularity>,
@ -62,14 +63,12 @@ where
})
.switch()?;
query_builder
.add_filter_clause("merchant_id", merchant_id)
.switch()?;
filters.set_filter_clause(&mut query_builder).switch()?;
time_range
.set_filter_clause(&mut query_builder)
.attach_printable("Error filtering time range")
.switch()?;
auth.set_filter_clause(&mut query_builder).switch()?;
for dim in dimensions.iter() {
query_builder

View File

@ -16,6 +16,7 @@ use crate::{
ToSql, Window,
},
types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult},
AuthInfo,
};
#[derive(Default)]
@ -33,7 +34,7 @@ where
{
async fn load_metrics(
&self,
merchant_id: &common_utils::id_type::MerchantId,
auth: &AuthInfo,
dimensions: &[AuthEventDimensions],
filters: &AuthEventFilters,
granularity: Option<Granularity>,
@ -64,10 +65,6 @@ where
})
.switch()?;
query_builder
.add_filter_clause("merchant_id", merchant_id)
.switch()?;
query_builder
.add_filter_clause("authentication_status", AuthenticationStatus::Failed)
.switch()?;
@ -84,6 +81,7 @@ where
.set_filter_clause(&mut query_builder)
.attach_printable("Error filtering time range")
.switch()?;
auth.set_filter_clause(&mut query_builder).switch()?;
for dim in dimensions.iter() {
query_builder

View File

@ -12,6 +12,7 @@ use super::AuthEventMetricRow;
use crate::{
query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window},
types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult},
AuthInfo,
};
#[derive(Default)]
@ -29,7 +30,7 @@ where
{
async fn load_metrics(
&self,
merchant_id: &common_utils::id_type::MerchantId,
auth: &AuthInfo,
dimensions: &[AuthEventDimensions],
filters: &AuthEventFilters,
granularity: Option<Granularity>,
@ -62,10 +63,6 @@ where
})
.switch()?;
query_builder
.add_filter_clause("merchant_id", merchant_id)
.switch()?;
query_builder
.add_filter_clause(AuthEventDimensions::ExemptionAccepted, true)
.switch()?;
@ -74,6 +71,7 @@ where
.set_filter_clause(&mut query_builder)
.attach_printable("Error filtering time range")
.switch()?;
auth.set_filter_clause(&mut query_builder).switch()?;
for dim in dimensions.iter() {
query_builder

View File

@ -12,6 +12,7 @@ use super::AuthEventMetricRow;
use crate::{
query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window},
types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult},
AuthInfo,
};
#[derive(Default)]
@ -29,7 +30,7 @@ where
{
async fn load_metrics(
&self,
merchant_id: &common_utils::id_type::MerchantId,
auth: &AuthInfo,
dimensions: &[AuthEventDimensions],
filters: &AuthEventFilters,
granularity: Option<Granularity>,
@ -62,10 +63,6 @@ where
})
.switch()?;
query_builder
.add_filter_clause("merchant_id", merchant_id)
.switch()?;
query_builder
.add_filter_clause(AuthEventDimensions::ExemptionRequested, true)
.switch()?;
@ -75,6 +72,7 @@ where
.set_filter_clause(&mut query_builder)
.attach_printable("Error filtering time range")
.switch()?;
auth.set_filter_clause(&mut query_builder).switch()?;
for dim in dimensions.iter() {
query_builder

View File

@ -15,6 +15,7 @@ use crate::{
Window,
},
types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult},
AuthInfo,
};
#[derive(Default)]
@ -32,7 +33,7 @@ where
{
async fn load_metrics(
&self,
merchant_id: &common_utils::id_type::MerchantId,
auth: &AuthInfo,
dimensions: &[AuthEventDimensions],
filters: &AuthEventFilters,
granularity: Option<Granularity>,
@ -65,10 +66,6 @@ where
})
.switch()?;
query_builder
.add_filter_clause("merchant_id", merchant_id)
.switch()?;
query_builder
.add_custom_filter_clause(
AuthEventDimensions::TransactionStatus,
@ -81,6 +78,7 @@ where
.set_filter_clause(&mut query_builder)
.attach_printable("Error filtering time range")
.switch()?;
auth.set_filter_clause(&mut query_builder).switch()?;
for dim in dimensions.iter() {
query_builder

View File

@ -13,6 +13,7 @@ use super::AuthEventMetricRow;
use crate::{
query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window},
types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult},
AuthInfo,
};
#[derive(Default)]
@ -30,7 +31,7 @@ where
{
async fn load_metrics(
&self,
merchant_id: &common_utils::id_type::MerchantId,
auth: &AuthInfo,
dimensions: &[AuthEventDimensions],
filters: &AuthEventFilters,
granularity: Option<Granularity>,
@ -63,10 +64,6 @@ where
})
.switch()?;
query_builder
.add_filter_clause("merchant_id", merchant_id)
.switch()?;
query_builder
.add_filter_clause("authentication_status", AuthenticationStatus::Success)
.switch()?;
@ -75,6 +72,7 @@ where
.set_filter_clause(&mut query_builder)
.attach_printable("Error filtering time range")
.switch()?;
auth.set_filter_clause(&mut query_builder).switch()?;
for dim in dimensions.iter() {
query_builder

View File

@ -13,6 +13,7 @@ use super::AuthEventMetricRow;
use crate::{
query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window},
types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult},
AuthInfo,
};
#[derive(Default)]
@ -30,7 +31,7 @@ where
{
async fn load_metrics(
&self,
merchant_id: &common_utils::id_type::MerchantId,
auth: &AuthInfo,
dimensions: &[AuthEventDimensions],
filters: &AuthEventFilters,
granularity: Option<Granularity>,
@ -63,10 +64,6 @@ where
})
.switch()?;
query_builder
.add_filter_clause("merchant_id", merchant_id)
.switch()?;
query_builder
.add_filter_clause(
"authentication_type",
@ -85,6 +82,7 @@ where
.set_filter_clause(&mut query_builder)
.attach_printable("Error filtering time range")
.switch()?;
auth.set_filter_clause(&mut query_builder).switch()?;
for dim in dimensions.iter() {
query_builder

View File

@ -13,6 +13,7 @@ use super::AuthEventMetricRow;
use crate::{
query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window},
types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult},
AuthInfo,
};
#[derive(Default)]
@ -30,7 +31,7 @@ where
{
async fn load_metrics(
&self,
merchant_id: &common_utils::id_type::MerchantId,
auth: &AuthInfo,
dimensions: &[AuthEventDimensions],
filters: &AuthEventFilters,
granularity: Option<Granularity>,
@ -63,10 +64,6 @@ where
})
.switch()?;
query_builder
.add_filter_clause("merchant_id", merchant_id)
.switch()?;
query_builder
.add_filter_clause(
"authentication_type",
@ -78,6 +75,7 @@ where
.set_filter_clause(&mut query_builder)
.attach_printable("Error filtering time range")
.switch()?;
auth.set_filter_clause(&mut query_builder).switch()?;
for dim in dimensions.iter() {
query_builder

View File

@ -13,6 +13,7 @@ use super::AuthEventMetricRow;
use crate::{
query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window},
types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult},
AuthInfo,
};
#[derive(Default)]
@ -30,7 +31,7 @@ where
{
async fn load_metrics(
&self,
merchant_id: &common_utils::id_type::MerchantId,
auth: &AuthInfo,
dimensions: &[AuthEventDimensions],
filters: &AuthEventFilters,
granularity: Option<Granularity>,
@ -63,10 +64,6 @@ where
})
.switch()?;
query_builder
.add_filter_clause("merchant_id", merchant_id)
.switch()?;
query_builder
.add_filter_clause("authentication_status", AuthenticationStatus::Success)
.switch()?;
@ -82,6 +79,7 @@ where
.set_filter_clause(&mut query_builder)
.attach_printable("Error filtering time range")
.switch()?;
auth.set_filter_clause(&mut query_builder).switch()?;
for dim in dimensions.iter() {
query_builder

View File

@ -13,6 +13,7 @@ use super::AuthEventMetricRow;
use crate::{
query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window},
types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult},
AuthInfo,
};
#[derive(Default)]
@ -30,7 +31,7 @@ where
{
async fn load_metrics(
&self,
merchant_id: &common_utils::id_type::MerchantId,
auth: &AuthInfo,
dimensions: &[AuthEventDimensions],
filters: &AuthEventFilters,
granularity: Option<Granularity>,
@ -63,10 +64,6 @@ where
})
.switch()?;
query_builder
.add_filter_clause("merchant_id", merchant_id)
.switch()?;
query_builder
.add_filter_clause(
"authentication_type",
@ -78,6 +75,7 @@ where
.set_filter_clause(&mut query_builder)
.attach_printable("Error filtering time range")
.switch()?;
auth.set_filter_clause(&mut query_builder).switch()?;
for dim in dimensions.iter() {
query_builder

View File

@ -13,6 +13,7 @@ use super::AuthEventMetricRow;
use crate::{
query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window},
types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult},
AuthInfo,
};
#[derive(Default)]
@ -30,7 +31,7 @@ where
{
async fn load_metrics(
&self,
merchant_id: &common_utils::id_type::MerchantId,
auth: &AuthInfo,
dimensions: &[AuthEventDimensions],
filters: &AuthEventFilters,
granularity: Option<Granularity>,
@ -63,10 +64,6 @@ where
})
.switch()?;
query_builder
.add_filter_clause("merchant_id", merchant_id)
.switch()?;
query_builder
.add_filter_clause(
"authentication_type",
@ -82,6 +79,7 @@ where
.set_filter_clause(&mut query_builder)
.attach_printable("Error filtering time range")
.switch()?;
auth.set_filter_clause(&mut query_builder).switch()?;
for dim in dimensions.iter() {
query_builder

View File

@ -912,7 +912,7 @@ impl AnalyticsProvider {
&self,
metric: &AuthEventMetrics,
dimensions: &[AuthEventDimensions],
merchant_id: &common_utils::id_type::MerchantId,
auth: &AuthInfo,
filters: &AuthEventFilters,
granularity: Option<Granularity>,
time_range: &TimeRange,
@ -921,20 +921,13 @@ impl AnalyticsProvider {
Self::Sqlx(_pool) => Err(report!(MetricsError::NotImplemented)),
Self::Clickhouse(pool) => {
metric
.load_metrics(
merchant_id,
dimensions,
filters,
granularity,
time_range,
pool,
)
.load_metrics(auth, dimensions, filters, granularity, time_range, pool)
.await
}
Self::CombinedCkh(_sqlx_pool, ckh_pool) | Self::CombinedSqlx(_sqlx_pool, ckh_pool) => {
metric
.load_metrics(
merchant_id,
auth,
dimensions,
filters,
granularity,

View File

@ -116,11 +116,7 @@ pub mod routes {
)
.service(
web::resource("metrics/auth_events")
.route(web::post().to(get_auth_event_metrics)),
)
.service(
web::resource("metrics/auth_events/sankey")
.route(web::post().to(get_auth_event_sankey)),
.route(web::post().to(get_merchant_auth_event_metrics)),
)
.service(
web::resource("filters/auth_events")
@ -177,6 +173,10 @@ pub mod routes {
web::resource("metrics/sankey")
.route(web::post().to(get_merchant_sankey)),
)
.service(
web::resource("metrics/auth_events/sankey")
.route(web::post().to(get_merchant_auth_event_sankey)),
)
.service(
web::scope("/merchant")
.service(
@ -191,6 +191,10 @@ pub mod routes {
web::resource("metrics/refunds")
.route(web::post().to(get_merchant_refund_metrics)),
)
.service(
web::resource("metrics/auth_events")
.route(web::post().to(get_merchant_auth_event_metrics)),
)
.service(
web::resource("filters/payments")
.route(web::post().to(get_merchant_payment_filters)),
@ -203,6 +207,10 @@ pub mod routes {
web::resource("filters/refunds")
.route(web::post().to(get_merchant_refund_filters)),
)
.service(
web::resource("filters/auth_events")
.route(web::post().to(get_merchant_auth_events_filters)),
)
.service(
web::resource("{domain}/info").route(web::get().to(get_info)),
)
@ -242,6 +250,10 @@ pub mod routes {
.service(
web::resource("metrics/sankey")
.route(web::post().to(get_merchant_sankey)),
)
.service(
web::resource("metrics/auth_events/sankey")
.route(web::post().to(get_merchant_auth_event_sankey)),
),
)
.service(
@ -277,10 +289,18 @@ pub mod routes {
web::resource("metrics/disputes")
.route(web::post().to(get_org_dispute_metrics)),
)
.service(
web::resource("metrics/auth_events")
.route(web::post().to(get_org_auth_event_metrics)),
)
.service(
web::resource("filters/disputes")
.route(web::post().to(get_org_dispute_filters)),
)
.service(
web::resource("filters/auth_events")
.route(web::post().to(get_org_auth_events_filters)),
)
.service(
web::resource("report/dispute")
.route(web::post().to(generate_org_dispute_report)),
@ -300,6 +320,10 @@ pub mod routes {
.service(
web::resource("metrics/sankey")
.route(web::post().to(get_org_sankey)),
)
.service(
web::resource("metrics/auth_events/sankey")
.route(web::post().to(get_org_auth_event_sankey)),
),
)
.service(
@ -335,10 +359,18 @@ pub mod routes {
web::resource("metrics/disputes")
.route(web::post().to(get_profile_dispute_metrics)),
)
.service(
web::resource("metrics/auth_events")
.route(web::post().to(get_profile_auth_event_metrics)),
)
.service(
web::resource("filters/disputes")
.route(web::post().to(get_profile_dispute_filters)),
)
.service(
web::resource("filters/auth_events")
.route(web::post().to(get_profile_auth_events_filters)),
)
.service(
web::resource("connector_event_logs")
.route(web::get().to(get_profile_connector_events)),
@ -379,6 +411,10 @@ pub mod routes {
.service(
web::resource("metrics/sankey")
.route(web::post().to(get_profile_sankey)),
)
.service(
web::resource("metrics/auth_events/sankey")
.route(web::post().to(get_profile_auth_event_sankey)),
),
),
)
@ -1043,7 +1079,7 @@ pub mod routes {
/// # Panics
///
/// Panics if `json_payload` array does not contain one `GetAuthEventMetricRequest` element.
pub async fn get_auth_event_metrics(
pub async fn get_merchant_auth_event_metrics(
state: web::Data<AppState>,
req: actix_web::HttpRequest,
json_payload: web::Json<[GetAuthEventMetricRequest; 1]>,
@ -1062,13 +1098,16 @@ pub mod routes {
&req,
payload,
|state, auth: AuthenticationData, req, _| async move {
analytics::auth_events::get_metrics(
&state.pool,
auth.merchant_account.get_id(),
req,
)
.await
.map(ApplicationResponse::Json)
let org_id = auth.merchant_account.get_org_id();
let merchant_id = auth.merchant_account.get_id();
let auth: AuthInfo = AuthInfo::MerchantLevel {
org_id: org_id.clone(),
merchant_ids: vec![merchant_id.clone()],
};
analytics::auth_events::get_metrics(&state.pool, &auth, req)
.await
.map(ApplicationResponse::Json)
},
&auth::JWTAuth {
permission: Permission::MerchantAnalyticsRead,
@ -1078,6 +1117,98 @@ pub mod routes {
.await
}
#[cfg(feature = "v1")]
/// # Panics
///
/// Panics if `json_payload` array does not contain one `GetAuthEventMetricRequest` element.
pub async fn get_profile_auth_event_metrics(
state: web::Data<AppState>,
req: actix_web::HttpRequest,
json_payload: web::Json<[GetAuthEventMetricRequest; 1]>,
) -> impl Responder {
// safety: This shouldn't panic owing to the data type
#[allow(clippy::expect_used)]
let payload = json_payload
.into_inner()
.to_vec()
.pop()
.expect("Couldn't get GetAuthEventMetricRequest");
let flow = AnalyticsFlow::GetAuthMetrics;
Box::pin(api::server_wrap(
flow,
state,
&req,
payload,
|state, auth: AuthenticationData, req, _| async move {
let org_id = auth.merchant_account.get_org_id();
let merchant_id = auth.merchant_account.get_id();
let profile_id = auth
.profile_id
.ok_or(report!(UserErrors::JwtProfileIdMissing))
.change_context(AnalyticsError::AccessForbiddenError)?;
let auth: AuthInfo = AuthInfo::ProfileLevel {
org_id: org_id.clone(),
merchant_id: merchant_id.clone(),
profile_ids: vec![profile_id.clone()],
};
analytics::auth_events::get_metrics(&state.pool, &auth, req)
.await
.map(ApplicationResponse::Json)
},
&auth::JWTAuth {
permission: Permission::ProfileAnalyticsRead,
},
api_locking::LockAction::NotApplicable,
))
.await
}
#[cfg(feature = "v1")]
/// # Panics
///
/// Panics if `json_payload` array does not contain one `GetAuthEventMetricRequest` element.
pub async fn get_org_auth_event_metrics(
state: web::Data<AppState>,
req: actix_web::HttpRequest,
json_payload: web::Json<[GetAuthEventMetricRequest; 1]>,
) -> impl Responder {
// safety: This shouldn't panic owing to the data type
#[allow(clippy::expect_used)]
let payload = json_payload
.into_inner()
.to_vec()
.pop()
.expect("Couldn't get GetAuthEventMetricRequest");
let flow = AnalyticsFlow::GetAuthMetrics;
Box::pin(api::server_wrap(
flow,
state,
&req,
payload,
|state, auth: AuthenticationData, req, _| async move {
let org_id = auth.merchant_account.get_org_id();
let auth: AuthInfo = AuthInfo::OrgLevel {
org_id: org_id.clone(),
};
analytics::auth_events::get_metrics(&state.pool, &auth, req)
.await
.map(ApplicationResponse::Json)
},
auth::auth_type(
&auth::PlatformOrgAdminAuth {
is_admin_auth_allowed: false,
organization_id: None,
},
&auth::JWTAuth {
permission: Permission::OrganizationAnalyticsRead,
},
req.headers(),
),
api_locking::LockAction::NotApplicable,
))
.await
}
pub async fn get_merchant_payment_filters(
state: web::Data<AppState>,
req: actix_web::HttpRequest,
@ -1120,13 +1251,16 @@ pub mod routes {
&req,
json_payload.into_inner(),
|state, auth: AuthenticationData, req, _| async move {
analytics::auth_events::get_filters(
&state.pool,
req,
auth.merchant_account.get_id(),
)
.await
.map(ApplicationResponse::Json)
let org_id = auth.merchant_account.get_org_id();
let merchant_id = auth.merchant_account.get_id();
let auth: AuthInfo = AuthInfo::MerchantLevel {
org_id: org_id.clone(),
merchant_ids: vec![merchant_id.clone()],
};
analytics::auth_events::get_filters(&state.pool, req, &auth)
.await
.map(ApplicationResponse::Json)
},
&auth::JWTAuth {
permission: Permission::MerchantAnalyticsRead,
@ -1136,6 +1270,80 @@ pub mod routes {
.await
}
#[cfg(feature = "v1")]
pub async fn get_org_auth_events_filters(
state: web::Data<AppState>,
req: actix_web::HttpRequest,
json_payload: web::Json<GetAuthEventFilterRequest>,
) -> impl Responder {
let flow = AnalyticsFlow::GetAuthEventFilters;
Box::pin(api::server_wrap(
flow,
state,
&req,
json_payload.into_inner(),
|state, auth: AuthenticationData, req, _| async move {
let org_id = auth.merchant_account.get_org_id();
let auth: AuthInfo = AuthInfo::OrgLevel {
org_id: org_id.clone(),
};
analytics::auth_events::get_filters(&state.pool, req, &auth)
.await
.map(ApplicationResponse::Json)
},
auth::auth_type(
&auth::PlatformOrgAdminAuth {
is_admin_auth_allowed: false,
organization_id: None,
},
&auth::JWTAuth {
permission: Permission::OrganizationAnalyticsRead,
},
req.headers(),
),
api_locking::LockAction::NotApplicable,
))
.await
}
#[cfg(feature = "v1")]
pub async fn get_profile_auth_events_filters(
state: web::Data<AppState>,
req: actix_web::HttpRequest,
json_payload: web::Json<GetAuthEventFilterRequest>,
) -> impl Responder {
let flow = AnalyticsFlow::GetAuthEventFilters;
Box::pin(api::server_wrap(
flow,
state,
&req,
json_payload.into_inner(),
|state, auth: AuthenticationData, req, _| async move {
let org_id = auth.merchant_account.get_org_id();
let merchant_id = auth.merchant_account.get_id();
let profile_id = auth
.profile_id
.ok_or(report!(UserErrors::JwtProfileIdMissing))
.change_context(AnalyticsError::AccessForbiddenError)?;
let auth: AuthInfo = AuthInfo::ProfileLevel {
org_id: org_id.clone(),
merchant_id: merchant_id.clone(),
profile_ids: vec![profile_id.clone()],
};
analytics::auth_events::get_filters(&state.pool, req, &auth)
.await
.map(ApplicationResponse::Json)
},
&auth::JWTAuth {
permission: Permission::ProfileAnalyticsRead,
},
api_locking::LockAction::NotApplicable,
))
.await
}
#[cfg(feature = "v1")]
pub async fn get_org_payment_filters(
state: web::Data<AppState>,
@ -2809,7 +3017,7 @@ pub mod routes {
.await
}
pub async fn get_auth_event_sankey(
pub async fn get_merchant_auth_event_sankey(
state: web::Data<AppState>,
req: actix_web::HttpRequest,
json_payload: web::Json<TimeRange>,
@ -2840,6 +3048,80 @@ pub mod routes {
.await
}
#[cfg(feature = "v1")]
pub async fn get_org_auth_event_sankey(
state: web::Data<AppState>,
req: actix_web::HttpRequest,
json_payload: web::Json<TimeRange>,
) -> impl Responder {
let flow = AnalyticsFlow::GetSankey;
let payload = json_payload.into_inner();
Box::pin(api::server_wrap(
flow,
state,
&req,
payload,
|state, auth: AuthenticationData, req, _| async move {
let org_id = auth.merchant_account.get_org_id();
let auth: AuthInfo = AuthInfo::OrgLevel {
org_id: org_id.clone(),
};
analytics::auth_events::get_sankey(&state.pool, &auth, req)
.await
.map(ApplicationResponse::Json)
},
auth::auth_type(
&auth::PlatformOrgAdminAuth {
is_admin_auth_allowed: false,
organization_id: None,
},
&auth::JWTAuth {
permission: Permission::OrganizationAnalyticsRead,
},
req.headers(),
),
api_locking::LockAction::NotApplicable,
))
.await
}
#[cfg(feature = "v1")]
pub async fn get_profile_auth_event_sankey(
state: web::Data<AppState>,
req: actix_web::HttpRequest,
json_payload: web::Json<TimeRange>,
) -> impl Responder {
let flow = AnalyticsFlow::GetSankey;
let payload = json_payload.into_inner();
Box::pin(api::server_wrap(
flow,
state,
&req,
payload,
|state, auth: AuthenticationData, req, _| async move {
let org_id = auth.merchant_account.get_org_id();
let merchant_id = auth.merchant_account.get_id();
let profile_id = auth
.profile_id
.ok_or(report!(UserErrors::JwtProfileIdMissing))
.change_context(AnalyticsError::AccessForbiddenError)?;
let auth: AuthInfo = AuthInfo::ProfileLevel {
org_id: org_id.clone(),
merchant_id: merchant_id.clone(),
profile_ids: vec![profile_id.clone()],
};
analytics::auth_events::get_sankey(&state.pool, &auth, req)
.await
.map(ApplicationResponse::Json)
},
&auth::JWTAuth {
permission: Permission::ProfileAnalyticsRead,
},
api_locking::LockAction::NotApplicable,
))
.await
}
#[cfg(feature = "v1")]
pub async fn get_org_sankey(
state: web::Data<AppState>,