[MM-26969] Add team filters to search teams (#15065)

* Add team filters to search teams

Remove unneeded logs

Add team filters to search teams

* Use bool pointers for filters

Re-add include group constrained

Fix lint

Return the union of filters
Этот коммит содержится в:
Farhan Munshi
2020-07-27 15:11:39 -04:00
коммит произвёл GitHub
родитель fbde669dda
Коммит c511042c0f
9 изменённых файлов: 216 добавлений и 84 удалений

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

@@ -1000,11 +1000,6 @@ func searchTeams(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
if len(props.Term) == 0 {
c.SetInvalidParam("term")
return
}
var teams []*model.Team
var totalCount int64
var err *model.AppError

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

@@ -763,9 +763,9 @@ func (a *App) GetAllPublicTeamsPageWithCount(offset int, limit int) (*model.Team
// SearchAllTeams returns a team list and the total count of the results
func (a *App) SearchAllTeams(searchOpts *model.TeamSearch) ([]*model.Team, int64, *model.AppError) {
if searchOpts.IsPaginated() {
return a.Srv().Store.Team().SearchAllPaged(searchOpts.Term, *searchOpts.Page, *searchOpts.PerPage)
return a.Srv().Store.Team().SearchAllPaged(searchOpts.Term, searchOpts)
}
results, err := a.Srv().Store.Team().SearchAll(searchOpts.Term)
results, err := a.Srv().Store.Team().SearchAll(searchOpts.Term, searchOpts)
return results, int64(len(results)), err
}

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

@@ -9,9 +9,12 @@ import (
)
type TeamSearch struct {
Term string `json:"term"`
Page *int `json:"page,omitempty"`
PerPage *int `json:"per_page,omitempty"`
Term string `json:"term"`
Page *int `json:"page,omitempty"`
PerPage *int `json:"per_page,omitempty"`
AllowOpenInvite *bool `json:"allow_open_invite,omitempty"`
GroupConstrained *bool `json:"group_constrained,omitempty"`
IncludeGroupConstrained *bool `json:"include_group_constrained,omitempty"`
}
func (t *TeamSearch) IsPaginated() bool {

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

@@ -7222,7 +7222,7 @@ func (s *OpenTracingLayerTeamStore) SaveMultipleMembers(members []*model.TeamMem
return resultVar0, resultVar1
}
func (s *OpenTracingLayerTeamStore) SearchAll(term string) ([]*model.Team, *model.AppError) {
func (s *OpenTracingLayerTeamStore) SearchAll(term string, opts *model.TeamSearch) ([]*model.Team, *model.AppError) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "TeamStore.SearchAll")
s.Root.Store.SetContext(newCtx)
@@ -7231,7 +7231,7 @@ func (s *OpenTracingLayerTeamStore) SearchAll(term string) ([]*model.Team, *mode
}()
defer span.Finish()
resultVar0, resultVar1 := s.TeamStore.SearchAll(term)
resultVar0, resultVar1 := s.TeamStore.SearchAll(term, opts)
if resultVar1 != nil {
span.LogFields(spanlog.Error(resultVar1))
ext.Error.Set(span, true)
@@ -7240,7 +7240,7 @@ func (s *OpenTracingLayerTeamStore) SearchAll(term string) ([]*model.Team, *mode
return resultVar0, resultVar1
}
func (s *OpenTracingLayerTeamStore) SearchAllPaged(term string, page int, perPage int) ([]*model.Team, int64, *model.AppError) {
func (s *OpenTracingLayerTeamStore) SearchAllPaged(term string, opts *model.TeamSearch) ([]*model.Team, int64, *model.AppError) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "TeamStore.SearchAllPaged")
s.Root.Store.SetContext(newCtx)
@@ -7249,7 +7249,7 @@ func (s *OpenTracingLayerTeamStore) SearchAllPaged(term string, page int, perPag
}()
defer span.Finish()
resultVar0, resultVar1, resultVar2 := s.TeamStore.SearchAllPaged(term, page, perPage)
resultVar0, resultVar1, resultVar2 := s.TeamStore.SearchAllPaged(term, opts)
if resultVar2 != nil {
span.LogFields(spanlog.Error(resultVar2))
ext.Error.Set(span, true)

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

@@ -372,20 +372,94 @@ func (s SqlTeamStore) GetByNames(names []string) ([]*model.Team, *model.AppError
return teams, nil
}
func (s SqlTeamStore) teamSearchQuery(term string, opts *model.TeamSearch, countQuery bool) sq.SelectBuilder {
var selectStr string
if countQuery {
selectStr = "count(*)"
} else {
selectStr = "*"
}
query := s.getQueryBuilder().
Select(selectStr).
From("Teams as t")
// Don't order or limit if getting count
if !countQuery {
query = query.OrderBy("t.DisplayName")
if opts.IsPaginated() {
query = query.Limit(uint64(*opts.PerPage)).Offset(uint64(*opts.Page * *opts.PerPage))
}
}
if len(term) > 0 {
term = sanitizeSearchTerm(term, "\\")
term = wildcardSearchTerm(term)
operatorKeyword := "ILIKE"
if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
operatorKeyword = "LIKE"
}
query = query.Where(fmt.Sprintf("(Name %[1]s ? OR DisplayName %[1]s ?)", operatorKeyword), term, term)
}
var teamFilters sq.Sqlizer
var openInviteFilter sq.Sqlizer
if opts.AllowOpenInvite != nil {
if *opts.AllowOpenInvite {
openInviteFilter = sq.Eq{"AllowOpenInvite": true}
} else {
openInviteFilter = sq.And{
sq.Or{
sq.NotEq{"AllowOpenInvite": true},
sq.Eq{"AllowOpenInvite": nil},
},
sq.Or{
sq.NotEq{"GroupConstrained": true},
sq.Eq{"GroupConstrained": nil},
},
}
}
teamFilters = openInviteFilter
}
var groupConstrainedFilter sq.Sqlizer
if opts.GroupConstrained != nil {
if *opts.GroupConstrained {
groupConstrainedFilter = sq.Eq{"GroupConstrained": true}
} else {
groupConstrainedFilter = sq.Or{
sq.NotEq{"GroupConstrained": true},
sq.Eq{"GroupConstrained": nil},
}
}
if teamFilters == nil {
teamFilters = groupConstrainedFilter
} else {
teamFilters = sq.Or{teamFilters, groupConstrainedFilter}
}
}
query = query.Where(teamFilters)
return query
}
// SearchAll returns from the database a list of teams that match the Name or DisplayName
// passed as the term search parameter.
func (s SqlTeamStore) SearchAll(term string) ([]*model.Team, *model.AppError) {
func (s SqlTeamStore) SearchAll(term string, opts *model.TeamSearch) ([]*model.Team, *model.AppError) {
var teams []*model.Team
term = sanitizeSearchTerm(term, "\\")
term = wildcardSearchTerm(term)
operatorKeyword := "ILIKE"
if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
operatorKeyword = "LIKE"
queryString, args, err := s.teamSearchQuery(term, opts, false).ToSql()
if err != nil {
return nil, model.NewAppError("SqlTeamStore.SearchAll", "store.sql.build_query.app_error", nil, err.Error(), http.StatusInternalServerError)
}
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 {
if _, err = s.GetReplica().Select(&teams, queryString, args...); err != nil {
return nil, model.NewAppError("SqlTeamStore.SearchAll", "store.sql_team.search_all_team.app_error", nil, "term="+term+", "+err.Error(), http.StatusInternalServerError)
}
@@ -393,24 +467,23 @@ func (s SqlTeamStore) SearchAll(term string) ([]*model.Team, *model.AppError) {
}
// SearchAllPaged returns a teams list and the total count of teams that matched the search.
func (s SqlTeamStore) SearchAllPaged(term string, page int, perPage int) ([]*model.Team, int64, *model.AppError) {
func (s SqlTeamStore) SearchAllPaged(term string, opts *model.TeamSearch) ([]*model.Team, int64, *model.AppError) {
var teams []*model.Team
var totalCount int64
offset := page * perPage
term = sanitizeSearchTerm(term, "\\")
term = wildcardSearchTerm(term)
operatorKeyword := "ILIKE"
if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
operatorKeyword = "LIKE"
queryString, args, err := s.teamSearchQuery(term, opts, false).ToSql()
if err != nil {
return nil, 0, model.NewAppError("SqlTeamStore.SearchAllPage", "store.sql.build_query.app_error", nil, err.Error(), http.StatusInternalServerError)
}
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 {
if _, err = s.GetReplica().Select(&teams, queryString, args...); err != nil {
return nil, 0, model.NewAppError("SqlTeamStore.SearchAllPage", "store.sql_team.search_all_team.app_error", nil, "term="+term+", "+err.Error(), http.StatusInternalServerError)
}
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})
queryString, args, err = s.teamSearchQuery(term, opts, true).ToSql()
if err != nil {
return nil, 0, model.NewAppError("SqlTeamStore.SearchAllPage", "store.sql.build_query.app_error", nil, err.Error(), http.StatusInternalServerError)
}
totalCount, err = s.GetReplica().SelectInt(queryString, args...)
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)
}

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

@@ -78,8 +78,8 @@ type TeamStore interface {
Get(id string) (*model.Team, *model.AppError)
GetByName(name string) (*model.Team, *model.AppError)
GetByNames(name []string) ([]*model.Team, *model.AppError)
SearchAll(term string) ([]*model.Team, *model.AppError)
SearchAllPaged(term string, page int, perPage int) ([]*model.Team, int64, *model.AppError)
SearchAll(term string, opts *model.TeamSearch) ([]*model.Team, *model.AppError)
SearchAllPaged(term string, opts *model.TeamSearch) ([]*model.Team, int64, *model.AppError)
SearchOpen(term string) ([]*model.Team, *model.AppError)
SearchPrivate(term string) ([]*model.Team, *model.AppError)
GetAll() ([]*model.Team, *model.AppError)

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

@@ -972,13 +972,13 @@ func (_m *TeamStore) SaveMultipleMembers(members []*model.TeamMember, maxUsersPe
return r0, r1
}
// SearchAll provides a mock function with given fields: term
func (_m *TeamStore) SearchAll(term string) ([]*model.Team, *model.AppError) {
ret := _m.Called(term)
// SearchAll provides a mock function with given fields: term, opts
func (_m *TeamStore) SearchAll(term string, opts *model.TeamSearch) ([]*model.Team, *model.AppError) {
ret := _m.Called(term, opts)
var r0 []*model.Team
if rf, ok := ret.Get(0).(func(string) []*model.Team); ok {
r0 = rf(term)
if rf, ok := ret.Get(0).(func(string, *model.TeamSearch) []*model.Team); ok {
r0 = rf(term, opts)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.Team)
@@ -986,8 +986,8 @@ func (_m *TeamStore) SearchAll(term string) ([]*model.Team, *model.AppError) {
}
var r1 *model.AppError
if rf, ok := ret.Get(1).(func(string) *model.AppError); ok {
r1 = rf(term)
if rf, ok := ret.Get(1).(func(string, *model.TeamSearch) *model.AppError); ok {
r1 = rf(term, opts)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*model.AppError)
@@ -997,13 +997,13 @@ func (_m *TeamStore) SearchAll(term string) ([]*model.Team, *model.AppError) {
return r0, r1
}
// SearchAllPaged provides a mock function with given fields: term, page, perPage
func (_m *TeamStore) SearchAllPaged(term string, page int, perPage int) ([]*model.Team, int64, *model.AppError) {
ret := _m.Called(term, page, perPage)
// SearchAllPaged provides a mock function with given fields: term, opts
func (_m *TeamStore) SearchAllPaged(term string, opts *model.TeamSearch) ([]*model.Team, int64, *model.AppError) {
ret := _m.Called(term, opts)
var r0 []*model.Team
if rf, ok := ret.Get(0).(func(string, int, int) []*model.Team); ok {
r0 = rf(term, page, perPage)
if rf, ok := ret.Get(0).(func(string, *model.TeamSearch) []*model.Team); ok {
r0 = rf(term, opts)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.Team)
@@ -1011,15 +1011,15 @@ func (_m *TeamStore) SearchAllPaged(term string, page int, perPage int) ([]*mode
}
var r1 int64
if rf, ok := ret.Get(1).(func(string, int, int) int64); ok {
r1 = rf(term, page, perPage)
if rf, ok := ret.Get(1).(func(string, *model.TeamSearch) int64); ok {
r1 = rf(term, opts)
} else {
r1 = ret.Get(1).(int64)
}
var r2 *model.AppError
if rf, ok := ret.Get(2).(func(string, int, int) *model.AppError); ok {
r2 = rf(term, page, perPage)
if rf, ok := ret.Get(2).(func(string, *model.TeamSearch) *model.AppError); ok {
r2 = rf(term, opts)
} else {
if ret.Get(2) != nil {
r2 = ret.Get(2).(*model.AppError)

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

@@ -222,7 +222,7 @@ func testTeamStoreSearchAll(t *testing.T, ss store.Store) {
require.Nil(t, err)
p := model.Team{}
p.DisplayName = "ADisplayName" + model.NewId()
p.DisplayName = "BDisplayName" + model.NewId()
p.Name = "zzzzzz-" + model.NewId() + "a"
p.Email = MakeEmail()
p.Type = model.TEAM_OPEN
@@ -231,6 +231,17 @@ func testTeamStoreSearchAll(t *testing.T, ss store.Store) {
_, err = ss.Team().Save(&p)
require.Nil(t, err)
g := model.Team{}
g.DisplayName = "CDisplayName" + model.NewId()
g.Name = "zzzzzz-" + model.NewId() + "a"
g.Email = MakeEmail()
g.Type = model.TEAM_OPEN
g.AllowOpenInvite = false
g.GroupConstrained = model.NewBool(true)
_, err = ss.Team().Save(&g)
require.Nil(t, err)
q := model.Team{}
q.DisplayName = "CHOCOLATE"
q.Name = "ilovecake"
@@ -243,80 +254,130 @@ func testTeamStoreSearchAll(t *testing.T, ss store.Store) {
testCases := []struct {
Name string
Term string
Opts *model.TeamSearch
ExpectedLenth int
ExpectedFirstId string
ExpectedTeamIds []string
}{
{
"Search chocolate by display name",
"ocola",
&model.TeamSearch{Term: "ocola"},
1,
q.Id,
[]string{q.Id},
},
{
"Search chocolate by display name",
"choc",
&model.TeamSearch{Term: "choc"},
1,
q.Id,
[]string{q.Id},
},
{
"Search chocolate by display name",
"late",
&model.TeamSearch{Term: "late"},
1,
q.Id,
[]string{q.Id},
},
{
"Search chocolate by name",
"ilov",
&model.TeamSearch{Term: "ilov"},
1,
q.Id,
[]string{q.Id},
},
{
"Search chocolate by name",
"ecake",
&model.TeamSearch{Term: "ecake"},
1,
q.Id,
[]string{q.Id},
},
{
"Search for open team name",
o.Name,
&model.TeamSearch{Term: o.Name},
1,
o.Id,
[]string{o.Id},
},
{
"Search for open team displayName",
o.DisplayName,
&model.TeamSearch{Term: o.DisplayName},
1,
o.Id,
[]string{o.Id},
},
{
"Search for open team without results",
"junk",
&model.TeamSearch{Term: "junk"},
0,
"",
[]string{},
},
{
"Search for private team",
p.DisplayName,
&model.TeamSearch{Term: p.DisplayName},
1,
p.Id,
[]string{p.Id},
},
{
"Search for both teams",
"zzzzzz",
"Search for all 3 z teams",
&model.TeamSearch{Term: "zzzzzz"},
3,
[]string{o.Id, p.Id, g.Id},
},
{
"Search for all 3 teams filter by allow open invite",
&model.TeamSearch{Term: "zzzzzz", AllowOpenInvite: model.NewBool(true)},
1,
[]string{o.Id},
},
{
"Search for all 3 teams filter by allow open invite = false",
&model.TeamSearch{Term: "zzzzzz", AllowOpenInvite: model.NewBool(false)},
1,
[]string{p.Id},
},
{
"Search for all 3 teams filter by group constrained",
&model.TeamSearch{Term: "zzzzzz", GroupConstrained: model.NewBool(true)},
1,
[]string{g.Id},
},
{
"Search for all 3 teams filter by group constrained = false",
&model.TeamSearch{Term: "zzzzzz", GroupConstrained: model.NewBool(false)},
2,
"",
[]string{o.Id, p.Id},
},
{
"Search for all 3 teams filter by allow open invite and include group constrained",
&model.TeamSearch{Term: "zzzzzz", AllowOpenInvite: model.NewBool(true), GroupConstrained: model.NewBool(true)},
2,
[]string{o.Id, g.Id},
},
{
"Search for all 3 teams filter by group constrained and not open invite",
&model.TeamSearch{Term: "zzzzzz", GroupConstrained: model.NewBool(true), AllowOpenInvite: model.NewBool(false)},
2,
[]string{g.Id, p.Id},
},
{
"Search for all 3 teams filter by group constrained false and open invite",
&model.TeamSearch{Term: "zzzzzz", GroupConstrained: model.NewBool(false), AllowOpenInvite: model.NewBool(true)},
2,
[]string{o.Id, p.Id},
},
{
"Search for all 3 teams filter by group constrained false and open invite false",
&model.TeamSearch{Term: "zzzzzz", GroupConstrained: model.NewBool(false), AllowOpenInvite: model.NewBool(false)},
2,
[]string{p.Id, o.Id},
},
}
for _, tc := range testCases {
t.Run(tc.Name, func(t *testing.T) {
r1, err := ss.Team().SearchAll(tc.Term)
response, err := ss.Team().SearchAll(tc.Opts.Term, tc.Opts)
require.Nil(t, err)
require.Equal(t, tc.ExpectedLenth, len(r1))
if tc.ExpectedFirstId != "" {
assert.Equal(t, tc.ExpectedFirstId, r1[0].Id)
require.Equal(t, tc.ExpectedLenth, len(response))
responseTeamIds := []string{}
for _, team := range response {
responseTeamIds = append(responseTeamIds, team.Id)
}
require.ElementsMatch(t, tc.ExpectedTeamIds, responseTeamIds)
})
}
}

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

@@ -6522,10 +6522,10 @@ func (s *TimerLayerTeamStore) SaveMultipleMembers(members []*model.TeamMember, m
return resultVar0, resultVar1
}
func (s *TimerLayerTeamStore) SearchAll(term string) ([]*model.Team, *model.AppError) {
func (s *TimerLayerTeamStore) SearchAll(term string, opts *model.TeamSearch) ([]*model.Team, *model.AppError) {
start := timemodule.Now()
resultVar0, resultVar1 := s.TeamStore.SearchAll(term)
resultVar0, resultVar1 := s.TeamStore.SearchAll(term, opts)
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
if s.Root.Metrics != nil {
@@ -6538,10 +6538,10 @@ func (s *TimerLayerTeamStore) SearchAll(term string) ([]*model.Team, *model.AppE
return resultVar0, resultVar1
}
func (s *TimerLayerTeamStore) SearchAllPaged(term string, page int, perPage int) ([]*model.Team, int64, *model.AppError) {
func (s *TimerLayerTeamStore) SearchAllPaged(term string, opts *model.TeamSearch) ([]*model.Team, int64, *model.AppError) {
start := timemodule.Now()
resultVar0, resultVar1, resultVar2 := s.TeamStore.SearchAllPaged(term, page, perPage)
resultVar0, resultVar1, resultVar2 := s.TeamStore.SearchAllPaged(term, opts)
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
if s.Root.Metrics != nil {