Skip to content

Commit

Permalink
feat: experiment filters in UI
Browse files Browse the repository at this point in the history
  • Loading branch information
Datron committed Feb 28, 2025
1 parent c18c836 commit 7d1efa3
Show file tree
Hide file tree
Showing 12 changed files with 452 additions and 47 deletions.
10 changes: 6 additions & 4 deletions crates/experimentation_platform/src/api/experiments/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -766,10 +766,12 @@ async fn list_experiments(
builder.filter(experiments::name.like(format!("%{}%", experiment_name)));
}
if let Some(ref context_search) = filters.context {
builder = builder.filter(
sql::<Bool>("context::text LIKE ")
.bind::<Text, _>(format!("%{}%", context_search)),
);
for c in context_search.iter() {
builder = builder.filter(
sql::<Bool>("context::text LIKE ")
.bind::<Text, _>(format!("%{}%", c)),
);
}
}
if let Some(ref created_by) = filters.created_by {
builder =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ pub struct ExperimentListFilters {
pub experiment_name: Option<String>,
pub experiment_ids: Option<CommaSeparatedStringQParams>,
pub created_by: Option<CommaSeparatedStringQParams>,
pub context: Option<String>,
pub context: Option<CommaSeparatedStringQParams>,
pub sort_on: Option<ExperimentSortOn>,
pub sort_by: Option<SortBy>,
}
Expand Down
3 changes: 2 additions & 1 deletion crates/frontend/src/components/button.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub fn button<F: Fn(MouseEvent) + 'static>(
on_click: F,
#[prop(default = String::new())] class: String,
#[prop(default = String::new())] id: String,
#[prop(default = String::from("ri-edit-2-line ml-2"))] icon_class: String,
#[prop(default = false)] loading: bool,
) -> impl IntoView {
let mut button_class = format!("btn-purple font-medium rounded-lg text-sm px-5 py-2.5 text-center me-2 mb-2 {class}");
Expand All @@ -22,7 +23,7 @@ pub fn button<F: Fn(MouseEvent) + 'static>(
</>
}
} else {
view! { <>{text} <i class="ri-edit-2-line ml-2"></i></> }
view! { <>{text}<i class={format!("{icon_class} ml-2")}></i></> }
}}

</button>
Expand Down
2 changes: 1 addition & 1 deletion crates/frontend/src/components/context_form/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ pub fn context_payload(
description: String,
change_reason: String,
) -> Value {
let context: Value = conditions.to_context_json();
let context: Value = conditions.as_context_json();
let payload = json!({
"override": overrides,
"context": context,
Expand Down
2 changes: 1 addition & 1 deletion crates/frontend/src/components/experiment_form/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pub async fn create_experiment(
let payload = ExperimentCreateRequest {
name,
variants: FromIterator::from_iter(variants),
context: conditions.to_context_json(),
context: conditions.as_context_json(),
description,
change_reason,
};
Expand Down
51 changes: 51 additions & 0 deletions crates/frontend/src/components/input.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::time::Duration;

use chrono::{DateTime, Utc};
use leptos::*;
use serde_json::{json, Map, Value};

Expand Down Expand Up @@ -475,6 +476,56 @@ pub fn monaco_input(
}
}

#[component]
pub fn date_input(
id: String,
class: String,
name: String,
on_change: Callback<DateTime<Utc>, ()>,
#[prop(into, default = Utc::now().format("%Y-%m-%d").to_string())] value: String,
#[prop(default = false)] disabled: bool,
#[prop(default = false)] required: bool,
#[prop(into, default = String::from("2020-01-01"))] min: String,
#[prop(into, default = Utc::now().format("%Y-%m-%d").to_string())] max: String,
) -> impl IntoView {
let (error_rs, error_ws) = create_signal::<String>(String::new());
view! {
<div class="flex flex-col gap-1">
<input
id=id
name=name
type="date"
class=format!("input input-bordered {}", class)
required=required
disabled=disabled
min=min
max=max
value=value
on:change=move |e| {
let date = format!("{}T00:00:00Z", event_target_value(&e));
logging::log!("The date selected is: {}", date);
match DateTime::parse_from_rfc3339(&date) {
Ok(v) => {
on_change.call(v.to_utc());
}
Err(e) => {
logging::log!("error occurred: {:?}", e);
error_ws.set(e.to_string());
},
}
}
/>
<Show when=move || !error_rs.get().is_empty() >
<span class="flex gap-2 px-4 text-xs font-semibold text-red-600">
<i class="ri-close-circle-line"></i>
{move || error_rs.get()}
</span>
</Show>

</div>
}
}

#[component]
pub fn input(
value: Value,
Expand Down
7 changes: 4 additions & 3 deletions crates/frontend/src/logic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ impl Display for Operator {
}
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Condition {
pub variable: String,
pub expression: Expression,
Expand Down Expand Up @@ -312,6 +312,7 @@ impl Condition {
#[derive(
Clone,
Debug,
PartialEq,
derive_more::Deref,
derive_more::DerefMut,
Serialize,
Expand All @@ -336,12 +337,12 @@ impl Conditions {
None => Condition::try_from_condition_map(context).map(|v| vec![v])?,
}))
}
pub fn to_context_json(self) -> serde_json::Value {
pub fn as_context_json(&self) -> serde_json::Value {
json!({
"and": self.iter().map(|v| v.to_condition_json()).collect::<Vec<serde_json::Value>>()
})
}
pub fn to_query_string(self) -> String {
pub fn as_query_string(&self) -> String {
self.iter()
.map(|v| v.to_condition_query_str())
.collect::<Vec<String>>()
Expand Down
Loading

0 comments on commit 7d1efa3

Please sign in to comment.