-
Notifications
You must be signed in to change notification settings - Fork 2.9k
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] [App] Use new query syntax #45617
[Search v2] [App] Use new query syntax #45617
Conversation
4925690
to
cb9d619
Compare
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 know this is a draft but I wanted to leave some early comments
cb9d619
to
fe0ec54
Compare
@luacmartins Ready for the review! I will add videos and tests on Monday |
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.
after initial review seems pretty nice, will take another look later
src/ROUTES.ts
Outdated
}, | ||
route: 'search', | ||
getRoute: ({query, queryKind = CONST.SEARCH.QUERY_KIND.CANNED_QUERY, policyIDs}: {query: SearchQueryString; queryKind?: QueryKind; policyIDs?: string}) => | ||
`search?${queryKind === CONST.SEARCH.QUERY_KIND.CANNED_QUERY ? 'q' : 'cq'}=${query}${policyIDs ? `&policyIDs=${policyIDs}` : ''}` as const, |
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 think we need to include the policyID
in the query
as well so that the AST includes it in the filters. Any reason to treat it differently?
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 think this is a transitional step.
BTW will current grammar handle policyID
? I don't see this keyword in the .peggy
file
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.
Ah makes sense that we do it like this for now. We should eventually move it to the AST and include it in the peggy grammar 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.
Do we need a separate issue to migrate the policyID
param to the AST?
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.
Do you think it should be in the root of QueryJSON
? So something like
type JSONQuery = {
input: string;
hash: number;
type: string;
status: string;
sortBy: string;
sortOrder: string;
offset: number;
filters: ASTNode;
policyID: string;
};
Also currently it is called policyIDs, as the assumption was that we wanted to be able to use multiple policyIDs in the future. Is this still the case?
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.
We'll add it to filters as policyID
. So the AST will look like this:
{filters: {
"operator": "eq",
"left": "policyID",
"right": "<policyID>"
I think we should use policyID
singular from now on since we have a bunch of other filters, e.g. category
, tag
, etc that accepts multiple values but are all singular 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.
I hope I understand this correctly. I will add policyID
to grammar and keep it inside AST, while removing it from URL
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.
read my comment here: #45617 (comment)
function normalizeQuery(query: string) { | ||
const normalizedQueryJSON = buildSearchQueryJSON(query); | ||
return buildSearchQueryString(normalizedQueryJSON); | ||
} |
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 do we need this function? I think that it could inject params into the query string that were not defined by the user and that might mess up with how we display the filter UI, no?
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 something we could discuss more about. We have a couple of questions here:
- Do we want to put defaults for sort etc in the URL? (I think yes, they were here in the previous version).
- Do we want to
organize
the query for the user? So is the:
type:expenses sortBy:date
same query assortBy:date type:expenses
I would say yes. In that case we should sort the keywords in the query to make it look the same.
And to address your concerns. It will inject defaults but nothing more. It shouldn't break UI filters.
Another argument for normalizing but it will be relevant in the future. If we ever want to change the defaults it could break the saved queries if we don't normalize them.
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.
Do we want to put defaults for sort etc in the URL? (I think yes, they were here in the previous version).
Thinking a bit ahead, we'd only want to do that if the user explicitly types the defaults in their queries, otherwise it'd be weird for the user to type amount>200
and then see the search bar update to type:expense status:all amount>200 sortBy:date sortOrder:desc offset:0
. I don't think we should modify the user input in that way and the URL is kinda the only thing tying everything up, e.g. the user input, the UI filters and the syntax itself. I think we'd only want to normalize it/build the AST when hashing and sending the API request
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 NAB for now, but something that we should align on before we get too deep in it
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, we may need to brainstorm this topic a little. I feel like not normalizing the query may be a pain in the future. On the other hand, it's not a perfect solution either.
Let's look at this example:
- User types simple query like
type:expenses
- Press the date column name to change the sorting order.
- Now the query looks like
type:expenses sortOrder:asc sortBy:date
- The user presses the column again and in theory we have same result as in the step 1 but the query looks differently
type:expenses sortOrder:desc sortBy:date
Maybe we could think about hiding the defaults in places we want to display the query to the user instead of removing them from the query and 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.
I agree with Adam and I feel the code will make much more sense if query
is a single source of truth and thus always contains all the values that are actually used when sending query to api.
I don't think we should worry how the URL looks for the user - it looks ugly anyways since it's url-encoded 😅
That being said this PR is blocking some other work, so I'm a fan of leaving this conversation for later and moving forward with this.
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 think the concern here is that we were gonna use the decoded URL to populate the search bar (once that's available) with the value the user input, but I see now that any interaction with the UI will also cause this issue. From the example above:
- User types simple query like
type:expenses
- Press the date column name to change the sorting order.
- Now the query looks like
type:expenses sortOrder:asc sortBy:date
- We'd display
type:expenses sortOrder:asc sortBy:date
in the Search bar, which is different than what the user typed - The user presses the column again and in theory we have same result as in the step 1 but the query looks differently
type:expenses sortOrder:desc sortBy:date
, which is also not what the user input was.
I think we might need another solution for this problem, so in all cases we only show type:expenses
to the user since that's what they typed into the search bar. That's a problem for v2.3, but we should keep it in mind.
src/types/onyx/SearchResults.ts
Outdated
// eslint-disable-next-line jsdoc/require-jsdoc | ||
drafts: boolean; | ||
// eslint-disable-next-line jsdoc/require-jsdoc | ||
approved: boolean; |
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.
We actually return all statuses to the client, e.g. all
, drafts
, outstanding
, approved
, paid
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.
So should I add all the fields? I am not sure if I understand this part of the code
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.
Yea, I think we should add all of them for now since they are always returned by the server
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.
Do you mean these statuses? I don't see any outstanding
in the costs.
STATUS: {
ALL: 'all',
SHARED: 'shared',
DRAFTS: 'drafts',
FINISHED: 'finished',
},
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.
Not sure where to look for these values 😄
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.
They are here. We should still support Shared
, Finished
for now, but we'll deprecate that one soon and add the other statuses mentioned in the doc, e.g. outstanding
, approved
, paid
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 do we have to add this in this PR? I don't understand why this suddenly appeared here, when I don't see this being used anywhere.
Can I just remove it if it's unused and we can add it in the PR that this is actually being used?
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 seems like loading more results is broken. We don't pass the correct offset
param to the API call even thought it's updated in the URL
Hi @luacmartins. @Kicu and I concluded that we should remove the Hash is generated from the query string. Offset is part of the query string. This means changing the offset will change the hash. This breaks caching and loading new elements. Deep linking with URL that contains offset also won't load elements properly. We can't simply jump to offset 150. We need to go through 0, 50, 100, 150 to load all elements. Instead, we could just keep it in the state in the Search component. I would propose to do it as a follow up up though. This PR is necessary to move forward. It would be great to merge it soon. BTW I will be ooo Jul 25-29 and I don't want to block others. |
// TODO_SEARCH: use this function after backend changes. | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
function searchV2(queryString: SearchQueryString) { | ||
const queryJSON = buildSearchQueryJSON(queryString); |
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 seems like we're already passing the AST here, so no need to try to build it again
const queryJSON = buildSearchQueryJSON(queryString); |
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.
Not sure if I understand. It looks like we pass queryString here not queryJSON.
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.
Hmm in my tests I had to remove this to make it work with the backend.
src/libs/actions/Search.ts
Outdated
|
||
// TODO_SEARCH: uncomment this line after backend changes | ||
// @ts-expect-error waiting for backend changes | ||
API.read(READ_COMMANDS.SEARCH, queryJSON, {optimisticData, finallyData}); |
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.
API.read(READ_COMMANDS.SEARCH, queryJSON, {optimisticData, finallyData}); | |
API.read(READ_COMMANDS.SEARCH, {hash, jsonQuery: JSON.stringify(jsonQuery)}, {optimisticData, finallyData}); |
@@ -53,6 +55,22 @@ function search({hash, query, policyIDs, offset, sortBy, sortOrder}: SearchParam | |||
API.read(READ_COMMANDS.SEARCH, {hash, query, offset, policyIDs, sortBy, sortOrder}, {optimisticData, finallyData}); | |||
} | |||
|
|||
// TODO_SEARCH: use this function after backend changes. | |||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | |||
function searchV2(queryString: SearchQueryString) { |
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.
function searchV2(queryString: SearchQueryString) { | |
function searchV2(jsonQuery: SearchQueryJSON) { |
@Kicu - Bug 2 is not reproducible on main. Maybe it's related to our changes concerning |
Ah yes might be, thanks I will test it. Meanwhile for bug 1 I think the problem might be that
Good catch. But in general I now have na "existential" 😅 problem with this PR. I expected that I will help fix several comments and we can finally close this. If we'd have to now remove it from params and move it to inside AST then that's editing 15 files and possibly breaking some functionality. IMO that will delay this PR for at least 2 days.
To sum up here are my questions about how to move forward @luacmartins |
@rayane-djouah my latest commit fixes Bug 2 via optional params. |
I think we should include the policyID in the hash so we don't break the existing functionality |
I tried to fix Bug 1 with my newest commit and upon quick check it should work, however i need to drop now so please double check if I didn't break anything else. Temporarily we can add policyID to has generation which should make our hashes stable and reacting to changing policyID. In future when we move |
Thank you! @Kicu |
@Kicu - Is there any reason we're not using the |
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.
LGTM and tests well
src/pages/Search/SearchPage.tsx
Outdated
|
||
const query = rawQuery as SearchQuery; | ||
const isValidQuery = Object.values(CONST.SEARCH.TAB).includes(query); | ||
const queryJSON = useMemo(() => buildSearchQueryJSON(route.params.q, policyIDs), [route.params]); |
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.
const queryJSON = useMemo(() => buildSearchQueryJSON(route.params.q, policyIDs), [route.params]); | |
const queryJSON = useMemo(() => buildSearchQueryJSON(route.params.q, policyIDs), [route.params, policyIDs]); |
}; | ||
|
||
function buildJSONQuery(query: string) { | ||
function buildSearchQueryJSON(query: SearchQueryString, policyID?: 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.
function buildSearchQueryJSON(query: SearchQueryString, policyID?: string) { | |
function buildSearchQueryJSON(query: SearchQueryString, policyIDs?: string) { |
// Temporary solution until we move policyID filter into the AST - then remove this line and keep only query | ||
const policyIDPart = policyID ?? ''; |
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.
// Temporary solution until we move policyID filter into the AST - then remove this line and keep only query | |
const policyIDPart = policyID ?? ''; | |
// Temporary solution until we move policyIDs filter into the AST - then remove this line and keep only query | |
const policyIDsPart = policyIDs ?? ''; |
|
||
// Temporary solution until we move policyID filter into the AST - then remove this line and keep only query | ||
const policyIDPart = policyID ?? ''; | ||
result.hash = getQueryHashFromString(query + policyIDPart); |
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.
result.hash = getQueryHashFromString(query + policyIDPart); | |
result.hash = getQueryHashFromString(query + policyIDsPart); |
We still need to address these comments: #45617 (comment) #45617 (comment) |
Bug 1 and 2 are no longer reproducible, great work! |
@luacmartins do you mind sharing here that we decided to straighten out |
@rayane-djouah #45617 (comment) is NAB, we'll do that in a follow up since it can introduce several regressions and we want this PR merged soon. |
Okay, I will retest on all platforms & upload videos shortly |
#45617 (comment) this one doesn't seem to be a blocker either. Offset is working fine for now. We'll need to make changes to the parser in a follow up, so this will be addressed then. |
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.
LGTM and test well! Thanks all!
@luacmartins all yours!
✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release. |
🚀 Deployed to staging by https://github.com/luacmartins in version: 9.0.13-0 🚀
|
🚀 Deployed to production by https://github.com/luacmartins in version: 9.0.13-4 🚀
|
Details
This PR introduces new query syntax. Now you can see that in the URL we have
q
orcq
params when on the search page.Fixed Issues
$ #45027
PROPOSAL:
Tests
Offline tests
QA Steps
Changing status option
Changing workspace
Wide layout only (web/desktop)
PR Author Checklist
### Fixed Issues
section aboveTests
sectionOffline steps
sectionQA steps
sectiontoggleReport
and notonIconClick
)myBool && <MyComponent />
.src/languages/*
files and using the translation methodSTYLE.md
) were followedAvatar
, I verified the components usingAvatar
are working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG)
)Avatar
is modified, I verified thatAvatar
is working as expected in all cases)Design
label and/or tagged@Expensify/design
so the design team can review the changes.ScrollView
component to make it scrollable when more elements are added to the page.main
branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTest
steps.Screenshots/Videos
Android: Native
android.mp4
Android: mWeb Chrome
androidWeb.mp4
iOS: Native
ios.mp4
iOS: mWeb Safari
iosWeb.mp4
MacOS: Chrome / Safari
web.mp4
MacOS: Desktop
desktop.mp4