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

[Search v2.4] - Same search query appears twice in Recent searches #51044

Open
5 of 8 tasks
lanitochka17 opened this issue Oct 17, 2024 · 23 comments
Open
5 of 8 tasks

[Search v2.4] - Same search query appears twice in Recent searches #51044

lanitochka17 opened this issue Oct 17, 2024 · 23 comments
Assignees
Labels
Engineering Reviewing Has a PR in review Weekly KSv2

Comments

@lanitochka17
Copy link

lanitochka17 commented Oct 17, 2024

If you haven’t already, check out our contributing guidelines for onboarding and email [email protected] to request to join our Slack channel!


Version Number: 9.0.50-3
Reproducible in staging?: Y
Reproducible in production?: N
If this was caught on HybridApp, is this reproducible on New Expensify Standalone?: N/A
If this was caught during regression testing, add the test name, ID and link from TestRail: N/A
Email or phone of affected tester (no customers): [email protected]
Issue reported by: Applause - Internal Team

Action Performed:

Precondition:

  • User has not searched using filters previously
  1. Go to staging.new.expensify.com
  2. Go to Search
  3. Click Filters
  4. Apply any filter and click View results
  5. Go to Inbox
  6. Click on the search icon on the report header
  7. Click on the search query under Recent searches
  8. Go back to Inbox
  9. Click on the search icon on the report header

Expected Result:

The search query made in Step 4 will only appear once after clicking on the same search query again in Step 7

Actual Result:

The search query made in Step 4 appears twice after clicking on the same search query again in Step 7

Workaround:

Unknown

Platforms:

Which of our officially supported platforms is this issue occurring on?

  • Android: Standalone
  • Android: HybridApp
  • Android: mWeb Chrome
  • iOS: Standalone
  • iOS: HybridApp
  • iOS: mWeb Safari
  • MacOS: Chrome / Safari
  • MacOS: Desktop

Screenshots/Videos

Add any screenshot/video evidence
Bug6637920_1729183854749.bandicam_2024-10-18_00-47-35-359.mp4

View all open jobs on GitHub

@lanitochka17 lanitochka17 added the DeployBlockerCash This issue or pull request should block deployment label Oct 17, 2024
Copy link

melvin-bot bot commented Oct 17, 2024

Triggered auto assignment to @dangrous (DeployBlockerCash), see https://stackoverflowteams.com/c/expensify/questions/9980/ for more details.

Copy link

melvin-bot bot commented Oct 17, 2024

💬 A slack conversation has been started in #expensify-open-source

Copy link
Contributor

👋 Friendly reminder that deploy blockers are time-sensitive ⏱ issues! Check out the open `StagingDeployCash` deploy checklist to see the list of PRs included in this release, then work quickly to do one of the following:

  1. Identify the pull request that introduced this issue and revert it.
  2. Find someone who can quickly fix the issue.
  3. Fix the issue yourself.

@lanitochka17
Copy link
Author

We think that this bug might be related to #wave-control

@luacmartins
Copy link
Contributor

Interesting, the Onyx data is different from what's being displayed
Screenshot 2024-10-17 at 11 22 32 AM

@jaydamani
Copy link
Contributor

Proposal

Please re-state the problem that we are trying to solve in this issue.

Same search query appears twice in Recent searches

What is the root cause of that problem?

When we search again, new entry is added to nvp_RecentSearches

What changes do you think we should make in order to solve the problem?

Add logic to remove duplicate queries and keep only most recent one in below line.

return Object.values(recentSearches ?? {}).sort((a, b) => b.timestamp.localeCompare(a.timestamp));

@jaydamani
Copy link
Contributor

Interesting, the Onyx data is different from what's being displayed Screenshot 2024-10-17 at 11 22 32 AM

FYI, It happens because we don't use sort related properties when making the search header in below code. So, we essentially ignore sort by, sort order and policy id from the query.

App/src/libs/SearchUtils.ts

Lines 812 to 815 in 91c6e0c

const {type, status} = queryJSON;
const filters = queryJSON.flatFilters ?? {};
let title = `type:${type} status:${status}`;

@dangrous
Copy link
Contributor

@luacmartins do you want to take this one since you know the search stuff well?

@SzymczakJ
Copy link
Contributor

I'd like to work on it, since I was implementing Search 2.4.

@luacmartins
Copy link
Contributor

@SzymczakJ all yours. I think we can demote this to NAB since it doesn't really break any functionality.

@luacmartins luacmartins added Daily KSv2 and removed DeployBlockerCash This issue or pull request should block deployment Hourly KSv2 labels Oct 17, 2024
@luacmartins luacmartins assigned luacmartins and unassigned dangrous Oct 17, 2024
@luacmartins luacmartins changed the title Search - Same search query appears twice in Recent searches [Search v2.4] - Same search query appears twice in Recent searches Oct 17, 2024
@SzymczakJ SzymczakJ mentioned this issue Oct 18, 2024
48 tasks
@SzymczakJ
Copy link
Contributor

This bug is on FE

Interesting, the Onyx data is different from what's being displayed

but after investigating this I think that it should be also fixed on BE. Recent Searches are generated on BE after searching for query. In case when we are searching for the same query twice, then it should be renewed in history and not duplicated. WDYT @luacmartins

@luacmartins
Copy link
Contributor

@SzymczakJ that is the current logic in the backend. As you can see the Onyx data is accurate, not sure why the Onyx data and the list are out of sync
377569515-04a7dd00-63b7-4bdb-bae5-78144cad27aa

@luacmartins
Copy link
Contributor

Oh I see that it shows the query because we have instances of the same query saved with and without sortBy:date sortOrder:desc. We must be adding that in the frontend when sending it back to the server so we end up saving it twice instead of updating the query.

@rayane-djouah
Copy link
Contributor

@luacmartins I agree, I commented here: #50921 (comment). Adding that on the frontend when sending it back to the server will fix the bug for this case. However, it will not resolve the issue in cases of the same query with different sortBy or sortOrder. In this comment, I suggested filtering the recent searches based on a secondary hash.

@luacmartins
Copy link
Contributor

luacmartins commented Oct 18, 2024

Hmm in the case of different sortBy or sortOrder (that's not the default), wouldn't we want to display that to the user, which would make it a different recent search? After all, they'd have to explicitly set those to values other than the default.

@rayane-djouah
Copy link
Contributor

rayane-djouah commented Oct 18, 2024

@luacmartins @SzymczakJ What do you think about this plan:

  1. Add sortBy and sortOrder (that's not the default) to buildFilterFormValuesFromQuery similar to type, status, and policyID:

App/src/libs/SearchUtils.ts

Lines 738 to 746 in 793dcaf

const [typeKey = '', typeValue] = Object.entries(CONST.SEARCH.DATA_TYPES).find(([, value]) => value === queryJSON.type) ?? [];
filtersForm[FILTER_KEYS.TYPE] = typeValue ? queryJSON.type : CONST.SEARCH.DATA_TYPES.EXPENSE;
const [statusKey] = Object.entries(CONST.SEARCH.STATUS).find(([, value]) => Object.values(value).includes(queryJSON.status)) ?? [];
filtersForm[FILTER_KEYS.STATUS] = typeKey === statusKey ? queryJSON.status : CONST.SEARCH.STATUS.EXPENSE.ALL;
if (queryJSON.policyID) {
filtersForm[FILTER_KEYS.POLICY_ID] = queryJSON.policyID;
}

  1. Change buildQueryStringFromFilterFormValues to use the existing sortBy and sortOrder (that's not the default) (if they exist) instead of including the default values like we're doing here.
  2. Create a function that uses getSearchHeaderTitle and include sortBy and sortOrder (that's not the default) and the policyID (if it exists) and use this function instead of getSearchHeaderTitle when displaying recent searches and saved searches here:

title = SearchUtils.getSearchHeaderTitle(jsonQuery, personalDetails, cardList, reports, taxRates);

and here:

text: searchQueryJSON ? SearchUtils.getSearchHeaderTitle(searchQueryJSON, personalDetails, cardList, reports, taxRates) : query,

and keep using getSearchHeaderTitle only for the search input in the header of the results page (to encourage th users to use the contextual filters to change the values of sortBy, sortOrder, and policyID instead of manually typing them in)

  1. Filter sortBy, sortOrder, and policyID (contextual filters) when determining shouldShowResetFilters (to avoid displaying the "Reset filters" button when the form is empty) :

const shouldShowResetFilters = Object.entries(searchAdvancedFilters)
.filter(([key]) => CONST.SEARCH.SYNTAX_ROOT_KEYS.TYPE !== key && CONST.SEARCH.SYNTAX_ROOT_KEYS.STATUS !== key)
.some(([, value]) => (Array.isArray(value) ? value.length !== 0 : !!value));

@luacmartins
Copy link
Contributor

That sounds good to me 👍

@melvin-bot melvin-bot bot added the Overdue label Oct 21, 2024
@SzymczakJ
Copy link
Contributor

SzymczakJ commented Oct 21, 2024

After talking about this with @Kicu and @289Adam289 I'd like to propose another solution:
Fix BE so same queries with changed sortOrder or sortBy should be treated as the same query and should be just renewed in history. Additionally don't show sortBy and sortOrder in SearchRouter and saved searches Here are arguments for that solution:

  1. In development changing searchOrder is technically a new search, but for user it doesn't make much difference, take a look at this video as an example:
argument.1.2.mov

Now after playing around with sortOrder and sortBy for a couple of times user gets a list of the same query just with different sortOrder etc.. His meaningful previous searches are pushed out of recent list by garbage which is only differing in searchOrder. 🥲

  1. Having SearchOrder in recent searches list can lead some UI/UX problems because Search router has limited space horizontally and we won't have enough space to view even medium length queries.
Screenshot 2024-10-21 at 13 01 00

This query has only one filter currency:PLN and even this one filter isn't visible 😄
3. The same goes for Saved searches:
Screenshot 2024-10-21 at 13 07 09

  1. And everything that is shown on mobile screen:
Screenshot 2024-10-21 at 13 08 18

After fixing this on BE we could think of some follow-ups like:

  • change mobile design a little, so it allows user to sort search results on mobile screen(add some button for this) without writing it manually into SearchRouter. Now it's not possible.
  • change designs a little bit, so recent searches and saved searches, can be displayed reasonably, because now only really short queries can be shown fully and showing any longer query is confusing.

@melvin-bot melvin-bot bot removed the Overdue label Oct 21, 2024
@luacmartins
Copy link
Contributor

I'm not sure that this is a backend fix. Currently, the logic in the backend is:

  1. If canned search, return
  2. Get currentRecentSearches
  3. If currentRecentSearches has the request hash, update its timestamp
  4. Else if currentRecentSearches > 5, purge oldest and add new search keyed by request hash
  5. Else add the search to currentRecentSearches keyed by request hash

So the backend logic is already updating the timestamp. The issue is that we use the hash as the key and that's changing with sortBy/order. Is there any benefit in changing the hash when sortBy/sortOrder changes? We exclude offset from the hash already, so maybe we should be doing the same for sorting

@Kicu
Copy link
Contributor

Kicu commented Oct 22, 2024

Is there any benefit in changing the hash when sortBy/sortOrder changes?

A few things we'd need to check.

  1. main thing is this - does the hash take any part in caching some parts of query or checking if the query is unique? I have completely misremembered that the hash is used as key in onyx, but after checking that does not seem to be the case
  2. I'm pretty sure hash takes some part in deciding uniqueness of saved search, and highlighting if saved search is active
  3. it's used as key in onyx for optimisticData for action saveSearch and deleteSavedSearch (something related to RBR which I don't fully understand) here: https://github.com/Expensify/App/blob/main/src/libs/actions/Search.ts#L63

So while this is one possible course of action, we'd need to verify if above cases don't break. Especially about point 1 - please remind me, we have never used hash as onyx key in front right? I imagined that? 😅


Now about what @SzymczakJ was trying to convey I think is this:
In case we keep hash as is right now, backend can see that the hash is different - so query is different.
However backend could additionally check if the different part of query is only sort values, and in that case don't treat it as new query; don't purge; just update the timestamp.

So basically for the user let's not save the same exact query that differs only by sorting.
However technically for us the query IS different, that's why I'm kinda reluctant to change hash generation.

In general we would like to avoid Recent Searches to look like this:

 | "sameQuery sortBy:date asc"
 | "sameQuery sortBy:date desc"
 | "sameQuery sortBy:from asc"
 | "sameQuery sortBy:from desc"
 | "sameQuery sortBy:date asc"

IMO ☝️ looks not really helpful. I'd rather see my actual older searches than the same search with changed sorts.

@SzymczakJ
Copy link
Contributor

SzymczakJ commented Oct 22, 2024

I think we have to include sortOrder and sortBy in hash because:

  1. it is used as Onyx key on every search action. In previous hash function we also included it in hash because when the text is parsed the default sortOrder and sortBy are added.
  2. changing sortOrder and sortBy can give us two different results because of pagination(here I explained more Search router polish  #50921 (comment))

Also, fixing this on BE would be a problem, so I think the only solution we have is to have two hashes, one for computing recentSearches(which doesn't include sortOrder and sortBy) and the other one for everything else(this hash would include sort). @rayane-djouah proposed it in #51044 (comment).
WDYT @luacmartins @rayane-djouah

@luacmartins
Copy link
Contributor

Yea, I think the simplest solution would be to have two hashes. We could add recentSearchHash to the top level of the jsonQuery object and in the backend we use recentSearchHash instead of hash to key recent searches. Otherwise the alternative solutions would be:

  1. Remove sort params for hash. This would lead to us losing caching based on sorting and maybe lead to other unknown issues now.
  2. Having to store the full jsonQuery in the database (instead of the query input string) and doing a deep object comparison with each search.

I like just adding the recentSearchHash and using it. Let's just leave a comment explaining what that hash does.

@Kicu
Copy link
Contributor

Kicu commented Oct 23, 2024

Agreed that in the current situation storing two hashes seems the most reasonable 👍

Feels to me like:

  • one hash is more "data oriented" - because sorting produces different datasets, we need different keys for them
  • the second hash will be more "user oriented" - so that user doesn't see (what looks like) identical queries

@melvin-bot melvin-bot bot added Reviewing Has a PR in review Weekly KSv2 and removed Daily KSv2 labels Oct 23, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Engineering Reviewing Has a PR in review Weekly KSv2
Projects
Status: Polish
Development

No branches or pull requests

7 participants