mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 01:57:45 +08:00 
			
		
		
		
	feat(analytics): Add Clickhouse based analytics (#2988)
Co-authored-by: harsh_sharma_juspay <harsh.sharma@juspay.in> Co-authored-by: Ivor Dsouza <ivor.dsouza@juspay.in> Co-authored-by: Chethan Rao <70657455+Chethan-rao@users.noreply.github.com> Co-authored-by: nain-F49FF806 <126972030+nain-F49FF806@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Co-authored-by: akshay.s <akshay.s@juspay.in> Co-authored-by: Gnanasundari24 <118818938+Gnanasundari24@users.noreply.github.com>
This commit is contained in:
		| @ -1,15 +1,20 @@ | ||||
| use std::collections::HashSet; | ||||
|  | ||||
| use common_utils::events::ApiEventMetric; | ||||
| use time::PrimitiveDateTime; | ||||
| use common_utils::pii::EmailStrategy; | ||||
| use masking::Secret; | ||||
|  | ||||
| use self::{ | ||||
|     payments::{PaymentDimensions, PaymentMetrics}, | ||||
|     api_event::{ApiEventDimensions, ApiEventMetrics}, | ||||
|     payments::{PaymentDimensions, PaymentDistributions, PaymentMetrics}, | ||||
|     refunds::{RefundDimensions, RefundMetrics}, | ||||
|     sdk_events::{SdkEventDimensions, SdkEventMetrics}, | ||||
| }; | ||||
| pub use crate::payments::TimeRange; | ||||
|  | ||||
| pub mod api_event; | ||||
| pub mod payments; | ||||
| pub mod refunds; | ||||
| pub mod sdk_events; | ||||
|  | ||||
| #[derive(Debug, serde::Serialize)] | ||||
| pub struct NameDescription { | ||||
| @ -25,23 +30,12 @@ pub struct GetInfoResponse { | ||||
|     pub dimensions: Vec<NameDescription>, | ||||
| } | ||||
|  | ||||
| impl ApiEventMetric for GetInfoResponse {} | ||||
|  | ||||
| #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, PartialEq, Eq, Hash)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct TimeRange { | ||||
|     #[serde(with = "common_utils::custom_serde::iso8601")] | ||||
|     pub start_time: PrimitiveDateTime, | ||||
|     #[serde(default, with = "common_utils::custom_serde::iso8601::option")] | ||||
|     pub end_time: Option<PrimitiveDateTime>, | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Copy, Debug, serde::Deserialize, masking::Serialize)] | ||||
| #[derive(Clone, Copy, Debug, serde::Deserialize, serde::Serialize)] | ||||
| pub struct TimeSeries { | ||||
|     pub granularity: Granularity, | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Copy, Debug, serde::Deserialize, masking::Serialize)] | ||||
| #[derive(Clone, Copy, Debug, serde::Deserialize, serde::Serialize)] | ||||
| pub enum Granularity { | ||||
|     #[serde(rename = "G_ONEMIN")] | ||||
|     OneMin, | ||||
| @ -57,7 +51,7 @@ pub enum Granularity { | ||||
|     OneDay, | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Debug, serde::Deserialize, masking::Serialize)] | ||||
| #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct GetPaymentMetricRequest { | ||||
|     pub time_series: Option<TimeSeries>, | ||||
| @ -67,13 +61,51 @@ pub struct GetPaymentMetricRequest { | ||||
|     #[serde(default)] | ||||
|     pub filters: payments::PaymentFilters, | ||||
|     pub metrics: HashSet<PaymentMetrics>, | ||||
|     pub distribution: Option<Distribution>, | ||||
|     #[serde(default)] | ||||
|     pub delta: bool, | ||||
| } | ||||
|  | ||||
| impl ApiEventMetric for GetPaymentMetricRequest {} | ||||
| #[derive(Clone, Copy, Debug, serde::Deserialize, serde::Serialize)] | ||||
| pub enum QueryLimit { | ||||
|     #[serde(rename = "TOP_5")] | ||||
|     Top5, | ||||
|     #[serde(rename = "TOP_10")] | ||||
|     Top10, | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Debug, serde::Deserialize, masking::Serialize)] | ||||
| #[allow(clippy::from_over_into)] | ||||
| impl Into<u64> for QueryLimit { | ||||
|     fn into(self) -> u64 { | ||||
|         match self { | ||||
|             Self::Top5 => 5, | ||||
|             Self::Top10 => 10, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct Distribution { | ||||
|     pub distribution_for: PaymentDistributions, | ||||
|     pub distribution_cardinality: QueryLimit, | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct ReportRequest { | ||||
|     pub time_range: TimeRange, | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct GenerateReportRequest { | ||||
|     pub request: ReportRequest, | ||||
|     pub merchant_id: String, | ||||
|     pub email: Secret<String, EmailStrategy>, | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct GetRefundMetricRequest { | ||||
|     pub time_series: Option<TimeSeries>, | ||||
| @ -87,14 +119,26 @@ pub struct GetRefundMetricRequest { | ||||
|     pub delta: bool, | ||||
| } | ||||
|  | ||||
| impl ApiEventMetric for GetRefundMetricRequest {} | ||||
| #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct GetSdkEventMetricRequest { | ||||
|     pub time_series: Option<TimeSeries>, | ||||
|     pub time_range: TimeRange, | ||||
|     #[serde(default)] | ||||
|     pub group_by_names: Vec<SdkEventDimensions>, | ||||
|     #[serde(default)] | ||||
|     pub filters: sdk_events::SdkEventFilters, | ||||
|     pub metrics: HashSet<SdkEventMetrics>, | ||||
|     #[serde(default)] | ||||
|     pub delta: bool, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, serde::Serialize)] | ||||
| pub struct AnalyticsMetadata { | ||||
|     pub current_time_range: TimeRange, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, serde::Deserialize, masking::Serialize)] | ||||
| #[derive(Debug, serde::Deserialize, serde::Serialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct GetPaymentFiltersRequest { | ||||
|     pub time_range: TimeRange, | ||||
| @ -102,16 +146,12 @@ pub struct GetPaymentFiltersRequest { | ||||
|     pub group_by_names: Vec<PaymentDimensions>, | ||||
| } | ||||
|  | ||||
| impl ApiEventMetric for GetPaymentFiltersRequest {} | ||||
|  | ||||
| #[derive(Debug, Default, serde::Serialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct PaymentFiltersResponse { | ||||
|     pub query_data: Vec<FilterValue>, | ||||
| } | ||||
|  | ||||
| impl ApiEventMetric for PaymentFiltersResponse {} | ||||
|  | ||||
| #[derive(Debug, serde::Serialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct FilterValue { | ||||
| @ -119,34 +159,88 @@ pub struct FilterValue { | ||||
|     pub values: Vec<String>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, serde::Deserialize, masking::Serialize)] | ||||
| #[derive(Debug, serde::Deserialize, serde::Serialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
|  | ||||
| pub struct GetRefundFilterRequest { | ||||
|     pub time_range: TimeRange, | ||||
|     #[serde(default)] | ||||
|     pub group_by_names: Vec<RefundDimensions>, | ||||
| } | ||||
|  | ||||
| impl ApiEventMetric for GetRefundFilterRequest {} | ||||
|  | ||||
| #[derive(Debug, Default, serde::Serialize, Eq, PartialEq)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct RefundFiltersResponse { | ||||
|     pub query_data: Vec<RefundFilterValue>, | ||||
| } | ||||
|  | ||||
| impl ApiEventMetric for RefundFiltersResponse {} | ||||
|  | ||||
| #[derive(Debug, serde::Serialize, Eq, PartialEq)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
|  | ||||
| pub struct RefundFilterValue { | ||||
|     pub dimension: RefundDimensions, | ||||
|     pub values: Vec<String>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, serde::Deserialize, serde::Serialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct GetSdkEventFiltersRequest { | ||||
|     pub time_range: TimeRange, | ||||
|     #[serde(default)] | ||||
|     pub group_by_names: Vec<SdkEventDimensions>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, serde::Serialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct SdkEventFiltersResponse { | ||||
|     pub query_data: Vec<SdkEventFilterValue>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, serde::Serialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct SdkEventFilterValue { | ||||
|     pub dimension: SdkEventDimensions, | ||||
|     pub values: Vec<String>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, serde::Serialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct MetricsResponse<T> { | ||||
|     pub query_data: Vec<T>, | ||||
|     pub meta_data: [AnalyticsMetadata; 1], | ||||
| } | ||||
|  | ||||
| #[derive(Debug, serde::Deserialize, serde::Serialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct GetApiEventFiltersRequest { | ||||
|     pub time_range: TimeRange, | ||||
|     #[serde(default)] | ||||
|     pub group_by_names: Vec<ApiEventDimensions>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, serde::Serialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct ApiEventFiltersResponse { | ||||
|     pub query_data: Vec<ApiEventFilterValue>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, serde::Serialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct ApiEventFilterValue { | ||||
|     pub dimension: ApiEventDimensions, | ||||
|     pub values: Vec<String>, | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct GetApiEventMetricRequest { | ||||
|     pub time_series: Option<TimeSeries>, | ||||
|     pub time_range: TimeRange, | ||||
|     #[serde(default)] | ||||
|     pub group_by_names: Vec<ApiEventDimensions>, | ||||
|     #[serde(default)] | ||||
|     pub filters: api_event::ApiEventFilters, | ||||
|     pub metrics: HashSet<ApiEventMetrics>, | ||||
|     #[serde(default)] | ||||
|     pub delta: bool, | ||||
| } | ||||
|  | ||||
							
								
								
									
										148
									
								
								crates/api_models/src/analytics/api_event.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								crates/api_models/src/analytics/api_event.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,148 @@ | ||||
| use std::{ | ||||
|     collections::hash_map::DefaultHasher, | ||||
|     hash::{Hash, Hasher}, | ||||
| }; | ||||
|  | ||||
| use super::{NameDescription, TimeRange}; | ||||
| #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] | ||||
| pub struct ApiLogsRequest { | ||||
|     #[serde(flatten)] | ||||
|     pub query_param: QueryType, | ||||
|     pub api_name_filter: Option<Vec<String>>, | ||||
| } | ||||
|  | ||||
| pub enum FilterType { | ||||
|     ApiCountFilter, | ||||
|     LatencyFilter, | ||||
|     StatusCodeFilter, | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] | ||||
| #[serde(tag = "type")] | ||||
| pub enum QueryType { | ||||
|     Payment { | ||||
|         payment_id: String, | ||||
|     }, | ||||
|     Refund { | ||||
|         payment_id: String, | ||||
|         refund_id: String, | ||||
|     }, | ||||
| } | ||||
|  | ||||
| #[derive( | ||||
|     Debug, | ||||
|     serde::Serialize, | ||||
|     serde::Deserialize, | ||||
|     strum::AsRefStr, | ||||
|     PartialEq, | ||||
|     PartialOrd, | ||||
|     Eq, | ||||
|     Ord, | ||||
|     strum::Display, | ||||
|     strum::EnumIter, | ||||
|     Clone, | ||||
|     Copy, | ||||
| )] | ||||
| #[serde(rename_all = "snake_case")] | ||||
| #[strum(serialize_all = "snake_case")] | ||||
| pub enum ApiEventDimensions { | ||||
|     // Do not change the order of these enums | ||||
|     // Consult the Dashboard FE folks since these also affects the order of metrics on FE | ||||
|     StatusCode, | ||||
|     FlowType, | ||||
|     ApiFlow, | ||||
| } | ||||
|  | ||||
| impl From<ApiEventDimensions> for NameDescription { | ||||
|     fn from(value: ApiEventDimensions) -> Self { | ||||
|         Self { | ||||
|             name: value.to_string(), | ||||
|             desc: String::new(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| #[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)] | ||||
| pub struct ApiEventFilters { | ||||
|     pub status_code: Vec<u64>, | ||||
|     pub flow_type: Vec<String>, | ||||
|     pub api_flow: Vec<String>, | ||||
| } | ||||
|  | ||||
| #[derive( | ||||
|     Clone, | ||||
|     Debug, | ||||
|     Hash, | ||||
|     PartialEq, | ||||
|     Eq, | ||||
|     serde::Serialize, | ||||
|     serde::Deserialize, | ||||
|     strum::Display, | ||||
|     strum::EnumIter, | ||||
|     strum::AsRefStr, | ||||
| )] | ||||
| #[strum(serialize_all = "snake_case")] | ||||
| #[serde(rename_all = "snake_case")] | ||||
| pub enum ApiEventMetrics { | ||||
|     Latency, | ||||
|     ApiCount, | ||||
|     StatusCodeCount, | ||||
| } | ||||
|  | ||||
| impl From<ApiEventMetrics> for NameDescription { | ||||
|     fn from(value: ApiEventMetrics) -> Self { | ||||
|         Self { | ||||
|             name: value.to_string(), | ||||
|             desc: String::new(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, serde::Serialize, Eq)] | ||||
| pub struct ApiEventMetricsBucketIdentifier { | ||||
|     #[serde(rename = "time_range")] | ||||
|     pub time_bucket: TimeRange, | ||||
|     // Coz FE sucks | ||||
|     #[serde(rename = "time_bucket")] | ||||
|     #[serde(with = "common_utils::custom_serde::iso8601custom")] | ||||
|     pub start_time: time::PrimitiveDateTime, | ||||
| } | ||||
|  | ||||
| impl ApiEventMetricsBucketIdentifier { | ||||
|     pub fn new(normalized_time_range: TimeRange) -> Self { | ||||
|         Self { | ||||
|             time_bucket: normalized_time_range, | ||||
|             start_time: normalized_time_range.start_time, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Hash for ApiEventMetricsBucketIdentifier { | ||||
|     fn hash<H: Hasher>(&self, state: &mut H) { | ||||
|         self.time_bucket.hash(state); | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl PartialEq for ApiEventMetricsBucketIdentifier { | ||||
|     fn eq(&self, other: &Self) -> bool { | ||||
|         let mut left = DefaultHasher::new(); | ||||
|         self.hash(&mut left); | ||||
|         let mut right = DefaultHasher::new(); | ||||
|         other.hash(&mut right); | ||||
|         left.finish() == right.finish() | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, serde::Serialize)] | ||||
| pub struct ApiEventMetricsBucketValue { | ||||
|     pub latency: Option<u64>, | ||||
|     pub api_count: Option<u64>, | ||||
|     pub status_code_count: Option<u64>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, serde::Serialize)] | ||||
| pub struct ApiMetricsBucketResponse { | ||||
|     #[serde(flatten)] | ||||
|     pub values: ApiEventMetricsBucketValue, | ||||
|     #[serde(flatten)] | ||||
|     pub dimensions: ApiEventMetricsBucketIdentifier, | ||||
| } | ||||
| @ -3,13 +3,12 @@ use std::{ | ||||
|     hash::{Hash, Hasher}, | ||||
| }; | ||||
|  | ||||
| use common_enums::enums::{AttemptStatus, AuthenticationType, Currency, PaymentMethod}; | ||||
| use common_utils::events::ApiEventMetric; | ||||
|  | ||||
| use super::{NameDescription, TimeRange}; | ||||
| use crate::{analytics::MetricsResponse, enums::Connector}; | ||||
| use crate::enums::{ | ||||
|     AttemptStatus, AuthenticationType, Connector, Currency, PaymentMethod, PaymentMethodType, | ||||
| }; | ||||
|  | ||||
| #[derive(Clone, Debug, Default, serde::Deserialize, masking::Serialize)] | ||||
| #[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)] | ||||
| pub struct PaymentFilters { | ||||
|     #[serde(default)] | ||||
|     pub currency: Vec<Currency>, | ||||
| @ -21,6 +20,8 @@ pub struct PaymentFilters { | ||||
|     pub auth_type: Vec<AuthenticationType>, | ||||
|     #[serde(default)] | ||||
|     pub payment_method: Vec<PaymentMethod>, | ||||
|     #[serde(default)] | ||||
|     pub payment_method_type: Vec<PaymentMethodType>, | ||||
| } | ||||
|  | ||||
| #[derive( | ||||
| @ -44,6 +45,7 @@ pub enum PaymentDimensions { | ||||
|     // Consult the Dashboard FE folks since these also affects the order of metrics on FE | ||||
|     Connector, | ||||
|     PaymentMethod, | ||||
|     PaymentMethodType, | ||||
|     Currency, | ||||
|     #[strum(serialize = "authentication_type")] | ||||
|     #[serde(rename = "authentication_type")] | ||||
| @ -73,6 +75,35 @@ pub enum PaymentMetrics { | ||||
|     PaymentSuccessCount, | ||||
|     PaymentProcessedAmount, | ||||
|     AvgTicketSize, | ||||
|     RetriesCount, | ||||
|     ConnectorSuccessRate, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, serde::Serialize)] | ||||
| pub struct ErrorResult { | ||||
|     pub reason: String, | ||||
|     pub count: i64, | ||||
|     pub percentage: f64, | ||||
| } | ||||
|  | ||||
| #[derive( | ||||
|     Clone, | ||||
|     Copy, | ||||
|     Debug, | ||||
|     Hash, | ||||
|     PartialEq, | ||||
|     Eq, | ||||
|     serde::Serialize, | ||||
|     serde::Deserialize, | ||||
|     strum::Display, | ||||
|     strum::EnumIter, | ||||
|     strum::AsRefStr, | ||||
| )] | ||||
| #[strum(serialize_all = "snake_case")] | ||||
| #[serde(rename_all = "snake_case")] | ||||
| pub enum PaymentDistributions { | ||||
|     #[strum(serialize = "error_message")] | ||||
|     PaymentErrorMessage, | ||||
| } | ||||
|  | ||||
| pub mod metric_behaviour { | ||||
| @ -109,6 +140,7 @@ pub struct PaymentMetricsBucketIdentifier { | ||||
|     #[serde(rename = "authentication_type")] | ||||
|     pub auth_type: Option<AuthenticationType>, | ||||
|     pub payment_method: Option<String>, | ||||
|     pub payment_method_type: Option<String>, | ||||
|     #[serde(rename = "time_range")] | ||||
|     pub time_bucket: TimeRange, | ||||
|     // Coz FE sucks | ||||
| @ -124,6 +156,7 @@ impl PaymentMetricsBucketIdentifier { | ||||
|         connector: Option<String>, | ||||
|         auth_type: Option<AuthenticationType>, | ||||
|         payment_method: Option<String>, | ||||
|         payment_method_type: Option<String>, | ||||
|         normalized_time_range: TimeRange, | ||||
|     ) -> Self { | ||||
|         Self { | ||||
| @ -132,6 +165,7 @@ impl PaymentMetricsBucketIdentifier { | ||||
|             connector, | ||||
|             auth_type, | ||||
|             payment_method, | ||||
|             payment_method_type, | ||||
|             time_bucket: normalized_time_range, | ||||
|             start_time: normalized_time_range.start_time, | ||||
|         } | ||||
| @ -145,6 +179,7 @@ impl Hash for PaymentMetricsBucketIdentifier { | ||||
|         self.connector.hash(state); | ||||
|         self.auth_type.map(|i| i.to_string()).hash(state); | ||||
|         self.payment_method.hash(state); | ||||
|         self.payment_method_type.hash(state); | ||||
|         self.time_bucket.hash(state); | ||||
|     } | ||||
| } | ||||
| @ -166,6 +201,10 @@ pub struct PaymentMetricsBucketValue { | ||||
|     pub payment_success_count: Option<u64>, | ||||
|     pub payment_processed_amount: Option<u64>, | ||||
|     pub avg_ticket_size: Option<f64>, | ||||
|     pub payment_error_message: Option<Vec<ErrorResult>>, | ||||
|     pub retries_count: Option<u64>, | ||||
|     pub retries_amount_processed: Option<u64>, | ||||
|     pub connector_success_rate: Option<f64>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, serde::Serialize)] | ||||
| @ -175,6 +214,3 @@ pub struct MetricsBucketResponse { | ||||
|     #[serde(flatten)] | ||||
|     pub dimensions: PaymentMetricsBucketIdentifier, | ||||
| } | ||||
|  | ||||
| impl ApiEventMetric for MetricsBucketResponse {} | ||||
| impl ApiEventMetric for MetricsResponse<MetricsBucketResponse> {} | ||||
|  | ||||
| @ -3,10 +3,7 @@ use std::{ | ||||
|     hash::{Hash, Hasher}, | ||||
| }; | ||||
|  | ||||
| use common_enums::enums::{Currency, RefundStatus}; | ||||
| use common_utils::events::ApiEventMetric; | ||||
|  | ||||
| use crate::analytics::MetricsResponse; | ||||
| use crate::{enums::Currency, refunds::RefundStatus}; | ||||
|  | ||||
| #[derive( | ||||
|     Clone, | ||||
| @ -20,7 +17,7 @@ use crate::analytics::MetricsResponse; | ||||
|     strum::Display, | ||||
|     strum::EnumString, | ||||
| )] | ||||
| // TODO RefundType common_enums need to mapped to storage_model | ||||
| // TODO RefundType api_models_oss need to mapped to storage_model | ||||
| #[serde(rename_all = "snake_case")] | ||||
| #[strum(serialize_all = "snake_case")] | ||||
| pub enum RefundType { | ||||
| @ -31,7 +28,7 @@ pub enum RefundType { | ||||
| } | ||||
|  | ||||
| use super::{NameDescription, TimeRange}; | ||||
| #[derive(Clone, Debug, Default, serde::Deserialize, masking::Serialize)] | ||||
| #[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)] | ||||
| pub struct RefundFilters { | ||||
|     #[serde(default)] | ||||
|     pub currency: Vec<Currency>, | ||||
| @ -115,8 +112,9 @@ impl From<RefundDimensions> for NameDescription { | ||||
| #[derive(Debug, serde::Serialize, Eq)] | ||||
| pub struct RefundMetricsBucketIdentifier { | ||||
|     pub currency: Option<Currency>, | ||||
|     pub refund_status: Option<RefundStatus>, | ||||
|     pub refund_status: Option<String>, | ||||
|     pub connector: Option<String>, | ||||
|  | ||||
|     pub refund_type: Option<String>, | ||||
|     #[serde(rename = "time_range")] | ||||
|     pub time_bucket: TimeRange, | ||||
| @ -128,7 +126,7 @@ pub struct RefundMetricsBucketIdentifier { | ||||
| impl Hash for RefundMetricsBucketIdentifier { | ||||
|     fn hash<H: Hasher>(&self, state: &mut H) { | ||||
|         self.currency.hash(state); | ||||
|         self.refund_status.map(|i| i.to_string()).hash(state); | ||||
|         self.refund_status.hash(state); | ||||
|         self.connector.hash(state); | ||||
|         self.refund_type.hash(state); | ||||
|         self.time_bucket.hash(state); | ||||
| @ -147,7 +145,7 @@ impl PartialEq for RefundMetricsBucketIdentifier { | ||||
| impl RefundMetricsBucketIdentifier { | ||||
|     pub fn new( | ||||
|         currency: Option<Currency>, | ||||
|         refund_status: Option<RefundStatus>, | ||||
|         refund_status: Option<String>, | ||||
|         connector: Option<String>, | ||||
|         refund_type: Option<String>, | ||||
|         normalized_time_range: TimeRange, | ||||
| @ -162,7 +160,6 @@ impl RefundMetricsBucketIdentifier { | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, serde::Serialize)] | ||||
| pub struct RefundMetricsBucketValue { | ||||
|     pub refund_success_rate: Option<f64>, | ||||
| @ -170,7 +167,6 @@ pub struct RefundMetricsBucketValue { | ||||
|     pub refund_success_count: Option<u64>, | ||||
|     pub refund_processed_amount: Option<u64>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, serde::Serialize)] | ||||
| pub struct RefundMetricsBucketResponse { | ||||
|     #[serde(flatten)] | ||||
| @ -178,6 +174,3 @@ pub struct RefundMetricsBucketResponse { | ||||
|     #[serde(flatten)] | ||||
|     pub dimensions: RefundMetricsBucketIdentifier, | ||||
| } | ||||
|  | ||||
| impl ApiEventMetric for RefundMetricsBucketResponse {} | ||||
| impl ApiEventMetric for MetricsResponse<RefundMetricsBucketResponse> {} | ||||
|  | ||||
							
								
								
									
										215
									
								
								crates/api_models/src/analytics/sdk_events.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										215
									
								
								crates/api_models/src/analytics/sdk_events.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,215 @@ | ||||
| use std::{ | ||||
|     collections::hash_map::DefaultHasher, | ||||
|     hash::{Hash, Hasher}, | ||||
| }; | ||||
|  | ||||
| use super::{NameDescription, TimeRange}; | ||||
|  | ||||
| #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct SdkEventsRequest { | ||||
|     pub payment_id: String, | ||||
|     pub time_range: TimeRange, | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)] | ||||
| pub struct SdkEventFilters { | ||||
|     #[serde(default)] | ||||
|     pub payment_method: Vec<String>, | ||||
|     #[serde(default)] | ||||
|     pub platform: Vec<String>, | ||||
|     #[serde(default)] | ||||
|     pub browser_name: Vec<String>, | ||||
|     #[serde(default)] | ||||
|     pub source: Vec<String>, | ||||
|     #[serde(default)] | ||||
|     pub component: Vec<String>, | ||||
|     #[serde(default)] | ||||
|     pub payment_experience: Vec<String>, | ||||
| } | ||||
|  | ||||
| #[derive( | ||||
|     Debug, | ||||
|     serde::Serialize, | ||||
|     serde::Deserialize, | ||||
|     strum::AsRefStr, | ||||
|     PartialEq, | ||||
|     PartialOrd, | ||||
|     Eq, | ||||
|     Ord, | ||||
|     strum::Display, | ||||
|     strum::EnumIter, | ||||
|     Clone, | ||||
|     Copy, | ||||
| )] | ||||
| #[serde(rename_all = "snake_case")] | ||||
| #[strum(serialize_all = "snake_case")] | ||||
| pub enum SdkEventDimensions { | ||||
|     // Do not change the order of these enums | ||||
|     // Consult the Dashboard FE folks since these also affects the order of metrics on FE | ||||
|     PaymentMethod, | ||||
|     Platform, | ||||
|     BrowserName, | ||||
|     Source, | ||||
|     Component, | ||||
|     PaymentExperience, | ||||
| } | ||||
|  | ||||
| #[derive( | ||||
|     Clone, | ||||
|     Debug, | ||||
|     Hash, | ||||
|     PartialEq, | ||||
|     Eq, | ||||
|     serde::Serialize, | ||||
|     serde::Deserialize, | ||||
|     strum::Display, | ||||
|     strum::EnumIter, | ||||
|     strum::AsRefStr, | ||||
| )] | ||||
| #[strum(serialize_all = "snake_case")] | ||||
| #[serde(rename_all = "snake_case")] | ||||
| pub enum SdkEventMetrics { | ||||
|     PaymentAttempts, | ||||
|     PaymentSuccessCount, | ||||
|     PaymentMethodsCallCount, | ||||
|     SdkRenderedCount, | ||||
|     SdkInitiatedCount, | ||||
|     PaymentMethodSelectedCount, | ||||
|     PaymentDataFilledCount, | ||||
|     AveragePaymentTime, | ||||
| } | ||||
|  | ||||
| #[derive( | ||||
|     Clone, | ||||
|     Debug, | ||||
|     Hash, | ||||
|     PartialEq, | ||||
|     Eq, | ||||
|     serde::Serialize, | ||||
|     serde::Deserialize, | ||||
|     strum::Display, | ||||
|     strum::EnumIter, | ||||
|     strum::AsRefStr, | ||||
| )] | ||||
| #[strum(serialize_all = "SCREAMING_SNAKE_CASE")] | ||||
| #[serde(rename_all = "SCREAMING_SNAKE_CASE")] | ||||
| pub enum SdkEventNames { | ||||
|     StripeElementsCalled, | ||||
|     AppRendered, | ||||
|     PaymentMethodChanged, | ||||
|     PaymentDataFilled, | ||||
|     PaymentAttempt, | ||||
|     PaymentSuccess, | ||||
|     PaymentMethodsCall, | ||||
|     ConfirmCall, | ||||
|     SessionsCall, | ||||
|     CustomerPaymentMethodsCall, | ||||
|     RedirectingUser, | ||||
|     DisplayBankTransferInfoPage, | ||||
|     DisplayQrCodeInfoPage, | ||||
| } | ||||
|  | ||||
| pub mod metric_behaviour { | ||||
|     pub struct PaymentAttempts; | ||||
|     pub struct PaymentSuccessCount; | ||||
|     pub struct PaymentMethodsCallCount; | ||||
|     pub struct SdkRenderedCount; | ||||
|     pub struct SdkInitiatedCount; | ||||
|     pub struct PaymentMethodSelectedCount; | ||||
|     pub struct PaymentDataFilledCount; | ||||
|     pub struct AveragePaymentTime; | ||||
| } | ||||
|  | ||||
| impl From<SdkEventMetrics> for NameDescription { | ||||
|     fn from(value: SdkEventMetrics) -> Self { | ||||
|         Self { | ||||
|             name: value.to_string(), | ||||
|             desc: String::new(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<SdkEventDimensions> for NameDescription { | ||||
|     fn from(value: SdkEventDimensions) -> Self { | ||||
|         Self { | ||||
|             name: value.to_string(), | ||||
|             desc: String::new(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, serde::Serialize, Eq)] | ||||
| pub struct SdkEventMetricsBucketIdentifier { | ||||
|     pub payment_method: Option<String>, | ||||
|     pub platform: Option<String>, | ||||
|     pub browser_name: Option<String>, | ||||
|     pub source: Option<String>, | ||||
|     pub component: Option<String>, | ||||
|     pub payment_experience: Option<String>, | ||||
|     pub time_bucket: Option<String>, | ||||
| } | ||||
|  | ||||
| impl SdkEventMetricsBucketIdentifier { | ||||
|     pub fn new( | ||||
|         payment_method: Option<String>, | ||||
|         platform: Option<String>, | ||||
|         browser_name: Option<String>, | ||||
|         source: Option<String>, | ||||
|         component: Option<String>, | ||||
|         payment_experience: Option<String>, | ||||
|         time_bucket: Option<String>, | ||||
|     ) -> Self { | ||||
|         Self { | ||||
|             payment_method, | ||||
|             platform, | ||||
|             browser_name, | ||||
|             source, | ||||
|             component, | ||||
|             payment_experience, | ||||
|             time_bucket, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Hash for SdkEventMetricsBucketIdentifier { | ||||
|     fn hash<H: Hasher>(&self, state: &mut H) { | ||||
|         self.payment_method.hash(state); | ||||
|         self.platform.hash(state); | ||||
|         self.browser_name.hash(state); | ||||
|         self.source.hash(state); | ||||
|         self.component.hash(state); | ||||
|         self.payment_experience.hash(state); | ||||
|         self.time_bucket.hash(state); | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl PartialEq for SdkEventMetricsBucketIdentifier { | ||||
|     fn eq(&self, other: &Self) -> bool { | ||||
|         let mut left = DefaultHasher::new(); | ||||
|         self.hash(&mut left); | ||||
|         let mut right = DefaultHasher::new(); | ||||
|         other.hash(&mut right); | ||||
|         left.finish() == right.finish() | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, serde::Serialize)] | ||||
| pub struct SdkEventMetricsBucketValue { | ||||
|     pub payment_attempts: Option<u64>, | ||||
|     pub payment_success_count: Option<u64>, | ||||
|     pub payment_methods_call_count: Option<u64>, | ||||
|     pub average_payment_time: Option<f64>, | ||||
|     pub sdk_rendered_count: Option<u64>, | ||||
|     pub sdk_initiated_count: Option<u64>, | ||||
|     pub payment_method_selected_count: Option<u64>, | ||||
|     pub payment_data_filled_count: Option<u64>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, serde::Serialize)] | ||||
| pub struct MetricsBucketResponse { | ||||
|     #[serde(flatten)] | ||||
|     pub values: SdkEventMetricsBucketValue, | ||||
|     #[serde(flatten)] | ||||
|     pub dimensions: SdkEventMetricsBucketIdentifier, | ||||
| } | ||||
| @ -14,8 +14,16 @@ use common_utils::{ | ||||
| }; | ||||
|  | ||||
| use crate::{ | ||||
|     admin::*, api_keys::*, cards_info::*, disputes::*, files::*, mandates::*, payment_methods::*, | ||||
|     payments::*, verifications::*, | ||||
|     admin::*, | ||||
|     analytics::{api_event::*, sdk_events::*, *}, | ||||
|     api_keys::*, | ||||
|     cards_info::*, | ||||
|     disputes::*, | ||||
|     files::*, | ||||
|     mandates::*, | ||||
|     payment_methods::*, | ||||
|     payments::*, | ||||
|     verifications::*, | ||||
| }; | ||||
|  | ||||
| impl ApiEventMetric for TimeRange {} | ||||
| @ -63,7 +71,23 @@ impl_misc_api_event_type!( | ||||
|     ApplepayMerchantVerificationRequest, | ||||
|     ApplepayMerchantResponse, | ||||
|     ApplepayVerifiedDomainsResponse, | ||||
|     UpdateApiKeyRequest | ||||
|     UpdateApiKeyRequest, | ||||
|     GetApiEventFiltersRequest, | ||||
|     ApiEventFiltersResponse, | ||||
|     GetInfoResponse, | ||||
|     GetPaymentMetricRequest, | ||||
|     GetRefundMetricRequest, | ||||
|     GetSdkEventMetricRequest, | ||||
|     GetPaymentFiltersRequest, | ||||
|     PaymentFiltersResponse, | ||||
|     GetRefundFilterRequest, | ||||
|     RefundFiltersResponse, | ||||
|     GetSdkEventFiltersRequest, | ||||
|     SdkEventFiltersResponse, | ||||
|     ApiLogsRequest, | ||||
|     GetApiEventMetricRequest, | ||||
|     SdkEventsRequest, | ||||
|     ReportRequest | ||||
| ); | ||||
|  | ||||
| #[cfg(feature = "stripe")] | ||||
| @ -76,3 +100,9 @@ impl_misc_api_event_type!( | ||||
|     CustomerPaymentMethodListResponse, | ||||
|     CreateCustomerResponse | ||||
| ); | ||||
|  | ||||
| impl<T> ApiEventMetric for MetricsResponse<T> { | ||||
|     fn get_api_event_type(&self) -> Option<ApiEventsType> { | ||||
|         Some(ApiEventsType::Miscellaneous) | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -2339,9 +2339,11 @@ pub struct PaymentListFilters { | ||||
| pub struct TimeRange { | ||||
|     /// The start time to filter payments list or to get list of filters. To get list of filters start time is needed to be passed | ||||
|     #[serde(with = "common_utils::custom_serde::iso8601")] | ||||
|     #[serde(alias = "startTime")] | ||||
|     pub start_time: PrimitiveDateTime, | ||||
|     /// The end time to filter payments list or to get list of filters. If not passed the default time is now | ||||
|     #[serde(default, with = "common_utils::custom_serde::iso8601::option")] | ||||
|     #[serde(alias = "endTime")] | ||||
|     pub end_time: Option<PrimitiveDateTime>, | ||||
| } | ||||
|  | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Sampras Lopes
					Sampras Lopes