Skip to content

Commit

Permalink
feat(analytics): Analytics Request Validator and config driven forex …
Browse files Browse the repository at this point in the history
…feature (#6733)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
Co-authored-by: Sandeep Kumar <[email protected]>
  • Loading branch information
3 people authored Dec 17, 2024
1 parent 94ad90f commit c883aa5
Show file tree
Hide file tree
Showing 16 changed files with 429 additions and 161 deletions.
1 change: 1 addition & 0 deletions config/config.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,7 @@ pm_auth_key = "Some_pm_auth_key"
# Analytics configuration.
[analytics]
source = "sqlx" # The Analytics source/strategy to be used
forex_enabled = false # Enable or disable forex conversion for analytics

[analytics.clickhouse]
username = "" # Clickhouse username
Expand Down
1 change: 1 addition & 0 deletions config/deployments/env_specific.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ database_name = "clickhouse_db_name" # Clickhouse database name
# Analytics configuration.
[analytics]
source = "sqlx" # The Analytics source/strategy to be used
forex_enabled = false # Boolean to enable or disable forex conversion

[analytics.sqlx]
username = "db_user" # Analytics DB Username
Expand Down
1 change: 1 addition & 0 deletions config/development.toml
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,7 @@ authentication_analytics_topic = "hyperswitch-authentication-events"

[analytics]
source = "sqlx"
forex_enabled = false

[analytics.clickhouse]
username = "default"
Expand Down
6 changes: 6 additions & 0 deletions crates/analytics/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,12 @@ To use Forex services, you need to sign up and get your API keys from the follow
- It will be in dashboard, labeled as `access key`.

### Configuring Forex APIs
To enable Forex functionality, update the `config/development.toml` or `config/docker_compose.toml` file:

```toml
[analytics]
forex_enabled = true # default set to false
```

To configure the Forex APIs, update the `config/development.toml` or `config/docker_compose.toml` file with your API keys:

Expand Down
58 changes: 49 additions & 9 deletions crates/analytics/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -969,21 +969,25 @@ impl AnalyticsProvider {
tenant: &dyn storage_impl::config::TenantConfig,
) -> Self {
match config {
AnalyticsConfig::Sqlx { sqlx } => {
AnalyticsConfig::Sqlx { sqlx, .. } => {
Self::Sqlx(SqlxClient::from_conf(sqlx, tenant.get_schema()).await)
}
AnalyticsConfig::Clickhouse { clickhouse } => Self::Clickhouse(ClickhouseClient {
AnalyticsConfig::Clickhouse { clickhouse, .. } => Self::Clickhouse(ClickhouseClient {
config: Arc::new(clickhouse.clone()),
database: tenant.get_clickhouse_database().to_string(),
}),
AnalyticsConfig::CombinedCkh { sqlx, clickhouse } => Self::CombinedCkh(
AnalyticsConfig::CombinedCkh {
sqlx, clickhouse, ..
} => Self::CombinedCkh(
SqlxClient::from_conf(sqlx, tenant.get_schema()).await,
ClickhouseClient {
config: Arc::new(clickhouse.clone()),
database: tenant.get_clickhouse_database().to_string(),
},
),
AnalyticsConfig::CombinedSqlx { sqlx, clickhouse } => Self::CombinedSqlx(
AnalyticsConfig::CombinedSqlx {
sqlx, clickhouse, ..
} => Self::CombinedSqlx(
SqlxClient::from_conf(sqlx, tenant.get_schema()).await,
ClickhouseClient {
config: Arc::new(clickhouse.clone()),
Expand All @@ -1000,20 +1004,35 @@ impl AnalyticsProvider {
pub enum AnalyticsConfig {
Sqlx {
sqlx: Database,
forex_enabled: bool,
},
Clickhouse {
clickhouse: ClickhouseConfig,
forex_enabled: bool,
},
CombinedCkh {
sqlx: Database,
clickhouse: ClickhouseConfig,
forex_enabled: bool,
},
CombinedSqlx {
sqlx: Database,
clickhouse: ClickhouseConfig,
forex_enabled: bool,
},
}

impl AnalyticsConfig {
pub fn get_forex_enabled(&self) -> bool {
match self {
Self::Sqlx { forex_enabled, .. }
| Self::Clickhouse { forex_enabled, .. }
| Self::CombinedCkh { forex_enabled, .. }
| Self::CombinedSqlx { forex_enabled, .. } => *forex_enabled,
}
}
}

#[async_trait::async_trait]
impl SecretsHandler for AnalyticsConfig {
async fn convert_to_raw_secret(
Expand All @@ -1024,7 +1043,7 @@ impl SecretsHandler for AnalyticsConfig {
let decrypted_password = match analytics_config {
// Todo: Perform kms decryption of clickhouse password
Self::Clickhouse { .. } => masking::Secret::new(String::default()),
Self::Sqlx { sqlx }
Self::Sqlx { sqlx, .. }
| Self::CombinedCkh { sqlx, .. }
| Self::CombinedSqlx { sqlx, .. } => {
secret_management_client
Expand All @@ -1034,26 +1053,46 @@ impl SecretsHandler for AnalyticsConfig {
};

Ok(value.transition_state(|conf| match conf {
Self::Sqlx { sqlx } => Self::Sqlx {
Self::Sqlx {
sqlx,
forex_enabled,
} => Self::Sqlx {
sqlx: Database {
password: decrypted_password,
..sqlx
},
forex_enabled,
},
Self::Clickhouse { clickhouse } => Self::Clickhouse { clickhouse },
Self::CombinedCkh { sqlx, clickhouse } => Self::CombinedCkh {
Self::Clickhouse {
clickhouse,
forex_enabled,
} => Self::Clickhouse {
clickhouse,
forex_enabled,
},
Self::CombinedCkh {
sqlx,
clickhouse,
forex_enabled,
} => Self::CombinedCkh {
sqlx: Database {
password: decrypted_password,
..sqlx
},
clickhouse,
forex_enabled,
},
Self::CombinedSqlx { sqlx, clickhouse } => Self::CombinedSqlx {
Self::CombinedSqlx {
sqlx,
clickhouse,
forex_enabled,
} => Self::CombinedSqlx {
sqlx: Database {
password: decrypted_password,
..sqlx
},
clickhouse,
forex_enabled,
},
}))
}
Expand All @@ -1063,6 +1102,7 @@ impl Default for AnalyticsConfig {
fn default() -> Self {
Self::Sqlx {
sqlx: Database::default(),
forex_enabled: false,
}
}
}
Expand Down
170 changes: 97 additions & 73 deletions crates/analytics/src/payment_intents/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ pub async fn get_sankey(
#[instrument(skip_all)]
pub async fn get_metrics(
pool: &AnalyticsProvider,
ex_rates: &ExchangeRates,
ex_rates: &Option<ExchangeRates>,
auth: &AuthInfo,
req: GetPaymentIntentMetricRequest,
) -> AnalyticsResult<PaymentIntentsMetricsResponse<MetricsBucketResponse>> {
Expand Down Expand Up @@ -201,67 +201,76 @@ pub async fn get_metrics(
total += total_count;
}
if let Some(retried_amount) = collected_values.smart_retried_amount {
let amount_in_usd = id
.currency
.and_then(|currency| {
i64::try_from(retried_amount)
.inspect_err(|e| logger::error!("Amount conversion error: {:?}", e))
.ok()
.and_then(|amount_i64| {
convert(ex_rates, currency, Currency::USD, amount_i64)
.inspect_err(|e| {
logger::error!("Currency conversion error: {:?}", e)
})
.ok()
})
})
.map(|amount| (amount * rust_decimal::Decimal::new(100, 0)).to_u64())
.unwrap_or_default();
let amount_in_usd = if let Some(ex_rates) = ex_rates {
id.currency
.and_then(|currency| {
i64::try_from(retried_amount)
.inspect_err(|e| logger::error!("Amount conversion error: {:?}", e))
.ok()
.and_then(|amount_i64| {
convert(ex_rates, currency, Currency::USD, amount_i64)
.inspect_err(|e| {
logger::error!("Currency conversion error: {:?}", e)
})
.ok()
})
})
.map(|amount| (amount * rust_decimal::Decimal::new(100, 0)).to_u64())
.unwrap_or_default()
} else {
None
};
collected_values.smart_retried_amount_in_usd = amount_in_usd;
total_smart_retried_amount += retried_amount;
total_smart_retried_amount_in_usd += amount_in_usd.unwrap_or(0);
}
if let Some(retried_amount) =
collected_values.smart_retried_amount_without_smart_retries
{
let amount_in_usd = id
.currency
.and_then(|currency| {
i64::try_from(retried_amount)
.inspect_err(|e| logger::error!("Amount conversion error: {:?}", e))
.ok()
.and_then(|amount_i64| {
convert(ex_rates, currency, Currency::USD, amount_i64)
.inspect_err(|e| {
logger::error!("Currency conversion error: {:?}", e)
})
.ok()
})
})
.map(|amount| (amount * rust_decimal::Decimal::new(100, 0)).to_u64())
.unwrap_or_default();
let amount_in_usd = if let Some(ex_rates) = ex_rates {
id.currency
.and_then(|currency| {
i64::try_from(retried_amount)
.inspect_err(|e| logger::error!("Amount conversion error: {:?}", e))
.ok()
.and_then(|amount_i64| {
convert(ex_rates, currency, Currency::USD, amount_i64)
.inspect_err(|e| {
logger::error!("Currency conversion error: {:?}", e)
})
.ok()
})
})
.map(|amount| (amount * rust_decimal::Decimal::new(100, 0)).to_u64())
.unwrap_or_default()
} else {
None
};
collected_values.smart_retried_amount_without_smart_retries_in_usd = amount_in_usd;
total_smart_retried_amount_without_smart_retries += retried_amount;
total_smart_retried_amount_without_smart_retries_in_usd +=
amount_in_usd.unwrap_or(0);
}
if let Some(amount) = collected_values.payment_processed_amount {
let amount_in_usd = id
.currency
.and_then(|currency| {
i64::try_from(amount)
.inspect_err(|e| logger::error!("Amount conversion error: {:?}", e))
.ok()
.and_then(|amount_i64| {
convert(ex_rates, currency, Currency::USD, amount_i64)
.inspect_err(|e| {
logger::error!("Currency conversion error: {:?}", e)
})
.ok()
})
})
.map(|amount| (amount * rust_decimal::Decimal::new(100, 0)).to_u64())
.unwrap_or_default();
let amount_in_usd = if let Some(ex_rates) = ex_rates {
id.currency
.and_then(|currency| {
i64::try_from(amount)
.inspect_err(|e| logger::error!("Amount conversion error: {:?}", e))
.ok()
.and_then(|amount_i64| {
convert(ex_rates, currency, Currency::USD, amount_i64)
.inspect_err(|e| {
logger::error!("Currency conversion error: {:?}", e)
})
.ok()
})
})
.map(|amount| (amount * rust_decimal::Decimal::new(100, 0)).to_u64())
.unwrap_or_default()
} else {
None
};
collected_values.payment_processed_amount_in_usd = amount_in_usd;
total_payment_processed_amount_in_usd += amount_in_usd.unwrap_or(0);
total_payment_processed_amount += amount;
Expand All @@ -270,22 +279,25 @@ pub async fn get_metrics(
total_payment_processed_count += count;
}
if let Some(amount) = collected_values.payment_processed_amount_without_smart_retries {
let amount_in_usd = id
.currency
.and_then(|currency| {
i64::try_from(amount)
.inspect_err(|e| logger::error!("Amount conversion error: {:?}", e))
.ok()
.and_then(|amount_i64| {
convert(ex_rates, currency, Currency::USD, amount_i64)
.inspect_err(|e| {
logger::error!("Currency conversion error: {:?}", e)
})
.ok()
})
})
.map(|amount| (amount * rust_decimal::Decimal::new(100, 0)).to_u64())
.unwrap_or_default();
let amount_in_usd = if let Some(ex_rates) = ex_rates {
id.currency
.and_then(|currency| {
i64::try_from(amount)
.inspect_err(|e| logger::error!("Amount conversion error: {:?}", e))
.ok()
.and_then(|amount_i64| {
convert(ex_rates, currency, Currency::USD, amount_i64)
.inspect_err(|e| {
logger::error!("Currency conversion error: {:?}", e)
})
.ok()
})
})
.map(|amount| (amount * rust_decimal::Decimal::new(100, 0)).to_u64())
.unwrap_or_default()
} else {
None
};
collected_values.payment_processed_amount_without_smart_retries_in_usd =
amount_in_usd;
total_payment_processed_amount_without_smart_retries_in_usd +=
Expand Down Expand Up @@ -322,14 +334,26 @@ pub async fn get_metrics(
total_payment_processed_amount_without_smart_retries: Some(
total_payment_processed_amount_without_smart_retries,
),
total_smart_retried_amount_in_usd: Some(total_smart_retried_amount_in_usd),
total_smart_retried_amount_without_smart_retries_in_usd: Some(
total_smart_retried_amount_without_smart_retries_in_usd,
),
total_payment_processed_amount_in_usd: Some(total_payment_processed_amount_in_usd),
total_payment_processed_amount_without_smart_retries_in_usd: Some(
total_payment_processed_amount_without_smart_retries_in_usd,
),
total_smart_retried_amount_in_usd: if ex_rates.is_some() {
Some(total_smart_retried_amount_in_usd)
} else {
None
},
total_smart_retried_amount_without_smart_retries_in_usd: if ex_rates.is_some() {
Some(total_smart_retried_amount_without_smart_retries_in_usd)
} else {
None
},
total_payment_processed_amount_in_usd: if ex_rates.is_some() {
Some(total_payment_processed_amount_in_usd)
} else {
None
},
total_payment_processed_amount_without_smart_retries_in_usd: if ex_rates.is_some() {
Some(total_payment_processed_amount_without_smart_retries_in_usd)
} else {
None
},
total_payment_processed_count: Some(total_payment_processed_count),
total_payment_processed_count_without_smart_retries: Some(
total_payment_processed_count_without_smart_retries,
Expand Down
Loading

0 comments on commit c883aa5

Please sign in to comment.