fix(analytics): fix bugs in payments page metrics in Analytics V2 dashboard (#6654)

This commit is contained in:
Sandeep Kumar
2024-11-28 15:29:55 +05:30
committed by GitHub
parent 2c865156a2
commit 93459fde5f
6 changed files with 80 additions and 121 deletions

View File

@ -273,8 +273,14 @@ impl PaymentIntentMetricAccumulator for PaymentsDistributionAccumulator {
} }
} }
if let Some(total) = metrics.count.and_then(|total| u32::try_from(total).ok()) { if status.as_ref() != &storage_enums::IntentStatus::RequiresCustomerAction
self.total += total; && 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;
}
} }
} }
} }

View File

@ -8,10 +8,9 @@ use api_models::analytics::{
}, },
GetPaymentIntentFiltersRequest, GetPaymentIntentMetricRequest, PaymentIntentFilterValue, GetPaymentIntentFiltersRequest, GetPaymentIntentMetricRequest, PaymentIntentFilterValue,
PaymentIntentFiltersResponse, PaymentIntentsAnalyticsMetadata, PaymentIntentsMetricsResponse, PaymentIntentFiltersResponse, PaymentIntentsAnalyticsMetadata, PaymentIntentsMetricsResponse,
SankeyResponse,
}; };
use bigdecimal::ToPrimitive; use bigdecimal::ToPrimitive;
use common_enums::{Currency, IntentStatus}; use common_enums::Currency;
use common_utils::{errors::CustomResult, types::TimeRange}; use common_utils::{errors::CustomResult, types::TimeRange};
use currency_conversion::{conversion::convert, types::ExchangeRates}; use currency_conversion::{conversion::convert, types::ExchangeRates};
use error_stack::ResultExt; use error_stack::ResultExt;
@ -24,7 +23,7 @@ use router_env::{
use super::{ use super::{
filters::{get_payment_intent_filter_for_dimension, PaymentIntentFilterRow}, filters::{get_payment_intent_filter_for_dimension, PaymentIntentFilterRow},
metrics::PaymentIntentMetricRow, metrics::PaymentIntentMetricRow,
sankey::{get_sankey_data, SessionizerRefundStatus}, sankey::{get_sankey_data, SankeyRow},
PaymentIntentMetricsAccumulator, PaymentIntentMetricsAccumulator,
}; };
use crate::{ use crate::{
@ -51,7 +50,7 @@ pub async fn get_sankey(
pool: &AnalyticsProvider, pool: &AnalyticsProvider,
auth: &AuthInfo, auth: &AuthInfo,
req: TimeRange, req: TimeRange,
) -> AnalyticsResult<SankeyResponse> { ) -> AnalyticsResult<Vec<SankeyRow>> {
match pool { match pool {
AnalyticsProvider::Sqlx(_) => Err(AnalyticsError::NotImplemented( AnalyticsProvider::Sqlx(_) => Err(AnalyticsError::NotImplemented(
"Sankey not implemented for sqlx", "Sankey not implemented for sqlx",
@ -62,69 +61,7 @@ pub async fn get_sankey(
let sankey_rows = get_sankey_data(ckh_pool, auth, &req) let sankey_rows = get_sankey_data(ckh_pool, auth, &req)
.await .await
.change_context(AnalyticsError::UnknownError)?; .change_context(AnalyticsError::UnknownError)?;
let mut sankey_response = SankeyResponse::default(); Ok(sankey_rows)
for i in sankey_rows {
match (
i.status.as_ref(),
i.refunds_status.unwrap_or_default().as_ref(),
i.attempt_count,
) {
(IntentStatus::Succeeded, SessionizerRefundStatus::FullRefunded, 1) => {
sankey_response.refunded += i.count;
sankey_response.normal_success += i.count
}
(IntentStatus::Succeeded, SessionizerRefundStatus::PartialRefunded, 1) => {
sankey_response.partial_refunded += i.count;
sankey_response.normal_success += i.count
}
(IntentStatus::Succeeded, SessionizerRefundStatus::FullRefunded, _) => {
sankey_response.refunded += i.count;
sankey_response.smart_retried_success += i.count
}
(IntentStatus::Succeeded, SessionizerRefundStatus::PartialRefunded, _) => {
sankey_response.partial_refunded += i.count;
sankey_response.smart_retried_success += i.count
}
(
IntentStatus::Succeeded
| IntentStatus::PartiallyCaptured
| IntentStatus::PartiallyCapturedAndCapturable
| IntentStatus::RequiresCapture,
SessionizerRefundStatus::NotRefunded,
1,
) => sankey_response.normal_success += i.count,
(
IntentStatus::Succeeded
| IntentStatus::PartiallyCaptured
| IntentStatus::PartiallyCapturedAndCapturable
| IntentStatus::RequiresCapture,
SessionizerRefundStatus::NotRefunded,
_,
) => sankey_response.smart_retried_success += i.count,
(IntentStatus::Failed, _, 1) => sankey_response.normal_failure += i.count,
(IntentStatus::Failed, _, _) => {
sankey_response.smart_retried_failure += i.count
}
(IntentStatus::Cancelled, _, _) => sankey_response.cancelled += i.count,
(IntentStatus::Processing, _, _) => sankey_response.pending += i.count,
(IntentStatus::RequiresCustomerAction, _, _) => {
sankey_response.customer_awaited += i.count
}
(IntentStatus::RequiresMerchantAction, _, _) => {
sankey_response.merchant_awaited += i.count
}
(IntentStatus::RequiresPaymentMethod, _, _) => {
sankey_response.pm_awaited += i.count
}
(IntentStatus::RequiresConfirmation, _, _) => {
sankey_response.confirmation_awaited += i.count
}
i @ (_, _, _) => {
router_env::logger::error!(status=?i, "Unknown status in sankey data");
}
}
}
Ok(sankey_response)
} }
} }
} }

View File

@ -5,7 +5,6 @@ use common_utils::{
}; };
use error_stack::ResultExt; use error_stack::ResultExt;
use router_env::logger; use router_env::logger;
use time::PrimitiveDateTime;
use crate::{ use crate::{
clickhouse::ClickhouseClient, clickhouse::ClickhouseClient,
@ -13,29 +12,19 @@ use crate::{
types::{AnalyticsCollection, DBEnumWrapper, MetricsError, MetricsResult}, types::{AnalyticsCollection, DBEnumWrapper, MetricsError, MetricsResult},
}; };
#[derive(Debug, PartialEq, Eq, serde::Deserialize, Hash)]
pub struct PaymentIntentMetricRow {
pub profile_id: Option<String>,
pub connector: Option<String>,
pub authentication_type: Option<DBEnumWrapper<enums::AuthenticationType>>,
pub payment_method: Option<String>,
pub payment_method_type: Option<String>,
pub card_network: Option<String>,
pub merchant_id: Option<String>,
pub card_last_4: Option<String>,
pub card_issuer: Option<String>,
pub error_reason: Option<String>,
pub first_attempt: Option<i64>,
pub total: Option<bigdecimal::BigDecimal>,
pub count: Option<i64>,
#[serde(with = "common_utils::custom_serde::iso8601::option")]
pub start_bucket: Option<PrimitiveDateTime>,
#[serde(with = "common_utils::custom_serde::iso8601::option")]
pub end_bucket: Option<PrimitiveDateTime>,
}
#[derive( #[derive(
Debug, Default, serde::Deserialize, strum::AsRefStr, strum::EnumString, strum::Display, Clone,
Copy,
Debug,
Default,
Eq,
Hash,
PartialEq,
serde::Deserialize,
serde::Serialize,
strum::Display,
strum::EnumIter,
strum::EnumString,
)] )]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub enum SessionizerRefundStatus { pub enum SessionizerRefundStatus {
@ -45,13 +34,36 @@ pub enum SessionizerRefundStatus {
PartialRefunded, PartialRefunded,
} }
#[derive(Debug, serde::Deserialize)] #[derive(
Clone,
Copy,
Debug,
Default,
Eq,
Hash,
PartialEq,
serde::Deserialize,
serde::Serialize,
strum::Display,
strum::EnumIter,
strum::EnumString,
)]
#[serde(rename_all = "snake_case")]
pub enum SessionizerDisputeStatus {
DisputePresent,
#[default]
NotDisputed,
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct SankeyRow { pub struct SankeyRow {
pub count: i64,
pub status: DBEnumWrapper<enums::IntentStatus>, pub status: DBEnumWrapper<enums::IntentStatus>,
#[serde(default)] #[serde(default)]
pub refunds_status: Option<DBEnumWrapper<SessionizerRefundStatus>>, pub refunds_status: Option<DBEnumWrapper<SessionizerRefundStatus>>,
pub attempt_count: i64, #[serde(default)]
pub count: i64, pub dispute_status: Option<DBEnumWrapper<SessionizerDisputeStatus>>,
pub first_attempt: i64,
} }
impl TryInto<SankeyRow> for serde_json::Value { impl TryInto<SankeyRow> for serde_json::Value {
@ -90,7 +102,12 @@ pub async fn get_sankey_data(
.change_context(MetricsError::QueryBuildingError)?; .change_context(MetricsError::QueryBuildingError)?;
query_builder query_builder
.add_select_column("attempt_count") .add_select_column("dispute_status")
.attach_printable("Error adding select clause")
.change_context(MetricsError::QueryBuildingError)?;
query_builder
.add_select_column("(attempt_count = 1) as first_attempt")
.attach_printable("Error adding select clause") .attach_printable("Error adding select clause")
.change_context(MetricsError::QueryBuildingError)?; .change_context(MetricsError::QueryBuildingError)?;
@ -112,7 +129,12 @@ pub async fn get_sankey_data(
.change_context(MetricsError::QueryBuildingError)?; .change_context(MetricsError::QueryBuildingError)?;
query_builder query_builder
.add_group_by_clause("attempt_count") .add_group_by_clause("dispute_status")
.attach_printable("Error adding group by clause")
.change_context(MetricsError::QueryBuildingError)?;
query_builder
.add_group_by_clause("first_attempt")
.attach_printable("Error adding group by clause") .attach_printable("Error adding group by clause")
.change_context(MetricsError::QueryBuildingError)?; .change_context(MetricsError::QueryBuildingError)?;

View File

@ -218,12 +218,19 @@ impl PaymentMetricAccumulator for PaymentsDistributionAccumulator {
} }
} }
} }
if let Some(total) = metrics.count.and_then(|total| u32::try_from(total).ok()) { if status.as_ref() != &storage_enums::AttemptStatus::AuthenticationFailed
self.total += total; && status.as_ref() != &storage_enums::AttemptStatus::PaymentMethodAwaited
if metrics.first_attempt.unwrap_or(false) { && status.as_ref() != &storage_enums::AttemptStatus::DeviceDataCollectionPending
self.total_without_retries += total; && status.as_ref() != &storage_enums::AttemptStatus::ConfirmationAwaited
} else { && status.as_ref() != &storage_enums::AttemptStatus::Unresolved
self.total_with_only_retries += total; {
if let Some(total) = metrics.count.and_then(|total| u32::try_from(total).ok()) {
self.total += total;
if metrics.first_attempt.unwrap_or(false) {
self.total_without_retries += total;
} else {
self.total_with_only_retries += total;
}
} }
} }
} }

View File

@ -160,11 +160,6 @@ where
.switch()?; .switch()?;
} }
outer_query_builder
.set_limit_by(5, &filtered_dimensions)
.attach_printable("Error adding limit clause")
.switch()?;
outer_query_builder outer_query_builder
.execute_query::<PaymentMetricRow, _>(pool) .execute_query::<PaymentMetricRow, _>(pool)
.await .await

View File

@ -458,17 +458,9 @@ pub struct GetDisputeMetricRequest {
#[derive(Clone, Debug, Default, serde::Serialize)] #[derive(Clone, Debug, Default, serde::Serialize)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub struct SankeyResponse { pub struct SankeyResponse {
pub normal_success: i64, pub count: i64,
pub normal_failure: i64, pub status: String,
pub cancelled: i64, pub refunds_status: Option<String>,
pub smart_retried_success: i64, pub dispute_status: Option<String>,
pub smart_retried_failure: i64, pub first_attempt: i64,
pub pending: i64,
pub partial_refunded: i64,
pub refunded: i64,
pub disputed: i64,
pub pm_awaited: i64,
pub customer_awaited: i64,
pub merchant_awaited: i64,
pub confirmation_awaited: i64,
} }