Bug: feat(globalsearch): Add search_tags based filter for global search in dashboard



Add a filter based on `search_tags` for global search in dashboard
The search_tags must be hashed before pushing them to kafka payment-intent events and opensearch.
diff --git a/crates/analytics/src/search.rs b/crates/analytics/src/search.rs
index 269864bf44a..a5bd117bd63 100644
--- a/crates/analytics/src/search.rs
+++ b/crates/analytics/src/search.rs
@@ -66,6 +66,24 @@ pub async fn msearch_results(
                     .switch()?;
             }
         };
+        if let Some(search_tags) = filters.search_tags {
+            if !search_tags.is_empty() {
+                query_builder
+                    .add_filter_clause(
+                        "feature_metadata.search_tags.keyword".to_string(),
+                        search_tags
+                            .iter()
+                            .filter_map(|search_tag| {
+                                // TODO: Add trait based inputs instead of converting this to strings
+                                serde_json::to_value(search_tag)
+                                    .ok()
+                                    .and_then(|a| a.as_str().map(|a| a.to_string()))
+                            })
+                            .collect(),
+                    )
+                    .switch()?;
+            }
+        };
     };
 
     let response_text: OpenMsearchOutput = client
@@ -173,6 +191,24 @@ pub async fn search_results(
                     .switch()?;
             }
         };
+        if let Some(search_tags) = filters.search_tags {
+            if !search_tags.is_empty() {
+                query_builder
+                    .add_filter_clause(
+                        "feature_metadata.search_tags.keyword".to_string(),
+                        search_tags
+                            .iter()
+                            .filter_map(|search_tag| {
+                                // TODO: Add trait based inputs instead of converting this to strings
+                                serde_json::to_value(search_tag)
+                                    .ok()
+                                    .and_then(|a| a.as_str().map(|a| a.to_string()))
+                            })
+                            .collect(),
+                    )
+                    .switch()?;
+            }
+        };
     };
     query_builder
         .set_offset_n_count(search_req.offset, search_req.count)
diff --git a/crates/api_models/src/analytics/search.rs b/crates/api_models/src/analytics/search.rs
index 034a2a94356..d1af00de569 100644
--- a/crates/api_models/src/analytics/search.rs
+++ b/crates/api_models/src/analytics/search.rs
@@ -1,4 +1,5 @@
 use common_utils::hashing::HashedString;
+use masking::WithType;
 use serde_json::Value;
 
 #[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)]
@@ -7,6 +8,7 @@ pub struct SearchFilters {
     pub currency: Option<Vec<String>>,
     pub status: Option<Vec<String>>,
     pub customer_email: Option<Vec<HashedString<common_utils::pii::EmailStrategy>>>,
+    pub search_tags: Option<Vec<HashedString<WithType>>>,
 }
 
 #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs
index bcc67100e1c..bf39e3d6dd5 100644
--- a/crates/api_models/src/payments.rs
+++ b/crates/api_models/src/payments.rs
@@ -9,11 +9,12 @@ use common_utils::{
     consts::default_payments_list_limit,
     crypto,
     ext_traits::{ConfigExt, Encode},
+    hashing::HashedString,
     id_type,
     pii::{self, Email},
     types::{MinorUnit, StringMajorUnit},
 };
-use masking::{PeekInterface, Secret};
+use masking::{PeekInterface, Secret, WithType};
 use router_derive::Setter;
 use serde::{
     de::{self, Unexpected, Visitor},
@@ -4955,11 +4956,12 @@ pub struct PaymentsStartRequest {
 #[derive(Debug, Clone, serde::Deserialize, serde::Serialize, ToSchema)]
 pub struct FeatureMetadata {
     /// Redirection response coming in request as metadata field only for redirection scenarios
+    #[schema(value_type = Option<RedirectResponse>)]
     pub redirect_response: Option<RedirectResponse>,
     // TODO: Convert this to hashedstrings to avoid PII sensitive data
     /// Additional tags to be used for global search
-    #[schema(value_type = Option<RedirectResponse>)]
-    pub search_tags: Option<Vec<Secret<String>>>,
+    #[schema(value_type = Option<Vec<String>>)]
+    pub search_tags: Option<Vec<HashedString<WithType>>>,
 }
 
 ///frm message is an object sent inside the payments response...when frm is invoked, its value is Some(...), else its None
diff --git a/crates/masking/src/strategy.rs b/crates/masking/src/strategy.rs
index 8b4d9b0ec34..eb705ca490a 100644
--- a/crates/masking/src/strategy.rs
+++ b/crates/masking/src/strategy.rs
@@ -7,6 +7,8 @@ pub trait Strategy<T> {
 }
 
 /// Debug with type
+#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
+#[derive(Debug, Copy, Clone)]
 pub enum WithType {}
 
 impl<T> Strategy<T> for WithType {

## 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 a filter based on `search_tags` in  `feature_metadata` for global search in dashboard
- The `search_tags` are hashed before pushing them to kafka payment-intent events and opensearch.

### 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).
-->
Better and strict search experience, based on `search_tags` present in `feature_metadata`

## 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 through curls in postman:
- Payment:
```bash
curl --location 'http://localhost:8080/payments' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: dev_IG9ateOUhGE0KLFEItNN1tsJXcr5M62N7zlbtOlUtStTfPpzSomswbQa4aO2k1B4' \
--data-raw '{
  "amount": 6540,
  "currency": "INR",
  "confirm": true,
  "capture_method": "automatic",
  "capture_on": "2022-09-10T10:11:12Z",
  "amount_to_capture": 6540,
  "customer_id": "StripeCustomer",
  "email": "test@test2.com",
  "name": "John Doe",
  "phone": "999999999",
  "phone_country_code": "+1",
  "description": "Its my first payment request",
  "authentication_type": "no_three_ds",
  "return_url": "https://google.com",
  "payment_method": "card",
  "payment_method_type": "credit",
  "payment_method_data": {
    "card": {
      "card_number": "4242424242424242",
      "card_exp_month": "10",
      "card_exp_year": "25",
      "card_holder_name": "joseph Doe",
      "card_cvc": "123"
    }
  },
  "billing": {
    "address": {
      "line1": "1467",
      "line2": "Harrison Street",
      "line3": "Harrison Street",
      "city": "San Fransico",
      "state": "California",
      "zip": "94122",
      "country": "US",
      "first_name": "joseph",
      "last_name": "Doe"
    },
    "phone": {
      "number": "8056594427",
      "country_code": "+91"
    }
  },
  "shipping": {
    "address": {
      "line1": "1467",
      "line2": "Harrison Street",
      "line3": "Harrison Street",
      "city": "San Fransico",
      "state": "California",
      "zip": "94122",
      "country": "US",
      "first_name": "joseph",
      "last_name": "Doe"
    },
    "phone": {
      "number": "8056594427",
      "country_code": "+91"
    }
  },
  "statement_descriptor_name": "joseph",
  "statement_descriptor_suffix": "JS",
  "feature_metadata": {
    "search_tags": ["lucknow", "Kolkata"]
  },
  "metadata": {
    "data2": "camel",
    "new_customer": "true",
    "login_date": "2019-09-10T10:11:12Z"
  }
}'
```
<img width="1373" alt="Screenshot 2024-07-16 at 5 36 53 PM" src="https://github.com/user-attachments/assets/6fe1860e-1fe6-4941-86c9-0cb15d752f99">
<img width="1712" alt="Screenshot 2024-07-16 at 5 37 41 PM" src="https://github.com/user-attachments/assets/cbe2b79c-f194-4dd7-9914-4ef771c0908f">

- Search based on filters:
```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.eyJ1c2VyX2lkIjoiNGU2ZWVhYzEtMWVjNi00ZWQyLWJhY2YtMDUzOTFiZGE3YjI1IiwibWVyY2hhbnRfaWQiOiJtZXJjaGFudF8xNzE5OTA0MzA1Iiwicm9sZV9pZCI6Im9yZ19hZG1pbiIsImV4cCI6MTcyMTIxMTIwOCwib3JnX2lkIjoib3JnX0c4Zk85a09UTXl1OHBXUEVQSUcwIn0.hLDGqg8n0ygYHKbZU-mmcD8f78GOFjZiuPOlwKi_zCs' \
--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-raw '{
    "query": "merchant_1719904305",
    "filters": {
        
        "customer_email": [
            "test@test2.com"
        ],
        "currency": [
            "INR"
        ],
        "search_tags": [
            "lucknow"
        ]
    }
}'
```
<img width="737" alt="Screenshot 2024-07-16 at 5 39 52 PM" src="https://github.com/user-attachments/assets/4ae84f9c-531e-4149-988e-fe40a6421c90">

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