Skip to content

Commit

Permalink
fix: fixed condition parsing and display
Browse files Browse the repository at this point in the history
  • Loading branch information
ShubhranshuSanjeev committed May 9, 2024
1 parent d5c001c commit 519da2c
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 143 deletions.
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
use super::utils::{extract_and_format, parse_conditions};
use crate::components::condition_pills::types::ConditionOperator;

use super::utils::extract_conditions;
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());
let conditions = extract_conditions(&context);

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! {
<span class="inline-flex items-center rounded-md bg-gray-50 px-2 py-1 text-xs ring-1 ring-inset ring-purple-700/10 shadow-md gap-x-2">
<span class="font-mono font-medium context_condition text-gray-500">
{dimension}
</span>
<span class="font-mono font-medium text-gray-650 context_condition ">
{op}
{op.to_string()}
</span>

{match operator.trim() {
"BETWEEN" => {
{match op {
ConditionOperator::Between => {
let split_val: Vec<String> = val
.clone()
.split(",")
Expand Down
118 changes: 117 additions & 1 deletion crates/frontend/src/components/condition_pills/types.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,122 @@
use std::fmt::Display;

use serde_json::{Map, Value};

#[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<Value>)> for ConditionOperator {
fn from(value: (String, &Vec<Value>)) -> 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<String, Value>> for Condition {
type Error = &'static str;
fn try_from(source: &Map<String, Value>) -> Result<Self, Self::Error> {
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_string(), 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::<Vec<String>>()
.join(",");

return Ok(Condition {
operator,
left_operand: dimension_name.to_string(),
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<Self, Self::Error> {
let obj = value
.as_object()
.ok_or("not a valid condition value, should be an object")?;
Condition::try_from(obj)
}
}

impl Into<String> for Condition {
fn into(self) -> String {
format!(
"{} {} {}",
self.left_operand, self.operator, self.right_operand
)
}
}
147 changes: 16 additions & 131 deletions crates/frontend/src/components/condition_pills/utils.rs
Original file line number Diff line number Diff line change
@@ -1,135 +1,20 @@
use std::mem::swap;

use super::types::Condition;
use serde_json::Value;

pub fn parse_conditions(input: Vec<Condition>) -> Vec<Condition> {
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<Condition> {
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<Condition> {
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<Condition> {
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::<Vec<Condition>>(),
)
})
.or_else(|| Condition::try_from(obj).ok().and_then(|v| Some(vec![v])))
})
.unwrap_or_default()
}
3 changes: 2 additions & 1 deletion crates/frontend/src/components/context_form/context_form.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>| {
Expand Down
4 changes: 2 additions & 2 deletions crates/frontend/src/components/experiment/experiment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::rc::Rc;

use leptos::*;

use crate::components::condition_pills::utils::extract_and_format;
use crate::components::condition_pills::utils::extract_conditions;
use crate::components::table::table::Table;

use super::utils::gen_variant_table;
Expand All @@ -23,7 +23,7 @@ where
HE: Fn() + 'static + Clone,
{
let experiment_rc = Rc::new(experiment.clone());
let contexts = extract_and_format(&experiment_rc.clone().context);
let contexts = extract_conditions(&experiment_rc.clone().context);

view! {
<div class="flex flex-col overflow-x-auto p-2 bg-transparent">
Expand Down

0 comments on commit 519da2c

Please sign in to comment.