Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
87289c9
feat(analytics): Add metrics, filters and APIs for Analytics v3 Dashb…
tsdk02 Sep 12, 2024
50040a2
chore: run formatter
hyperswitch-bot[bot] Sep 12, 2024
81ff495
add: auth_declined_rate as a metric for payment intents
tsdk02 Sep 12, 2024
41a5f40
fix: clippy errors
tsdk02 Sep 12, 2024
29d7801
merged main
tsdk02 Sep 13, 2024
8cfb11c
merged main
tsdk02 Sep 18, 2024
f85da4e
add: global filters for payment attempts and intents for new analytic…
tsdk02 Sep 19, 2024
7f504a5
chore: run formatter
hyperswitch-bot[bot] Sep 19, 2024
b375b14
minor fixes
tsdk02 Sep 19, 2024
b5439e6
added sessonizer_table and restructured api request and response for …
tsdk02 Sep 20, 2024
b25565a
add: payments_success_rate in queryData and metaData
tsdk02 Sep 24, 2024
5356550
fix: clippy errors
tsdk02 Sep 24, 2024
18237b2
fix: more clippy errors
tsdk02 Sep 24, 2024
959aed7
add: payment_success_rate without smart_retries
tsdk02 Sep 25, 2024
db03f10
chore: run formatter
hyperswitch-bot[bot] Sep 25, 2024
7fc438f
fix: clippy errors
tsdk02 Sep 25, 2024
117e5a7
add: Successful Payments Distribution Metric
tsdk02 Sep 25, 2024
b3d93b2
chore: run formatter
hyperswitch-bot[bot] Sep 25, 2024
d146835
add: Failed Payments Distribution
tsdk02 Sep 26, 2024
e927b0d
add: Payment processed amount and count without smart retries
tsdk02 Sep 26, 2024
56d8765
add: smart retried amount without smart retries
tsdk02 Sep 26, 2024
39488ad
minor fixes
tsdk02 Sep 26, 2024
867347b
fix: clippy errors
tsdk02 Sep 26, 2024
5cd0687
Merge branch 'main' into analytics-v3
lsampras Sep 26, 2024
7fb2292
feat(analytics): add card network filters
lsampras Sep 25, 2024
d32e004
fix(lints): fix fmt lints
lsampras Sep 26, 2024
bf92d88
fix(analytics): fx compilation errors
lsampras Sep 26, 2024
33ad3f6
add: failure reasons distribution
tsdk02 Sep 26, 2024
605cc19
resolved conflicts
tsdk02 Sep 26, 2024
bd88d0d
Squashed commit of the following:
lsampras Sep 30, 2024
3c624b4
chore: run formatter
hyperswitch-bot[bot] Sep 30, 2024
d94a7f1
resolved errors for sankey and modified failure_reasons query
tsdk02 Oct 2, 2024
f15f26f
resolved clippy errors
tsdk02 Oct 2, 2024
fb97b06
modified failure_reasons accumulator logic and response structure
tsdk02 Oct 2, 2024
8eead65
changed field name for failure_reasons count in metadata
tsdk02 Oct 2, 2024
7efe5a3
minor nits
tsdk02 Oct 2, 2024
ed8c0e2
Merge branch 'main' into analytics-v3
tsdk02 Oct 3, 2024
88391e6
convert sankey response structure to snake_case
tsdk02 Oct 3, 2024
f383a46
merged main and resolved conflicts
tsdk02 Oct 6, 2024
562ac7b
minor nits in failure reasons metric
tsdk02 Oct 8, 2024
f185cad
feat(analytics-backwards-compatibility): isolate the changes to a new…
lsampras Oct 9, 2024
20404bc
merged analytics-backwards-compat branch and fixed minor bugs
tsdk02 Oct 10, 2024
873986d
chore: run formatter
hyperswitch-bot[bot] Oct 10, 2024
9797a4d
add: backwards compatibility for payment_intent metrics
tsdk02 Oct 10, 2024
de72671
add: payment_processed_amount and count as a metric for payment_intents
tsdk02 Oct 10, 2024
67c4cdb
add: payments_distribution as a metric for payment_intents
tsdk02 Oct 10, 2024
3087ba4
update: formula for calculating payment_success_rate_distribution and…
tsdk02 Oct 10, 2024
3b78940
update: forumla for calculating failure_distribution metric for intents
tsdk02 Oct 11, 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/clickhouse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,12 @@ impl AnalyticsDataSource for ClickhouseClient {
fn get_table_engine(table: AnalyticsCollection) -> TableEngine {
match table {
AnalyticsCollection::Payment
| AnalyticsCollection::PaymentSessionized
| AnalyticsCollection::Refund
| AnalyticsCollection::RefundSessionized
| AnalyticsCollection::FraudCheck
| AnalyticsCollection::PaymentIntent
| AnalyticsCollection::PaymentIntentSessionized
| AnalyticsCollection::Dispute => {
TableEngine::CollapsingMergeTree { sign: "sign_flag" }
}
Expand Down Expand Up @@ -423,13 +426,16 @@ impl ToSql<ClickhouseClient> for AnalyticsCollection {
fn to_sql(&self, _table_engine: &TableEngine) -> error_stack::Result<String, ParsingError> {
match self {
Self::Payment => Ok("payment_attempts".to_string()),
Self::PaymentSessionized => Ok("sessionizer_payment_attempts".to_string()),
Self::Refund => Ok("refunds".to_string()),
Self::RefundSessionized => Ok("sessionizer_refunds".to_string()),
Self::FraudCheck => Ok("fraud_check".to_string()),
Self::SdkEvents => Ok("sdk_events_audit".to_string()),
Self::SdkEventsAnalytics => Ok("sdk_events".to_string()),
Self::ApiEvents => Ok("api_events_audit".to_string()),
Self::ApiEventsAnalytics => Ok("api_events".to_string()),
Self::PaymentIntent => Ok("payment_intents".to_string()),
Self::PaymentIntentSessionized => Ok("sessionizer_payment_intents".to_string()),
Self::ConnectorEvents => Ok("connector_events_audit".to_string()),
Self::OutgoingWebhookEvent => Ok("outgoing_webhook_events_audit".to_string()),
Self::Dispute => Ok("dispute".to_string()),
Expand Down
1 change: 1 addition & 0 deletions crates/analytics/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -996,6 +996,7 @@ pub enum AnalyticsFlow {
GetSearchResults,
GetDisputeFilters,
GetDisputeMetrics,
GetSankey,
}

impl FlowMetric for AnalyticsFlow {}
3 changes: 2 additions & 1 deletion crates/analytics/src/payment_intents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ pub mod accumulator;
mod core;
pub mod filters;
pub mod metrics;
pub mod sankey;
pub mod types;
pub use accumulator::{PaymentIntentMetricAccumulator, PaymentIntentMetricsAccumulator};

Expand All @@ -10,4 +11,4 @@ pub trait PaymentIntentAnalytics:
{
}

pub use self::core::{get_filters, get_metrics};
pub use self::core::{get_filters, get_metrics, get_sankey};
265 changes: 255 additions & 10 deletions crates/analytics/src/payment_intents/accumulator.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
use api_models::analytics::payment_intents::PaymentIntentMetricsBucketValue;
use bigdecimal::ToPrimitive;
use diesel_models::enums as storage_enums;

use super::metrics::PaymentIntentMetricRow;

#[derive(Debug, Default)]
pub struct PaymentIntentMetricsAccumulator {
pub successful_smart_retries: CountAccumulator,
pub total_smart_retries: CountAccumulator,
pub smart_retried_amount: SumAccumulator,
pub smart_retried_amount: SmartRetriedAmountAccumulator,
pub payment_intent_count: CountAccumulator,
pub payments_success_rate: PaymentsSuccessRateAccumulator,
pub payment_processed_amount: ProcessedAmountAccumulator,
pub payments_distribution: PaymentsDistributionAccumulator,
}

#[derive(Debug, Default)]
Expand Down Expand Up @@ -38,9 +42,31 @@ pub trait PaymentIntentMetricAccumulator {
}

#[derive(Debug, Default)]
#[repr(transparent)]
pub struct SumAccumulator {
pub total: Option<i64>,
pub struct SmartRetriedAmountAccumulator {
pub amount: Option<i64>,
pub amount_without_retries: Option<i64>,
}

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

#[derive(Debug, Default)]
pub struct ProcessedAmountAccumulator {
pub count_with_retries: Option<i64>,
pub total_with_retries: Option<i64>,
pub count_without_retries: Option<i64>,
pub total_without_retries: Option<i64>,
}

#[derive(Debug, Default)]
pub struct PaymentsDistributionAccumulator {
pub success_without_retries: u32,
pub failed_without_retries: u32,
pub total: u32,
}

impl PaymentIntentMetricAccumulator for CountAccumulator {
Expand All @@ -59,32 +85,251 @@ impl PaymentIntentMetricAccumulator for CountAccumulator {
}
}

impl PaymentIntentMetricAccumulator for SumAccumulator {
type MetricOutput = Option<u64>;
impl PaymentIntentMetricAccumulator for SmartRetriedAmountAccumulator {
type MetricOutput = (Option<u64>, Option<u64>);
#[inline]
fn add_metrics_bucket(&mut self, metrics: &PaymentIntentMetricRow) {
self.total = match (
self.total,
self.amount = match (
self.amount,
metrics.total.as_ref().and_then(ToPrimitive::to_i64),
) {
(None, None) => None,
(None, i @ Some(_)) | (i @ Some(_), None) => i,
(Some(a), Some(b)) => Some(a + b),
};
if metrics.first_attempt.unwrap_or(0) == 1 {
self.amount_without_retries = match (
self.amount_without_retries,
metrics.total.as_ref().and_then(ToPrimitive::to_i64),
) {
(None, None) => None,
(None, i @ Some(_)) | (i @ Some(_), None) => i,
(Some(a), Some(b)) => Some(a + b),
}
} else {
self.amount_without_retries = Some(0);
}
}
#[inline]
fn collect(self) -> Self::MetricOutput {
self.total.and_then(|i| u64::try_from(i).ok())
let with_retries = self.amount.and_then(|i| u64::try_from(i).ok()).or(Some(0));
let without_retries = self
.amount_without_retries
.and_then(|i| u64::try_from(i).ok())
.or(Some(0));
(with_retries, without_retries)
}
}

impl PaymentIntentMetricAccumulator for PaymentsSuccessRateAccumulator {
type MetricOutput = (
Option<u32>,
Option<u32>,
Option<u32>,
Option<f64>,
Option<f64>,
);

fn add_metrics_bucket(&mut self, metrics: &PaymentIntentMetricRow) {
if let Some(ref status) = metrics.status {
if status.as_ref() == &storage_enums::IntentStatus::Succeeded {
if let Some(success) = metrics
.count
.and_then(|success| u32::try_from(success).ok())
{
self.success += success;
if metrics.first_attempt.unwrap_or(0) == 1 {
self.success_without_retries += success;
}
}
}
if status.as_ref() != &storage_enums::IntentStatus::RequiresCustomerAction
&& status.as_ref() != &storage_enums::IntentStatus::RequiresPaymentMethod
&& status.as_ref() != &storage_enums::IntentStatus::RequiresMerchantAction
&& status.as_ref() != &storage_enums::IntentStatus::RequiresConfirmation
{
if let Some(total) = metrics.count.and_then(|total| u32::try_from(total).ok()) {
self.total += total;
}
}
}
}

fn collect(self) -> Self::MetricOutput {
if self.total == 0 {
(None, None, None, None, None)
} else {
let success = Some(self.success);
let success_without_retries = Some(self.success_without_retries);
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 success_without_retries_rate = match (success_without_retries, total) {
(Some(s), Some(t)) if t > 0 => Some(f64::from(s) * 100.0 / f64::from(t)),
_ => None,
};

(
success,
success_without_retries,
total,
success_rate,
success_without_retries_rate,
)
}
}
}

impl PaymentIntentMetricAccumulator for ProcessedAmountAccumulator {
type MetricOutput = (Option<u64>, Option<u64>, Option<u64>, Option<u64>);
#[inline]
fn add_metrics_bucket(&mut self, metrics: &PaymentIntentMetricRow) {
self.total_with_retries = match (
self.total_with_retries,
metrics.total.as_ref().and_then(ToPrimitive::to_i64),
) {
(None, None) => None,
(None, i @ Some(_)) | (i @ Some(_), None) => i,
(Some(a), Some(b)) => Some(a + b),
};

self.count_with_retries = match (self.count_with_retries, metrics.count) {
(None, None) => None,
(None, i @ Some(_)) | (i @ Some(_), None) => i,
(Some(a), Some(b)) => Some(a + b),
};

if metrics.first_attempt.unwrap_or(0) == 1 {
self.total_without_retries = match (
self.total_without_retries,
metrics.total.as_ref().and_then(ToPrimitive::to_i64),
) {
(None, None) => None,
(None, i @ Some(_)) | (i @ Some(_), None) => i,
(Some(a), Some(b)) => Some(a + b),
};

self.count_without_retries = match (self.count_without_retries, 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 {
let total_with_retries = u64::try_from(self.total_with_retries.unwrap_or(0)).ok();
let count_with_retries = self.count_with_retries.and_then(|i| u64::try_from(i).ok());

let total_without_retries = u64::try_from(self.total_without_retries.unwrap_or(0)).ok();
let count_without_retries = self
.count_without_retries
.and_then(|i| u64::try_from(i).ok());

(
total_with_retries,
count_with_retries,
total_without_retries,
count_without_retries,
)
}
}

impl PaymentIntentMetricAccumulator for PaymentsDistributionAccumulator {
type MetricOutput = (Option<f64>, Option<f64>);

fn add_metrics_bucket(&mut self, metrics: &PaymentIntentMetricRow) {
let first_attempt = metrics.first_attempt.unwrap_or(0);
if let Some(ref status) = metrics.status {
if status.as_ref() == &storage_enums::IntentStatus::Succeeded {
if let Some(success) = metrics
.count
.and_then(|success| u32::try_from(success).ok())
{
if first_attempt == 1 {
self.success_without_retries += success;
}
}
}
if let Some(failed) = metrics.count.and_then(|failed| u32::try_from(failed).ok()) {
if first_attempt == 0
|| (first_attempt == 1
&& status.as_ref() == &storage_enums::IntentStatus::Failed)
{
self.failed_without_retries += failed;
}
}

if let Some(total) = metrics.count.and_then(|total| u32::try_from(total).ok()) {
self.total += total;
}
}
}

fn collect(self) -> Self::MetricOutput {
if self.total == 0 {
(None, None)
} else {
let success_without_retries = Some(self.success_without_retries);
let failed_without_retries = Some(self.failed_without_retries);
let total = Some(self.total);

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

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

impl PaymentIntentMetricsAccumulator {
pub fn collect(self) -> PaymentIntentMetricsBucketValue {
let (
successful_payments,
successful_payments_without_smart_retries,
total_payments,
payments_success_rate,
payments_success_rate_without_smart_retries,
) = self.payments_success_rate.collect();
let (smart_retried_amount, smart_retried_amount_without_smart_retries) =
self.smart_retried_amount.collect();
let (
payment_processed_amount,
payment_processed_count,
payment_processed_amount_without_smart_retries,
payment_processed_count_without_smart_retries,
) = self.payment_processed_amount.collect();
let (
payments_success_rate_distribution_without_smart_retries,
payments_failure_rate_distribution_without_smart_retries,
) = self.payments_distribution.collect();
PaymentIntentMetricsBucketValue {
successful_smart_retries: self.successful_smart_retries.collect(),
total_smart_retries: self.total_smart_retries.collect(),
smart_retried_amount: self.smart_retried_amount.collect(),
smart_retried_amount,
smart_retried_amount_without_smart_retries,
payment_intent_count: self.payment_intent_count.collect(),
successful_payments,
successful_payments_without_smart_retries,
total_payments,
payments_success_rate,
payments_success_rate_without_smart_retries,
payment_processed_amount,
payment_processed_count,
payment_processed_amount_without_smart_retries,
payment_processed_count_without_smart_retries,
payments_success_rate_distribution_without_smart_retries,
payments_failure_rate_distribution_without_smart_retries,
}
}
}
Loading
Loading