From 5e4039ec860c2eb8abe9115d3b5739a37677c736 Mon Sep 17 00:00:00 2001 From: Kartik Date: Mon, 11 Nov 2024 19:20:47 +0530 Subject: [PATCH] feat: add date and status filters to experiments listing --- .../frontend/src/components/context_form.rs | 4 +- crates/frontend/src/components/input.rs | 54 +++ crates/frontend/src/pages/experiment_list.rs | 371 ++++++++++++++++-- .../src/pages/experiment_list/utils.rs | 13 +- crates/frontend/src/types.rs | 34 +- 5 files changed, 440 insertions(+), 36 deletions(-) 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 b6fd7642..a2718586 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/pages/experiment_list.rs b/crates/frontend/src/pages/experiment_list.rs index 0b46f5cf..6d88ca8b 100644 --- a/crates/frontend/src/pages/experiment_list.rs +++ b/crates/frontend/src/pages/experiment_list.rs @@ -1,20 +1,34 @@ 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, PaginatedResponse}; +use crate::types::{ + ExperimentListFilters, ExperimentResponse, ExperimentStatusType, PaginatedResponse, + StatusTypes, +}; use crate::utils::update_page_direction; use self::utils::experiment_table_columns; @@ -31,30 +45,335 @@ struct CombinedResource { default_config: Vec, } +#[component] +fn experiment_table_filter_widget( + filters_rws: RwSignal, + combined_resource: Resource<(String, ExperimentListFilters), 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_rws.get(); + let new_filters = ExperimentListFilters { + from_date: Some(new_date), + ..old_filters + }; + filters_buffer_rws.set(new_filters); + }) + /> +
+ +
+ + | { + let old_filters = filters_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(), - 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 filters_rws = create_rw_signal(ExperimentListFilters::default()); let (reset_exp_form, set_exp_form) = create_signal(0); let combined_resource: Resource<(String, ExperimentListFilters), CombinedResource> = create_blocking_resource( - move || (tenant_rs.get(), filters_rs.get()), + move || (tenant_rs.get(), filters_rws.get()), |(current_tenant, filters)| async move { // Perform all fetch operations concurrently let experiments_future = @@ -96,18 +415,17 @@ pub fn experiment_list() -> impl IntoView { }; let handle_next_click = Callback::new(move |total_pages: i64| { - filters_ws.update(|f| { + filters_rws.update(|f| { f.page = update_page_direction(f.page, total_pages, true); }); }); let handle_prev_click = Callback::new(move |_| { - filters_ws.update(|f| { + filters_rws.update(|f| { f.page = update_page_direction(f.page, 1, false); }); }); - // TODO: Add filters view! {
}> @@ -142,8 +460,8 @@ pub fn experiment_list() -> impl IntoView {
{move || { let value = combined_resource.get(); - let filters = filters_rs.get(); - let table_columns = experiment_table_columns(filters_rs, filters_ws); + let filters = filters_rws.get(); + let table_columns = experiment_table_columns(filters_rws); match value { Some(v) => { let data = v @@ -179,6 +497,9 @@ 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![ @@ -167,14 +166,14 @@ pub fn experiment_table_columns( ColumnSortable::Yes { sort_on: ExperimentSortOn::CreatedAt.to_string(), 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(), current: current_sort_on == ExperimentSortOn::CreatedAt, @@ -186,14 +185,14 @@ pub fn experiment_table_columns( ColumnSortable::Yes { sort_on: ExperimentSortOn::LastModifiedAt.to_string(), sort_fn: Callback::new(move |_| { - let filters = filters_rs.get(); + let filters = filters_rws.get(); let sort_by = filters.sort_by.as_ref().map(|i| i.flip()); let new_filters = ExperimentListFilters { sort_on: Some(ExperimentSortOn::LastModifiedAt), sort_by, ..filters }; - filters_ws.set(new_filters); + filters_rws.set(new_filters); }), sort_by: current_sort_by, current: current_sort_on == ExperimentSortOn::LastModifiedAt, diff --git a/crates/frontend/src/types.rs b/crates/frontend/src/types.rs index 7bc492b0..b073edd8 100644 --- a/crates/frontend/src/types.rs +++ b/crates/frontend/src/types.rs @@ -79,7 +79,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 { @@ -125,6 +125,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, @@ -136,11 +146,31 @@ 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), + page: Some(1), + count: Some(10), + all: None, + experiment_name: None, + experiment_ids: None, + created_by: None, + context: None, + sort_on: None, + sort_by: None, + } + } +} + impl ExperimentListFilters { pub fn get_query_params(self) -> String { let mut query_params = vec![];