diff --git a/crates/frontend/src/components/context_form.rs b/crates/frontend/src/components/context_form.rs index 72e13335..5dc3ba2c 100644 --- a/crates/frontend/src/components/context_form.rs +++ b/crates/frontend/src/components/context_form.rs @@ -403,7 +403,7 @@ where } > - + @@ -413,7 +413,7 @@ where {move || { if last_idx.get() != idx { view! { -
+
"&&"
} diff --git a/crates/frontend/src/components/input.rs b/crates/frontend/src/components/input.rs index af119db5..c35083a8 100644 --- a/crates/frontend/src/components/input.rs +++ b/crates/frontend/src/components/input.rs @@ -1,5 +1,6 @@ use std::time::Duration; +use chrono::{DateTime, Utc}; use leptos::*; use serde_json::{json, Map, Value}; @@ -21,6 +22,7 @@ pub enum InputType { Monaco, Select(EnumVariants), Disabled, + Date, } impl InputType { @@ -33,6 +35,8 @@ impl InputType { | InputType::Select(_) => "text", InputType::Number | InputType::Integer => "number", + + InputType::Date => "datetime-local", } } } @@ -415,6 +419,56 @@ pub fn monaco_input( } } +#[component] +pub fn date_input( + id: String, + class: String, + name: String, + on_change: Callback, ()>, + #[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::new()); + view! { +
+ { + on_change.call(v.to_utc()); + } + Err(e) => { + logging::log!("error occurred: {:?}", e); + error_ws.set(e.to_string()); + }, + } + } + /> + + + + {move || error_rs.get()} + + + +
+ } +} + #[component] pub fn input( value: Value, diff --git a/crates/frontend/src/components/table.rs b/crates/frontend/src/components/table.rs index 14a08983..8a3d472a 100644 --- a/crates/frontend/src/components/table.rs +++ b/crates/frontend/src/components/table.rs @@ -46,8 +46,8 @@ pub fn table( { match (currently_sorted, sort_by) { (false, _) => view! { }, - (_, SortBy::Desc) => view! { }, - (_, SortBy::Asc) => view! { }, + (true, SortBy::Desc) => view! { }, + (true, SortBy::Asc) => view! { }, } } diff --git a/crates/frontend/src/pages/experiment_list.rs b/crates/frontend/src/pages/experiment_list.rs index aa940ee7..14e22a0a 100644 --- a/crates/frontend/src/pages/experiment_list.rs +++ b/crates/frontend/src/pages/experiment_list.rs @@ -1,21 +1,33 @@ pub mod utils; +use std::collections::HashSet; + +use chrono::DateTime; use futures::join; use leptos::*; -use chrono::{prelude::Utc, TimeZone}; +use chrono::prelude::Utc; use serde::{Deserialize, Serialize}; -use superposition_types::SortBy; +use crate::components::condition_pills::utils::extract_conditions; +use crate::components::context_form::utils::construct_context; use crate::components::drawer::{close_drawer, Drawer, DrawerBtn}; +use crate::components::dropdown::DropdownDirection; use crate::components::skeleton::Skeleton; -use crate::components::table::types::TablePaginationProps; -use crate::components::{experiment_form::ExperimentForm, stat::Stat, table::Table}; +use crate::components::{ + button::Button, + context_form::ContextForm, + experiment_form::ExperimentForm, + input::DateInput, + stat::Stat, + table::{types::TablePaginationProps, Table}, +}; use crate::providers::condition_collapse_provider::ConditionCollapseProvider; use crate::providers::editor_provider::EditorProvider; use crate::types::{ - ExperimentListFilters, ExperimentResponse, ListFilters, PaginatedResponse, + ExperimentListFilters, ExperimentResponse, ExperimentStatusType, ListFilters, + PaginatedResponse, StatusTypes, }; use crate::utils::update_page_direction; @@ -33,32 +45,343 @@ struct CombinedResource { default_config: Vec, } +#[component] +fn experiment_table_filter_widget( + filters_rws: RwSignal, + combined_resource: Resource< + (String, ExperimentListFilters, ListFilters), + CombinedResource, + >, +) -> impl IntoView { + let filters = filters_rws.get_untracked(); + let filters_buffer_rws = create_rw_signal(filters.clone()); + let context = filters + .context + .map(|i| extract_conditions(&i)) + .unwrap_or_default(); + + let dim = combined_resource + .get() + .unwrap_or(CombinedResource { + experiments: PaginatedResponse { + total_items: 0, + total_pages: 0, + data: vec![], + }, + dimensions: vec![], + default_config: vec![], + }) + .dimensions; + + let status_filter_management = + move |checked: bool, filter_status: ExperimentStatusType| { + let vec = filters_buffer_rws.get().status.unwrap_or_default(); + let mut old_status_vec: HashSet = + HashSet::from_iter(vec.clone().0); + + if checked { + old_status_vec.insert(filter_status); + } else { + old_status_vec.remove(&filter_status); + } + let vector = old_status_vec.into_iter().collect(); + let filters = ExperimentListFilters { + status: Some(StatusTypes(vector)), + ..filters_buffer_rws.get() + }; + filters_buffer_rws.set(filters) + }; + view! { + + Filters + + + +
+
+ + +
+
+
+ + | { + let old_filters = filters_buffer_rws.get(); + let new_filters = ExperimentListFilters { + from_date: Some(new_date), + ..old_filters + }; + filters_buffer_rws.set(new_filters); + }) + /> +
+ +
+ + | { + let old_filters = filters_buffer_rws.get(); + let new_filters = ExperimentListFilters { + to_date: Some(new_date), + ..old_filters + }; + filters_buffer_rws.set(new_filters); + }) + /> +
+
+ +
+ +
+ + + + + + + + +
+
+
+
+ + +
+
+ + +
+
+ + + +
+
+
+
+
+
+ } +} + #[component] pub fn experiment_list() -> impl IntoView { // acquire tenant let tenant_rs = use_context::>().unwrap(); - let (filters_rs, filters_ws) = create_signal(ExperimentListFilters { - status: None, - from_date: Utc.timestamp_opt(0, 0).single(), - to_date: Utc.timestamp_opt(4130561031, 0).single(), - experiment_name: None, - experiment_ids: None, - created_by: None, - context: None, - sort_on: Some(utils::ExperimentSortOn::LastModifiedAt), - sort_by: Some(SortBy::Desc), - }); + let filters_rws = create_rw_signal(ExperimentListFilters::default()); let (pagination_filters_rs, pagination_filters_ws) = create_signal(ListFilters { page: Some(1), count: Some(10), all: None, - experiment_name: None, - experiment_ids: None, - created_by: None, - context: None, - sort_on: Some(utils::ExperimentSortOn::LastModifiedAt), - sort_by: Some(SortBy::Desc), }); let (reset_exp_form, set_exp_form) = create_signal(0); @@ -70,7 +393,7 @@ pub fn experiment_list() -> impl IntoView { move || { ( tenant_rs.get(), - filters_rs.get(), + filters_rws.get(), pagination_filters_rs.get(), ) }, @@ -86,24 +409,23 @@ pub fn experiment_list() -> impl IntoView { let config_future = fetch_default_config(&pagination_filters, current_tenant.to_string()); - let (experiments_result, dimensions_result, config_result) = - join!(experiments_future, dimensions_future, config_future); - // Construct the combined result, handling errors as needed - CombinedResource { - experiments: experiments_result - .unwrap_or(PaginatedResponse::default()), - dimensions: dimensions_result - .unwrap_or(PaginatedResponse::default()) - .data - .into_iter() - .filter(|d| d.dimension != "variantIds") - .collect(), - default_config: config_result - .unwrap_or(PaginatedResponse::default()) - .data, - } - }, - ); + let (experiments_result, dimensions_result, config_result) = + join!(experiments_future, dimensions_future, config_future); + // Construct the combined result, handling errors as needed + CombinedResource { + experiments: experiments_result.unwrap_or(PaginatedResponse::default()), + dimensions: dimensions_result + .unwrap_or(PaginatedResponse::default()) + .data + .into_iter() + .filter(|d| d.dimension != "variantIds") + .collect(), + default_config: config_result + .unwrap_or(PaginatedResponse::default()) + .data, + } + }, + ); let handle_submit_experiment_form = move || { combined_resource.refetch(); @@ -125,7 +447,6 @@ pub fn experiment_list() -> impl IntoView { }); }); - // TODO: Add filters view! {
}> @@ -161,7 +482,7 @@ pub fn experiment_list() -> impl IntoView { {move || { let value = combined_resource.get(); let pagination = pagination_filters_rs.get(); - let table_columns = experiment_table_columns(filters_rs, filters_ws); + let table_columns = experiment_table_columns(filters_rws); match value { Some(v) => { let data = v @@ -197,6 +518,12 @@ pub fn experiment_list() -> impl IntoView { }; view! { +
+ +
, - filters_ws: WriteSignal, + filters_rws: RwSignal, ) -> Vec { - let current_filters = filters_rs.get(); + let current_filters = filters_rws.get(); let current_sort_on = current_filters.sort_on.unwrap_or_default(); let current_sort_by = current_filters.sort_by.unwrap_or_default(); vec![ @@ -166,14 +165,14 @@ pub fn experiment_table_columns( "created_at".to_string(), ColumnSortable::Yes { sort_fn: Callback::new(move |_| { - let filters = filters_rs.get(); + let filters = filters_rws.get(); let sort_by = filters.sort_by.unwrap_or_default().flip(); let new_filters = ExperimentListFilters { sort_on: Some(ExperimentSortOn::CreatedAt), sort_by: Some(sort_by), ..filters }; - filters_ws.set(new_filters); + filters_rws.set(new_filters); }), sort_by: current_sort_by.clone(), currently_sorted: current_sort_on == ExperimentSortOn::CreatedAt, @@ -184,14 +183,14 @@ pub fn experiment_table_columns( "last_modified".to_string(), ColumnSortable::Yes { sort_fn: Callback::new(move |_| { - let filters = filters_rs.get(); - let sort_by = filters.sort_by.as_ref().map(|i| i.flip()); + let filters = filters_rws.get(); + let sort_by = filters.sort_by.unwrap_or_default().flip(); let new_filters = ExperimentListFilters { sort_on: Some(ExperimentSortOn::LastModifiedAt), - sort_by, + sort_by: Some(sort_by), ..filters }; - filters_ws.set(new_filters); + filters_rws.set(new_filters); }), sort_by: current_sort_by, currently_sorted: current_sort_on == ExperimentSortOn::LastModifiedAt, diff --git a/crates/frontend/src/types.rs b/crates/frontend/src/types.rs index 2cd32e49..556afef7 100644 --- a/crates/frontend/src/types.rs +++ b/crates/frontend/src/types.rs @@ -80,7 +80,7 @@ pub struct FunctionTestResponse { /*********************** Experimentation Types ****************************************/ #[derive( - Debug, Clone, Copy, PartialEq, Deserialize, Serialize, strum_macros::Display, + Debug, Clone, Copy, Eq, Hash, PartialEq, Deserialize, Serialize, strum_macros::Display, )] #[strum(serialize_all = "UPPERCASE")] pub enum ExperimentStatusType { @@ -126,6 +126,16 @@ pub struct ExperimentsResponse { #[derive(Serialize, Deserialize, Debug, Clone, Deref, DerefMut, PartialEq)] pub struct StatusTypes(pub Vec); +impl Default for StatusTypes { + fn default() -> Self { + Self(vec![ + ExperimentStatusType::CREATED, + ExperimentStatusType::INPROGRESS, + ExperimentStatusType::CONCLUDED, + ]) + } +} + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct ExperimentListFilters { pub status: Option, @@ -134,11 +144,28 @@ pub struct ExperimentListFilters { pub experiment_name: Option, pub experiment_ids: Option, pub created_by: Option, - pub context: Option, + pub context: Option, pub sort_on: Option, pub sort_by: Option, } +impl Default for ExperimentListFilters { + fn default() -> Self { + let now = Utc::now(); + Self { + status: None, + from_date: Some(now - chrono::Duration::days(30)), + to_date: Some(now), + experiment_name: None, + experiment_ids: None, + created_by: None, + context: None, + sort_on: None, + sort_by: None, + } + } +} + impl Display for ExperimentListFilters { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut query_params = vec![];