Bug: feat(analytics): add card network feature



Add a filter in payment analytics for the card network column
diff --git a/crates/analytics/src/opensearch.rs b/crates/analytics/src/opensearch.rs
index e0235c67bed..149c77212a0 100644
--- a/crates/analytics/src/opensearch.rs
+++ b/crates/analytics/src/opensearch.rs
@@ -280,7 +280,10 @@ impl HealthCheck for OpenSearchClient {
         if health.status != OpenSearchHealthStatus::Red {
             Ok(())
         } else {
-            Err(QueryExecutionError::DatabaseError.into())
+            Err::<(), error_stack::Report<QueryExecutionError>>(
+                QueryExecutionError::DatabaseError.into(),
+            )
+            .attach_printable_lazy(|| format!("Opensearch cluster health is red: {health:?}"))
         }
     }
 }
diff --git a/crates/analytics/src/payments/core.rs b/crates/analytics/src/payments/core.rs
index dca42b6802e..f41e1719450 100644
--- a/crates/analytics/src/payments/core.rs
+++ b/crates/analytics/src/payments/core.rs
@@ -296,6 +296,7 @@ pub async fn get_filters(
             PaymentDimensions::ClientSource => fil.client_source,
             PaymentDimensions::ClientVersion => fil.client_version,
             PaymentDimensions::ProfileId => fil.profile_id,
+            PaymentDimensions::CardNetwork => fil.card_network,
         })
         .collect::<Vec<String>>();
         res.query_data.push(FilterValue {
diff --git a/crates/analytics/src/payments/filters.rs b/crates/analytics/src/payments/filters.rs
index 8e62c77a81f..094fd574169 100644
--- a/crates/analytics/src/payments/filters.rs
+++ b/crates/analytics/src/payments/filters.rs
@@ -59,4 +59,5 @@ pub struct PaymentFilterRow {
     pub client_source: Option<String>,
     pub client_version: Option<String>,
     pub profile_id: Option<String>,
+    pub card_network: Option<String>,
 }
diff --git a/crates/analytics/src/payments/types.rs b/crates/analytics/src/payments/types.rs
index f5139a9ea2b..b7984a19ea3 100644
--- a/crates/analytics/src/payments/types.rs
+++ b/crates/analytics/src/payments/types.rs
@@ -65,6 +65,25 @@ where
                 .add_filter_in_range_clause(PaymentDimensions::ProfileId, &self.profile_id)
                 .attach_printable("Error adding profile id filter")?;
         }
+        if !self.card_network.is_empty() {
+            let card_networks: Vec<String> = self
+                .card_network
+                .iter()
+                .flat_map(|cn| {
+                    [
+                        format!("\"{cn}\""),
+                        cn.to_string(),
+                        format!("\"{cn}\"").to_uppercase(),
+                    ]
+                })
+                .collect();
+            builder
+                .add_filter_in_range_clause(
+                    PaymentDimensions::CardNetwork,
+                    card_networks.as_slice(),
+                )
+                .attach_printable("Error adding card network filter")?;
+        }
         Ok(())
     }
 }
diff --git a/crates/analytics/src/sqlx.rs b/crates/analytics/src/sqlx.rs
index 76a5f5a0918..e9395439209 100644
--- a/crates/analytics/src/sqlx.rs
+++ b/crates/analytics/src/sqlx.rs
@@ -489,6 +489,10 @@ impl<'a> FromRow<'a, PgRow> for super::payments::filters::PaymentFilterRow {
             ColumnNotFound(_) => Ok(Default::default()),
             e => Err(e),
         })?;
+        let card_network: Option<String> = row.try_get("card_network").or_else(|e| match e {
+            ColumnNotFound(_) => Ok(Default::default()),
+            e => Err(e),
+        })?;
         Ok(Self {
             currency,
             status,
@@ -499,6 +503,7 @@ impl<'a> FromRow<'a, PgRow> for super::payments::filters::PaymentFilterRow {
             client_source,
             client_version,
             profile_id,
+            card_network,
         })
     }
 }
diff --git a/crates/api_models/src/analytics/payments.rs b/crates/api_models/src/analytics/payments.rs
index 40ff4bf2607..c474d47ee63 100644
--- a/crates/api_models/src/analytics/payments.rs
+++ b/crates/api_models/src/analytics/payments.rs
@@ -7,7 +7,8 @@ use common_utils::id_type;
 
 use super::{NameDescription, TimeRange};
 use crate::enums::{
-    AttemptStatus, AuthenticationType, Connector, Currency, PaymentMethod, PaymentMethodType,
+    AttemptStatus, AuthenticationType, CardNetwork, Connector, Currency, PaymentMethod,
+    PaymentMethodType,
 };
 
 #[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)]
@@ -29,6 +30,8 @@ pub struct PaymentFilters {
     #[serde(default)]
     pub client_version: Vec<String>,
     #[serde(default)]
+    pub card_network: Vec<CardNetwork>,
+    #[serde(default)]
     pub profile_id: Vec<id_type::ProfileId>,
 }
 
@@ -64,6 +67,7 @@ pub enum PaymentDimensions {
     ClientSource,
     ClientVersion,
     ProfileId,
+    CardNetwork,
 }
 
 #[derive(

## Type of Change
<!-- Put an `x` in the boxes that apply -->

- [ ] Bugfix
- [x] New feature
- [ ] Enhancement
- [ ] Refactoring
- [ ] Dependency updates
- [ ] Documentation
- [ ] CI/CD

## Description
<!-- Describe your changes in detail -->
- Add card network analytics filter in payment analytics

### Additional Changes

- [ ] This PR modifies the API contract
- [ ] This PR modifies the database schema
- [ ] This PR modifies application configuration/environment variables

<!--
Provide links to the files with corresponding changes.

Following are the paths where you can find config files:
1. `config`
2. `crates/router/src/configs`
3. `loadtest/config`
-->


## Motivation and Context
<!--
Why is this change required? What problem does it solve?
If it fixes an open issue, please link to the issue here.

If you don't have an issue, we'd recommend starting with one first so the PR
can focus on the implementation (unless it is an obvious bug or documentation fix
that will have little conversation).
-->

## How did you test it?
<!--
Did you write an integration/unit/API test to verify the code changes?
Or did you test this change manually (provide relevant screenshots)?
-->

<img width="431" alt="image" src="https://github.com/user-attachments/assets/d209f786-a967-4245-a780-e9539a5c7469">


## Checklist
<!-- Put an `x` in the boxes that apply -->

- [ ] I formatted the code `cargo +nightly fmt --all`
- [ ] I addressed lints thrown by `cargo clippy`
- [ ] I reviewed the submitted code
- [ ] I added unit tests for my changes where possible
