From b7eee7700fc85334916389b4c6ea25e68ca82bc7 Mon Sep 17 00:00:00 2001 From: Shubhranshu Sanjeev Date: Mon, 6 May 2024 19:41:56 +0530 Subject: [PATCH] fix: fixed condition parsing and display --- .../condition_pills/condition_pills.rs | 19 +-- .../src/components/condition_pills/types.rs | 141 ++++++++++++++++- .../src/components/condition_pills/utils.rs | 147 ++---------------- .../components/context_form/context_form.rs | 45 +++--- .../default_config_form.rs | 5 +- .../frontend/src/components/drawer/drawer.rs | 1 + .../src/components/experiment/experiment.rs | 4 +- .../components/override_form/override_form.rs | 3 +- .../pages/ContextOverride/context_override.rs | 47 ++---- crates/frontend/src/pages/Home/Home.rs | 8 +- .../src/pages/experiment_list/utils.rs | 5 +- 11 files changed, 216 insertions(+), 209 deletions(-) diff --git a/crates/frontend/src/components/condition_pills/condition_pills.rs b/crates/frontend/src/components/condition_pills/condition_pills.rs index 5c180e6e..93868d64 100644 --- a/crates/frontend/src/components/condition_pills/condition_pills.rs +++ b/crates/frontend/src/components/condition_pills/condition_pills.rs @@ -1,31 +1,28 @@ -use super::utils::{extract_and_format, parse_conditions}; +use crate::components::condition_pills::types::ConditionOperator; + +use super::types::Condition; use leptos::*; -use serde_json::Value; #[component] -pub fn context_pills(context: Value) -> impl IntoView { - let condition = extract_and_format(&context); - let ctx_values = parse_conditions(condition.clone()); - +pub fn condition_pills(#[prop(into)] conditions: Vec) -> impl IntoView { view! { - {ctx_values + {conditions .into_iter() .map(|condition| { let dimension = condition.left_operand; let op = condition.operator; let val = condition.right_operand; - let operator = op.clone(); view! { {dimension} - {op} + {op.to_string()} - {match operator.trim() { - "BETWEEN" => { + {match op { + ConditionOperator::Between => { let split_val: Vec = val .clone() .split(",") diff --git a/crates/frontend/src/components/condition_pills/types.rs b/crates/frontend/src/components/condition_pills/types.rs index 05681bb1..0ace1f6c 100644 --- a/crates/frontend/src/components/condition_pills/types.rs +++ b/crates/frontend/src/components/condition_pills/types.rs @@ -1,6 +1,145 @@ +use std::fmt::Display; + +use serde_json::{Map, Value}; + +use crate::types::Context; + +#[derive(Debug, Clone)] +pub enum ConditionOperator { + Is, + In, + Has, + Between, + Other(String), +} + +impl Display for ConditionOperator { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Has => f.write_str("has"), + Self::Is => f.write_str("is"), + Self::In => f.write_str("in"), + Self::Between => f.write_str("between"), + Self::Other(o) => f.write_str(o), + } + } +} + +impl From<(String, &Vec)> for ConditionOperator { + fn from(value: (String, &Vec)) -> Self { + let (operator, operands) = value; + let operand_0 = operands.get(0); + let operand_1 = operands.get(1); + let operand_2 = operands.get(2); + match (operator.as_str(), operand_0, operand_1, operand_2) { + // assuming there will be only two operands, one with the dimension name and other with the value + ("==", _, _, None) => ConditionOperator::Is, + ("<=", Some(_), Some(Value::Object(a)), Some(_)) if a.contains_key("var") => { + ConditionOperator::Between + } + // assuming there will be only two operands, one with the dimension name and other with the value + ("in", Some(Value::Object(a)), Some(_), None) if a.contains_key("var") => { + ConditionOperator::In + } + // assuming there will be only two operands, one with the dimension name and other with the value + ("in", Some(_), Some(Value::Object(a)), None) if a.contains_key("var") => { + ConditionOperator::Has + } + _ => ConditionOperator::Other(operator), + } + } +} + #[derive(Clone)] pub struct Condition { pub left_operand: String, - pub operator: String, + pub operator: ConditionOperator, pub right_operand: String, } + +impl TryFrom<&Map> for Condition { + type Error = &'static str; + fn try_from(source: &Map) -> Result { + if let Some(operator) = source.keys().next() { + let emty_vec = vec![]; + let operands = source[operator].as_array().unwrap_or(&emty_vec); + + let operator = ConditionOperator::from((operator.to_owned(), operands)); + + let dimension_name = operands + .iter() + .find_map(|item| match item.as_object() { + Some(o) if o.contains_key("var") => { + Some(o["var"].as_str().unwrap_or("")) + } + _ => None, + }) + .unwrap_or(""); + + let other_operands = operands + .iter() + .filter_map(|item| { + if item.is_object() && item.as_object().unwrap().contains_key("var") { + return None; + } + + match item { + Value::Null => String::from("null"), + Value::String(v) => v.clone(), + _ => format!("{}", item), + } + .into() + }) + .collect::>() + .join(","); + + return Ok(Condition { + operator, + left_operand: dimension_name.to_owned(), + right_operand: other_operands, + }); + } + + Err("not a valid condition map") + } +} + +impl TryFrom<&Value> for Condition { + type Error = &'static str; + fn try_from(value: &Value) -> Result { + let obj = value + .as_object() + .ok_or("not a valid condition value, should be an object")?; + Condition::try_from(obj) + } +} + +impl TryFrom<&Context> for Vec { + type Error = &'static str; + fn try_from(context: &Context) -> Result { + context + .condition + .as_object() + .ok_or("failed to parse context.condition as an object") + .and_then(|obj| match obj.get("and") { + Some(v) => v + .as_array() + .ok_or("failed to parse value of and as array") + .and_then(|arr| { + arr.iter() + .map(|condition| Condition::try_from(condition)) + .collect::, &'static str>>() + }), + None => Condition::try_from(obj).and_then(|v| Ok(vec![v])), + }) + } +} + +impl Into for Condition { + fn into(self) -> String { + format!( + "{} {} {}", + self.left_operand, self.operator, self.right_operand + ) + } +} diff --git a/crates/frontend/src/components/condition_pills/utils.rs b/crates/frontend/src/components/condition_pills/utils.rs index bd6f93ff..7b8946be 100644 --- a/crates/frontend/src/components/condition_pills/utils.rs +++ b/crates/frontend/src/components/condition_pills/utils.rs @@ -1,135 +1,20 @@ -use std::mem::swap; - use super::types::Condition; use serde_json::Value; -pub fn parse_conditions(input: Vec) -> Vec { - let mut conditions = Vec::new(); - - // Split the string by "&&" and iterate over each condition - for condition in input { - let mut key = condition.left_operand; - let mut op = condition.operator; - let mut val = condition.right_operand; - if op == "in" { - swap(&mut key, &mut val) - } - // Add a space after key - key.push(' '); - match op.as_str() { - "==" => { - val = val.trim_matches('"').to_string(); - op = "is".to_string(); - } - "<=" => { - val = val.trim_matches('"').to_string(); - op = "BETWEEN".to_string(); - } - _ => { - val = val.trim_matches('"').to_string(); - op = "has".to_string(); - } - } - op.push(' '); - - conditions.push(Condition { - left_operand: key, - operator: op, - right_operand: val, - }); - } - - conditions -} - -pub fn extract_and_format(condition: &Value) -> Vec { - let mut formatted_conditions = Vec::new(); - if condition.is_object() && condition.get("and").is_some() { - // Handling complex "and" conditions - let empty_vec = vec![]; - let conditions_json = condition - .get("and") - .and_then(|val| val.as_array()) - .unwrap_or(&empty_vec); // Default to an empty vector if not an array - - for cond in conditions_json { - if let Some(formatted_condition) = format_condition(cond) { - formatted_conditions.push(formatted_condition); - } - } - } else if let Some(formatted_condition) = format_condition(condition) { - // Handling single conditions - formatted_conditions.push(formatted_condition); - } - formatted_conditions -} - -fn format_condition(condition: &Value) -> Option { - if let Some(ref operator) = condition.as_object().and_then(|obj| obj.keys().next()) { - let empty_vec = vec![]; - let operands = condition[operator].as_array().unwrap_or(&empty_vec); - - // Handling the "in" operator differently - if operator.as_str() == "in" { - let left_operand = &operands[0]; - let right_operand = &operands[1]; - - let left_str = if left_operand.is_string() { - format!("\"{}\"", left_operand.as_str().unwrap()) - } else { - format!("{}", left_operand) - }; - - if right_operand.is_object() && right_operand["var"].is_string() { - let var_str = right_operand["var"].as_str().unwrap(); - return Some(Condition { - left_operand: left_str, - operator: operator.to_string(), - right_operand: var_str.to_string(), - }); - } - } - - // Handline the "<=" operator differently - if operator.as_str() == "<=" { - let left_operand = &operands[0]; - let right_operand = &operands[2]; - let mid_operand = &operands[1]; - - let left_str = format!("{}", left_operand).trim_matches('"').to_string(); - let right_str = format!("{}", right_operand).trim_matches('"').to_string(); - - if mid_operand.is_object() && mid_operand["var"].is_string() { - let var_str = mid_operand["var"].as_str().unwrap(); - return Some(Condition { - left_operand: var_str.to_string(), - operator: operator.to_string(), - right_operand: left_str + "," + &right_str, - }); - } - } - // Handling regular operators - if let Some(first_operand) = operands.get(0) { - if first_operand.is_object() && first_operand["var"].is_string() { - let key = first_operand["var"] - .as_str() - .unwrap_or("UnknownVar") - .to_string(); - if let Some(value) = operands.get(1) { - let val_str = match value { - Value::Null => "null".to_string(), - Value::String(v) => v.clone(), - _ => value.to_string(), - }; - return Some(Condition { - left_operand: key.to_string(), - operator: operator.to_string(), - right_operand: val_str, - }); - } - } - } - } - - None +pub fn extract_conditions(context: &Value) -> Vec { + context + .as_object() + .and_then(|obj| { + obj.get("and") + .and_then(|v| v.as_array()) + .and_then(|arr| { + Some( + arr.iter() + .filter_map(|condition| Condition::try_from(condition).ok()) + .collect::>(), + ) + }) + .or_else(|| Condition::try_from(obj).ok().and_then(|v| Some(vec![v]))) + }) + .unwrap_or_default() } diff --git a/crates/frontend/src/components/context_form/context_form.rs b/crates/frontend/src/components/context_form/context_form.rs index 123a6c46..4ba62a5a 100644 --- a/crates/frontend/src/components/context_form/context_form.rs +++ b/crates/frontend/src/components/context_form/context_form.rs @@ -35,13 +35,14 @@ where create_effect(move |_| { let f_context = context.get(); + logging::log!("{:?}", f_context); handle_change(f_context.clone()); }); let handle_select_dropdown_option = move |selected_dimension: Dimension| { let dimension_name = selected_dimension.dimension; set_context.update(|value| { - leptos::logging::log!("{:?}", value); + logging::log!("{:?}", value); value.push((dimension_name.to_string(), "".to_string(), "".to_string())) }); set_used_dimensions.update(|value: &mut HashSet| { @@ -127,7 +128,7 @@ where > "IS" -