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

feat: experiment filters in UI #424

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open

feat: experiment filters in UI #424

wants to merge 1 commit into from

Conversation

Datron
Copy link
Collaborator

@Datron Datron commented Feb 19, 2025

Problem

It is hard to search for experiments

Solution

Add the ability to filter and manage experiments. Search is done on the backend, so even paginated experiments can be searched for

Screen.Recording.2025-02-19.at.7.06.34.PM.mov

@Datron Datron requested a review from a team as a code owner February 19, 2025 13:39
Comment on lines 55 to 58
combined_resource: Resource<
(String, ExperimentListFilters, PaginationParams, String),
CombinedResource,
>,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
combined_resource: Resource<
(String, ExperimentListFilters, PaginationParams, String),
CombinedResource,
>,
combined_resource: CombinedResource,

Comment on lines 64 to 75
let dim = combined_resource
.get()
.unwrap_or(CombinedResource {
experiments: PaginatedResponse {
total_items: 0,
total_pages: 0,
data: vec![],
},
dimensions: vec![],
default_config: vec![],
})
.dimensions;
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think there is no point of taking a Resource<...> here as this is not going to reactive since its outside a move closure, better to send in the CombinedResource.

Comment on lines +180 to +173
checked=move || {
filters_buffer_rws
.get()
.status
.unwrap_or_default()
.iter()
.any(|item| *item == ExperimentStatusType::CREATED)
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Passing a move clousre to checked prop does it really work as intented?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah! Hopefully. Maybe...


let status_filter_management =
move |checked: bool, filter_status: ExperimentStatusType| {
let vec = filters_buffer_rws.get().status.unwrap_or_default();
Copy link
Collaborator

Choose a reason for hiding this comment

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

vec name is already used by macro vec!.

Comment on lines 313 to 318
let filters = filters_buffer_rws.get();
let filters = ExperimentListFilters {
experiment_ids: id,
..filters
};
filters_buffer_rws.set(filters);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
let filters = filters_buffer_rws.get();
let filters = ExperimentListFilters {
experiment_ids: id,
..filters
};
filters_buffer_rws.set(filters);
filters_buffer_rws.update(|value| {
value.experiments_ids = id;
})

sql::<Bool>("context::text LIKE ")
.bind::<Text, _>(format!("%{}%", context_search)),
);
for c in context_search.iter() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Little bit confused by this filter, isn't the context column a json? How does this exactly work?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Take an example:

{"and":[{"==":[{"var":"city"},"BLR"]}]}

 {"and":[{"==":[{"var":"city"},"MUM"]},{"==":[{"var":"vehicle_type"},"suv"]}]}

Lets say you want to find the second one, the query formed will look like:

SELECT * FROM <schema>.experiments WHERE context::text = '%{"==":[{"var":"city"},"MUM"]}%' AND context = '%{"==":[{"var":"vehicle_type"},"suv"]}%';

FAQs
Q. The type is JSON, but you're using LIKE which needs text. How does this work?
A. The ::text does the conversion

Q. Why not just do the whole context?
A. Imagine single searches, like

city = BLR

If I searched by the whole context, it would only match records that had this structure,

{"and":[{"==":[{"var":"city"},"BLR"]}]}

and would ignore records like these:

{"and":[{"==":[{"var":"city"},"BLR"]}]}

{"and":[{"==":[{"var":"city"},"BLR"]},{"==":[{"var":"vehicle_type"},"suv"]}]}

{"and":[{"==":[{"var":"city"},"BLR"]},{"==":[{"var":"vehicle_type"},"sedan"]}]}

Copy link
Collaborator

Choose a reason for hiding this comment

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

Might be a bit compute heavy, so we should think about a conservative LIMIT clause here.
PS: @knutties looks like we CAN query w/ JSON as well :P

@@ -340,12 +341,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 to_context_json(&self) -> serde_json::Value {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Since it's not longer consuming, maybe it's better to call it as_context_json. Would be more coherent imo.

json!({
"and": self.iter().map(|v| v.to_condition_json()).collect::<Vec<serde_json::Value>>()
})
}
pub fn to_query_string(self) -> String {
pub fn to_query_string(&self) -> String {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same for this one.

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) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Since the user can't really type anything & all the input is via the calender-widget, I think we can just unwrap w/o any consequences.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

They can still programmatically change the field in inspect, better safe than sorry.

Copy link
Collaborator

Choose a reason for hiding this comment

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

  1. How is what I'm saying any worse? It'll just panic and nothing will happen. Anyway the user chose to break the UI.
  2. If we're willing to believe that, then the user can also just run the API call themselves, so any protection here isn't real anyway. So there's no point in bothering about any sort of protection if we are assuming such case.

@@ -46,8 +46,8 @@ pub fn table(
{
match (currently_sorted, sort_by) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

You know, maybe you can keep sort_by as Option<SortBy>, that way you can avoid keeping currently_sorted. Also this way it's also less error prone, as you if forget to change currently_sorted, the UI change will not be performed.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Could you elaborate?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Just saying that you can collapse the current_sorted field (in ColumnSortable::Yes) into sort_by by changing it's type to Option<SortBy>. So, if a column is not sorted, instead of making currently_sorted false you'd just change sort_by to None. Basically in experiment_table_columns, you'd change it to:
sort_by: if current_sort_on == ExperimentSortOn::LastModifiedAt { Some(current_sort_by) } else { None }
To simplify, currently there is possibility of one changing the sort_by but forgetting to correctly set the value of current_sorted, w/ this you eliminate that possibility itself. Quite minute, but something I personally prefer.
Just a suggestion.


let dim = combined_resource
.get()
.unwrap_or(CombinedResource {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why not unwrap_or_default? CombinedResource already has Default derived for it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The resource it wraps isn't defined

event.prevent_default();
let filter = filters_buffer_rws.get();
filters_rws.set(filter);
close_drawer("experiment_filter_drawer")
Copy link
Collaborator

Choose a reason for hiding this comment

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

This string probably should either an enum or a constant.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Its the element ID, too dynamic for enum. If its wrong it won't work and its pretty obvious to the person who is building and testing the drawer

close_drawer("experiment_filter_drawer")
}
/>
<Button
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should be styled as some kind of cancellation button, maybe a cross?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It resets your filters, don't think cancellation is the right word 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.

I'll try restart

Copy link
Collaborator

Choose a reason for hiding this comment

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

What I meant was, it should convey a drop of filters visually, that's all, restart/reset is good.

impl Default for StatusTypes {
fn default() -> Self {
Self(vec![
ExperimentStatusType::CREATED,
Copy link
Collaborator

@ShreyBana ShreyBana Feb 27, 2025

Choose a reason for hiding this comment

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

For listing out all the variants there is a macro, & if add a new variant for this enum, we will probably miss it this way. Can you use that macro? Here's the link: https://docs.rs/strum/latest/strum/derive.EnumIter.html


<div class="form-control w-full">
<label class="label">
<span class="label-text">Experiment Status</span>
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could have looped over the variants to get this out, in-fact, if some body adds a new variant, then we might forget it. Can you loop over the variants instead?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The colors change for each variant which isn't captured by the enum, how would I apply that here?

Copy link
Collaborator

Choose a reason for hiding this comment

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

If I am understanding you correctly, that can just be a match inside the loop:
let color = match status ...
Not sure if that was your concern though.

@Datron Datron force-pushed the experiment-filters branch from 8702820 to 909cd94 Compare February 28, 2025 08:02
@Datron Datron force-pushed the experiment-filters branch 2 times, most recently from 7022fb4 to 7d1efa3 Compare February 28, 2025 09:25
@Datron Datron force-pushed the experiment-filters branch from 7d1efa3 to 551133e Compare February 28, 2025 12:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants