From 8716140f38bde9c2881ca8f8a00117cdd244fd80 Mon Sep 17 00:00:00 2001 From: Nikhil Sinha Date: Tue, 6 May 2025 04:38:01 -0400 Subject: [PATCH 1/5] revamp home api update the implementation of `/api/prism/v1/home` remove titles of alerts, dashboards, correlations, filters from home api updated response - ``` { "alerts_info": { "total": 0, "silenced": 0, "resolved": 0, "triggered": 0, "low": 0, "medium": 0, "high": 0, "critical": 0 }, "stats_details": [ { "date": "2025-04-30", "events": 0, "ingestion_size": 0, "storage_size": 0 }, { "date": "2025-05-01", "events": 0, "ingestion_size": 0, "storage_size": 0 }, { "date": "2025-05-02", "events": 3866860, "ingestion_size": 5260602290, "storage_size": 736510108 }, { "date": "2025-05-03", "events": 0, "ingestion_size": 0, "storage_size": 0 }, { "date": "2025-05-04", "events": 0, "ingestion_size": 0, "storage_size": 0 }, { "date": "2025-05-05", "events": 0, "ingestion_size": 0, "storage_size": 0 }, { "date": "2025-05-06", "events": 2027400, "ingestion_size": 2757834546, "storage_size": 384782870 } ], "datasets": [ { "title": "test15", "dataset_type": "Logs" } ] } ``` add another api to be called from prism `/api/prism/v1/home/search` server sends title and id of alerts, dashboards, correlations, filters ``` { "alerts": [], "correlations": [], "dashboards": [], "filters": [ { "title": "body not null", "id": "e71d1affa4ad72136e03092a717a4b0e0c3fd6d643a09572ad65e1748a5c2df8" }, { "title": "select *", "id": "6fe16f99b05566d7d4a598a7f0fa2604379d75164cdda5657c7fdbf61e54a555" } ] } ``` update datasets api - update readers priviledge - add `GetRetention` action remove distinct values from datasets api prism to call the query apis directly for p_src_ip and p_user_agent --- src/handlers/http/modal/server.rs | 6 +- src/handlers/http/prism_home.rs | 11 +- src/prism/home/mod.rs | 240 +++++++++++++++--------------- src/prism/logstream/mod.rs | 66 +------- src/rbac/role.rs | 1 + 5 files changed, 138 insertions(+), 186 deletions(-) diff --git a/src/handlers/http/modal/server.rs b/src/handlers/http/modal/server.rs index 1674c3ee2..467712646 100644 --- a/src/handlers/http/modal/server.rs +++ b/src/handlers/http/modal/server.rs @@ -148,8 +148,10 @@ impl ParseableServer for Server { } impl Server { - pub fn get_prism_home() -> Resource { - web::resource("/home").route(web::get().to(http::prism_home::home_api)) + pub fn get_prism_home() -> Scope { + web::scope("/home") + .service(web::resource("").route(web::get().to(http::prism_home::home_api))) + .service(web::resource("/search").route(web::get().to(http::prism_home::home_search))) } pub fn get_prism_logstream() -> Scope { diff --git a/src/handlers/http/prism_home.rs b/src/handlers/http/prism_home.rs index a8a0328a6..b9374e83a 100644 --- a/src/handlers/http/prism_home.rs +++ b/src/handlers/http/prism_home.rs @@ -19,7 +19,7 @@ use actix_web::{web, HttpRequest, Responder}; use crate::{ - prism::home::{generate_home_response, PrismHomeError}, + prism::home::{generate_home_response, generate_home_search_response, PrismHomeError}, utils::actix::extract_session_key_from_req, }; @@ -37,3 +37,12 @@ pub async fn home_api(req: HttpRequest) -> Result Result { + let key = extract_session_key_from_req(&req) + .map_err(|err| PrismHomeError::Anyhow(anyhow::Error::msg(err.to_string())))?; + + let res = generate_home_search_response(&key).await?; + + Ok(web::Json(res)) +} diff --git a/src/prism/home/mod.rs b/src/prism/home/mod.rs index 39bd5c4e3..87d66b66c 100644 --- a/src/prism/home/mod.rs +++ b/src/prism/home/mod.rs @@ -44,7 +44,7 @@ use crate::{ type StreamMetadataResponse = Result<(String, Vec, DataSetType), PrismHomeError>; #[derive(Debug, Serialize, Default)] -struct StreamInfo { +struct StreamStats { // stream_count: u32, // log_source_count: u32, stats_summary: Stats, @@ -79,40 +79,25 @@ struct DataSet { #[derive(Debug, Serialize)] pub struct HomeResponse { - alert_titles: Vec, alerts_info: AlertsInfo, - correlation_titles: Vec, - stream_info: StreamInfo, stats_details: Vec, - stream_titles: Vec, datasets: Vec, - dashboard_titles: Vec, - filter_titles: Vec, +} + +#[derive(Debug, Serialize)] +pub struct HomeSearchResponse { + alerts: Vec, + correlations: Vec, + dashboards: Vec, + filters: Vec, } pub async fn generate_home_response(key: &SessionKey) -> Result { // Execute these operations concurrently - let ( - stream_titles_result, - alert_titles_result, - correlation_titles_result, - dashboards_result, - filters_result, - alerts_info_result, - ) = tokio::join!( - get_stream_titles(key), - get_alert_titles(key), - get_correlation_titles(key), - get_dashboard_titles(key), - get_filter_titles(key), - get_alerts_info() - ); + let (stream_titles_result, alerts_info_result) = + tokio::join!(get_stream_titles(key), get_alerts_info()); let stream_titles = stream_titles_result?; - let alert_titles = alert_titles_result?; - let correlation_titles = correlation_titles_result?; - let dashboard_titles = dashboards_result?; - let filter_titles = filters_result?; let alerts_info = alerts_info_result?; // Generate dates for date-wise stats @@ -161,7 +146,7 @@ pub async fn generate_home_response(key: &SessionKey) -> Result Result Result, PrismHomeError> { - let stream_titles: Vec = PARSEABLE - .storage - .get_object_store() - .list_streams() - .await - .map_err(|e| PrismHomeError::Anyhow(anyhow::Error::new(e)))? - .into_iter() - .filter(|logstream| { - Users.authorize(key.clone(), Action::ListStream, Some(logstream), None) - == crate::rbac::Response::Authorized - }) - .sorted() - .collect_vec(); - - Ok(stream_titles) -} - -async fn get_alert_titles(key: &SessionKey) -> Result, PrismHomeError> { - let alert_titles = ALERTS - .list_alerts_for_user(key.clone()) - .await? - .iter() - .map(|alert| TitleAndId { - title: alert.title.clone(), - id: alert.id.to_string(), - }) - .collect_vec(); - - Ok(alert_titles) -} - -async fn get_correlation_titles(key: &SessionKey) -> Result, PrismHomeError> { - let correlation_titles = CORRELATIONS - .list_correlations(key) - .await? - .iter() - .map(|corr| TitleAndId { - title: corr.title.clone(), - id: corr.id.clone(), - }) - .collect_vec(); - - Ok(correlation_titles) -} - -async fn get_dashboard_titles(key: &SessionKey) -> Result, PrismHomeError> { - let dashboard_titles = DASHBOARDS - .list_dashboards(key) - .await - .iter() - .map(|dashboard| TitleAndId { - title: dashboard.name.clone(), - id: dashboard - .dashboard_id - .as_ref() - .ok_or_else(|| anyhow::Error::msg("Dashboard ID is null")) - .unwrap() - .clone(), - }) - .collect_vec(); - - Ok(dashboard_titles) -} - -async fn get_filter_titles(key: &SessionKey) -> Result, PrismHomeError> { - let filter_titles = FILTERS - .list_filters(key) - .await - .iter() - .map(|filter| TitleAndId { - title: filter.filter_name.clone(), - id: filter - .filter_id - .as_ref() - .ok_or_else(|| anyhow::Error::msg("Filter ID is null")) - .unwrap() - .clone(), - }) - .collect_vec(); - - Ok(filter_titles) -} - async fn get_stream_metadata( stream: String, ) -> Result<(String, Vec, DataSetType), PrismHomeError> { @@ -374,6 +266,114 @@ async fn get_stream_stats_for_date( )) } +pub async fn generate_home_search_response( + key: &SessionKey, +) -> Result { + let (alert_titles, correlation_titles, dashboard_titles, filter_titles) = tokio::join!( + get_alert_titles(key), + get_correlation_titles(key), + get_dashboard_titles(key), + get_filter_titles(key) + ); + + let alerts = alert_titles?; + let correlations = correlation_titles?; + let dashboards = dashboard_titles?; + let filters = filter_titles?; + + Ok(HomeSearchResponse { + alerts, + correlations, + dashboards, + filters, + }) +} + +// Helper functions to split the work +async fn get_stream_titles(key: &SessionKey) -> Result, PrismHomeError> { + let stream_titles: Vec = PARSEABLE + .storage + .get_object_store() + .list_streams() + .await + .map_err(|e| PrismHomeError::Anyhow(anyhow::Error::new(e)))? + .into_iter() + .filter(|logstream| { + Users.authorize(key.clone(), Action::ListStream, Some(logstream), None) + == crate::rbac::Response::Authorized + }) + .sorted() + .collect_vec(); + + Ok(stream_titles) +} + +async fn get_alert_titles(key: &SessionKey) -> Result, PrismHomeError> { + let alert_titles = ALERTS + .list_alerts_for_user(key.clone()) + .await? + .iter() + .map(|alert| TitleAndId { + title: alert.title.clone(), + id: alert.id.to_string(), + }) + .collect_vec(); + + Ok(alert_titles) +} + +async fn get_correlation_titles(key: &SessionKey) -> Result, PrismHomeError> { + let correlation_titles = CORRELATIONS + .list_correlations(key) + .await? + .iter() + .map(|corr| TitleAndId { + title: corr.title.clone(), + id: corr.id.clone(), + }) + .collect_vec(); + + Ok(correlation_titles) +} + +async fn get_dashboard_titles(key: &SessionKey) -> Result, PrismHomeError> { + let dashboard_titles = DASHBOARDS + .list_dashboards(key) + .await + .iter() + .map(|dashboard| TitleAndId { + title: dashboard.name.clone(), + id: dashboard + .dashboard_id + .as_ref() + .ok_or_else(|| anyhow::Error::msg("Dashboard ID is null")) + .unwrap() + .clone(), + }) + .collect_vec(); + + Ok(dashboard_titles) +} + +async fn get_filter_titles(key: &SessionKey) -> Result, PrismHomeError> { + let filter_titles = FILTERS + .list_filters(key) + .await + .iter() + .map(|filter| TitleAndId { + title: filter.filter_name.clone(), + id: filter + .filter_id + .as_ref() + .ok_or_else(|| anyhow::Error::msg("Filter ID is null")) + .unwrap() + .clone(), + }) + .collect_vec(); + + Ok(filter_titles) +} + #[derive(Debug, thiserror::Error)] pub enum PrismHomeError { #[error("Error: {0}")] diff --git a/src/prism/logstream/mod.rs b/src/prism/logstream/mod.rs index 9352e3f21..4f409e892 100644 --- a/src/prism/logstream/mod.rs +++ b/src/prism/logstream/mod.rs @@ -23,7 +23,6 @@ use arrow_schema::Schema; use chrono::Utc; use http::StatusCode; use serde::{Deserialize, Serialize}; -use serde_json::{json, Value}; use tracing::warn; use crate::{ @@ -33,18 +32,15 @@ use crate::{ utils::{merge_quried_stats, IngestionStats, QueriedStats, StorageStats}, }, logstream::error::StreamError, - query::{into_query, update_schema_when_distributed, Query, QueryError}, + query::{update_schema_when_distributed, QueryError}, }, hottier::{HotTierError, HotTierManager, StreamHotTier}, parseable::{StreamNotFound, PARSEABLE}, - query::{error::ExecuteError, execute, CountsRequest, CountsResponse, QUERY_SESSION}, + query::{error::ExecuteError, CountsRequest, CountsResponse}, rbac::{map::SessionKey, role::Action, Users}, stats, storage::{retention::Retention, StreamInfo, StreamType}, - utils::{ - arrow::record_batches_to_json, - time::{TimeParseError, TimeRange}, - }, + utils::time::TimeParseError, validator::error::HotTierValidationError, LOCK_EXPECT, }; @@ -215,8 +211,6 @@ pub struct PrismDatasetResponse { hottier: Option, /// Count of records in the specified time range counts: CountsResponse, - /// Collection of distinct values for source identifiers - distinct_sources: Value, } /// Request parameters for retrieving Prism dataset information. @@ -322,15 +316,6 @@ impl PrismDatasetRequest { // Get counts let counts = self.get_counts(&stream).await?; - // Get distinct entries concurrently - let (ips_result, user_agents_result) = futures::join!( - self.get_distinct_entries(&stream, "p_src_ip"), - self.get_distinct_entries(&stream, "p_user_agent") - ); - - let ips = ips_result.ok(); - let user_agents = user_agents_result.ok(); - Ok(PrismDatasetResponse { stream, info: info.info, @@ -339,10 +324,6 @@ impl PrismDatasetRequest { retention: info.retention, hottier, counts, - distinct_sources: json!({ - "ips": ips, - "user_agents": user_agents - }), }) } @@ -376,47 +357,6 @@ impl PrismDatasetRequest { records, }) } - - /// Retrieves distinct values for a specific field in a stream. - /// - /// # Parameters - /// - `stream_name`: Name of the stream to query - /// - `field`: Field name to get distinct values for - /// - /// # Returns - /// - `Ok(Vec)`: List of distinct values found for the field - /// - `Err(QueryError)`: If the query fails or field doesn't exist - async fn get_distinct_entries( - &self, - stream_name: &str, - field: &str, - ) -> Result, QueryError> { - let query = Query { - query: format!("SELECT DISTINCT({field}) FROM \"{stream_name}\""), - start_time: "1h".to_owned(), - end_time: "now".to_owned(), - send_null: false, - filter_tags: None, - fields: true, - }; - let time_range = TimeRange::parse_human_time("1h", "now")?; - - let session_state = QUERY_SESSION.state(); - let query = into_query(&query, &session_state, time_range).await?; - let (records, _) = execute(query, stream_name).await?; - let response = record_batches_to_json(&records)?; - // Extract field values from the JSON response - let values = response - .iter() - .flat_map(|row| { - row.get(field) - .and_then(|s| s.as_str()) - .map(|s| s.to_string()) - }) - .collect(); - - Ok(values) - } } #[derive(Debug, thiserror::Error)] diff --git a/src/rbac/role.rs b/src/rbac/role.rs index 863c984d2..f1ce887d7 100644 --- a/src/rbac/role.rs +++ b/src/rbac/role.rs @@ -324,6 +324,7 @@ pub mod model { Action::GetDashboard, Action::CreateDashboard, Action::DeleteDashboard, + Action::GetRetention, Action::GetStreamInfo, Action::GetUserRoles, Action::GetAlert, From 196a2a0ef02598456f07fe763286dca0b22d1c77 Mon Sep 17 00:00:00 2001 From: Nikhil Sinha Date: Tue, 6 May 2025 07:58:03 -0400 Subject: [PATCH 2/5] remove unused --- src/prism/home/mod.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/prism/home/mod.rs b/src/prism/home/mod.rs index 87d66b66c..c4a6fea2b 100644 --- a/src/prism/home/mod.rs +++ b/src/prism/home/mod.rs @@ -36,20 +36,12 @@ use crate::{ }, parseable::PARSEABLE, rbac::{map::SessionKey, role::Action, Users}, - stats::Stats, storage::{ObjectStorageError, ObjectStoreFormat, STREAM_ROOT_DIRECTORY}, users::{dashboards::DASHBOARDS, filters::FILTERS}, }; type StreamMetadataResponse = Result<(String, Vec, DataSetType), PrismHomeError>; -#[derive(Debug, Serialize, Default)] -struct StreamStats { - // stream_count: u32, - // log_source_count: u32, - stats_summary: Stats, -} - #[derive(Debug, Serialize, Default)] struct DatedStats { date: String, @@ -146,14 +138,10 @@ pub async fn generate_home_response(key: &SessionKey) -> Result { - summary.stats_summary.events += dated_stats.events; - summary.stats_summary.ingestion += dated_stats.ingestion_size; - summary.stats_summary.storage += dated_stats.storage_size; stream_details.push(dated_stats); } Err(e) => { From 17c417e6732741d7bb7f801dd59318b692f5e4d6 Mon Sep 17 00:00:00 2001 From: Nikhil Sinha Date: Tue, 6 May 2025 08:12:53 -0400 Subject: [PATCH 3/5] refactor --- src/prism/home/mod.rs | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/src/prism/home/mod.rs b/src/prism/home/mod.rs index c4a6fea2b..373bb056b 100644 --- a/src/prism/home/mod.rs +++ b/src/prism/home/mod.rs @@ -97,8 +97,7 @@ pub async fn generate_home_response(key: &SessionKey) -> Result Result, Prism .iter() .map(|dashboard| TitleAndId { title: dashboard.name.clone(), - id: dashboard - .dashboard_id - .as_ref() - .ok_or_else(|| anyhow::Error::msg("Dashboard ID is null")) - .unwrap() - .clone(), + id: dashboard.dashboard_id.as_ref().unwrap().clone(), }) .collect_vec(); @@ -350,12 +344,7 @@ async fn get_filter_titles(key: &SessionKey) -> Result, PrismHom .iter() .map(|filter| TitleAndId { title: filter.filter_name.clone(), - id: filter - .filter_id - .as_ref() - .ok_or_else(|| anyhow::Error::msg("Filter ID is null")) - .unwrap() - .clone(), + id: filter.filter_id.as_ref().unwrap().clone(), }) .collect_vec(); From 041ef30b2323a3044470b2f92eb11fb597babbab Mon Sep 17 00:00:00 2001 From: Nikhil Sinha Date: Wed, 7 May 2025 05:30:57 -0400 Subject: [PATCH 4/5] fix: home search api request - `http://localhost:8000/api/prism/v1/home/search?key=frontend` response - ``` { "resources": [ { "id": "e890c2f70362528b57d528b65a39de62bbd3c65f90f6923dce25e27a8a66e8a4", "name": "select flags not 0", "resource_type": "Filter" }, { "id": "e71d1affa4ad72136e03092a717a4b0e0c3fd6d643a09572ad65e1748a5c2df8", "name": "body not null", "resource_type": "Filter" }, { "id": "6fe16f99b05566d7d4a598a7f0fa2604379d75164cdda5657c7fdbf61e54a555", "name": "select *", "resource_type": "Filter" }, { "id": "fcda9d6a2b98c3368cbf1d903bb408e59f084df2dcb7c7d5d7b044e800ec1437", "name": "frontend limit", "resource_type": "Filter" } ] } ``` --- src/handlers/http/prism_home.rs | 20 +++- src/prism/home/mod.rs | 157 +++++++++++++++++++++++--------- 2 files changed, 132 insertions(+), 45 deletions(-) diff --git a/src/handlers/http/prism_home.rs b/src/handlers/http/prism_home.rs index b9374e83a..77449a5d0 100644 --- a/src/handlers/http/prism_home.rs +++ b/src/handlers/http/prism_home.rs @@ -23,6 +23,8 @@ use crate::{ utils::actix::extract_session_key_from_req, }; +const HOME_SEARCH_QUERY_PARAM: &str = "key"; + /// Fetches the data to populate Prism's home /// /// @@ -41,8 +43,22 @@ pub async fn home_api(req: HttpRequest) -> Result Result { let key = extract_session_key_from_req(&req) .map_err(|err| PrismHomeError::Anyhow(anyhow::Error::msg(err.to_string())))?; + let query_string = req.query_string(); + if query_string.is_empty() { + return Ok(web::Json(serde_json::json!({}))); + } + // Validate query string format + let query_parts: Vec<&str> = query_string.split('=').collect(); + if query_parts.len() != 2 || query_parts[0] != HOME_SEARCH_QUERY_PARAM { + return Err(PrismHomeError::InvalidQueryParameter( + HOME_SEARCH_QUERY_PARAM.to_string(), + )); + } - let res = generate_home_search_response(&key).await?; + let query_value = query_parts[1].to_lowercase(); + let res = generate_home_search_response(&key, &query_value).await?; + let json_res = serde_json::to_value(res) + .map_err(|err| PrismHomeError::Anyhow(anyhow::Error::msg(err.to_string())))?; - Ok(web::Json(res)) + Ok(web::Json(json_res)) } diff --git a/src/prism/home/mod.rs b/src/prism/home/mod.rs index 373bb056b..82e24eb9d 100644 --- a/src/prism/home/mod.rs +++ b/src/prism/home/mod.rs @@ -50,12 +50,6 @@ struct DatedStats { storage_size: u64, } -#[derive(Debug, Serialize)] -struct TitleAndId { - title: String, - id: String, -} - #[derive(Debug, Serialize)] enum DataSetType { Logs, @@ -76,12 +70,25 @@ pub struct HomeResponse { datasets: Vec, } +#[derive(Debug, Serialize)] +pub enum ResourceType { + Alert, + Correlation, + Dashboard, + Filter, + DataSet, +} + +#[derive(Debug, Serialize)] +pub struct Resource { + id: String, + name: String, + resource_type: ResourceType, +} + #[derive(Debug, Serialize)] pub struct HomeSearchResponse { - alerts: Vec, - correlations: Vec, - dashboards: Vec, - filters: Vec, + resources: Vec, } pub async fn generate_home_response(key: &SessionKey) -> Result { @@ -255,25 +262,37 @@ async fn get_stream_stats_for_date( pub async fn generate_home_search_response( key: &SessionKey, + query_value: &str, ) -> Result { - let (alert_titles, correlation_titles, dashboard_titles, filter_titles) = tokio::join!( - get_alert_titles(key), - get_correlation_titles(key), - get_dashboard_titles(key), - get_filter_titles(key) + let mut resources = Vec::new(); + let (alert_titles, correlation_titles, dashboard_titles, filter_titles, stream_titles) = tokio::join!( + get_alert_titles(key, query_value), + get_correlation_titles(key, query_value), + get_dashboard_titles(key, query_value), + get_filter_titles(key, query_value), + get_stream_titles(key) ); let alerts = alert_titles?; + resources.extend(alerts); let correlations = correlation_titles?; + resources.extend(correlations); let dashboards = dashboard_titles?; + resources.extend(dashboards); let filters = filter_titles?; - - Ok(HomeSearchResponse { - alerts, - correlations, - dashboards, - filters, - }) + resources.extend(filters); + let stream_titles = stream_titles?; + + for title in stream_titles { + if title.to_lowercase().contains(query_value) { + resources.push(Resource { + id: title.clone(), + name: title, + resource_type: ResourceType::DataSet, + }); + } + } + Ok(HomeSearchResponse { resources }) } // Helper functions to split the work @@ -295,59 +314,108 @@ async fn get_stream_titles(key: &SessionKey) -> Result, PrismHomeErr Ok(stream_titles) } -async fn get_alert_titles(key: &SessionKey) -> Result, PrismHomeError> { - let alert_titles = ALERTS +async fn get_alert_titles( + key: &SessionKey, + query_value: &str, +) -> Result, PrismHomeError> { + let alerts = ALERTS .list_alerts_for_user(key.clone()) .await? .iter() - .map(|alert| TitleAndId { - title: alert.title.clone(), - id: alert.id.to_string(), + .filter_map(|alert| { + if alert.title.to_lowercase().contains(query_value) + || alert.id.to_string().to_lowercase().contains(query_value) + { + Some(Resource { + id: alert.id.to_string(), + name: alert.title.clone(), + resource_type: ResourceType::Alert, + }) + } else { + None + } }) .collect_vec(); - Ok(alert_titles) + Ok(alerts) } -async fn get_correlation_titles(key: &SessionKey) -> Result, PrismHomeError> { - let correlation_titles = CORRELATIONS +async fn get_correlation_titles( + key: &SessionKey, + query_value: &str, +) -> Result, PrismHomeError> { + let correlations = CORRELATIONS .list_correlations(key) .await? .iter() - .map(|corr| TitleAndId { - title: corr.title.clone(), - id: corr.id.clone(), + .filter_map(|correlation| { + if correlation.title.to_lowercase().contains(query_value) + || correlation.id.to_lowercase().contains(query_value) + { + Some(Resource { + id: correlation.id.to_string(), + name: correlation.title.clone(), + resource_type: ResourceType::Correlation, + }) + } else { + None + } }) .collect_vec(); - Ok(correlation_titles) + Ok(correlations) } -async fn get_dashboard_titles(key: &SessionKey) -> Result, PrismHomeError> { +async fn get_dashboard_titles( + key: &SessionKey, + query_value: &str, +) -> Result, PrismHomeError> { let dashboard_titles = DASHBOARDS .list_dashboards(key) .await .iter() - .map(|dashboard| TitleAndId { - title: dashboard.name.clone(), - id: dashboard.dashboard_id.as_ref().unwrap().clone(), + .filter_map(|dashboard| { + let dashboard_id = dashboard.dashboard_id.as_ref().unwrap().clone(); + if dashboard.name.to_lowercase().contains(query_value) + || dashboard_id.to_lowercase().contains(query_value) + { + Some(Resource { + id: dashboard_id, + name: dashboard.name.clone(), + resource_type: ResourceType::Dashboard, + }) + } else { + None + } }) .collect_vec(); Ok(dashboard_titles) } -async fn get_filter_titles(key: &SessionKey) -> Result, PrismHomeError> { +async fn get_filter_titles( + key: &SessionKey, + query_value: &str, +) -> Result, PrismHomeError> { let filter_titles = FILTERS .list_filters(key) .await .iter() - .map(|filter| TitleAndId { - title: filter.filter_name.clone(), - id: filter.filter_id.as_ref().unwrap().clone(), + .filter_map(|filter| { + let filter_id = filter.filter_id.as_ref().unwrap().clone(); + if filter.filter_name.to_lowercase().contains(query_value) + || filter_id.to_lowercase().contains(query_value) + { + Some(Resource { + id: filter_id, + name: filter.filter_name.clone(), + resource_type: ResourceType::Filter, + }) + } else { + None + } }) .collect_vec(); - Ok(filter_titles) } @@ -363,6 +431,8 @@ pub enum PrismHomeError { StreamError(#[from] StreamError), #[error("ObjectStorageError: {0}")] ObjectStorageError(#[from] ObjectStorageError), + #[error("Invalid query parameter: {0}")] + InvalidQueryParameter(String), } impl actix_web::ResponseError for PrismHomeError { @@ -373,6 +443,7 @@ impl actix_web::ResponseError for PrismHomeError { PrismHomeError::CorrelationError(e) => e.status_code(), PrismHomeError::StreamError(e) => e.status_code(), PrismHomeError::ObjectStorageError(_) => StatusCode::INTERNAL_SERVER_ERROR, + PrismHomeError::InvalidQueryParameter(_) => StatusCode::BAD_REQUEST, } } From cfa008d5d989c543c9aebd3249f9ce047f29457e Mon Sep 17 00:00:00 2001 From: Nikhil Sinha Date: Wed, 7 May 2025 05:54:51 -0400 Subject: [PATCH 5/5] web query for query string --- src/handlers/http/prism_home.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/handlers/http/prism_home.rs b/src/handlers/http/prism_home.rs index 77449a5d0..10ff0f04c 100644 --- a/src/handlers/http/prism_home.rs +++ b/src/handlers/http/prism_home.rs @@ -16,6 +16,8 @@ * */ +use std::collections::HashMap; + use actix_web::{web, HttpRequest, Responder}; use crate::{ @@ -43,19 +45,17 @@ pub async fn home_api(req: HttpRequest) -> Result Result { let key = extract_session_key_from_req(&req) .map_err(|err| PrismHomeError::Anyhow(anyhow::Error::msg(err.to_string())))?; - let query_string = req.query_string(); - if query_string.is_empty() { + let query_map = web::Query::>::from_query(req.query_string()) + .map_err(|_| PrismHomeError::InvalidQueryParameter(HOME_SEARCH_QUERY_PARAM.to_string()))?; + + if query_map.is_empty() { return Ok(web::Json(serde_json::json!({}))); } - // Validate query string format - let query_parts: Vec<&str> = query_string.split('=').collect(); - if query_parts.len() != 2 || query_parts[0] != HOME_SEARCH_QUERY_PARAM { - return Err(PrismHomeError::InvalidQueryParameter( - HOME_SEARCH_QUERY_PARAM.to_string(), - )); - } - let query_value = query_parts[1].to_lowercase(); + let query_value = query_map + .get(HOME_SEARCH_QUERY_PARAM) + .ok_or_else(|| PrismHomeError::InvalidQueryParameter(HOME_SEARCH_QUERY_PARAM.to_string()))? + .to_lowercase(); let res = generate_home_search_response(&key, &query_value).await?; let json_res = serde_json::to_value(res) .map_err(|err| PrismHomeError::Anyhow(anyhow::Error::msg(err.to_string())))?;