Skip to content

Commit

Permalink
feat(analytics): add sessionized_refunds_distribution metric
Browse files Browse the repository at this point in the history
  • Loading branch information
tsdk02 committed Nov 20, 2024
1 parent ccea3fb commit 0369df6
Show file tree
Hide file tree
Showing 6 changed files with 190 additions and 1 deletion.
55 changes: 55 additions & 0 deletions crates/analytics/src/refunds/accumulator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub struct RefundMetricsAccumulator {
pub refund_count: CountAccumulator,
pub refund_success: CountAccumulator,
pub processed_amount: RefundProcessedAmountAccumulator,
pub refunds_distribution: RefundsDistributionAccumulator,
}

#[derive(Debug, Default)]
Expand All @@ -26,6 +27,13 @@ pub struct RefundProcessedAmountAccumulator {
pub total: Option<i64>,
}

#[derive(Debug, Default)]
pub struct RefundsDistributionAccumulator {
pub success: u32,
pub failed: u32,
pub total: u32,
}

pub trait RefundMetricAccumulator {
type MetricOutput;

Expand Down Expand Up @@ -115,12 +123,57 @@ impl RefundMetricAccumulator for SuccessRateAccumulator {
}
}

impl RefundMetricAccumulator for RefundsDistributionAccumulator {
type MetricOutput = (Option<f64>, Option<f64>);

fn add_metrics_bucket(&mut self, metrics: &RefundMetricRow) {
if let Some(ref refund_status) = metrics.refund_status {
if refund_status.as_ref() == &storage_enums::RefundStatus::Success {
if let Some(success) = metrics
.count
.and_then(|success| u32::try_from(success).ok())
{
self.success += success;
}
}
if refund_status.as_ref() == &storage_enums::RefundStatus::Failure {
if let Some(failed) = metrics.count.and_then(|failed| u32::try_from(failed).ok()) {
self.failed += failed;
}
}
};
if let Some(total) = metrics.count.and_then(|total| u32::try_from(total).ok()) {
self.total += total;
}
}

fn collect(self) -> Self::MetricOutput {
let success = Some(self.success);
let failed = Some(self.failed);
let total = Some(self.total);

let success_rate = match (success, total) {
(Some(s), Some(t)) if t > 0 => Some(f64::from(s) * 100.0 / f64::from(t)),
_ => None,
};

let failed_rate = match (failed, total) {
(Some(s), Some(t)) if t > 0 => Some(f64::from(s) * 100.0 / f64::from(t)),
_ => None,
};

(success_rate, failed_rate)
}
}

impl RefundMetricsAccumulator {
pub fn collect(self) -> RefundMetricsBucketValue {
let (successful_refunds, total_refunds, refund_success_rate) =
self.refund_success_rate.collect();
let (refund_processed_amount, refund_processed_count, refund_processed_amount_in_usd) =
self.processed_amount.collect();
let (refund_success_rate_distribution, refund_failure_rate_distribution) =
self.refunds_distribution.collect();
RefundMetricsBucketValue {
successful_refunds,
total_refunds,
Expand All @@ -130,6 +183,8 @@ impl RefundMetricsAccumulator {
refund_processed_amount,
refund_processed_amount_in_usd,
refund_processed_count,
refund_success_rate_distribution,
refund_failure_rate_distribution,
}
}
}
3 changes: 3 additions & 0 deletions crates/analytics/src/refunds/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@ pub async fn get_metrics(
| RefundMetrics::SessionizedRefundProcessedAmount => {
metrics_builder.processed_amount.add_metrics_bucket(&value)
}
RefundMetrics::SessionizedRefundsDistribution => metrics_builder
.refunds_distribution
.add_metrics_bucket(&value),
}
}

Expand Down
5 changes: 5 additions & 0 deletions crates/analytics/src/refunds/metrics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,11 @@ where
.load_metrics(dimensions, auth, filters, granularity, time_range, pool)
.await
}
Self::SessionizedRefundsDistribution => {
sessionized_metrics::RefundsDistribution::default()
.load_metrics(dimensions, auth, filters, granularity, time_range, pool)
.await
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ mod refund_count;
mod refund_processed_amount;
mod refund_success_count;
mod refund_success_rate;
mod refunds_distribution;

pub(super) use refund_count::RefundCount;
pub(super) use refund_processed_amount::RefundProcessedAmount;
pub(super) use refund_success_count::RefundSuccessCount;
pub(super) use refund_success_rate::RefundSuccessRate;
pub(super) use refunds_distribution::RefundsDistribution;

pub use super::{RefundMetric, RefundMetricAnalytics, RefundMetricRow};
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
use std::collections::HashSet;

use api_models::analytics::{
refunds::{RefundDimensions, RefundFilters, RefundMetricsBucketIdentifier},
Granularity, TimeRange,
};
use common_utils::errors::ReportSwitchExt;
use error_stack::ResultExt;
use time::PrimitiveDateTime;

use super::RefundMetricRow;
use crate::{
enums::AuthInfo,
query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window},
types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult},
};
#[derive(Default)]
pub(crate) struct RefundsDistribution {}

#[async_trait::async_trait]
impl<T> super::RefundMetric<T> for RefundsDistribution
where
T: AnalyticsDataSource + super::RefundMetricAnalytics,
PrimitiveDateTime: ToSql<T>,
AnalyticsCollection: ToSql<T>,
Granularity: GroupByClause<T>,
Aggregate<&'static str>: ToSql<T>,
Window<&'static str>: ToSql<T>,
{
async fn load_metrics(
&self,
dimensions: &[RefundDimensions],
auth: &AuthInfo,
filters: &RefundFilters,
granularity: &Option<Granularity>,
time_range: &TimeRange,
pool: &T,
) -> MetricsResult<HashSet<(RefundMetricsBucketIdentifier, RefundMetricRow)>>
where
T: AnalyticsDataSource + super::RefundMetricAnalytics,
{
let mut query_builder: QueryBuilder<T> =
QueryBuilder::new(AnalyticsCollection::RefundSessionized);

let mut dimensions = dimensions.to_vec();

dimensions.push(RefundDimensions::RefundStatus);

for dim in dimensions.iter() {
query_builder.add_select_column(dim).switch()?;
}

query_builder
.add_select_column(Aggregate::Count {
field: None,
alias: Some("count"),
})
.switch()?;
query_builder
.add_select_column(Aggregate::Min {
field: "created_at",
alias: Some("start_bucket"),
})
.switch()?;
query_builder
.add_select_column(Aggregate::Max {
field: "created_at",
alias: Some("end_bucket"),
})
.switch()?;

filters.set_filter_clause(&mut query_builder).switch()?;

auth.set_filter_clause(&mut query_builder).switch()?;

time_range
.set_filter_clause(&mut query_builder)
.attach_printable("Error filtering time range")
.switch()?;

for dim in dimensions.iter() {
query_builder.add_group_by_clause(dim).switch()?;
}

if let Some(granularity) = granularity.as_ref() {
granularity
.set_group_by_clause(&mut query_builder)
.switch()?;
}

query_builder
.execute_query::<RefundMetricRow, _>(pool)
.await
.change_context(MetricsError::QueryBuildingError)?
.change_context(MetricsError::QueryExecutionFailure)?
.into_iter()
.map(|i| {
Ok((
RefundMetricsBucketIdentifier::new(
i.currency.as_ref().map(|i| i.0),
None,
i.connector.clone(),
i.refund_type.as_ref().map(|i| i.0.to_string()),
i.profile_id.clone(),
TimeRange {
start_time: match (granularity, i.start_bucket) {
(Some(g), Some(st)) => g.clip_to_start(st)?,
_ => time_range.start_time,
},
end_time: granularity.as_ref().map_or_else(
|| Ok(time_range.end_time),
|g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(),
)?,
},
),
i,
))
})
.collect::<error_stack::Result<HashSet<_>, crate::query::PostProcessingError>>()
.change_context(MetricsError::PostProcessingFailure)
}
}
4 changes: 3 additions & 1 deletion crates/api_models/src/analytics/refunds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ pub enum RefundMetrics {
SessionizedRefundCount,
SessionizedRefundSuccessCount,
SessionizedRefundProcessedAmount,
SessionizedRefundsDistribution,
}

pub mod metric_behaviour {
Expand Down Expand Up @@ -124,7 +125,6 @@ pub struct RefundMetricsBucketIdentifier {
pub currency: Option<Currency>,
pub refund_status: Option<String>,
pub connector: Option<String>,

pub refund_type: Option<String>,
pub profile_id: Option<String>,
#[serde(rename = "time_range")]
Expand Down Expand Up @@ -184,6 +184,8 @@ pub struct RefundMetricsBucketValue {
pub refund_processed_amount: Option<u64>,
pub refund_processed_amount_in_usd: Option<u64>,
pub refund_processed_count: Option<u64>,
pub refund_success_rate_distribution: Option<f64>,
pub refund_failure_rate_distribution: Option<f64>,
}
#[derive(Debug, serde::Serialize)]
pub struct RefundMetricsBucketResponse {
Expand Down

0 comments on commit 0369df6

Please sign in to comment.