-
Notifications
You must be signed in to change notification settings - Fork 20
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
Support searching and sorting experiments via API and sorting on UI #280
Conversation
c666640
to
523a478
Compare
|
||
impl Default for SortBy { | ||
fn default() -> Self { | ||
Self::Desc |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel default should be Asc
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pratikmishra356 you always want to see the latest data, especially if you've been using Superposition for years.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Datron but it can differ based on entity , position/weight needs to be sort in asc order
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can set it in the sort function based on needs, don't need to call default
} | ||
if let Some(ref context_search) = filters.context { | ||
builder = builder.filter( | ||
sql::<Bool>("context::text LIKE ") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
are we type casting here ? context
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes we are, this just lets me modify the context type so I can use LIKE on it. It's currently an experiment to see if this is a viable way to search through contexts
all_contexts = all_contexts | ||
.into_iter() | ||
.filter_map(|mut context| { | ||
Context::filter_keys_by_prefix(&context, &prefix_list) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Datron filter_keys_by_prefix uses try_from which results in lots of error logs
2024-11-25T07:06:27Z ERROR superposition_types::config] Override validation error: Override is empty
Should we instead check if map is empty and then probably try_from
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pratikmishra356 yes we can, but that is out of scope for this PR
@@ -633,56 +633,54 @@ async fn list_contexts( | |||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we separate Asc, Desc like you did in experiment/list ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What would be the benefit? Could you share an example?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
94e1bd4
to
4a57a01
Compare
#[derive(Debug, Deserialize, Clone, Deref)] | ||
#[deref(forward)] | ||
pub struct StringArgs( | ||
#[serde(deserialize_with = "deserialize_stringified_list")] pub Vec<String>, | ||
); | ||
|
||
pub fn deserialize_stringified_list<'de, D, I>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we instead implement the Deserialize
trait on StringArgs
and write the logic in it instead of writing this as a function
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried Deserialize, it was more verbose, and all examples I found did it this way. Can we continue with this method for now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let me know if this works:
https://github.com/juspay/superposition/pull/280/files#r1865864524
} | ||
if let Some(ref context_search) = filters.context { | ||
builder = builder.filter( | ||
sql::<Bool>("context::text LIKE ") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what is this query doing here ?
@@ -122,10 +126,71 @@ pub struct ExperimentsResponse { | |||
pub struct StatusTypes(pub Vec<ExperimentStatusType>); | |||
|
|||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] | |||
pub struct ExpListFilters { | |||
pub struct ExperimentListFilters { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this type not there in experimentation_platform
for the api type ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is present, do you recommend moving this to superposition_types?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
its good to have, or we can wait till this PR goes in:
#291
after this PR is merged, I would raise a separate PR for moving the api types to superposition_types
probably we can move it then
4a57a01
to
9bc4033
Compare
9bc4033
to
bc964e7
Compare
crates/experimentation_platform/src/api/experiments/handlers.rs
Outdated
Show resolved
Hide resolved
#[derive(Debug, Deserialize, Clone, Deref)] | ||
#[deref(forward)] | ||
pub struct StringArgs( | ||
#[serde(deserialize_with = "deserialize_stringified_list")] pub Vec<String>, | ||
); | ||
|
||
pub fn deserialize_stringified_list<'de, D, I>( | ||
deserializer: D, | ||
) -> std::result::Result<Vec<I>, D::Error> | ||
where | ||
D: de::Deserializer<'de>, | ||
I: de::DeserializeOwned, | ||
{ | ||
struct StringVecVisitor<I>(std::marker::PhantomData<I>); | ||
|
||
impl<'de, I> de::Visitor<'de> for StringVecVisitor<I> | ||
where | ||
I: de::DeserializeOwned, | ||
{ | ||
type Value = Vec<I>; | ||
|
||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { | ||
formatter.write_str( | ||
"a string containing comma separated values eg: CREATED,INPROGRESS", | ||
) | ||
} | ||
|
||
fn visit_str<E>(self, v: &str) -> std::result::Result<Self::Value, E> | ||
where | ||
E: de::Error, | ||
{ | ||
let mut query_vector = Vec::new(); | ||
for param in v.split(',') { | ||
let p: I = I::deserialize(param.into_deserializer())?; | ||
query_vector.push(p); | ||
} | ||
Ok(query_vector) | ||
} | ||
} | ||
|
||
deserializer.deserialize_any(StringVecVisitor(std::marker::PhantomData::<I>)) | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should work, right ?
#[derive(Debug, Deserialize, Clone, Deref)] | |
#[deref(forward)] | |
pub struct StringArgs( | |
#[serde(deserialize_with = "deserialize_stringified_list")] pub Vec<String>, | |
); | |
pub fn deserialize_stringified_list<'de, D, I>( | |
deserializer: D, | |
) -> std::result::Result<Vec<I>, D::Error> | |
where | |
D: de::Deserializer<'de>, | |
I: de::DeserializeOwned, | |
{ | |
struct StringVecVisitor<I>(std::marker::PhantomData<I>); | |
impl<'de, I> de::Visitor<'de> for StringVecVisitor<I> | |
where | |
I: de::DeserializeOwned, | |
{ | |
type Value = Vec<I>; | |
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { | |
formatter.write_str( | |
"a string containing comma separated values eg: CREATED,INPROGRESS", | |
) | |
} | |
fn visit_str<E>(self, v: &str) -> std::result::Result<Self::Value, E> | |
where | |
E: de::Error, | |
{ | |
let mut query_vector = Vec::new(); | |
for param in v.split(',') { | |
let p: I = I::deserialize(param.into_deserializer())?; | |
query_vector.push(p); | |
} | |
Ok(query_vector) | |
} | |
} | |
deserializer.deserialize_any(StringVecVisitor(std::marker::PhantomData::<I>)) | |
} | |
#[derive(Debug, Clone, Deref)] | |
#[deref(forward)] | |
pub struct StringArgs(pub Vec<String>); | |
impl<'de> Deserialize<'de> for StringArgs { | |
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> | |
where | |
D: Deserializer<'de>, | |
{ | |
let s = String::deserialize(deserializer)?; | |
let items = s.split(',').map(|item| item.trim().to_string()).collect(); | |
Ok(Self(items)) | |
} | |
}``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the case you present, if the user passes a wrong value then there is no error, which I want.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this does return an error, if the data passed is not a string, which is more than enough to determine if the value is right or not
as the data in it must contain is not mandatory, because there can always be a single element in the array which does not require any sorts of delimiter
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ayushjain17 I tried this, but this function is used to deserialize a lot of other vectors and type cast them. I don't want to manually impl deserialize on all of them so I won't change this for now.
bc964e7
to
e2ee925
Compare
)] | ||
#[serde(rename_all = "lowercase")] | ||
pub enum SortBy { | ||
#[display(fmt = "desc")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is a dedicated how to display
definition required, rename_all
doesn't handle this case too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rename_all is for deserialization, the to_string representation would be different me thinks
#[strum(to_string = "last_modified_at")] | ||
LastModifiedAt, | ||
#[strum(to_string = "created_at")] | ||
CreatedAt, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This also won't rename_all
handle the case of to_string
which is just the snake_case
repr.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Didn't get you
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
he meant why do we need strum to_string
here, is serde rename_all
not enough here ?
just like here
pub enum ColumnSortable { | ||
Yes { | ||
sort_fn: Callback<()>, | ||
sort_by: SortBy, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sort_by: SortBy, | |
order: Order, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No
e2ee925
to
64469a6
Compare
filters.to_string(), | ||
pagination |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we make it consistent over here, I got confused that how are these two not having the exact same syntax
filters.to_string(), | |
pagination | |
filters.to_string(), | |
pagination.to_string() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You won't get confused if you see the whole function.
format!( | ||
"{}/experiments?{}&{}", | ||
host, | ||
filters.to_string(), | ||
pagination | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this does not seem correct, if someone does not send filters
, the url will become:
base_url/experimnets?&pagi_filt1=abc
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are always date filters
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that is not being guaranteed by the type
all fields in ExperimentListFilters
are optional, so one can simply avoid sending any params and then it breaks over here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is guaranteed by code,
from_date: Utc.timestamp_opt(0, 0).single(),
to_date: Utc.timestamp_opt(4130561031, 0).single(),
Doesn't need to be guaranteed by the type
let value: String = row | ||
.get(cname) | ||
.unwrap_or(&Value::String(String::new())) | ||
.html_display(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we rather do something like this:
let value: String = row | |
.get(cname) | |
.unwrap_or(&Value::String(String::new())) | |
.html_display(); | |
let value = row.get(cname).map(|v| v.html_display()).unwrap_or_default(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why the difference?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
its better memory wise, right
in the default case we just need a String, and thats what would be get directly
else, currently in default case, you are getting a Value
type which again is converted to String
by html_display
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will look into that, can we take it as it is now?
Problem
It is difficult to find experiments on different filters and sort them
Solution
PS: more frontend changes are to be expected to support searching. That is NOT present in this PR.
Screen.Recording.2024-11-08.at.2.35.02.PM.mov