Hide unactive on explore users and some refactors (#2741)

* hide unactive on explore users and some refactors

* fix test for removed Organizations

* fix test for removed Organizations

* fix imports

* fix logic bug

* refactor the toConds

* Rename TestOrganizations to TestSearchUsers and add tests for users

* fix other tests

* fix other tests

* fix watchers tests

* fix comments and remove unused code
This commit is contained in:
Lunny Xiao 2017-10-25 01:36:19 +08:00 committed by Lauris BH
parent 03900303a9
commit 6eeadb2082
15 changed files with 143 additions and 151 deletions

View File

@ -1,6 +1,6 @@
- -
id: 1 id: 1
user_id: 1 user_id: 9
issue_id: 1 issue_id: 1
is_watching: true is_watching: true
created_unix: 946684800 created_unix: 946684800

View File

@ -13,6 +13,7 @@
avatar: avatar1 avatar: avatar1
avatar_email: user1@example.com avatar_email: user1@example.com
num_repos: 0 num_repos: 0
is_active: true
- -
id: 2 id: 2
@ -62,6 +63,7 @@
avatar_email: user4@example.com avatar_email: user4@example.com
num_repos: 0 num_repos: 0
num_following: 1 num_following: 1
is_active: true
- -
id: 5 id: 5

View File

@ -10,5 +10,5 @@
- -
id: 3 id: 3
user_id: 10 user_id: 9
repo_id: 1 repo_id: 1

View File

@ -25,7 +25,7 @@ func TestCreateOrUpdateIssueWatch(t *testing.T) {
func TestGetIssueWatch(t *testing.T) { func TestGetIssueWatch(t *testing.T) {
assert.NoError(t, PrepareTestDatabase()) assert.NoError(t, PrepareTestDatabase())
_, exists, err := GetIssueWatch(1, 1) _, exists, err := GetIssueWatch(9, 1)
assert.Equal(t, true, exists) assert.Equal(t, true, exists)
assert.NoError(t, err) assert.NoError(t, err)

View File

@ -16,10 +16,13 @@ func TestCreateOrUpdateIssueNotifications(t *testing.T) {
assert.NoError(t, CreateOrUpdateIssueNotifications(issue, 2)) assert.NoError(t, CreateOrUpdateIssueNotifications(issue, 2))
// Two watchers are inactive, thus only notification for user 10 is created // User 9 is inactive, thus notifications for user 1 and 4 are created
notf := AssertExistsAndLoadBean(t, &Notification{UserID: 10, IssueID: issue.ID}).(*Notification) notf := AssertExistsAndLoadBean(t, &Notification{UserID: 1, IssueID: issue.ID}).(*Notification)
assert.Equal(t, NotificationStatusUnread, notf.Status) assert.Equal(t, NotificationStatusUnread, notf.Status)
CheckConsistencyFor(t, &Issue{ID: issue.ID}) CheckConsistencyFor(t, &Issue{ID: issue.ID})
notf = AssertExistsAndLoadBean(t, &Notification{UserID: 4, IssueID: issue.ID}).(*Notification)
assert.Equal(t, NotificationStatusUnread, notf.Status)
} }
func TestNotificationsForUser(t *testing.T) { func TestNotificationsForUser(t *testing.T) {

View File

@ -201,23 +201,6 @@ func CountOrganizations() int64 {
return count return count
} }
// Organizations returns number of organizations in given page.
func Organizations(opts *SearchUserOptions) ([]*User, error) {
orgs := make([]*User, 0, opts.PageSize)
if len(opts.OrderBy) == 0 {
opts.OrderBy = "name ASC"
}
sess := x.
Limit(opts.PageSize, (opts.Page-1)*opts.PageSize).
Where("type=1")
return orgs, sess.
OrderBy(opts.OrderBy).
Find(&orgs)
}
// DeleteOrganization completely and permanently deletes everything of organization. // DeleteOrganization completely and permanently deletes everything of organization.
func DeleteOrganization(org *User) (err error) { func DeleteOrganization(org *User) (err error) {
sess := x.NewSession() sess := x.NewSession()

View File

@ -237,27 +237,6 @@ func TestCountOrganizations(t *testing.T) {
assert.Equal(t, expected, CountOrganizations()) assert.Equal(t, expected, CountOrganizations())
} }
func TestOrganizations(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
testSuccess := func(opts *SearchUserOptions, expectedOrgIDs []int64) {
orgs, err := Organizations(opts)
assert.NoError(t, err)
if assert.Len(t, orgs, len(expectedOrgIDs)) {
for i, expectedOrgID := range expectedOrgIDs {
assert.EqualValues(t, expectedOrgID, orgs[i].ID)
}
}
}
testSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 1, PageSize: 2},
[]int64{3, 6})
testSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 2, PageSize: 2},
[]int64{7, 17})
testSuccess(&SearchUserOptions{Page: 3, PageSize: 2},
[]int64{})
}
func TestDeleteOrganization(t *testing.T) { func TestDeleteOrganization(t *testing.T) {
assert.NoError(t, PrepareTestDatabase()) assert.NoError(t, PrepareTestDatabase())
org := AssertExistsAndLoadBean(t, &User{ID: 6}).(*User) org := AssertExistsAndLoadBean(t, &User{ID: 6}).(*User)

View File

@ -40,8 +40,8 @@ func TestGetWatchers(t *testing.T) {
repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository)
watches, err := GetWatchers(repo.ID) watches, err := GetWatchers(repo.ID)
assert.NoError(t, err) assert.NoError(t, err)
// Two watchers are inactive, thus minus 2 // One watchers are inactive, thus minus 1
assert.Len(t, watches, repo.NumWatches-2) assert.Len(t, watches, repo.NumWatches-1)
for _, watch := range watches { for _, watch := range watches {
assert.EqualValues(t, repo.ID, watch.RepoID) assert.EqualValues(t, repo.ID, watch.RepoID)
} }
@ -62,7 +62,7 @@ func TestRepository_GetWatchers(t *testing.T) {
AssertExistsAndLoadBean(t, &Watch{UserID: watcher.ID, RepoID: repo.ID}) AssertExistsAndLoadBean(t, &Watch{UserID: watcher.ID, RepoID: repo.ID})
} }
repo = AssertExistsAndLoadBean(t, &Repository{ID: 10}).(*Repository) repo = AssertExistsAndLoadBean(t, &Repository{ID: 9}).(*Repository)
watchers, err = repo.GetWatchers(1) watchers, err = repo.GetWatchers(1)
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, watchers, 0) assert.Len(t, watchers, 0)
@ -78,7 +78,7 @@ func TestNotifyWatchers(t *testing.T) {
} }
assert.NoError(t, NotifyWatchers(action)) assert.NoError(t, NotifyWatchers(action))
// Two watchers are inactive, thus action is only created for user 8, 10 // One watchers are inactive, thus action is only created for user 8, 1, 4
AssertExistsAndLoadBean(t, &Action{ AssertExistsAndLoadBean(t, &Action{
ActUserID: action.ActUserID, ActUserID: action.ActUserID,
UserID: 8, UserID: 8,
@ -87,7 +87,13 @@ func TestNotifyWatchers(t *testing.T) {
}) })
AssertExistsAndLoadBean(t, &Action{ AssertExistsAndLoadBean(t, &Action{
ActUserID: action.ActUserID, ActUserID: action.ActUserID,
UserID: 10, UserID: 1,
RepoID: action.RepoID,
OpType: action.OpType,
})
AssertExistsAndLoadBean(t, &Action{
ActUserID: action.ActUserID,
UserID: 4,
RepoID: action.RepoID, RepoID: action.RepoID,
OpType: action.OpType, OpType: action.OpType,
}) })

View File

@ -36,6 +36,7 @@ import (
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
) )
// UserType defines the user type // UserType defines the user type
@ -729,22 +730,6 @@ func CountUsers() int64 {
return countUsers(x) return countUsers(x)
} }
// Users returns number of users in given page.
func Users(opts *SearchUserOptions) ([]*User, error) {
if len(opts.OrderBy) == 0 {
opts.OrderBy = "name ASC"
}
users := make([]*User, 0, opts.PageSize)
sess := x.
Limit(opts.PageSize, (opts.Page-1)*opts.PageSize).
Where("type=0")
return users, sess.
OrderBy(opts.OrderBy).
Find(&users)
}
// get user by verify code // get user by verify code
func getVerifyUser(code string) (user *User) { func getVerifyUser(code string) (user *User) {
if len(code) <= base.TimeLimitCodeLength { if len(code) <= base.TimeLimitCodeLength {
@ -1284,15 +1269,34 @@ type SearchUserOptions struct {
OrderBy string OrderBy string
Page int Page int
PageSize int // Can be smaller than or equal to setting.UI.ExplorePagingNum PageSize int // Can be smaller than or equal to setting.UI.ExplorePagingNum
IsActive util.OptionalBool
} }
// SearchUserByName takes keyword and part of user name to search, func (opts *SearchUserOptions) toConds() builder.Cond {
// it returns results in given range and number of total results. var cond builder.Cond = builder.Eq{"type": opts.Type}
func SearchUserByName(opts *SearchUserOptions) (users []*User, _ int64, _ error) { if len(opts.Keyword) > 0 {
if len(opts.Keyword) == 0 { lowerKeyword := strings.ToLower(opts.Keyword)
return users, 0, nil cond = cond.And(builder.Or(
builder.Like{"lower_name", lowerKeyword},
builder.Like{"LOWER(full_name)", lowerKeyword},
))
}
if !opts.IsActive.IsNone() {
cond = cond.And(builder.Eq{"is_active": opts.IsActive.IsTrue()})
}
return cond
}
// SearchUsers takes options i.e. keyword and part of user name to search,
// it returns results in given range and number of total results.
func SearchUsers(opts *SearchUserOptions) (users []*User, _ int64, _ error) {
cond := opts.toConds()
count, err := x.Where(cond).Count(new(User))
if err != nil {
return nil, 0, fmt.Errorf("Count: %v", err)
} }
opts.Keyword = strings.ToLower(opts.Keyword)
if opts.PageSize <= 0 || opts.PageSize > setting.UI.ExplorePagingNum { if opts.PageSize <= 0 || opts.PageSize > setting.UI.ExplorePagingNum {
opts.PageSize = setting.UI.ExplorePagingNum opts.PageSize = setting.UI.ExplorePagingNum
@ -1300,29 +1304,15 @@ func SearchUserByName(opts *SearchUserOptions) (users []*User, _ int64, _ error)
if opts.Page <= 0 { if opts.Page <= 0 {
opts.Page = 1 opts.Page = 1
} }
if len(opts.OrderBy) == 0 {
opts.OrderBy = "name ASC"
}
users = make([]*User, 0, opts.PageSize) users = make([]*User, 0, opts.PageSize)
return users, count, x.Where(cond).
// Append conditions Limit(opts.PageSize, (opts.Page-1)*opts.PageSize).
cond := builder.And( OrderBy(opts.OrderBy).
builder.Eq{"type": opts.Type}, Find(&users)
builder.Or(
builder.Like{"lower_name", opts.Keyword},
builder.Like{"LOWER(full_name)", opts.Keyword},
),
)
count, err := x.Where(cond).Count(new(User))
if err != nil {
return nil, 0, fmt.Errorf("Count: %v", err)
}
sess := x.Where(cond).
Limit(opts.PageSize, (opts.Page-1)*opts.PageSize)
if len(opts.OrderBy) > 0 {
sess.OrderBy(opts.OrderBy)
}
return users, count, sess.Find(&users)
} }
// GetStarredRepos returns the repos starred by a particular user // GetStarredRepos returns the repos starred by a particular user

View File

@ -8,6 +8,7 @@ import (
"testing" "testing"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -38,6 +39,56 @@ func TestCanCreateOrganization(t *testing.T) {
assert.False(t, user.CanCreateOrganization()) assert.False(t, user.CanCreateOrganization())
} }
func TestSearchUsers(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
testSuccess := func(opts *SearchUserOptions, expectedUserOrOrgIDs []int64) {
users, _, err := SearchUsers(opts)
assert.NoError(t, err)
if assert.Len(t, users, len(expectedUserOrOrgIDs)) {
for i, expectedID := range expectedUserOrOrgIDs {
assert.EqualValues(t, expectedID, users[i].ID)
}
}
}
// test orgs
testOrgSuccess := func(opts *SearchUserOptions, expectedOrgIDs []int64) {
opts.Type = UserTypeOrganization
testSuccess(opts, expectedOrgIDs)
}
testOrgSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 1, PageSize: 2},
[]int64{3, 6})
testOrgSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 2, PageSize: 2},
[]int64{7, 17})
testOrgSuccess(&SearchUserOptions{Page: 3, PageSize: 2},
[]int64{})
// test users
testUserSuccess := func(opts *SearchUserOptions, expectedUserIDs []int64) {
opts.Type = UserTypeIndividual
testSuccess(opts, expectedUserIDs)
}
testUserSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 1},
[]int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18})
testUserSuccess(&SearchUserOptions{Page: 1, IsActive: util.OptionalBoolFalse},
[]int64{9})
testUserSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 1, IsActive: util.OptionalBoolTrue},
[]int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18})
testUserSuccess(&SearchUserOptions{Keyword: "user1", OrderBy: "id ASC", Page: 1, IsActive: util.OptionalBoolTrue},
[]int64{1, 10, 11, 12, 13, 14, 15, 16, 18})
// order by name asc default
testUserSuccess(&SearchUserOptions{Keyword: "user1", Page: 1, IsActive: util.OptionalBoolTrue},
[]int64{1, 10, 11, 12, 13, 14, 15, 16, 18})
}
func TestDeleteUser(t *testing.T) { func TestDeleteUser(t *testing.T) {
test := func(userID int64) { test := func(userID int64) {
assert.NoError(t, PrepareTestDatabase()) assert.NoError(t, PrepareTestDatabase())

View File

@ -16,6 +16,21 @@ const (
OptionalBoolFalse OptionalBoolFalse
) )
// IsTrue return true if equal to OptionalBoolTrue
func (o OptionalBool) IsTrue() bool {
return o == OptionalBoolTrue
}
// IsFalse return true if equal to OptionalBoolFalse
func (o OptionalBool) IsFalse() bool {
return o == OptionalBoolFalse
}
// IsNone return true if equal to OptionalBoolNone
func (o OptionalBool) IsNone() bool {
return o == OptionalBoolNone
}
// OptionalBoolOf get the corresponding OptionalBool of a bool // OptionalBoolOf get the corresponding OptionalBool of a bool
func OptionalBoolOf(b bool) OptionalBool { func OptionalBoolOf(b bool) OptionalBool {
if b { if b {

View File

@ -22,11 +22,8 @@ func Organizations(ctx *context.Context) {
ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdmin"] = true
ctx.Data["PageIsAdminOrganizations"] = true ctx.Data["PageIsAdminOrganizations"] = true
routers.RenderUserSearch(ctx, &routers.UserSearchOptions{ routers.RenderUserSearch(ctx, &models.SearchUserOptions{
Type: models.UserTypeOrganization, Type: models.UserTypeOrganization,
Counter: models.CountOrganizations,
Ranger: models.Organizations,
PageSize: setting.UI.Admin.OrgPagingNum, PageSize: setting.UI.Admin.OrgPagingNum,
TplName: tplOrgs, }, tplOrgs)
})
} }

View File

@ -30,13 +30,10 @@ func Users(ctx *context.Context) {
ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdmin"] = true
ctx.Data["PageIsAdminUsers"] = true ctx.Data["PageIsAdminUsers"] = true
routers.RenderUserSearch(ctx, &routers.UserSearchOptions{ routers.RenderUserSearch(ctx, &models.SearchUserOptions{
Type: models.UserTypeIndividual, Type: models.UserTypeIndividual,
Counter: models.CountUsers,
Ranger: models.Users,
PageSize: setting.UI.Admin.UserPagingNum, PageSize: setting.UI.Admin.UserPagingNum,
TplName: tplUsers, }, tplUsers)
})
} }
// NewUser render adding a new user page // NewUser render adding a new user page

View File

@ -35,7 +35,7 @@ func Search(ctx *context.APIContext) {
opts.PageSize = 10 opts.PageSize = 10
} }
users, _, err := models.SearchUserByName(opts) users, _, err := models.SearchUsers(opts)
if err != nil { if err != nil {
ctx.JSON(500, map[string]interface{}{ ctx.JSON(500, map[string]interface{}{
"ok": false, "ok": false,

View File

@ -12,6 +12,7 @@ import (
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/user" "code.gitea.io/gitea/routers/user"
"github.com/Unknwon/paginater" "github.com/Unknwon/paginater"
@ -147,20 +148,11 @@ func ExploreRepos(ctx *context.Context) {
}) })
} }
// UserSearchOptions options when render search user page
type UserSearchOptions struct {
Type models.UserType
Counter func() int64
Ranger func(*models.SearchUserOptions) ([]*models.User, error)
PageSize int
TplName base.TplName
}
// RenderUserSearch render user search page // RenderUserSearch render user search page
func RenderUserSearch(ctx *context.Context, opts *UserSearchOptions) { func RenderUserSearch(ctx *context.Context, opts *models.SearchUserOptions, tplName base.TplName) {
page := ctx.QueryInt("page") opts.Page = ctx.QueryInt("page")
if page <= 1 { if opts.Page <= 1 {
page = 1 opts.Page = 1
} }
var ( var (
@ -189,40 +181,22 @@ func RenderUserSearch(ctx *context.Context, opts *UserSearchOptions) {
orderBy = "name ASC" orderBy = "name ASC"
} }
keyword := strings.Trim(ctx.Query("q"), " ") opts.Keyword = strings.Trim(ctx.Query("q"), " ")
if len(keyword) == 0 { opts.OrderBy = orderBy
users, err = opts.Ranger(&models.SearchUserOptions{ if len(opts.Keyword) == 0 || isKeywordValid(opts.Keyword) {
OrderBy: orderBy, users, count, err = models.SearchUsers(opts)
Page: page,
PageSize: opts.PageSize,
})
if err != nil { if err != nil {
ctx.Handle(500, "opts.Ranger", err) ctx.Handle(500, "SearchUsers", err)
return
}
count = opts.Counter()
} else {
if isKeywordValid(keyword) {
users, count, err = models.SearchUserByName(&models.SearchUserOptions{
Keyword: keyword,
Type: opts.Type,
OrderBy: orderBy,
Page: page,
PageSize: opts.PageSize,
})
if err != nil {
ctx.Handle(500, "SearchUserByName", err)
return return
} }
} }
} ctx.Data["Keyword"] = opts.Keyword
ctx.Data["Keyword"] = keyword
ctx.Data["Total"] = count ctx.Data["Total"] = count
ctx.Data["Page"] = paginater.New(int(count), opts.PageSize, page, 5) ctx.Data["Page"] = paginater.New(int(count), opts.PageSize, opts.Page, 5)
ctx.Data["Users"] = users ctx.Data["Users"] = users
ctx.Data["ShowUserEmail"] = setting.UI.ShowUserEmail ctx.Data["ShowUserEmail"] = setting.UI.ShowUserEmail
ctx.HTML(200, opts.TplName) ctx.HTML(200, tplName)
} }
// ExploreUsers render explore users page // ExploreUsers render explore users page
@ -231,13 +205,11 @@ func ExploreUsers(ctx *context.Context) {
ctx.Data["PageIsExplore"] = true ctx.Data["PageIsExplore"] = true
ctx.Data["PageIsExploreUsers"] = true ctx.Data["PageIsExploreUsers"] = true
RenderUserSearch(ctx, &UserSearchOptions{ RenderUserSearch(ctx, &models.SearchUserOptions{
Type: models.UserTypeIndividual, Type: models.UserTypeIndividual,
Counter: models.CountUsers,
Ranger: models.Users,
PageSize: setting.UI.ExplorePagingNum, PageSize: setting.UI.ExplorePagingNum,
TplName: tplExploreUsers, IsActive: util.OptionalBoolTrue,
}) }, tplExploreUsers)
} }
// ExploreOrganizations render explore organizations page // ExploreOrganizations render explore organizations page
@ -246,13 +218,10 @@ func ExploreOrganizations(ctx *context.Context) {
ctx.Data["PageIsExplore"] = true ctx.Data["PageIsExplore"] = true
ctx.Data["PageIsExploreOrganizations"] = true ctx.Data["PageIsExploreOrganizations"] = true
RenderUserSearch(ctx, &UserSearchOptions{ RenderUserSearch(ctx, &models.SearchUserOptions{
Type: models.UserTypeOrganization, Type: models.UserTypeOrganization,
Counter: models.CountOrganizations,
Ranger: models.Organizations,
PageSize: setting.UI.ExplorePagingNum, PageSize: setting.UI.ExplorePagingNum,
TplName: tplExploreOrganizations, }, tplExploreOrganizations)
})
} }
// NotFound render 404 page // NotFound render 404 page