-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
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: Search filtering logic #6968
Conversation
alextran1502
commented
Feb 7, 2024
•
edited
Loading
edited
.distinctOn(['exif.country']) | ||
.getMany(); | ||
|
||
return entity.map((e) => e.country ?? '').filter((c) => c !== ''); |
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 move the filter to the query. e.country
should also never be nullish.
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 was thinking that we always return an object of all search options. This way, we could update the suggestions on select of an option, not when opening a combobox. Should help with performance a lot.
I was debating on this, it means we will do the filtering on the client. I opt-in for filtering on the server instead, to keep the payload light. |
Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
…h into dev/search-filter-logic
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.
Some suggested cleanup for the web code and a general design question for how we want to handle the search DTO
export class SearchSuggestionRequestDto { | ||
@IsEnum(SearchSuggestionType) | ||
@IsNotEmpty() | ||
@ApiProperty({ enumName: 'SearchSuggestionType', enum: SearchSuggestionType }) | ||
type!: SearchSuggestionType; | ||
|
||
@IsString() | ||
@IsOptional() | ||
country?: string; | ||
|
||
@IsString() | ||
@IsOptional() | ||
state?: string; | ||
|
||
@IsString() | ||
@IsOptional() | ||
make?: string; | ||
|
||
@IsString() | ||
@IsOptional() | ||
model?: 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.
This dto is kinda awkward. It isn't clear when these fields will get used. For example, if the type is STATE
, then only country
is used: state
, make
and model
are ignored. But you can't tell that from looking at this.
I don't know if we ever settled on whether we want to do the filters in separate calls or in the same one, but this dto only makes sense if we intend to move toward getting all the suggestions in the same call down the line.
@@ -122,4 +125,28 @@ export class SearchService { | |||
userIds.push(...partnersIds); | |||
return userIds; | |||
} | |||
|
|||
async getSearchSuggestions(auth: AuthDto, dto: SearchSuggestionRequestDto): Promise<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.
Similar to the above comment, having a single method for this only makes sense if we want to change this later to return all suggestions. If we want to continue to have separate calls for each field, having separate methods would make that behavior clearer.
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 was my original idea: to split the suggestion into separate endpoints. It will be more code but clearer
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 disagree, we don't need one endpoint for every select distinct query we plan to make
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.
Maybe we can move the type part from a query param to a route param
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 disagree, we don't need one endpoint for every select distinct query we plan to make
So you're also in favor of one endpoint that returns an object of all suggestions for all fields, as I've described in Discord?
web/src/lib/components/shared-components/search-bar/search-filter-box.svelte
Outdated
Show resolved
Hide resolved
switch (type) { | ||
case SearchSuggestionType.Country: { | ||
for (const country of data) { | ||
suggestions.country = [...suggestions.country, { label: country, value: country }]; |
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 is creating a whole new array for each entry. This would be better
suggestions.country.push(...data.map(country => ({ label: country, value: country }))
If you can make the SearchSuggestionType
enum values match the properties of suggestions
, you can also fold all of this into:
suggestions[type].push(...data.map(entry => ({ label: entry, value: entry }))
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 will not work with Svelte's reactivity
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.
True, but you could do
suggestions.country = [...suggestions.country, ...data.map(country => ({ label: country, value: country }))]
at least. Alternatively, suggestions could be writable
and then you could trigger suggestions.update
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.
Yeah, that one-liner is good. If we have to make a global state, the filter's value would be better, so we can modify the filter when showing the search result page.
web/src/lib/components/shared-components/search-bar/search-filter-box.svelte
Outdated
Show resolved
Hide resolved
const handlePeopleSelection = (id: string) => { | ||
if (filter.people.some((p) => p.id === id)) { | ||
filter.people = filter.people.filter((p) => p.id !== id); | ||
sortPeople(); | ||
return; | ||
} | ||
|
||
const person = suggestions.people.find((p) => p.id === id); | ||
if (person) { | ||
filter.people = [...filter.people, person]; | ||
sortPeople(); | ||
} | ||
}; |
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 code is difficult to understand. The first part removes the id from the filtered people if it exists and the second part adds it if it doesn't. So it's like a toggle? And filter.people
is used in sortPeople
to show selected people first and for styling. Is there a significance to finding the person entry as a whole and adding it to filter.people
instead of using the id?
I think it'd be a bit clearer (and faster) if you made filter.people
a set. Renaming the filter
object to selected
would also make the intent more explicit. And if you changed sortPeople
to showSelectedPeopleFirst
, it'd convey what it's sorting by.
const handlePeopleSelection = (id: string) => {
if (selected.people.has(id)) {
selected.people.delete(id);
} else {
selected.people.add(id);
}
showSelectedPeopleFirst();
};
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.
The value in the array is PersonReponseDto
, it is an object so it doesn't really work with Set
. We will need the object to render the name and thumbnail in the people 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.
filter.people
as it is only seems to be used to check if a person ID exists in it. I don't think it needs to have the person object itself for this. It can just be a set of IDs. suggestions.people
is what's used to get the name etc. in the current code.
web/src/lib/components/shared-components/search-bar/search-filter-box.svelte
Outdated
Show resolved
Hide resolved
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 looks good to me, with the exception of the api design for the search suggestion endpoint. I think we should have more internal discussion around the final design before merging it. I'm not a fan of one endpoint returning all values for all fields at the same time, but I agree with mert that some separate endpoints would make the validation more meaningful, so I have some more ideas about this.