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: Search filtering logic #6968

Merged
merged 40 commits into from
Feb 13, 2024
Merged

feat: Search filtering logic #6968

merged 40 commits into from
Feb 13, 2024

Conversation

alextran1502
Copy link
Contributor

@alextran1502 alextran1502 commented Feb 7, 2024

image

@alextran1502 alextran1502 marked this pull request as draft February 7, 2024 18:23
@alextran1502 alextran1502 self-assigned this Feb 7, 2024
@alextran1502 alextran1502 changed the title dev/search filter logic feat: Search filtering logic Feb 7, 2024
Copy link

cloudflare-workers-and-pages bot commented Feb 7, 2024

Deploying with  Cloudflare Pages  Cloudflare Pages

Latest commit: 4cfc5b0
Status:⚡️  Build in progress...

View logs

.distinctOn(['exif.country'])
.getMany();

return entity.map((e) => e.country ?? '').filter((c) => c !== '');
Copy link
Contributor

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.

Copy link
Member

@danieldietzler danieldietzler left a 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.

@alextran1502
Copy link
Contributor Author

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.

@alextran1502 alextran1502 marked this pull request as ready for review February 10, 2024 02:39
Copy link
Contributor

@mertalev mertalev left a 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

Comment on lines +12 to +33
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;
}
Copy link
Contributor

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[]> {
Copy link
Contributor

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.

Copy link
Contributor Author

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

Copy link
Contributor

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

Copy link
Contributor

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

Copy link
Member

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?

switch (type) {
case SearchSuggestionType.Country: {
for (const country of data) {
suggestions.country = [...suggestions.country, { label: country, value: country }];
Copy link
Contributor

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 }))

Copy link
Contributor Author

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

Copy link
Member

@danieldietzler danieldietzler Feb 10, 2024

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

Copy link
Contributor Author

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.

Comment on lines 118 to 130
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();
}
};
Copy link
Contributor

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();
  };

Copy link
Contributor Author

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

Copy link
Contributor

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.

Copy link
Contributor

@jrasm91 jrasm91 left a 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.

@alextran1502 alextran1502 enabled auto-merge (squash) February 13, 2024 18:47
@alextran1502 alextran1502 merged commit 4b3f8d1 into main Feb 13, 2024
24 of 25 checks passed
@alextran1502 alextran1502 deleted the dev/search-filter-logic branch February 13, 2024 19:55
@waclaw66
Copy link
Contributor

waclaw66 commented Feb 14, 2024

Bugs:

  • X button covers new filter button when some search string is entered...
    obrazek
  • filter window cannot be closed clicking outside
  • search button doesn't work, it does nothing
  • using Enter to confirm search reloads the whole page on /photos?context=panorama&start-date=&end-date=&radio-type=all without any result - regular photos page
  • using new filter from the results returned by old simple search, reloads the page always with no results with message No results Try a synonym or more general keyword

browser: Firefox, Chrome

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

5 participants