-
Notifications
You must be signed in to change notification settings - Fork 3.8k
[Desktop Navigation] Add a dedicated Workspace filter to the Reports tab #59450
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
Merged
mountiny
merged 10 commits into
Expensify:main
from
software-mansion-labs:improve-desktop-nav/1-add-workspace-filter
Apr 8, 2025
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
5802fcf
Add SearchFiltersWorkspacePage
WojtekBoman 08cdc63
Add Nullable to types utils
WojtekBoman d35eebf
Handle unselecting workspace filter
WojtekBoman 767f164
Hide Workspace filter behind leftHandBar beta
WojtekBoman 5d7450c
Add SearchQueryUtils methods that handle displaying workspace filter
WojtekBoman a27dc82
Cleanup workspace filter code
WojtekBoman dc4e6b8
Fix variable names in typeFiltersKeysWithOptionalPolicy
WojtekBoman 6bc94aa
Sanitize workspace filter value
WojtekBoman d5ab426
Adjust comments of copied methods
WojtekBoman 09f9c68
Rewrite isCannedQuery logic in SearchPageNarrow.tsx
WojtekBoman File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -251,7 +251,7 @@ function getQueryHashes(query: SearchQueryJSON): {primaryHash: number; recentSea | |||
| orderedQuery += ` ${CONST.SEARCH.SYNTAX_ROOT_KEYS.SORT_BY}:${query.sortBy}`; | ||||
| orderedQuery += ` ${CONST.SEARCH.SYNTAX_ROOT_KEYS.SORT_ORDER}:${query.sortOrder}`; | ||||
| if (query.policyID) { | ||||
| orderedQuery += ` ${CONST.SEARCH.SYNTAX_ROOT_KEYS.POLICY_ID}:${query.policyID} `; | ||||
| orderedQuery += ` ${CONST.SEARCH.SYNTAX_FILTER_KEYS.POLICY_ID}:${query.policyID} `; | ||||
| } | ||||
| const primaryHash = hashText(orderedQuery, 2 ** 32); | ||||
|
|
||||
|
|
@@ -305,6 +305,10 @@ function buildSearchQueryString(queryJSON?: SearchQueryJSON) { | |||
| } | ||||
| } | ||||
|
|
||||
| if (queryJSON?.policyID) { | ||||
| queryParts.push(`${CONST.SEARCH.SYNTAX_FILTER_KEYS.POLICY_ID}:${queryJSON.policyID}`); | ||||
| } | ||||
|
|
||||
| if (!queryJSON) { | ||||
| return queryParts.join(' '); | ||||
| } | ||||
|
|
@@ -345,7 +349,7 @@ function buildQueryStringFromFilterFormValues(filterValues: Partial<SearchAdvanc | |||
|
|
||||
| if (policyID) { | ||||
| const sanitizedPolicyID = sanitizeSearchValue(policyID); | ||||
| filtersString.push(`${CONST.SEARCH.SYNTAX_ROOT_KEYS.POLICY_ID}:${sanitizedPolicyID}`); | ||||
| filtersString.push(`${CONST.SEARCH.SYNTAX_FILTER_KEYS.POLICY_ID}:${sanitizedPolicyID}`); | ||||
| } | ||||
|
|
||||
| const mappedFilters = Object.entries(otherFilters) | ||||
|
|
@@ -588,6 +592,71 @@ function getFilterDisplayValue( | |||
| return filterValue; | ||||
| } | ||||
|
|
||||
| /** | ||||
| * A copy of `buildUserReadableQueryString` handling the policy ID, used if you have access to the leftHandBar beta. | ||||
| * When this beta is no longer needed, this method will be renamed to `buildUserReadableQueryString` and will replace the old method. | ||||
| * Formats a given `SearchQueryJSON` object into the human-readable string version of query. | ||||
| * This format of query is the one which we want to display to users. | ||||
| * We try to replace every numeric id value with a display version of this value, | ||||
| * So: user IDs get turned into emails, report ids into report names etc. | ||||
| */ | ||||
| function buildUserReadableQueryStringWithPolicyID( | ||||
| queryJSON: SearchQueryJSON, | ||||
| PersonalDetails: OnyxTypes.PersonalDetailsList | undefined, | ||||
| reports: OnyxCollection<OnyxTypes.Report>, | ||||
| taxRates: Record<string, string[]>, | ||||
| cardList: OnyxTypes.CardList, | ||||
| cardFeedNamesWithType: CardFeedNamesWithType, | ||||
| policies: OnyxCollection<OnyxTypes.Policy>, | ||||
| ) { | ||||
| const {type, status, groupBy, policyID} = queryJSON; | ||||
| const filters = queryJSON.flatFilters; | ||||
|
|
||||
| let title = `type:${type} status:${Array.isArray(status) ? status.join(',') : status}`; | ||||
|
|
||||
| if (groupBy) { | ||||
| title += ` group-by:${groupBy}`; | ||||
| } | ||||
|
|
||||
| if (policyID) { | ||||
| const workspace = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]?.name ?? policyID; | ||||
| title += ` workspace:${sanitizeSearchValue(workspace)}`; | ||||
| } | ||||
|
|
||||
| for (const filterObject of filters) { | ||||
| const key = filterObject.key; | ||||
| const queryFilter = filterObject.filters; | ||||
|
|
||||
| let displayQueryFilters: QueryFilter[] = []; | ||||
| if (key === CONST.SEARCH.SYNTAX_FILTER_KEYS.TAX_RATE) { | ||||
| const taxRateIDs = queryFilter.map((filter) => filter.value.toString()); | ||||
| const taxRateNames = taxRateIDs | ||||
| .map((id) => { | ||||
| const taxRate = Object.entries(taxRates) | ||||
| .filter(([, IDs]) => IDs.includes(id)) | ||||
| .map(([name]) => name); | ||||
| return taxRate.length > 0 ? taxRate : id; | ||||
| }) | ||||
| .flat(); | ||||
|
|
||||
| const uniqueTaxRateNames = [...new Set(taxRateNames)]; | ||||
|
|
||||
| displayQueryFilters = uniqueTaxRateNames.map((taxRate) => ({ | ||||
| operator: queryFilter.at(0)?.operator ?? CONST.SEARCH.SYNTAX_OPERATORS.AND, | ||||
| value: taxRate, | ||||
| })); | ||||
| } else { | ||||
| displayQueryFilters = queryFilter.map((filter) => ({ | ||||
| operator: filter.operator, | ||||
| value: getFilterDisplayValue(key, filter.value.toString(), PersonalDetails, reports, cardList, cardFeedNamesWithType), | ||||
| })); | ||||
| } | ||||
| title += buildFilterValuesString(getUserFriendlyKey(key), displayQueryFilters); | ||||
| } | ||||
|
|
||||
| return title; | ||||
| } | ||||
|
|
||||
| /** | ||||
| * Formats a given `SearchQueryJSON` object into the human-readable string version of query. | ||||
| * This format of query is the one which we want to display to users. | ||||
|
|
@@ -680,6 +749,20 @@ function buildCannedSearchQuery({ | |||
| return buildSearchQueryString(normalizedQueryJSON); | ||||
| } | ||||
|
|
||||
| /** | ||||
| * A copy of `isCannedSearchQuery` handling the policy ID, used if you have access to the leftHandBar beta. | ||||
| * When this beta is no longer needed, this method will be renamed to `isCannedSearchQuery` and will replace the old method. | ||||
| * | ||||
| * Returns whether a given search query is a Canned query. | ||||
| * | ||||
| * Canned queries are simple predefined queries, that are defined only using type and status and no additional filters. | ||||
| * In addition, they can contain an optional policyID. | ||||
| * For example: "type:trip status:all" is a canned query. | ||||
| */ | ||||
| function isCannedSearchQueryWithPolicyIDCheck(queryJSON: SearchQueryJSON) { | ||||
| return !queryJSON.filters && !queryJSON.policyID; | ||||
| } | ||||
|
|
||||
| /** | ||||
| * Returns whether a given search query is a Canned query. | ||||
| * | ||||
|
|
@@ -691,6 +774,15 @@ function isCannedSearchQuery(queryJSON: SearchQueryJSON) { | |||
| return !queryJSON.filters; | ||||
| } | ||||
|
|
||||
| /** | ||||
| * A copy of `isDefaultExpensesQuery` handling the policy ID, used if you have access to the leftHandBar beta. | ||||
| * When this beta is no longer needed, this method will be renamed to `isDefaultExpensesQuery` and will replace the old method. | ||||
| * | ||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It will be done in this PR: #59943 |
||||
| */ | ||||
| function isDefaultExpensesQueryWithPolicyIDCheck(queryJSON: SearchQueryJSON) { | ||||
| return queryJSON.type === CONST.SEARCH.DATA_TYPES.EXPENSE && queryJSON.status === CONST.SEARCH.STATUS.EXPENSE.ALL && !queryJSON.filters && !queryJSON.groupBy && !queryJSON.policyID; | ||||
| } | ||||
|
|
||||
| function isDefaultExpensesQuery(queryJSON: SearchQueryJSON) { | ||||
| return queryJSON.type === CONST.SEARCH.DATA_TYPES.EXPENSE && queryJSON.status === CONST.SEARCH.STATUS.EXPENSE.ALL && !queryJSON.filters && !queryJSON.groupBy; | ||||
| } | ||||
|
|
@@ -765,14 +857,17 @@ export { | |||
| buildSearchQueryJSON, | ||||
| buildSearchQueryString, | ||||
| buildUserReadableQueryString, | ||||
| buildUserReadableQueryStringWithPolicyID, | ||||
| getFilterDisplayValue, | ||||
| buildQueryStringFromFilterFormValues, | ||||
| buildFilterFormValuesFromQuery, | ||||
| getPolicyIDFromSearchQuery, | ||||
| buildCannedSearchQuery, | ||||
| isCannedSearchQuery, | ||||
| isCannedSearchQueryWithPolicyIDCheck, | ||||
| sanitizeSearchValue, | ||||
| getQueryWithUpdatedValues, | ||||
| getUserFriendlyKey, | ||||
| isDefaultExpensesQuery, | ||||
| isDefaultExpensesQueryWithPolicyIDCheck, | ||||
| }; | ||||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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 only use policies to get the name of specific policyID, so I think we should pass the policyName: 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.
Hmm, I implemented it similarly to other filters, we pass the entire collection and extract data for specific ids/ids. Also, it will be useful when we implement a multiselect workspace filter