Bug: feat(opensearch): add profile_id and organization_id to /search APIs



Global Search is currently being done through the merchant_id authentication.
We have to enhance the global search to search through new hierarchical structure of OrgLevel, MerchantLevel and ProfileLevel.
Queries will now be of the following form: 
```
    OrgLevel {
        org_id: id_type::OrganizationId,
    },
    MerchantLevel {
        org_id: id_type::OrganizationId,
        merchant_ids: Vec<id_type::MerchantId>,
    },
    ProfileLevel {
        org_id: id_type::OrganizationId,
        merchant_id: id_type::MerchantId,
        profile_ids: Vec<id_type::ProfileId>,
    }
```
diff --git a/crates/analytics/src/opensearch.rs b/crates/analytics/src/opensearch.rs
index f3dd5eea441..7486ab50a51 100644
--- a/crates/analytics/src/opensearch.rs
+++ b/crates/analytics/src/opensearch.rs
@@ -24,7 +24,7 @@ use storage_impl::errors::ApplicationError;
 use time::PrimitiveDateTime;
 
 use super::{health_check::HealthCheck, query::QueryResult, types::QueryExecutionError};
-use crate::query::QueryBuildingError;
+use crate::{enums::AuthInfo, query::QueryBuildingError};
 
 #[derive(Clone, Debug, serde::Deserialize)]
 #[serde(tag = "auth")]
@@ -397,13 +397,15 @@ pub struct OpenSearchQueryBuilder {
     pub count: Option<i64>,
     pub filters: Vec<(String, Vec<String>)>,
     pub time_range: Option<OpensearchTimeRange>,
+    pub search_params: Vec<AuthInfo>,
 }
 
 impl OpenSearchQueryBuilder {
-    pub fn new(query_type: OpenSearchQuery, query: String) -> Self {
+    pub fn new(query_type: OpenSearchQuery, query: String, search_params: Vec<AuthInfo>) -> Self {
         Self {
             query_type,
             query,
+            search_params,
             offset: Default::default(),
             count: Default::default(),
             filters: Default::default(),
@@ -498,8 +500,80 @@ impl OpenSearchQueryBuilder {
             }));
         }
 
-        bool_obj.insert("filter".to_string(), Value::Array(filter_array));
-        query_obj.insert("bool".to_string(), Value::Object(bool_obj));
+        let should_array = self
+            .search_params
+            .iter()
+            .map(|user_level| match user_level {
+                AuthInfo::OrgLevel { org_id } => {
+                    let must_clauses = vec![json!({
+                        "term": {
+                            "organization_id.keyword": {
+                                "value": org_id
+                            }
+                        }
+                    })];
+
+                    json!({
+                        "bool": {
+                            "must": must_clauses
+                        }
+                    })
+                }
+                AuthInfo::MerchantLevel {
+                    org_id: _,
+                    merchant_ids,
+                } => {
+                    let must_clauses = vec![
+                        // TODO: Add org_id field to the filters
+                        json!({
+                            "terms": {
+                                "merchant_id.keyword": merchant_ids
+                            }
+                        }),
+                    ];
+
+                    json!({
+                        "bool": {
+                            "must": must_clauses
+                        }
+                    })
+                }
+                AuthInfo::ProfileLevel {
+                    org_id: _,
+                    merchant_id,
+                    profile_ids,
+                } => {
+                    let must_clauses = vec![
+                        // TODO: Add org_id field to the filters
+                        json!({
+                            "term": {
+                                "merchant_id.keyword": {
+                                    "value": merchant_id
+                                }
+                            }
+                        }),
+                        json!({
+                            "terms": {
+                                "profile_id.keyword": profile_ids
+                            }
+                        }),
+                    ];
+
+                    json!({
+                        "bool": {
+                            "must": must_clauses
+                        }
+                    })
+                }
+            })
+            .collect::<Vec<Value>>();
+
+        if !filter_array.is_empty() {
+            bool_obj.insert("filter".to_string(), Value::Array(filter_array));
+        }
+        if !bool_obj.is_empty() {
+            query_obj.insert("bool".to_string(), Value::Object(bool_obj));
+        }
 
         let mut query = Map::new();
         query.insert("query".to_string(), Value::Object(query_obj));
@@ -514,9 +588,19 @@ impl OpenSearchQueryBuilder {
                     .and_then(|f| f.as_array())
                     .map(|filters| self.replace_status_field(filters, index))
                     .unwrap_or_default();
-
+                let mut final_bool_obj = Map::new();
+                if !updated_query.is_empty() {
+                    final_bool_obj.insert("filter".to_string(), Value::Array(updated_query));
+                }
+                if !should_array.is_empty() {
+                    final_bool_obj.insert("should".to_string(), Value::Array(should_array.clone()));
+                    final_bool_obj
+                        .insert("minimum_should_match".to_string(), Value::Number(1.into()));
+                }
                 let mut final_query = Map::new();
-                final_query.insert("bool".to_string(), json!({ "filter": updated_query }));
+                if !final_bool_obj.is_empty() {
+                    final_query.insert("bool".to_string(), Value::Object(final_bool_obj));
+                }
 
                 let mut sort_obj = Map::new();
                 sort_obj.insert(
diff --git a/crates/analytics/src/search.rs b/crates/analytics/src/search.rs
index 95b7f204b97..c81ff2f416b 100644
--- a/crates/analytics/src/search.rs
+++ b/crates/analytics/src/search.rs
@@ -6,14 +6,15 @@ use common_utils::errors::{CustomResult, ReportSwitchExt};
 use error_stack::ResultExt;
 use router_env::tracing;
 
-use crate::opensearch::{
-    OpenSearchClient, OpenSearchError, OpenSearchQuery, OpenSearchQueryBuilder,
+use crate::{
+    enums::AuthInfo,
+    opensearch::{OpenSearchClient, OpenSearchError, OpenSearchQuery, OpenSearchQueryBuilder},
 };
 
 pub async fn msearch_results(
     client: &OpenSearchClient,
     req: GetGlobalSearchRequest,
-    merchant_id: &common_utils::id_type::MerchantId,
+    search_params: Vec<AuthInfo>,
     indexes: Vec<SearchIndex>,
 ) -> CustomResult<Vec<GetSearchResponse>, OpenSearchError> {
     if req.query.trim().is_empty()
@@ -27,15 +28,11 @@ pub async fn msearch_results(
         )
         .into());
     }
-    let mut query_builder =
-        OpenSearchQueryBuilder::new(OpenSearchQuery::Msearch(indexes.clone()), req.query);
-
-    query_builder
-        .add_filter_clause(
-            "merchant_id.keyword".to_string(),
-            vec![merchant_id.get_string_repr().to_owned()],
-        )
-        .switch()?;
+    let mut query_builder = OpenSearchQueryBuilder::new(
+        OpenSearchQuery::Msearch(indexes.clone()),
+        req.query,
+        search_params,
+    );
 
     if let Some(filters) = req.filters {
         if let Some(currency) = filters.currency {
@@ -152,19 +149,15 @@ pub async fn msearch_results(
 pub async fn search_results(
     client: &OpenSearchClient,
     req: GetSearchRequestWithIndex,
-    merchant_id: &common_utils::id_type::MerchantId,
+    search_params: Vec<AuthInfo>,
 ) -> CustomResult<GetSearchResponse, OpenSearchError> {
     let search_req = req.search_req;
 
-    let mut query_builder =
-        OpenSearchQueryBuilder::new(OpenSearchQuery::Search(req.index), search_req.query);
-
-    query_builder
-        .add_filter_clause(
-            "merchant_id.keyword".to_string(),
-            vec![merchant_id.get_string_repr().to_owned()],
-        )
-        .switch()?;
+    let mut query_builder = OpenSearchQueryBuilder::new(
+        OpenSearchQuery::Search(req.index),
+        search_req.query,
+        search_params,
+    );
 
     if let Some(filters) = search_req.filters {
         if let Some(currency) = filters.currency {
diff --git a/crates/router/src/analytics.rs b/crates/router/src/analytics.rs
index 1fcfbdb5df9..c9b0a956dbc 100644
--- a/crates/router/src/analytics.rs
+++ b/crates/router/src/analytics.rs
@@ -21,6 +21,7 @@ pub mod routes {
         GetSdkEventMetricRequest, ReportRequest,
     };
     use common_enums::EntityType;
+    use common_utils::id_type::{MerchantId, OrganizationId};
     use error_stack::{report, ResultExt};
 
     use crate::{
@@ -1840,10 +1841,17 @@ pub mod routes {
                     .map(|(i, _)| *i)
                     .collect();
 
+                let merchant_id: MerchantId = auth.merchant_id;
+                let org_id: OrganizationId = auth.org_id;
+                let search_params: Vec<AuthInfo> = vec![AuthInfo::MerchantLevel {
+                    org_id: org_id.clone(),
+                    merchant_ids: vec![merchant_id.clone()],
+                }];
+
                 analytics::search::msearch_results(
                     &state.opensearch_client,
                     req,
-                    &auth.merchant_id,
+                    search_params,
                     accessible_indexes,
                 )
                 .await
@@ -1888,7 +1896,15 @@ pub mod routes {
                     .filter(|(ind, _)| *ind == index)
                     .find(|i| i.1.iter().any(|p| permissions.contains(p)))
                     .ok_or(OpenSearchError::IndexAccessNotPermittedError(index))?;
-                analytics::search::search_results(&state.opensearch_client, req, &auth.merchant_id)
+
+                let merchant_id: MerchantId = auth.merchant_id;
+                let org_id: OrganizationId = auth.org_id;
+                let search_params: Vec<AuthInfo> = vec![AuthInfo::MerchantLevel {
+                    org_id: org_id.clone(),
+                    merchant_ids: vec![merchant_id.clone()],
+                }];
+
+                analytics::search::search_results(&state.opensearch_client, req, search_params)
                     .await
                     .map(ApplicationResponse::Json)
             },

## 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 -->
Global Search is currently being done through the `merchant_id` authentication.
Enhancing the global search to search through new hierarchical structure of OrgLevel, MerchantLevel and ProfileLevel.
Structure of the enum used:
```bash
pub enum AuthInfo {
    OrgLevel {
        org_id: id_type::OrganizationId,
    },
    MerchantLevel {
        org_id: id_type::OrganizationId,
        merchant_ids: Vec<id_type::MerchantId>,
    },
    ProfileLevel {
        org_id: id_type::OrganizationId,
        merchant_id: id_type::MerchantId,
        profile_ids: Vec<id_type::ProfileId>,
    },
}
```
- Modified the query-building for opensearch/elasticsearch by adding the orgLevel and ProfileLevel queries.
- Results will now be returned if any search query is matched, and for all matched queries
### 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).
-->
OrgLevel, MerchantLevel and ProfileLevel provide for enhanced analytics insights while using global search to the users

## 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)?
-->
- Sample Query Structure
![image](https://github.com/user-attachments/assets/e0690053-3f85-4fae-908d-a58b1d4a0f80)

- Hit the `/search` API (using query, filters and timeRange).
```bash
curl --location 'http://localhost:8080/analytics/v1/search' \
--header 'Accept: */*' \
--header 'Accept-Language: en-US,en;q=0.9' \
--header 'Connection: keep-alive' \
--header 'Content-Type: application/json' \
--header 'Origin: http://localhost:9000' \
--header 'Referer: http://localhost:9000/' \
--header 'Sec-Fetch-Dest: empty' \
--header 'Sec-Fetch-Mode: cors' \
--header 'Sec-Fetch-Site: same-site' \
--header 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36' \
--header 'api-key: hyperswitch' \
--header 'authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiY2M3M2ZiY2EtYTExMS00Nzk5LWI3MDEtN2E4NmU0OWIxMzY1IiwibWVyY2hhbnRfaWQiOiJtZXJjaGFudF8xNzI1MjY3Mzk3Iiwicm9sZV9pZCI6Im9yZ19hZG1pbiIsImV4cCI6MTcyNTUxNzc2Nywib3JnX2lkIjoib3JnX0lpbTRxOHpPdlA3aGp3QTRxTkpoIiwicHJvZmlsZV9pZCI6bnVsbH0.7vZllJaSLEw1EsU4tgXkEcpAjRmjlukk6swzZfa6uU0' \
--header 'sec-ch-ua: "Not/A)Brand";v="8", "Chromium";v="126", "Google Chrome";v="126"' \
--header 'sec-ch-ua-mobile: ?0' \
--header 'sec-ch-ua-platform: "macOS"' \
--data '{
    "query": "usd",
    "filters": {
        "status": [
            "succeeded", "charged", "success"
        ]
    },
    "timeRange": {
        "startTime": "2024-09-02T00:30:00Z",
        "endTime": "2024-09-04T21:45:00Z"
    }
}'
```
- Sample Response:
```bash
[
    {
        "count": 2,
        "index": "payment_attempts",
        "hits": [
            {
                "payment_id": "pay_mUtaBhUnlC2b7kTtxTJ0",
                "merchant_id": "merchant_1725267397",
                "attempt_id": "pay_mUtaBhUnlC2b7kTtxTJ0_1",
                "status": "charged",
                "amount": 6540,
                "currency": "USD",
                "save_to_locker": null,
                "connector": "stripe_test",
                "error_message": null,
                "offer_amount": null,
                "surcharge_amount": null,
                "tax_amount": null,
                "payment_method_id": null,
                "payment_method": "card",
                "connector_transaction_id": "pay_XO5KGoaMl5y7R8fM3Xya",
                "capture_method": "automatic",
                "capture_on": 1662804672,
                "confirm": true,
                "authentication_type": "no_three_ds",
                "created_at": 1725339016,
                "modified_at": 1725339017,
                "last_synced": 1725339016,
                "cancellation_reason": null,
                "amount_to_capture": 6540,
                "mandate_id": null,
                "browser_info": null,
                "error_code": null,
                "connector_metadata": null,
                "payment_experience": null,
                "payment_method_type": "credit",
                "payment_method_data": "{\"card\":{\"last4\":\"4242\",\"bank_code\":null,\"card_isin\":\"424242\",\"card_type\":null,\"card_issuer\":null,\"card_network\":null,\"card_exp_year\":\"25\",\"card_exp_month\":\"10\",\"payment_checks\":null,\"card_holder_name\":null,\"card_extended_bin\":null,\"authentication_data\":null,\"card_issuing_country\":null}}",
                "error_reason": null,
                "multiple_capture_count": null,
                "amount_capturable": 0,
                "merchant_connector_id": "mca_zPBTVwk8mQPMvW9rrW8n",
                "net_amount": 6540,
                "unified_code": null,
                "unified_message": null,
                "mandate_data": null,
                "client_source": null,
                "client_version": null,
                "profile_id": "pro_GOyb8ecA2QA4pXnJTvAL",
                "organization_id": "org_Iim4q8zOvP7hjwA4qNJh",
                "card_network": null,
                "sign_flag": 1,
                "tenant_id": "public",
                "clickhouse_database": "default",
                "@timestamp": "2024-09-03T04:50:16.000000000+00:00"
            },
            {
                "payment_id": "pay_CW0tpqeKk7F6q2TVKffH",
                "merchant_id": "merchant_1725267397",
                "attempt_id": "pay_CW0tpqeKk7F6q2TVKffH_1",
                "status": "charged",
                "amount": 6540,
                "currency": "USD",
                "save_to_locker": null,
                "connector": "stripe_test",
                "error_message": null,
                "offer_amount": null,
                "surcharge_amount": null,
                "tax_amount": null,
                "payment_method_id": null,
                "payment_method": "card",
                "connector_transaction_id": "pay_w5UHXOqOiLqRieOUQL63",
                "capture_method": "automatic",
                "capture_on": 1662804672,
                "confirm": true,
                "authentication_type": "no_three_ds",
                "created_at": 1725283209,
                "modified_at": 1725283210,
                "last_synced": 1725283209,
                "cancellation_reason": null,
                "amount_to_capture": 6540,
                "mandate_id": null,
                "browser_info": null,
                "error_code": null,
                "connector_metadata": null,
                "payment_experience": null,
                "payment_method_type": "credit",
                "payment_method_data": "{\"card\":{\"last4\":\"4242\",\"bank_code\":null,\"card_isin\":\"424242\",\"card_type\":null,\"card_issuer\":null,\"card_network\":null,\"card_exp_year\":\"25\",\"card_exp_month\":\"10\",\"payment_checks\":null,\"card_holder_name\":null,\"card_extended_bin\":null,\"authentication_data\":null,\"card_issuing_country\":null}}",
                "error_reason": null,
                "multiple_capture_count": null,
                "amount_capturable": 0,
                "merchant_connector_id": "mca_zPBTVwk8mQPMvW9rrW8n",
                "net_amount": 6540,
                "unified_code": null,
                "unified_message": null,
                "mandate_data": null,
                "client_source": null,
                "client_version": null,
                "profile_id": "pro_GOyb8ecA2QA4pXnJTvAL",
                "organization_id": "org_Iim4q8zOvP7hjwA4qNJh",
                "card_network": null,
                "sign_flag": 1,
                "tenant_id": "public",
                "clickhouse_database": "default",
                "@timestamp": "2024-09-02T13:20:09.000000000+00:00"
            }
        ],
        "status": "Success"
    },
    {
        "count": 2,
        "index": "payment_intents",
        "hits": [
            {
                "payment_id": "pay_mUtaBhUnlC2b7kTtxTJ0",
                "merchant_id": "merchant_1725267397",
                "status": "succeeded",
                "amount": 6540,
                "currency": "USD",
                "amount_captured": 6540,
                "customer_id": "StripeCustomer",
                "description": "Its my first payment request",
                "return_url": "https://google.com/",
                "metadata": "{\"data2\":\"camel\",\"login_date\":\"2019-09-10T10:11:12Z\",\"new_customer\":\"true\"}",
                "connector_id": null,
                "statement_descriptor_name": "joseph",
                "statement_descriptor_suffix": "JS",
                "created_at": 1725339016,
                "modified_at": 1725339017,
                "last_synced": 1725339016,
                "setup_future_usage": null,
                "off_session": null,
                "client_secret": "pay_mUtaBhUnlC2b7kTtxTJ0_secret_wiZ5NQnCZ2i9ajdAYyAJ",
                "active_attempt_id": "pay_mUtaBhUnlC2b7kTtxTJ0_1",
                "business_country": null,
                "business_label": "default",
                "attempt_count": 1,
                "profile_id": "pro_GOyb8ecA2QA4pXnJTvAL",
                "payment_confirm_source": null,
                "billing_details": null,
                "shipping_details": null,
                "customer_email": "f6cd0753a831880835a0e0d23136ef66318d8303605bd37fa80b4ecf248bd303",
                "feature_metadata": {
                    "redirect_response": null,
                    "search_tags": [
                        "9c202f2fabd12f758a12b449944f5cdaeadf39132680667543033158023bc805",
                        "ffd1c37eb18f3459e3da6f21f739866bce89a334061c248656cb71d83d2375f4"
                    ]
                },
                "merchant_order_reference_id": null,
                "organization_id": "org_Iim4q8zOvP7hjwA4qNJh",
                "sign_flag": 1,
                "tenant_id": "public",
                "clickhouse_database": "default",
                "@timestamp": "2024-09-03T04:50:16.000000000+00:00"
            },
            {
                "payment_id": "pay_CW0tpqeKk7F6q2TVKffH",
                "merchant_id": "merchant_1725267397",
                "status": "succeeded",
                "amount": 6540,
                "currency": "USD",
                "amount_captured": 6540,
                "customer_id": "StripeCustomer",
                "description": "Its my first payment request",
                "return_url": "https://google.com/",
                "metadata": "{\"data2\":\"camel\",\"login_date\":\"2019-09-10T10:11:12Z\",\"new_customer\":\"true\"}",
                "connector_id": null,
                "statement_descriptor_name": "joseph",
                "statement_descriptor_suffix": "JS",
                "created_at": 1725283209,
                "modified_at": 1725283210,
                "last_synced": 1725283209,
                "setup_future_usage": null,
                "off_session": null,
                "client_secret": "pay_CW0tpqeKk7F6q2TVKffH_secret_rZIT5OsQzbhkE7rhsUrq",
                "active_attempt_id": "pay_CW0tpqeKk7F6q2TVKffH_1",
                "business_country": null,
                "business_label": "default",
                "attempt_count": 1,
                "profile_id": "pro_GOyb8ecA2QA4pXnJTvAL",
                "payment_confirm_source": null,
                "billing_details": null,
                "shipping_details": null,
                "customer_email": "f6cd0753a831880835a0e0d23136ef66318d8303605bd37fa80b4ecf248bd303",
                "feature_metadata": {
                    "redirect_response": null,
                    "search_tags": [
                        "9c202f2fabd12f758a12b449944f5cdaeadf39132680667543033158023bc805",
                        "ffd1c37eb18f3459e3da6f21f739866bce89a334061c248656cb71d83d2375f4"
                    ]
                },
                "merchant_order_reference_id": null,
                "organization_id": "org_Iim4q8zOvP7hjwA4qNJh",
                "sign_flag": 1,
                "tenant_id": "public",
                "clickhouse_database": "default",
                "@timestamp": "2024-09-02T13:20:09.000000000+00:00"
            }
        ],
        "status": "Success"
    },
    {
        "count": 1,
        "index": "refunds",
        "hits": [
            {
                "internal_reference_id": "refid_yWIsLDexIn3LvMoEvXds",
                "refund_id": "ref_WBSqj48rx3gaOuz1kUJv",
                "payment_id": "pay_mUtaBhUnlC2b7kTtxTJ0",
                "merchant_id": "merchant_1725267397",
                "connector_transaction_id": "pay_XO5KGoaMl5y7R8fM3Xya",
                "connector": "stripe_test",
                "connector_refund_id": "dummy_ref_DA5VW0KwaiCvnTaeBtcA",
                "external_reference_id": "ref_WBSqj48rx3gaOuz1kUJv",
                "refund_type": "instant_refund",
                "total_amount": 6540,
                "currency": "USD",
                "refund_amount": 600,
                "refund_status": "success",
                "sent_to_gateway": true,
                "refund_error_message": null,
                "refund_arn": "",
                "created_at": 1725339184,
                "modified_at": 1725339185,
                "description": "Customer returned product",
                "attempt_id": "pay_mUtaBhUnlC2b7kTtxTJ0_1",
                "refund_reason": "Customer returned product",
                "refund_error_code": null,
                "profile_id": "pro_GOyb8ecA2QA4pXnJTvAL",
                "organization_id": "org_Iim4q8zOvP7hjwA4qNJh",
                "sign_flag": 1,
                "tenant_id": "public",
                "clickhouse_database": "default",
                "@timestamp": "2024-09-03T04:53:04.000000000+00:00"
            }
        ],
        "status": "Success"
    },
    {
        "count": 0,
        "index": "disputes",
        "hits": [],
        "status": "Failure"
    }
]
```
## Checklist
<!-- Put an `x` in the boxes that apply -->

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