Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: fixed condition parsing and display #37

Merged
merged 1 commit into from
May 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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<Condition>) -> 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! {
<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
141 changes: 140 additions & 1 deletion crates/frontend/src/components/condition_pills/types.rs
Original file line number Diff line number Diff line change
@@ -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 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use strum here ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was no way in strum to serialize this Other(String) into to just use the inner value, so went ahead with this approach.

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_owned(), operands));

let dimension_name = operands
.iter()
.find_map(|item| match item.as_object() {
Some(o) if o.contains_key("var") => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we have better name instead of "o"

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hey, o is just a throw away variable not even being used anywhere instead of that Some() arm, if its affecting readability I will change, if not lets go with this.
In my opinion I don't really think we need a full word for variable name for these kind of scenarios.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are using o in o.contains_key and in o["var"], when you are operating on any variable it would be better if it is not just o,a,b,c,d single alphabet,
atleast val would work

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_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<Self, Self::Error> {
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<Condition> {
type Error = &'static str;
fn try_from(context: &Context) -> Result<Self, Self::Error> {
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::<Result<Vec<Condition>, &'static str>>()
}),
None => Condition::try_from(obj).and_then(|v| Ok(vec![v])),
})
}
}

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()
}