From 5da458a16fa3a7fd930b7e9993c32a87983d1fc0 Mon Sep 17 00:00:00 2001 From: Harshil Sharma <18575143+harshilsharma63@users.noreply.github.com> Date: Wed, 29 Mar 2023 17:11:55 +0530 Subject: [PATCH] Allowed searcginbg users by substring in admin console (#22505) --- .../channels/store/searchtest/user_layer.go | 75 +++++++++++++++++++ server/channels/store/sqlstore/user_store.go | 2 +- .../src/selectors/entities/users.ts | 4 +- 3 files changed, 78 insertions(+), 3 deletions(-) diff --git a/server/channels/store/searchtest/user_layer.go b/server/channels/store/searchtest/user_layer.go index 66527fadde..bb2684b011 100644 --- a/server/channels/store/searchtest/user_layer.go +++ b/server/channels/store/searchtest/user_layer.go @@ -152,6 +152,11 @@ var searchUserStoreTests = []searchTest{ Fn: testSearchUsersInTeamUsernameWithUnderscore, Tags: []string{EngineAll}, }, + { + Name: "Should support search all users containing a substring in any name", + Fn: testSearchUserBySubstringInAnyName, + Tags: []string{EngineAll}, + }, } func TestSearchUserStore(t *testing.T, s store.Store, testEngine *SearchTestEngine) { @@ -865,6 +870,76 @@ func testSearchUsersByFullName(t *testing.T, th *SearchTestHelper) { }) } +func testSearchUserBySubstringInAnyName(t *testing.T, th *SearchTestHelper) { + t.Run("Should search users by substring in first name", func(t *testing.T) { + userAlternate, err := th.createUser("user-alternate", "user-alternate", "alternate helloooo first name", "alternate") + require.NoError(t, err) + defer th.deleteUser(userAlternate) + + // searching user without specifying team + options := createDefaultOptions(true, false, false) + users, err := th.Store.User().Search("", "hello", options) + require.NoError(t, err) + th.assertUsersMatchInAnyOrder(t, []*model.User{userAlternate}, users) + + // adding user to team to search by team + err = th.addUserToTeams(userAlternate, []string{th.Team.Id}) + require.NoError(t, err) + + err = th.addUserToChannels(userAlternate, []string{th.ChannelBasic.Id}) + require.NoError(t, err) + + options = createDefaultOptions(true, false, false) + users, err = th.Store.User().Search(th.Team.Id, "hello", options) + require.NoError(t, err) + th.assertUsersMatchInAnyOrder(t, []*model.User{userAlternate}, users) + }) + t.Run("Should search users by substring in last name name", func(t *testing.T) { + userAlternate, err := th.createUser("user-alternate", "user-alternate", "alternate", "alternate helloooo last name") + require.NoError(t, err) + defer th.deleteUser(userAlternate) + + options := createDefaultOptions(true, false, false) + users, err := th.Store.User().Search("", "hello", options) + require.NoError(t, err) + th.assertUsersMatchInAnyOrder(t, []*model.User{userAlternate}, users) + + // adding user to team to search by team + err = th.addUserToTeams(userAlternate, []string{th.Team.Id}) + require.NoError(t, err) + + err = th.addUserToChannels(userAlternate, []string{th.ChannelBasic.Id}) + require.NoError(t, err) + + options = createDefaultOptions(true, false, false) + users, err = th.Store.User().Search(th.Team.Id, "hello", options) + require.NoError(t, err) + th.assertUsersMatchInAnyOrder(t, []*model.User{userAlternate}, users) + }) + t.Run("Should search users by substring in nickname name", func(t *testing.T) { + userAlternate, err := th.createUser("user-alternate", "alternate helloooo nickname", "alternate hello first name", "alternate") + require.NoError(t, err) + defer th.deleteUser(userAlternate) + + options := createDefaultOptions(true, false, false) + users, err := th.Store.User().Search("", "hello", options) + require.NoError(t, err) + th.assertUsersMatchInAnyOrder(t, []*model.User{userAlternate}, users) + + // adding user to team to search by team + err = th.addUserToTeams(userAlternate, []string{th.Team.Id}) + require.NoError(t, err) + + err = th.addUserToChannels(userAlternate, []string{th.ChannelBasic.Id}) + require.NoError(t, err) + + options = createDefaultOptions(true, false, false) + users, err = th.Store.User().Search(th.Team.Id, "hello", options) + require.NoError(t, err) + th.assertUsersMatchInAnyOrder(t, []*model.User{userAlternate}, users) + }) +} + func createDefaultOptions(allowFullName, allowEmails, allowInactive bool) *model.UserSearchOptions { return &model.UserSearchOptions{ AllowFullNames: allowFullName, diff --git a/server/channels/store/sqlstore/user_store.go b/server/channels/store/sqlstore/user_store.go index c298148af8..293d78c9a4 100644 --- a/server/channels/store/sqlstore/user_store.go +++ b/server/channels/store/sqlstore/user_store.go @@ -1540,7 +1540,7 @@ func generateSearchQuery(query sq.SelectBuilder, terms []string, fields []string } else { searchFields = append(searchFields, fmt.Sprintf("%s LIKE ? escape '*' ", field)) } - termArgs = append(termArgs, fmt.Sprintf("%s%%", strings.TrimLeft(term, "@"))) + termArgs = append(termArgs, fmt.Sprintf("%%%s%%", strings.TrimLeft(term, "@"))) } query = query.Where(fmt.Sprintf("(%s)", strings.Join(searchFields, " OR ")), termArgs...) } diff --git a/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/users.ts b/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/users.ts index 1a79d4526d..131a60a281 100644 --- a/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/users.ts +++ b/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/users.ts @@ -442,7 +442,7 @@ export function makeSearchProfilesStartingWithTerm(): (state: GlobalState, term: (state: GlobalState, term: string, skipCurrent?: boolean) => skipCurrent || false, (stateGlobalState, term: string, skipCurrent?: boolean, filters?: Filters) => filters, (users, currentUserId, term, skipCurrent, filters) => { - const profiles = filterProfilesStartingWithTerm(Object.values(users), term); + const profiles = filterProfilesMatchingWithTerm(Object.values(users), term); return filterFromProfiles(currentUserId, profiles, skipCurrent, filters); }, ); @@ -509,7 +509,7 @@ export function searchProfilesInCurrentTeam(state: GlobalState, term: string, sk } export function searchProfilesInTeam(state: GlobalState, teamId: Team['id'], term: string, skipCurrent = false, filters?: Filters): UserProfile[] { - const profiles = filterProfilesStartingWithTerm(getProfilesInTeam(state, teamId, filters), term); + const profiles = filterProfilesMatchingWithTerm(getProfilesInTeam(state, teamId, filters), term); if (skipCurrent) { removeCurrentUserFromList(profiles, getCurrentUserId(state)); }