Bug: feat(globalsearch): Implement tag-based filters in global search



Add more explicit filters to global search:
Currently we rely on free form text search where opensearch matches the keywords across the entire document
We want to add a more refined & strict search query where we do exact matches on fields

filters to be added:
- payment method
- currency
- status
- customer_email

will need to add a filter_in_range function for the query builder as well to generate the corresponding query
diff --git a/crates/analytics/src/opensearch.rs b/crates/analytics/src/opensearch.rs
index b8c582df1bf..05f89b655cf 100644
--- a/crates/analytics/src/opensearch.rs
+++ b/crates/analytics/src/opensearch.rs
@@ -371,7 +371,7 @@ pub struct OpenSearchQueryBuilder {
     pub query: String,
     pub offset: Option<i64>,
     pub count: Option<i64>,
-    pub filters: Vec<(String, String)>,
+    pub filters: Vec<(String, Vec<String>)>,
 }
 
 impl OpenSearchQueryBuilder {
@@ -391,7 +391,7 @@ impl OpenSearchQueryBuilder {
         Ok(())
     }
 
-    pub fn add_filter_clause(&mut self, lhs: String, rhs: String) -> QueryResult<()> {
+    pub fn add_filter_clause(&mut self, lhs: String, rhs: Vec<String>) -> QueryResult<()> {
         self.filters.push((lhs, rhs));
         Ok(())
     }
@@ -403,7 +403,7 @@ impl OpenSearchQueryBuilder {
         let mut filters = self
             .filters
             .iter()
-            .map(|(k, v)| json!({"match_phrase" : {k : v}}))
+            .map(|(k, v)| json!({"terms" : {k : v}}))
             .collect::<Vec<Value>>();
 
         query.append(&mut filters);
diff --git a/crates/analytics/src/search.rs b/crates/analytics/src/search.rs
index 373ca3acbb6..1237db061bd 100644
--- a/crates/analytics/src/search.rs
+++ b/crates/analytics/src/search.rs
@@ -20,9 +20,43 @@ pub async fn msearch_results(
         OpenSearchQueryBuilder::new(OpenSearchQuery::Msearch(indexes.clone()), req.query);
 
     query_builder
-        .add_filter_clause("merchant_id".to_string(), merchant_id.to_string())
+        .add_filter_clause(
+            "merchant_id.keyword".to_string(),
+            vec![merchant_id.to_string()],
+        )
         .switch()?;
 
+    if let Some(filters) = req.filters {
+        if let Some(currency) = filters.currency {
+            if !currency.is_empty() {
+                query_builder
+                    .add_filter_clause("currency.keyword".to_string(), currency.clone())
+                    .switch()?;
+            }
+        };
+        if let Some(status) = filters.status {
+            if !status.is_empty() {
+                query_builder
+                    .add_filter_clause("status.keyword".to_string(), status.clone())
+                    .switch()?;
+            }
+        };
+        if let Some(payment_method) = filters.payment_method {
+            if !payment_method.is_empty() {
+                query_builder
+                    .add_filter_clause("payment_method.keyword".to_string(), payment_method.clone())
+                    .switch()?;
+            }
+        };
+        if let Some(customer_email) = filters.customer_email {
+            if !customer_email.is_empty() {
+                query_builder
+                    .add_filter_clause("customer_email.keyword".to_string(), customer_email.clone())
+                    .switch()?;
+            }
+        };
+    };
+
     let response_text: OpenMsearchOutput = client
         .execute(query_builder)
         .await
@@ -82,9 +116,42 @@ pub async fn search_results(
         OpenSearchQueryBuilder::new(OpenSearchQuery::Search(req.index), search_req.query);
 
     query_builder
-        .add_filter_clause("merchant_id".to_string(), merchant_id.to_string())
+        .add_filter_clause(
+            "merchant_id.keyword".to_string(),
+            vec![merchant_id.to_string()],
+        )
         .switch()?;
 
+    if let Some(filters) = search_req.filters {
+        if let Some(currency) = filters.currency {
+            if !currency.is_empty() {
+                query_builder
+                    .add_filter_clause("currency.keyword".to_string(), currency.clone())
+                    .switch()?;
+            }
+        };
+        if let Some(status) = filters.status {
+            if !status.is_empty() {
+                query_builder
+                    .add_filter_clause("status.keyword".to_string(), status.clone())
+                    .switch()?;
+            }
+        };
+        if let Some(payment_method) = filters.payment_method {
+            if !payment_method.is_empty() {
+                query_builder
+                    .add_filter_clause("payment_method.keyword".to_string(), payment_method.clone())
+                    .switch()?;
+            }
+        };
+        if let Some(customer_email) = filters.customer_email {
+            if !customer_email.is_empty() {
+                query_builder
+                    .add_filter_clause("customer_email.keyword".to_string(), customer_email.clone())
+                    .switch()?;
+            }
+        };
+    };
     query_builder
         .set_offset_n_count(search_req.offset, search_req.count)
         .switch()?;
diff --git a/crates/api_models/src/analytics/search.rs b/crates/api_models/src/analytics/search.rs
index a01349d987f..b2af4f6759c 100644
--- a/crates/api_models/src/analytics/search.rs
+++ b/crates/api_models/src/analytics/search.rs
@@ -3,6 +3,9 @@ use serde_json::Value;
 #[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)]
 pub struct SearchFilters {
     pub payment_method: Option<Vec<String>>,
+    pub currency: Option<Vec<String>>,
+    pub status: Option<Vec<String>>,
+    pub customer_email: Option<Vec<String>>,
 }
 
 #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]

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

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

## Description
<!-- Describe your changes in detail -->
Added more explicit filters to global search:
Currently we rely on free form text search where opensearch matches the keywords across the entire document


Filters added currently:

- payment method
- currency
- status
- customer_email

Can map multiple options for every filter for global search

### 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).
-->
Adding a more refined & strict search query where we do exact matches on fields provides better results

## 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)?
-->
Tested using local data by sending cURL requests through Postman.
```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.eyJ1c2VyX2lkIjoiZjQ1YWViMDgtOTBmNS00ZTM5LTljMjMtNDRkZmYxM2E4ODgwIiwibWVyY2hhbnRfaWQiOiJtZXJjaGFudF8xNzE4MDE4MjIyIiwicm9sZV9pZCI6Im9yZ19hZG1pbiIsImV4cCI6MTcxOTk5MTA0MSwib3JnX2lkIjoib3JnXzFuR09tR2N2dUxPVnBXUDJYMUFIIn0.VWP2Lj3US2qPb0nIZtpR2cHyfDpYBsR9lc9PvfQNdXY' \
--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": "merchant_1718018222",
    "filters": {
        "currency": [
            "INR", "USD"
        ]
    }
}'
```
<img width="1240" alt="Screenshot 2024-06-28 at 1 48 53 AM" src="https://github.com/juspay/hyperswitch/assets/83278309/5e99971b-7724-417e-bfe8-99cd96e38091">
<img width="1242" alt="Screenshot 2024-06-28 at 1 51 11 AM" src="https://github.com/juspay/hyperswitch/assets/83278309/c4f4e0b5-979e-48a3-9c1b-bff5295bd62e">


## 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
