App: [HOLD for payment 2023-05-16] [$1000] Manage member search does not reconsider search terms after space like it does throughout the website and also on invite members

If you haven’t already, check out our contributing guidelines for onboarding and email contributors@expensify.com to request to join our Slack channel!


Action Performed:

  1. Open the app
  2. Open settings
  3. Open Workspaces
  4. Open any workspace
  5. Open Manage members
  6. Search using terms which provides no result found message, enter space and again search for users using terms that should provide users in result eg jsdlkjfskljg then jsdlkjfskljg lauren

Expected Result:

App should reset search after space i.e. app should start new search after space and use the new terms after space to get the results like it does on invite members and on searches throughout the website

Actual Result:

App does not reset search after space on manage members page

Workaround:

unknown

Platforms:

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

  • Android / native
  • Android / Chrome
  • iOS / native
  • iOS / Safari
  • MacOS / Chrome / Safari
  • MacOS / Desktop

Version Number: v1.2.93-4 Reproducible in staging?: y Reproducible in production?: y If this was caught during regression testing, add the test name, ID and link from TestRail: Email or phone of affected tester (no customers): Logs: https://stackoverflow.com/c/expensify/questions/4856 Notes/Photos/Videos: Any additional supporting documentation

https://user-images.githubusercontent.com/43996225/229304010-f5fad08a-6628-4984-bfa5-439719f52d6b.mp4

https://user-images.githubusercontent.com/43996225/229304017-82da46bf-63f2-4f69-bcea-7eaa75d2e17c.mp4

Expensify/Expensify Issue URL: Issue reported by: @dhanashree-sawant Slack conversation: https://expensify.slack.com/archives/C049HHMV9SM/p1680334833058189

View all open jobs on GitHub

Upwork Automation - Do Not Edit
  • Upwork Job URL: https://www.upwork.com/jobs/~0160c5e8329faf9c07
  • Upwork Job ID: 1646544650618916864
  • Last Price Increase: 2023-04-13

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Comments: 61 (44 by maintainers)

Most upvoted comments

Looks good @mollfpr. Closing now as we done! Great job everyone

Everyone has been paid in Upwork ✔️

@mollfpr let me know when you have the update ready so we can pass onto QA and close this

Sharing the timeline of this issue to help with the eventual analysis, calculated with this tool.

🐛 Issue created at: Sat, 01 Apr 2023 16:51:09 GMT 🧯 Help Wanted at: Thu, 13 Apr 2023 16:03:48 GMT 🕵🏻 Contributor assigned at: Tue, 02 May 2023 07:32:28 GMT 🛸 PR created at: Tue, 02 May 2023 08:00:54 GMT 🎯 PR merged at: Wed, 03 May 2023 15:20:51 GMT ⌛ Business Days Elapsed between assignment and PR merge: 1

Upwork is being weird - I’m creating a new GH issue here https://github.com/Expensify/App/issues/18334 to manage payment of this,

@mollfpr it took some time to look over the proposals, but I think the one from @Prince-Mendiratta looks good. Let’s move forward. @laurenreidexpensify do you need to do the assigning?

@dukenv0307 I see that I miss searchValue === '' from your early proposal. That’s why it’s not showing the data the first time.

@PrashantMangukiya Ah yes, sorry I just tested again your solution and it seems to show all the data filtered. I got distracted by another logic.

@mollfpr -

@joelbettner @laurenreidexpensify I need clarity on the expected result. E.g I search first second. Which of the list should be shown? Showing the result from first and second word searched Only show the result from the second word search

we would follow whatever logic we have in the code here for invite members -

like it does on invite members and on searches throughout the website

@mollfpr In the current behaviour, whenever the user enters a text, let’s say “User One”, the whole string is taken as one term and this single term is iterated through all possible fields that can have this term:

  • Display Name
  • Email
  • Phone Number
  • First Name
  • Last Name

Each of these fields is checked for the existence of the term “User One”, one by one by calling the isKeywordMatch each time.

The isKeywordMatch simply converts each field value in lowercase, removes leading/trailing spaces and matches the search keyword, which is also lowercase and trimmed.

If we were to use isSearchStringMatch, then instead of searching for “User One”, we will search all the above fields for both, “User” and “One”, the existence of either would return true. This is the behaviour on the Search Page, which also deals with searching for users.

However, we can also create another function that’ll cater to the needs of the WorkspaceMembersPage better. This would be helpful in simplifying the logic here: https://github.com/Expensify/App/blob/e8bc6a86f13fd2f5561de5e6251abffa8f890384/src/pages/workspace/WorkspaceMembersPage.js#L333-L339

We can create a function getMemberOptions, where we will delegate the logic of filtering through all the fields and checking each word. The above mentioned lines can be simplified to simply be:

data = getMemberOptions(data, this.state.searchValue);

The function will look something like this:

    /**
     * This function will iterate through the details of each policy member to check if the
     * search string matches with any detail and return that filter.
     * @param {Array} policyMembersPersonalDetails - This is the list of policy members
     * @param {*} searchValue - This is the string that the user has entered
     * @returns {Array} - The list of policy members that have anything similar to the searchValue
     */
    getMemberOptions(policyMembersPersonalDetails, searchValue) {
        // If no search value, we return all members.
        if (_.isEmpty(searchValue)) {
            return policyMembersPersonalDetails;
        }

        // We will filter through each policy member details to determine if they should be shown
        return _.filter(policyMembersPersonalDetails, (member) => {
            // Get all the search separated terms entered by the user
            const searchWords = _.compact(OptionsListUtils.uniqFast([
                searchValue,
                ..._.map(searchValue.replace(/,/g, ' ').split(' '), word => word.trim()),
            ]));

            let memberDetails = '';
            if (member.login) {
                memberDetails += member.login.toLowerCase();
            }
            if (member.firstName) {
                memberDetails += member.firstName.toLowerCase();
            }
            if (member.lastName) {
                memberDetails += member.lastName.toLowerCase();
            }
            if (member.displayName) {
                memberDetails += member.displayName.toLowerCase();
            }
            if (member.phoneNumber) {
                memberDetails += member.phoneNumber.toLowerCase();
            }
            return _.some(searchWords, (word) => {
                const matchRegex = new RegExp(Str.escapeForRegExp(word), 'i');
                return matchRegex.test(memberDetails);
            });
        });
    }

Logic:

  1. Since for a given word, let’s say ‘Luth’, we will search for all the personal details if this string or anything similar to ‘luth’ exists.
  2. Since that is the case, we might as well combine all these details in a single string that will have all the details like name, phone, display name etc.
  3. When we match, we can simply check for the existence of this string easily.
  4. For the string ‘Luth’, it will return all matches to ‘luth’.
  5. For the string ‘Luth Prince’, it will return all the members that have either ‘luth’ or ‘prince’ in either of their details.

This will get us the desired results, in an easy to understand way.

Results

https://user-images.githubusercontent.com/54077356/232119498-66676e1e-361e-4445-8773-919868ea9557.mp4