[MM-25268] Implement prefix/suffix searching on teams and channel page in system console (#14698)

* Implement prefix/suffix search on teams page

* Make Channel Page prefix/suffix search as well

* address PR comments

* add tests

* fix styling

* take postgres into account

* add more api tests

* update test

* add team store test

* write store test for team and channel

* fix lint

* update description

* revert go mod

Co-authored-by: mattermod <mattermod@users.noreply.github.com>
Этот коммит содержится в:
Hossein Ahmadian-Yazdi
2020-06-15 11:21:42 -04:00
коммит произвёл GitHub
родитель 92c98dac3e
Коммит 2c9c58ff50
6 изменённых файлов: 308 добавлений и 14 удалений

Просмотреть файл

@@ -2610,7 +2610,7 @@ func (s SqlChannelStore) buildLIKEClause(term string, searchColumns string) (lik
}
likeClause = fmt.Sprintf("(%s)", strings.Join(searchFields, " OR "))
likeTerm += "%"
likeTerm = wildcardSearchTerm(likeTerm)
return
}

Просмотреть файл

@@ -77,6 +77,10 @@ func teamMemberToSlice(member *model.TeamMember) []interface{} {
return resultSlice
}
func wildcardSearchTerm(term string) string {
return strings.ToLower("%" + term + "%")
}
type rolesInfo struct {
roles []string
explicitRoles []string
@@ -374,8 +378,14 @@ func (s SqlTeamStore) SearchAll(term string) ([]*model.Team, *model.AppError) {
var teams []*model.Team
term = sanitizeSearchTerm(term, "\\")
term = wildcardSearchTerm(term)
if _, err := s.GetReplica().Select(&teams, "SELECT * FROM Teams WHERE Name LIKE :Term OR DisplayName LIKE :Term", map[string]interface{}{"Term": term + "%"}); err != nil {
operatorKeyword := "ILIKE"
if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
operatorKeyword = "LIKE"
}
queryString := fmt.Sprintf("SELECT * FROM Teams WHERE Name %[1]s :Term OR DisplayName %[1]s :Term", operatorKeyword)
if _, err := s.GetReplica().Select(&teams, queryString, map[string]interface{}{"Term": term}); err != nil {
return nil, model.NewAppError("SqlTeamStore.SearchAll", "store.sql_team.search_all_team.app_error", nil, "term="+term+", "+err.Error(), http.StatusInternalServerError)
}
@@ -389,12 +399,18 @@ func (s SqlTeamStore) SearchAllPaged(term string, page int, perPage int) ([]*mod
offset := page * perPage
term = sanitizeSearchTerm(term, "\\")
if _, err := s.GetReplica().Select(&teams, "SELECT * FROM Teams WHERE Name LIKE :Term OR DisplayName LIKE :Term ORDER BY DisplayName, Name LIMIT :Limit OFFSET :Offset", map[string]interface{}{"Term": term + "%", "Limit": perPage, "Offset": offset}); err != nil {
term = wildcardSearchTerm(term)
operatorKeyword := "ILIKE"
if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
operatorKeyword = "LIKE"
}
queryString := fmt.Sprintf("SELECT * FROM Teams WHERE Name %[1]s :Term OR DisplayName %[1]s :Term ORDER BY DisplayName, Name LIMIT :Limit OFFSET :Offset", operatorKeyword)
if _, err := s.GetReplica().Select(&teams, queryString, map[string]interface{}{"Term": term, "Limit": perPage, "Offset": offset}); err != nil {
return nil, 0, model.NewAppError("SqlTeamStore.SearchAllPage", "store.sql_team.search_all_team.app_error", nil, "term="+term+", "+err.Error(), http.StatusInternalServerError)
}
totalCount, err := s.GetReplica().SelectInt("SELECT COUNT(*) FROM Teams WHERE Name LIKE :Term OR DisplayName LIKE :Term", map[string]interface{}{"Term": term + "%"})
queryString = fmt.Sprintf("SELECT COUNT(*) FROM Teams WHERE Name %[1]s :Term OR DisplayName %[1]s :Term", operatorKeyword)
totalCount, err := s.GetReplica().SelectInt(queryString, map[string]interface{}{"Term": term})
if err != nil {
return nil, 0, model.NewAppError("SqlTeamStore.SearchAllPage", "store.sql_team.search_all_team.app_error", nil, "term="+term+", "+err.Error(), http.StatusInternalServerError)
}
@@ -408,8 +424,13 @@ func (s SqlTeamStore) SearchOpen(term string) ([]*model.Team, *model.AppError) {
var teams []*model.Team
term = sanitizeSearchTerm(term, "\\")
if _, err := s.GetReplica().Select(&teams, "SELECT * FROM Teams WHERE Type = 'O' AND AllowOpenInvite = true AND (Name LIKE :Term OR DisplayName LIKE :Term)", map[string]interface{}{"Term": term + "%"}); err != nil {
term = wildcardSearchTerm(term)
operatorKeyword := "ILIKE"
if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
operatorKeyword = "LIKE"
}
queryString := fmt.Sprintf("SELECT * FROM Teams WHERE Type = 'O' AND AllowOpenInvite = true AND (Name %[1]s :Term OR DisplayName %[1]s :Term)", operatorKeyword)
if _, err := s.GetReplica().Select(&teams, queryString, map[string]interface{}{"Term": term}); err != nil {
return nil, model.NewAppError("SqlTeamStore.SearchOpen", "store.sql_team.search_open_team.app_error", nil, "term="+term+", "+err.Error(), http.StatusInternalServerError)
}
@@ -422,15 +443,19 @@ func (s SqlTeamStore) SearchPrivate(term string) ([]*model.Team, *model.AppError
var teams []*model.Team
term = sanitizeSearchTerm(term, "\\")
query :=
`SELECT *
term = wildcardSearchTerm(term)
operatorKeyword := "ILIKE"
if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
operatorKeyword = "LIKE"
}
query := fmt.Sprintf(`
SELECT *
FROM
Teams
WHERE
(Type != 'O' OR AllowOpenInvite = false) AND
(Name LIKE :Term OR DisplayName LIKE :Term)`
if _, err := s.GetReplica().Select(&teams, query, map[string]interface{}{"Term": term + "%"}); err != nil {
(Name %[1]s :Term OR DisplayName %[1]s :Term)`, operatorKeyword)
if _, err := s.GetReplica().Select(&teams, query, map[string]interface{}{"Term": term}); err != nil {
return nil, model.NewAppError("SqlTeamStore.SearchPrivate", "store.sql_team.search_private_team.app_error", nil, "term="+term+", "+err.Error(), http.StatusInternalServerError)
}
return teams, nil

Просмотреть файл

@@ -5240,6 +5240,14 @@ func testChannelStoreSearchAllChannels(t *testing.T, ss store.Store) {
nErr = ss.Channel().Delete(o13.Id, o13.DeleteAt)
require.Nil(t, nErr, "channel should have been deleted")
o14 := model.Channel{
TeamId: t2.Id,
DisplayName: "FOOBAR",
Name: "whatever",
Type: model.CHANNEL_OPEN,
}
_, nErr = ss.Channel().Save(&o14, -1)
require.Nil(t, nErr)
testCases := []struct {
Description string
Term string
@@ -5247,9 +5255,14 @@ func testChannelStoreSearchAllChannels(t *testing.T, ss store.Store) {
ExpectedResults *model.ChannelList
TotalCount int
}{
{"Search FooBar by display name", "oob", store.ChannelSearchOpts{IncludeDeleted: false}, &model.ChannelList{&o14}, 1},
{"Search FooBar by display name2", "foo", store.ChannelSearchOpts{IncludeDeleted: false}, &model.ChannelList{&o14}, 1},
{"Search FooBar by display name3", "bar", store.ChannelSearchOpts{IncludeDeleted: false}, &model.ChannelList{&o14}, 1},
{"Search FooBar by name", "what", store.ChannelSearchOpts{IncludeDeleted: false}, &model.ChannelList{&o14}, 1},
{"Search FooBar by name2", "ever", store.ChannelSearchOpts{IncludeDeleted: false}, &model.ChannelList{&o14}, 1},
{"ChannelA", "ChannelA", store.ChannelSearchOpts{IncludeDeleted: false}, &model.ChannelList{&o1, &o2, &o3}, 0},
{"ChannelA, include deleted", "ChannelA", store.ChannelSearchOpts{IncludeDeleted: true}, &model.ChannelList{&o1, &o2, &o3, &o13}, 0},
{"empty string", "", store.ChannelSearchOpts{IncludeDeleted: false}, &model.ChannelList{&o1, &o2, &o3, &o4, &o5, &o12, &o11, &o8, &o7, &o6, &o10, &o9}, 0},
{"empty string", "", store.ChannelSearchOpts{IncludeDeleted: false}, &model.ChannelList{&o1, &o2, &o3, &o4, &o5, &o12, &o14, &o11, &o8, &o7, &o6, &o10, &o9}, 0},
{"no matches", "blargh", store.ChannelSearchOpts{IncludeDeleted: false}, &model.ChannelList{}, 0},
{"prefix", "off-", store.ChannelSearchOpts{IncludeDeleted: false}, &model.ChannelList{&o8, &o7, &o6}, 0},
{"full match with dash", "off-topic", store.ChannelSearchOpts{IncludeDeleted: false}, &model.ChannelList{&o6}, 0},

Просмотреть файл

@@ -231,12 +231,52 @@ func testTeamStoreSearchAll(t *testing.T, ss store.Store) {
_, err = ss.Team().Save(&p)
require.Nil(t, err)
q := model.Team{}
q.DisplayName = "CHOCOLATE"
q.Name = "ilovecake"
q.Email = MakeEmail()
q.Type = model.TEAM_OPEN
q.AllowOpenInvite = false
_, err = ss.Team().Save(&q)
require.Nil(t, err)
testCases := []struct {
Name string
Term string
ExpectedLenth int
ExpectedFirstId string
}{
{
"Search chocolate by display name",
"ocola",
1,
q.Id,
},
{
"Search chocolate by display name",
"choc",
1,
q.Id,
},
{
"Search chocolate by display name",
"late",
1,
q.Id,
},
{
"Search chocolate by name",
"ilov",
1,
q.Id,
},
{
"Search chocolate by name",
"ecake",
1,
q.Id,
},
{
"Search for open team name",
o.Name,
@@ -302,12 +342,52 @@ func testTeamStoreSearchOpen(t *testing.T, ss store.Store) {
_, err = ss.Team().Save(&p)
require.Nil(t, err)
q := model.Team{}
q.DisplayName = "PINEAPPLEPIE"
q.Name = "ihadsomepineapplepiewithstrawberry"
q.Email = MakeEmail()
q.Type = model.TEAM_OPEN
q.AllowOpenInvite = true
_, err = ss.Team().Save(&q)
require.Nil(t, err)
testCases := []struct {
Name string
Term string
ExpectedLength int
ExpectedFirstId string
}{
{
"Search PINEAPPLEPIE by display name",
"neapplep",
1,
q.Id,
},
{
"Search PINEAPPLEPIE by display name",
"pine",
1,
q.Id,
},
{
"Search PINEAPPLEPIE by display name",
"epie",
1,
q.Id,
},
{
"Search PINEAPPLEPIE by name",
"ihadsome",
1,
q.Id,
},
{
"Search PINEAPPLEPIE by name",
"pineapplepiewithstrawberry",
1,
q.Id,
},
{
"Search for open team name",
o.Name,
@@ -368,12 +448,52 @@ func testTeamStoreSearchPrivate(t *testing.T, ss store.Store) {
_, err = ss.Team().Save(&p)
require.Nil(t, err)
q := model.Team{}
q.DisplayName = "FOOBAR"
q.Name = "whatever"
q.Email = MakeEmail()
q.Type = model.TEAM_OPEN
q.AllowOpenInvite = false
_, err = ss.Team().Save(&q)
require.Nil(t, err)
testCases := []struct {
Name string
Term string
ExpectedLength int
ExpectedFirstId string
}{
{
"Search FooBar by display name from text in the middle of display name",
"ooba",
1,
q.Id,
},
{
"Search FooBar by display name from text at the beginning of display name",
"foo",
1,
q.Id,
},
{
"Search FooBar by display name from text at the end of display name",
"bar",
1,
q.Id,
},
{
"Search FooBar by name from text at the beginning name",
"what",
1,
q.Id,
},
{
"Search FooBar by name from text at the end of name",
"ever",
1,
q.Id,
},
{
"Search for private team name",
p.Name,