Browse Source

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
Lunny Xiao 1 year ago
parent
commit
6eeadb2082

+ 1 - 1
models/fixtures/issue_watch.yml

@@ -1,6 +1,6 @@
1 1
 -
2 2
   id: 1
3
-  user_id: 1
3
+  user_id: 9
4 4
   issue_id: 1
5 5
   is_watching: true
6 6
   created_unix: 946684800

+ 2 - 0
models/fixtures/user.yml

@@ -13,6 +13,7 @@
13 13
   avatar: avatar1
14 14
   avatar_email: user1@example.com
15 15
   num_repos: 0
16
+  is_active: true
16 17
 
17 18
 -
18 19
   id: 2
@@ -62,6 +63,7 @@
62 63
   avatar_email: user4@example.com
63 64
   num_repos: 0
64 65
   num_following: 1
66
+  is_active: true
65 67
 
66 68
 -
67 69
   id: 5

+ 1 - 1
models/fixtures/watch.yml

@@ -10,5 +10,5 @@
10 10
 
11 11
 -
12 12
   id: 3
13
-  user_id: 10
13
+  user_id: 9
14 14
   repo_id: 1

+ 1 - 1
models/issue_watch_test.go

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

+ 5 - 2
models/notification_test.go

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

+ 0 - 17
models/org.go

@@ -201,23 +201,6 @@ func CountOrganizations() int64 {
201 201
 	return count
202 202
 }
203 203
 
204
-// Organizations returns number of organizations in given page.
205
-func Organizations(opts *SearchUserOptions) ([]*User, error) {
206
-	orgs := make([]*User, 0, opts.PageSize)
207
-
208
-	if len(opts.OrderBy) == 0 {
209
-		opts.OrderBy = "name ASC"
210
-	}
211
-
212
-	sess := x.
213
-		Limit(opts.PageSize, (opts.Page-1)*opts.PageSize).
214
-		Where("type=1")
215
-
216
-	return orgs, sess.
217
-		OrderBy(opts.OrderBy).
218
-		Find(&orgs)
219
-}
220
-
221 204
 // DeleteOrganization completely and permanently deletes everything of organization.
222 205
 func DeleteOrganization(org *User) (err error) {
223 206
 	sess := x.NewSession()

+ 0 - 21
models/org_test.go

@@ -237,27 +237,6 @@ func TestCountOrganizations(t *testing.T) {
237 237
 	assert.Equal(t, expected, CountOrganizations())
238 238
 }
239 239
 
240
-func TestOrganizations(t *testing.T) {
241
-	assert.NoError(t, PrepareTestDatabase())
242
-	testSuccess := func(opts *SearchUserOptions, expectedOrgIDs []int64) {
243
-		orgs, err := Organizations(opts)
244
-		assert.NoError(t, err)
245
-		if assert.Len(t, orgs, len(expectedOrgIDs)) {
246
-			for i, expectedOrgID := range expectedOrgIDs {
247
-				assert.EqualValues(t, expectedOrgID, orgs[i].ID)
248
-			}
249
-		}
250
-	}
251
-	testSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 1, PageSize: 2},
252
-		[]int64{3, 6})
253
-
254
-	testSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 2, PageSize: 2},
255
-		[]int64{7, 17})
256
-
257
-	testSuccess(&SearchUserOptions{Page: 3, PageSize: 2},
258
-		[]int64{})
259
-}
260
-
261 240
 func TestDeleteOrganization(t *testing.T) {
262 241
 	assert.NoError(t, PrepareTestDatabase())
263 242
 	org := AssertExistsAndLoadBean(t, &User{ID: 6}).(*User)

+ 11 - 5
models/repo_watch_test.go

@@ -40,8 +40,8 @@ func TestGetWatchers(t *testing.T) {
40 40
 	repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository)
41 41
 	watches, err := GetWatchers(repo.ID)
42 42
 	assert.NoError(t, err)
43
-	// Two watchers are inactive, thus minus 2
44
-	assert.Len(t, watches, repo.NumWatches-2)
43
+	// One watchers are inactive, thus minus 1
44
+	assert.Len(t, watches, repo.NumWatches-1)
45 45
 	for _, watch := range watches {
46 46
 		assert.EqualValues(t, repo.ID, watch.RepoID)
47 47
 	}
@@ -62,7 +62,7 @@ func TestRepository_GetWatchers(t *testing.T) {
62 62
 		AssertExistsAndLoadBean(t, &Watch{UserID: watcher.ID, RepoID: repo.ID})
63 63
 	}
64 64
 
65
-	repo = AssertExistsAndLoadBean(t, &Repository{ID: 10}).(*Repository)
65
+	repo = AssertExistsAndLoadBean(t, &Repository{ID: 9}).(*Repository)
66 66
 	watchers, err = repo.GetWatchers(1)
67 67
 	assert.NoError(t, err)
68 68
 	assert.Len(t, watchers, 0)
@@ -78,7 +78,7 @@ func TestNotifyWatchers(t *testing.T) {
78 78
 	}
79 79
 	assert.NoError(t, NotifyWatchers(action))
80 80
 
81
-	// Two watchers are inactive, thus action is only created for user 8, 10
81
+	// One watchers are inactive, thus action is only created for user 8, 1, 4
82 82
 	AssertExistsAndLoadBean(t, &Action{
83 83
 		ActUserID: action.ActUserID,
84 84
 		UserID:    8,
@@ -87,7 +87,13 @@ func TestNotifyWatchers(t *testing.T) {
87 87
 	})
88 88
 	AssertExistsAndLoadBean(t, &Action{
89 89
 		ActUserID: action.ActUserID,
90
-		UserID:    10,
90
+		UserID:    1,
91
+		RepoID:    action.RepoID,
92
+		OpType:    action.OpType,
93
+	})
94
+	AssertExistsAndLoadBean(t, &Action{
95
+		ActUserID: action.ActUserID,
96
+		UserID:    4,
91 97
 		RepoID:    action.RepoID,
92 98
 		OpType:    action.OpType,
93 99
 	})

+ 32 - 42
models/user.go

@@ -36,6 +36,7 @@ import (
36 36
 	"code.gitea.io/gitea/modules/base"
37 37
 	"code.gitea.io/gitea/modules/log"
38 38
 	"code.gitea.io/gitea/modules/setting"
39
+	"code.gitea.io/gitea/modules/util"
39 40
 )
40 41
 
41 42
 // UserType defines the user type
@@ -729,22 +730,6 @@ func CountUsers() int64 {
729 730
 	return countUsers(x)
730 731
 }
731 732
 
732
-// Users returns number of users in given page.
733
-func Users(opts *SearchUserOptions) ([]*User, error) {
734
-	if len(opts.OrderBy) == 0 {
735
-		opts.OrderBy = "name ASC"
736
-	}
737
-
738
-	users := make([]*User, 0, opts.PageSize)
739
-	sess := x.
740
-		Limit(opts.PageSize, (opts.Page-1)*opts.PageSize).
741
-		Where("type=0")
742
-
743
-	return users, sess.
744
-		OrderBy(opts.OrderBy).
745
-		Find(&users)
746
-}
747
-
748 733
 // get user by verify code
749 734
 func getVerifyUser(code string) (user *User) {
750 735
 	if len(code) <= base.TimeLimitCodeLength {
@@ -1284,45 +1269,50 @@ type SearchUserOptions struct {
1284 1269
 	OrderBy  string
1285 1270
 	Page     int
1286 1271
 	PageSize int // Can be smaller than or equal to setting.UI.ExplorePagingNum
1272
+	IsActive util.OptionalBool
1287 1273
 }
1288 1274
 
1289
-// SearchUserByName takes keyword and part of user name to search,
1290
-// it returns results in given range and number of total results.
1291
-func SearchUserByName(opts *SearchUserOptions) (users []*User, _ int64, _ error) {
1292
-	if len(opts.Keyword) == 0 {
1293
-		return users, 0, nil
1275
+func (opts *SearchUserOptions) toConds() builder.Cond {
1276
+	var cond builder.Cond = builder.Eq{"type": opts.Type}
1277
+	if len(opts.Keyword) > 0 {
1278
+		lowerKeyword := strings.ToLower(opts.Keyword)
1279
+		cond = cond.And(builder.Or(
1280
+			builder.Like{"lower_name", lowerKeyword},
1281
+			builder.Like{"LOWER(full_name)", lowerKeyword},
1282
+		))
1294 1283
 	}
1295
-	opts.Keyword = strings.ToLower(opts.Keyword)
1296 1284
 
1297
-	if opts.PageSize <= 0 || opts.PageSize > setting.UI.ExplorePagingNum {
1298
-		opts.PageSize = setting.UI.ExplorePagingNum
1299
-	}
1300
-	if opts.Page <= 0 {
1301
-		opts.Page = 1
1285
+	if !opts.IsActive.IsNone() {
1286
+		cond = cond.And(builder.Eq{"is_active": opts.IsActive.IsTrue()})
1302 1287
 	}
1303 1288
 
1304
-	users = make([]*User, 0, opts.PageSize)
1305
-
1306
-	// Append conditions
1307
-	cond := builder.And(
1308
-		builder.Eq{"type": opts.Type},
1309
-		builder.Or(
1310
-			builder.Like{"lower_name", opts.Keyword},
1311
-			builder.Like{"LOWER(full_name)", opts.Keyword},
1312
-		),
1313
-	)
1289
+	return cond
1290
+}
1314 1291
 
1292
+// SearchUsers takes options i.e. keyword and part of user name to search,
1293
+// it returns results in given range and number of total results.
1294
+func SearchUsers(opts *SearchUserOptions) (users []*User, _ int64, _ error) {
1295
+	cond := opts.toConds()
1315 1296
 	count, err := x.Where(cond).Count(new(User))
1316 1297
 	if err != nil {
1317 1298
 		return nil, 0, fmt.Errorf("Count: %v", err)
1318 1299
 	}
1319 1300
 
1320
-	sess := x.Where(cond).
1321
-		Limit(opts.PageSize, (opts.Page-1)*opts.PageSize)
1322
-	if len(opts.OrderBy) > 0 {
1323
-		sess.OrderBy(opts.OrderBy)
1301
+	if opts.PageSize <= 0 || opts.PageSize > setting.UI.ExplorePagingNum {
1302
+		opts.PageSize = setting.UI.ExplorePagingNum
1303
+	}
1304
+	if opts.Page <= 0 {
1305
+		opts.Page = 1
1324 1306
 	}
1325
-	return users, count, sess.Find(&users)
1307
+	if len(opts.OrderBy) == 0 {
1308
+		opts.OrderBy = "name ASC"
1309
+	}
1310
+
1311
+	users = make([]*User, 0, opts.PageSize)
1312
+	return users, count, x.Where(cond).
1313
+		Limit(opts.PageSize, (opts.Page-1)*opts.PageSize).
1314
+		OrderBy(opts.OrderBy).
1315
+		Find(&users)
1326 1316
 }
1327 1317
 
1328 1318
 // GetStarredRepos returns the repos starred by a particular user

+ 51 - 0
models/user_test.go

@@ -8,6 +8,7 @@ import (
8 8
 	"testing"
9 9
 
10 10
 	"code.gitea.io/gitea/modules/setting"
11
+	"code.gitea.io/gitea/modules/util"
11 12
 
12 13
 	"github.com/stretchr/testify/assert"
13 14
 )
@@ -38,6 +39,56 @@ func TestCanCreateOrganization(t *testing.T) {
38 39
 	assert.False(t, user.CanCreateOrganization())
39 40
 }
40 41
 
42
+func TestSearchUsers(t *testing.T) {
43
+	assert.NoError(t, PrepareTestDatabase())
44
+	testSuccess := func(opts *SearchUserOptions, expectedUserOrOrgIDs []int64) {
45
+		users, _, err := SearchUsers(opts)
46
+		assert.NoError(t, err)
47
+		if assert.Len(t, users, len(expectedUserOrOrgIDs)) {
48
+			for i, expectedID := range expectedUserOrOrgIDs {
49
+				assert.EqualValues(t, expectedID, users[i].ID)
50
+			}
51
+		}
52
+	}
53
+
54
+	// test orgs
55
+	testOrgSuccess := func(opts *SearchUserOptions, expectedOrgIDs []int64) {
56
+		opts.Type = UserTypeOrganization
57
+		testSuccess(opts, expectedOrgIDs)
58
+	}
59
+
60
+	testOrgSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 1, PageSize: 2},
61
+		[]int64{3, 6})
62
+
63
+	testOrgSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 2, PageSize: 2},
64
+		[]int64{7, 17})
65
+
66
+	testOrgSuccess(&SearchUserOptions{Page: 3, PageSize: 2},
67
+		[]int64{})
68
+
69
+	// test users
70
+	testUserSuccess := func(opts *SearchUserOptions, expectedUserIDs []int64) {
71
+		opts.Type = UserTypeIndividual
72
+		testSuccess(opts, expectedUserIDs)
73
+	}
74
+
75
+	testUserSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 1},
76
+		[]int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18})
77
+
78
+	testUserSuccess(&SearchUserOptions{Page: 1, IsActive: util.OptionalBoolFalse},
79
+		[]int64{9})
80
+
81
+	testUserSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 1, IsActive: util.OptionalBoolTrue},
82
+		[]int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18})
83
+
84
+	testUserSuccess(&SearchUserOptions{Keyword: "user1", OrderBy: "id ASC", Page: 1, IsActive: util.OptionalBoolTrue},
85
+		[]int64{1, 10, 11, 12, 13, 14, 15, 16, 18})
86
+
87
+	// order by name asc default
88
+	testUserSuccess(&SearchUserOptions{Keyword: "user1", Page: 1, IsActive: util.OptionalBoolTrue},
89
+		[]int64{1, 10, 11, 12, 13, 14, 15, 16, 18})
90
+}
91
+
41 92
 func TestDeleteUser(t *testing.T) {
42 93
 	test := func(userID int64) {
43 94
 		assert.NoError(t, PrepareTestDatabase())

+ 15 - 0
modules/util/util.go

@@ -16,6 +16,21 @@ const (
16 16
 	OptionalBoolFalse
17 17
 )
18 18
 
19
+// IsTrue return true if equal to OptionalBoolTrue
20
+func (o OptionalBool) IsTrue() bool {
21
+	return o == OptionalBoolTrue
22
+}
23
+
24
+// IsFalse return true if equal to OptionalBoolFalse
25
+func (o OptionalBool) IsFalse() bool {
26
+	return o == OptionalBoolFalse
27
+}
28
+
29
+// IsNone return true if equal to OptionalBoolNone
30
+func (o OptionalBool) IsNone() bool {
31
+	return o == OptionalBoolNone
32
+}
33
+
19 34
 // OptionalBoolOf get the corresponding OptionalBool of a bool
20 35
 func OptionalBoolOf(b bool) OptionalBool {
21 36
 	if b {

+ 2 - 5
routers/admin/orgs.go

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

+ 2 - 5
routers/admin/users.go

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

+ 1 - 1
routers/api/v1/user/user.go

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

+ 18 - 49
routers/home.go

@@ -12,6 +12,7 @@ import (
12 12
 	"code.gitea.io/gitea/modules/base"
13 13
 	"code.gitea.io/gitea/modules/context"
14 14
 	"code.gitea.io/gitea/modules/setting"
15
+	"code.gitea.io/gitea/modules/util"
15 16
 	"code.gitea.io/gitea/routers/user"
16 17
 
17 18
 	"github.com/Unknwon/paginater"
@@ -147,20 +148,11 @@ func ExploreRepos(ctx *context.Context) {
147 148
 	})
148 149
 }
149 150
 
150
-// UserSearchOptions options when render search user page
151
-type UserSearchOptions struct {
152
-	Type     models.UserType
153
-	Counter  func() int64
154
-	Ranger   func(*models.SearchUserOptions) ([]*models.User, error)
155
-	PageSize int
156
-	TplName  base.TplName
157
-}
158
-
159 151
 // RenderUserSearch render user search page
160
-func RenderUserSearch(ctx *context.Context, opts *UserSearchOptions) {
161
-	page := ctx.QueryInt("page")
162
-	if page <= 1 {
163
-		page = 1
152
+func RenderUserSearch(ctx *context.Context, opts *models.SearchUserOptions, tplName base.TplName) {
153
+	opts.Page = ctx.QueryInt("page")
154
+	if opts.Page <= 1 {
155
+		opts.Page = 1
164 156
 	}
165 157
 
166 158
 	var (
@@ -189,40 +181,22 @@ func RenderUserSearch(ctx *context.Context, opts *UserSearchOptions) {
189 181
 		orderBy = "name ASC"
190 182
 	}
191 183
 
192
-	keyword := strings.Trim(ctx.Query("q"), " ")
193
-	if len(keyword) == 0 {
194
-		users, err = opts.Ranger(&models.SearchUserOptions{
195
-			OrderBy:  orderBy,
196
-			Page:     page,
197
-			PageSize: opts.PageSize,
198
-		})
184
+	opts.Keyword = strings.Trim(ctx.Query("q"), " ")
185
+	opts.OrderBy = orderBy
186
+	if len(opts.Keyword) == 0 || isKeywordValid(opts.Keyword) {
187
+		users, count, err = models.SearchUsers(opts)
199 188
 		if err != nil {
200
-			ctx.Handle(500, "opts.Ranger", err)
189
+			ctx.Handle(500, "SearchUsers", err)
201 190
 			return
202 191
 		}
203
-		count = opts.Counter()
204
-	} else {
205
-		if isKeywordValid(keyword) {
206
-			users, count, err = models.SearchUserByName(&models.SearchUserOptions{
207
-				Keyword:  keyword,
208
-				Type:     opts.Type,
209
-				OrderBy:  orderBy,
210
-				Page:     page,
211
-				PageSize: opts.PageSize,
212
-			})
213
-			if err != nil {
214
-				ctx.Handle(500, "SearchUserByName", err)
215
-				return
216
-			}
217
-		}
218 192
 	}
219
-	ctx.Data["Keyword"] = keyword
193
+	ctx.Data["Keyword"] = opts.Keyword
220 194
 	ctx.Data["Total"] = count
221
-	ctx.Data["Page"] = paginater.New(int(count), opts.PageSize, page, 5)
195
+	ctx.Data["Page"] = paginater.New(int(count), opts.PageSize, opts.Page, 5)
222 196
 	ctx.Data["Users"] = users
223 197
 	ctx.Data["ShowUserEmail"] = setting.UI.ShowUserEmail
224 198
 
225
-	ctx.HTML(200, opts.TplName)
199
+	ctx.HTML(200, tplName)
226 200
 }
227 201
 
228 202
 // ExploreUsers render explore users page
@@ -231,13 +205,11 @@ func ExploreUsers(ctx *context.Context) {
231 205
 	ctx.Data["PageIsExplore"] = true
232 206
 	ctx.Data["PageIsExploreUsers"] = true
233 207
 
234
-	RenderUserSearch(ctx, &UserSearchOptions{
208
+	RenderUserSearch(ctx, &models.SearchUserOptions{
235 209
 		Type:     models.UserTypeIndividual,
236
-		Counter:  models.CountUsers,
237
-		Ranger:   models.Users,
238 210
 		PageSize: setting.UI.ExplorePagingNum,
239
-		TplName:  tplExploreUsers,
240
-	})
211
+		IsActive: util.OptionalBoolTrue,
212
+	}, tplExploreUsers)
241 213
 }
242 214
 
243 215
 // ExploreOrganizations render explore organizations page
@@ -246,13 +218,10 @@ func ExploreOrganizations(ctx *context.Context) {
246 218
 	ctx.Data["PageIsExplore"] = true
247 219
 	ctx.Data["PageIsExploreOrganizations"] = true
248 220
 
249
-	RenderUserSearch(ctx, &UserSearchOptions{
221
+	RenderUserSearch(ctx, &models.SearchUserOptions{
250 222
 		Type:     models.UserTypeOrganization,
251
-		Counter:  models.CountOrganizations,
252
-		Ranger:   models.Organizations,
253 223
 		PageSize: setting.UI.ExplorePagingNum,
254
-		TplName:  tplExploreOrganizations,
255
-	})
224
+	}, tplExploreOrganizations)
256 225
 }
257 226
 
258 227
 // NotFound render 404 page