mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-27 19:46:48 +08:00
feat(analytics): FRM Analytics (#4880)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Co-authored-by: Abhitator216 <abhishek.kanojia@juspay.in> Co-authored-by: Abhishek Kanojia <89402434+Abhitator216@users.noreply.github.com> Co-authored-by: ivor-juspay <138492857+ivor-juspay@users.noreply.github.com> Co-authored-by: Sampras Lopes <sampras.lopes@juspay.in>
This commit is contained in:
@ -35,7 +35,6 @@ url = { version = "2.5.0", features = ["serde"] }
|
||||
utoipa = { version = "4.2.0", features = ["preserve_order", "preserve_path_order"] }
|
||||
frunk = "0.4.2"
|
||||
frunk_core = "0.4.2"
|
||||
|
||||
# First party crates
|
||||
cards = { version = "0.1.0", path = "../cards" }
|
||||
common_enums = { version = "0.1.0", path = "../common_enums" }
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use common_utils::pii::EmailStrategy;
|
||||
use common_utils::{events::ApiEventMetric, pii::EmailStrategy};
|
||||
use masking::Secret;
|
||||
|
||||
use self::{
|
||||
@ -8,6 +8,7 @@ use self::{
|
||||
api_event::{ApiEventDimensions, ApiEventMetrics},
|
||||
auth_events::AuthEventMetrics,
|
||||
disputes::{DisputeDimensions, DisputeMetrics},
|
||||
frm::{FrmDimensions, FrmMetrics},
|
||||
payment_intents::{PaymentIntentDimensions, PaymentIntentMetrics},
|
||||
payments::{PaymentDimensions, PaymentDistributions, PaymentMetrics},
|
||||
refunds::{RefundDimensions, RefundMetrics},
|
||||
@ -20,6 +21,7 @@ pub mod api_event;
|
||||
pub mod auth_events;
|
||||
pub mod connector_events;
|
||||
pub mod disputes;
|
||||
pub mod frm;
|
||||
pub mod outgoing_webhook_event;
|
||||
pub mod payment_intents;
|
||||
pub mod payments;
|
||||
@ -144,6 +146,22 @@ pub struct GetRefundMetricRequest {
|
||||
pub delta: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GetFrmMetricRequest {
|
||||
pub time_series: Option<TimeSeries>,
|
||||
pub time_range: TimeRange,
|
||||
#[serde(default)]
|
||||
pub group_by_names: Vec<FrmDimensions>,
|
||||
#[serde(default)]
|
||||
pub filters: frm::FrmFilters,
|
||||
pub metrics: HashSet<FrmMetrics>,
|
||||
#[serde(default)]
|
||||
pub delta: bool,
|
||||
}
|
||||
|
||||
impl ApiEventMetric for GetFrmMetricRequest {}
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GetSdkEventMetricRequest {
|
||||
@ -247,6 +265,33 @@ pub struct RefundFilterValue {
|
||||
pub values: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GetFrmFilterRequest {
|
||||
pub time_range: TimeRange,
|
||||
#[serde(default)]
|
||||
pub group_by_names: Vec<FrmDimensions>,
|
||||
}
|
||||
|
||||
impl ApiEventMetric for GetFrmFilterRequest {}
|
||||
|
||||
#[derive(Debug, Default, serde::Serialize, Eq, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FrmFiltersResponse {
|
||||
pub query_data: Vec<FrmFilterValue>,
|
||||
}
|
||||
|
||||
impl ApiEventMetric for FrmFiltersResponse {}
|
||||
|
||||
#[derive(Debug, serde::Serialize, Eq, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FrmFilterValue {
|
||||
pub dimension: FrmDimensions,
|
||||
pub values: Vec<String>,
|
||||
}
|
||||
|
||||
impl ApiEventMetric for FrmFilterValue {}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GetSdkEventFiltersRequest {
|
||||
|
||||
163
crates/api_models/src/analytics/frm.rs
Normal file
163
crates/api_models/src/analytics/frm.rs
Normal file
@ -0,0 +1,163 @@
|
||||
use std::{
|
||||
collections::hash_map::DefaultHasher,
|
||||
hash::{Hash, Hasher},
|
||||
};
|
||||
|
||||
use common_enums::enums::FraudCheckStatus;
|
||||
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
Default,
|
||||
Eq,
|
||||
PartialEq,
|
||||
serde::Serialize,
|
||||
serde::Deserialize,
|
||||
strum::Display,
|
||||
strum::EnumString,
|
||||
)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum FrmTransactionType {
|
||||
#[default]
|
||||
PreFrm,
|
||||
PostFrm,
|
||||
}
|
||||
|
||||
use super::{NameDescription, TimeRange};
|
||||
#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)]
|
||||
pub struct FrmFilters {
|
||||
#[serde(default)]
|
||||
pub frm_status: Vec<FraudCheckStatus>,
|
||||
#[serde(default)]
|
||||
pub frm_name: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub frm_transaction_type: Vec<FrmTransactionType>,
|
||||
}
|
||||
|
||||
#[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 FrmDimensions {
|
||||
FrmStatus,
|
||||
FrmName,
|
||||
FrmTransactionType,
|
||||
}
|
||||
|
||||
#[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 FrmMetrics {
|
||||
FrmTriggeredAttempts,
|
||||
FrmBlockedRate,
|
||||
}
|
||||
|
||||
pub mod metric_behaviour {
|
||||
pub struct FrmTriggeredAttempts;
|
||||
pub struct FrmBlockRate;
|
||||
}
|
||||
|
||||
impl From<FrmMetrics> for NameDescription {
|
||||
fn from(value: FrmMetrics) -> Self {
|
||||
Self {
|
||||
name: value.to_string(),
|
||||
desc: String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FrmDimensions> for NameDescription {
|
||||
fn from(value: FrmDimensions) -> Self {
|
||||
Self {
|
||||
name: value.to_string(),
|
||||
desc: String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize, Eq)]
|
||||
pub struct FrmMetricsBucketIdentifier {
|
||||
pub frm_status: Option<String>,
|
||||
pub frm_name: Option<String>,
|
||||
pub frm_transaction_type: Option<String>,
|
||||
#[serde(rename = "time_range")]
|
||||
pub time_bucket: TimeRange,
|
||||
#[serde(rename = "time_bucket")]
|
||||
#[serde(with = "common_utils::custom_serde::iso8601custom")]
|
||||
pub start_time: time::PrimitiveDateTime,
|
||||
}
|
||||
|
||||
impl Hash for FrmMetricsBucketIdentifier {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.frm_status.hash(state);
|
||||
self.frm_name.hash(state);
|
||||
self.frm_transaction_type.hash(state);
|
||||
self.time_bucket.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for FrmMetricsBucketIdentifier {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
impl FrmMetricsBucketIdentifier {
|
||||
pub fn new(
|
||||
frm_status: Option<String>,
|
||||
frm_name: Option<String>,
|
||||
frm_transaction_type: Option<String>,
|
||||
normalized_time_range: TimeRange,
|
||||
) -> Self {
|
||||
Self {
|
||||
frm_status,
|
||||
frm_name,
|
||||
frm_transaction_type,
|
||||
time_bucket: normalized_time_range,
|
||||
start_time: normalized_time_range.start_time,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
pub struct FrmMetricsBucketValue {
|
||||
pub frm_triggered_attempts: Option<u64>,
|
||||
pub frm_blocked_rate: Option<f64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
pub struct FrmMetricsBucketResponse {
|
||||
#[serde(flatten)]
|
||||
pub values: FrmMetricsBucketValue,
|
||||
#[serde(flatten)]
|
||||
pub dimensions: FrmMetricsBucketIdentifier,
|
||||
}
|
||||
Reference in New Issue
Block a user