mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-26 12:27:06 +00:00 
			
		
		
		
	Add pagination for dashboard and user activity feeds (#22937)
Previously only the last few activities where available. This works for all activity and for activity on a date chosen on the heatmap.
This commit is contained in:
		 Brecht Van Lommel
					Brecht Van Lommel
				
			
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			 GitHub
						GitHub
					
				
			
						parent
						
							740a5ecdd9
						
					
				
				
					commit
					f4920c9c7f
				
			| @@ -380,14 +380,14 @@ type GetFeedsOptions struct { | ||||
| } | ||||
|  | ||||
| // GetFeeds returns actions according to the provided options | ||||
| func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, error) { | ||||
| func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, error) { | ||||
| 	if opts.RequestedUser == nil && opts.RequestedTeam == nil && opts.RequestedRepo == nil { | ||||
| 		return nil, fmt.Errorf("need at least one of these filters: RequestedUser, RequestedTeam, RequestedRepo") | ||||
| 		return nil, 0, fmt.Errorf("need at least one of these filters: RequestedUser, RequestedTeam, RequestedRepo") | ||||
| 	} | ||||
|  | ||||
| 	cond, err := activityQueryCondition(opts) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 		return nil, 0, err | ||||
| 	} | ||||
|  | ||||
| 	sess := db.GetEngine(ctx).Where(cond). | ||||
| @@ -398,16 +398,16 @@ func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, error) { | ||||
| 	sess = db.SetSessionPagination(sess, &opts) | ||||
|  | ||||
| 	actions := make([]*Action, 0, opts.PageSize) | ||||
|  | ||||
| 	if err := sess.Desc("`action`.created_unix").Find(&actions); err != nil { | ||||
| 		return nil, fmt.Errorf("Find: %w", err) | ||||
| 	count, err := sess.Desc("`action`.created_unix").FindAndCount(&actions) | ||||
| 	if err != nil { | ||||
| 		return nil, 0, fmt.Errorf("FindAndCount: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	if err := ActionList(actions).loadAttributes(ctx); err != nil { | ||||
| 		return nil, fmt.Errorf("LoadAttributes: %w", err) | ||||
| 		return nil, 0, fmt.Errorf("LoadAttributes: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	return actions, nil | ||||
| 	return actions, count, nil | ||||
| } | ||||
|  | ||||
| // ActivityReadable return whether doer can read activities of user | ||||
|   | ||||
| @@ -44,7 +44,7 @@ func TestGetFeeds(t *testing.T) { | ||||
| 	assert.NoError(t, unittest.PrepareTestDatabase()) | ||||
| 	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) | ||||
|  | ||||
| 	actions, err := activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{ | ||||
| 	actions, count, err := activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{ | ||||
| 		RequestedUser:   user, | ||||
| 		Actor:           user, | ||||
| 		IncludePrivate:  true, | ||||
| @@ -56,8 +56,9 @@ func TestGetFeeds(t *testing.T) { | ||||
| 		assert.EqualValues(t, 1, actions[0].ID) | ||||
| 		assert.EqualValues(t, user.ID, actions[0].UserID) | ||||
| 	} | ||||
| 	assert.Equal(t, int64(1), count) | ||||
|  | ||||
| 	actions, err = activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{ | ||||
| 	actions, count, err = activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{ | ||||
| 		RequestedUser:   user, | ||||
| 		Actor:           user, | ||||
| 		IncludePrivate:  false, | ||||
| @@ -65,6 +66,7 @@ func TestGetFeeds(t *testing.T) { | ||||
| 	}) | ||||
| 	assert.NoError(t, err) | ||||
| 	assert.Len(t, actions, 0) | ||||
| 	assert.Equal(t, int64(0), count) | ||||
| } | ||||
|  | ||||
| func TestGetFeedsForRepos(t *testing.T) { | ||||
| @@ -74,38 +76,42 @@ func TestGetFeedsForRepos(t *testing.T) { | ||||
| 	pubRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 8}) | ||||
|  | ||||
| 	// private repo & no login | ||||
| 	actions, err := activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{ | ||||
| 	actions, count, err := activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{ | ||||
| 		RequestedRepo:  privRepo, | ||||
| 		IncludePrivate: true, | ||||
| 	}) | ||||
| 	assert.NoError(t, err) | ||||
| 	assert.Len(t, actions, 0) | ||||
| 	assert.Equal(t, int64(0), count) | ||||
|  | ||||
| 	// public repo & no login | ||||
| 	actions, err = activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{ | ||||
| 	actions, count, err = activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{ | ||||
| 		RequestedRepo:  pubRepo, | ||||
| 		IncludePrivate: true, | ||||
| 	}) | ||||
| 	assert.NoError(t, err) | ||||
| 	assert.Len(t, actions, 1) | ||||
| 	assert.Equal(t, int64(1), count) | ||||
|  | ||||
| 	// private repo and login | ||||
| 	actions, err = activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{ | ||||
| 	actions, count, err = activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{ | ||||
| 		RequestedRepo:  privRepo, | ||||
| 		IncludePrivate: true, | ||||
| 		Actor:          user, | ||||
| 	}) | ||||
| 	assert.NoError(t, err) | ||||
| 	assert.Len(t, actions, 1) | ||||
| 	assert.Equal(t, int64(1), count) | ||||
|  | ||||
| 	// public repo & login | ||||
| 	actions, err = activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{ | ||||
| 	actions, count, err = activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{ | ||||
| 		RequestedRepo:  pubRepo, | ||||
| 		IncludePrivate: true, | ||||
| 		Actor:          user, | ||||
| 	}) | ||||
| 	assert.NoError(t, err) | ||||
| 	assert.Len(t, actions, 1) | ||||
| 	assert.Equal(t, int64(1), count) | ||||
| } | ||||
|  | ||||
| func TestGetFeeds2(t *testing.T) { | ||||
| @@ -114,7 +120,7 @@ func TestGetFeeds2(t *testing.T) { | ||||
| 	org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) | ||||
| 	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) | ||||
|  | ||||
| 	actions, err := activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{ | ||||
| 	actions, count, err := activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{ | ||||
| 		RequestedUser:   org, | ||||
| 		Actor:           user, | ||||
| 		IncludePrivate:  true, | ||||
| @@ -127,8 +133,9 @@ func TestGetFeeds2(t *testing.T) { | ||||
| 		assert.EqualValues(t, 2, actions[0].ID) | ||||
| 		assert.EqualValues(t, org.ID, actions[0].UserID) | ||||
| 	} | ||||
| 	assert.Equal(t, int64(1), count) | ||||
|  | ||||
| 	actions, err = activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{ | ||||
| 	actions, count, err = activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{ | ||||
| 		RequestedUser:   org, | ||||
| 		Actor:           user, | ||||
| 		IncludePrivate:  false, | ||||
| @@ -137,6 +144,7 @@ func TestGetFeeds2(t *testing.T) { | ||||
| 	}) | ||||
| 	assert.NoError(t, err) | ||||
| 	assert.Len(t, actions, 0) | ||||
| 	assert.Equal(t, int64(0), count) | ||||
| } | ||||
|  | ||||
| func TestActivityReadable(t *testing.T) { | ||||
| @@ -224,13 +232,14 @@ func TestGetFeedsCorrupted(t *testing.T) { | ||||
| 		RepoID: 1700, | ||||
| 	}) | ||||
|  | ||||
| 	actions, err := activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{ | ||||
| 	actions, count, err := activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{ | ||||
| 		RequestedUser:  user, | ||||
| 		Actor:          user, | ||||
| 		IncludePrivate: true, | ||||
| 	}) | ||||
| 	assert.NoError(t, err) | ||||
| 	assert.Len(t, actions, 0) | ||||
| 	assert.Equal(t, int64(0), count) | ||||
| } | ||||
|  | ||||
| func TestConsistencyUpdateAction(t *testing.T) { | ||||
|   | ||||
| @@ -73,7 +73,7 @@ func TestGetUserHeatmapDataByUser(t *testing.T) { | ||||
| 		} | ||||
|  | ||||
| 		// get the action for comparison | ||||
| 		actions, err := activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{ | ||||
| 		actions, count, err := activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{ | ||||
| 			RequestedUser:   user, | ||||
| 			Actor:           doer, | ||||
| 			IncludePrivate:  true, | ||||
| @@ -90,6 +90,7 @@ func TestGetUserHeatmapDataByUser(t *testing.T) { | ||||
| 		} | ||||
| 		assert.NoError(t, err) | ||||
| 		assert.Len(t, actions, contributions, "invalid action count: did the test data became too old?") | ||||
| 		assert.Equal(t, count, int64(contributions)) | ||||
| 		assert.Equal(t, tc.CountResult, contributions, fmt.Sprintf("testcase '%s'", tc.desc)) | ||||
|  | ||||
| 		// Test JSON rendering | ||||
|   | ||||
| @@ -26,7 +26,7 @@ func ShowUserFeedAtom(ctx *context.Context) { | ||||
| func showUserFeed(ctx *context.Context, formatType string) { | ||||
| 	includePrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == ctx.ContextUser.ID) | ||||
|  | ||||
| 	actions, err := activities_model.GetFeeds(ctx, activities_model.GetFeedsOptions{ | ||||
| 	actions, _, err := activities_model.GetFeeds(ctx, activities_model.GetFeedsOptions{ | ||||
| 		RequestedUser:   ctx.ContextUser, | ||||
| 		Actor:           ctx.Doer, | ||||
| 		IncludePrivate:  includePrivate, | ||||
|   | ||||
| @@ -15,7 +15,7 @@ import ( | ||||
|  | ||||
| // ShowRepoFeed shows user activity on the repo as RSS / Atom feed | ||||
| func ShowRepoFeed(ctx *context.Context, repo *repo_model.Repository, formatType string) { | ||||
| 	actions, err := activities_model.GetFeeds(ctx, activities_model.GetFeedsOptions{ | ||||
| 	actions, _, err := activities_model.GetFeeds(ctx, activities_model.GetFeedsOptions{ | ||||
| 		RequestedRepo:  repo, | ||||
| 		Actor:          ctx.Doer, | ||||
| 		IncludePrivate: true, | ||||
|   | ||||
| @@ -72,12 +72,23 @@ func Dashboard(ctx *context.Context) { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	var ( | ||||
| 		date = ctx.FormString("date") | ||||
| 		page = ctx.FormInt("page") | ||||
| 	) | ||||
|  | ||||
| 	// Make sure page number is at least 1. Will be posted to ctx.Data. | ||||
| 	if page <= 1 { | ||||
| 		page = 1 | ||||
| 	} | ||||
|  | ||||
| 	ctx.Data["Title"] = ctxUser.DisplayName() + " - " + ctx.Tr("dashboard") | ||||
| 	ctx.Data["PageIsDashboard"] = true | ||||
| 	ctx.Data["PageIsNews"] = true | ||||
| 	cnt, _ := organization.GetOrganizationCount(ctx, ctxUser) | ||||
| 	ctx.Data["UserOrgsCount"] = cnt | ||||
| 	ctx.Data["MirrorsEnabled"] = setting.Mirror.Enabled | ||||
| 	ctx.Data["Date"] = date | ||||
|  | ||||
| 	var uid int64 | ||||
| 	if ctxUser != nil { | ||||
| @@ -98,8 +109,7 @@ func Dashboard(ctx *context.Context) { | ||||
| 		ctx.Data["HeatmapData"] = data | ||||
| 	} | ||||
|  | ||||
| 	var err error | ||||
| 	ctx.Data["Feeds"], err = activities_model.GetFeeds(ctx, activities_model.GetFeedsOptions{ | ||||
| 	feeds, count, err := activities_model.GetFeeds(ctx, activities_model.GetFeedsOptions{ | ||||
| 		RequestedUser:   ctxUser, | ||||
| 		RequestedTeam:   ctx.Org.Team, | ||||
| 		Actor:           ctx.Doer, | ||||
| @@ -107,13 +117,22 @@ func Dashboard(ctx *context.Context) { | ||||
| 		OnlyPerformedBy: false, | ||||
| 		IncludeDeleted:  false, | ||||
| 		Date:            ctx.FormString("date"), | ||||
| 		ListOptions:     db.ListOptions{PageSize: setting.UI.FeedPagingNum}, | ||||
| 		ListOptions: db.ListOptions{ | ||||
| 			Page:     page, | ||||
| 			PageSize: setting.UI.FeedPagingNum, | ||||
| 		}, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		ctx.ServerError("GetFeeds", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	ctx.Data["Feeds"] = feeds | ||||
|  | ||||
| 	pager := context.NewPagination(int(count), setting.UI.FeedPagingNum, page, 5) | ||||
| 	pager.AddParam(ctx, "date", "Date") | ||||
| 	ctx.Data["Page"] = pager | ||||
|  | ||||
| 	ctx.HTML(http.StatusOK, tplDashboard) | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -119,6 +119,11 @@ func Profile(ctx *context.Context) { | ||||
| 		page = 1 | ||||
| 	} | ||||
|  | ||||
| 	pagingNum := setting.UI.User.RepoPagingNum | ||||
| 	if tab == "activity" { | ||||
| 		pagingNum = setting.UI.FeedPagingNum | ||||
| 	} | ||||
|  | ||||
| 	topicOnly := ctx.FormBool("topic") | ||||
|  | ||||
| 	var ( | ||||
| @@ -164,7 +169,7 @@ func Profile(ctx *context.Context) { | ||||
| 	switch tab { | ||||
| 	case "followers": | ||||
| 		items, count, err := user_model.GetUserFollowers(ctx, ctx.ContextUser, ctx.Doer, db.ListOptions{ | ||||
| 			PageSize: setting.UI.User.RepoPagingNum, | ||||
| 			PageSize: pagingNum, | ||||
| 			Page:     page, | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| @@ -176,7 +181,7 @@ func Profile(ctx *context.Context) { | ||||
| 		total = int(count) | ||||
| 	case "following": | ||||
| 		items, count, err := user_model.GetUserFollowing(ctx, ctx.ContextUser, ctx.Doer, db.ListOptions{ | ||||
| 			PageSize: setting.UI.User.RepoPagingNum, | ||||
| 			PageSize: pagingNum, | ||||
| 			Page:     page, | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| @@ -187,24 +192,32 @@ func Profile(ctx *context.Context) { | ||||
|  | ||||
| 		total = int(count) | ||||
| 	case "activity": | ||||
| 		ctx.Data["Feeds"], err = activities_model.GetFeeds(ctx, activities_model.GetFeedsOptions{ | ||||
| 		date := ctx.FormString("date") | ||||
| 		items, count, err := activities_model.GetFeeds(ctx, activities_model.GetFeedsOptions{ | ||||
| 			RequestedUser:   ctx.ContextUser, | ||||
| 			Actor:           ctx.Doer, | ||||
| 			IncludePrivate:  showPrivate, | ||||
| 			OnlyPerformedBy: true, | ||||
| 			IncludeDeleted:  false, | ||||
| 			Date:            ctx.FormString("date"), | ||||
| 			ListOptions:     db.ListOptions{PageSize: setting.UI.FeedPagingNum}, | ||||
| 			Date:            date, | ||||
| 			ListOptions: db.ListOptions{ | ||||
| 				PageSize: pagingNum, | ||||
| 				Page:     page, | ||||
| 			}, | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			ctx.ServerError("GetFeeds", err) | ||||
| 			return | ||||
| 		} | ||||
| 		ctx.Data["Feeds"] = items | ||||
| 		ctx.Data["Date"] = date | ||||
|  | ||||
| 		total = int(count) | ||||
| 	case "stars": | ||||
| 		ctx.Data["PageIsProfileStarList"] = true | ||||
| 		repos, count, err = repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{ | ||||
| 			ListOptions: db.ListOptions{ | ||||
| 				PageSize: setting.UI.User.RepoPagingNum, | ||||
| 				PageSize: pagingNum, | ||||
| 				Page:     page, | ||||
| 			}, | ||||
| 			Actor:              ctx.Doer, | ||||
| @@ -236,7 +249,7 @@ func Profile(ctx *context.Context) { | ||||
| 	case "watching": | ||||
| 		repos, count, err = repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{ | ||||
| 			ListOptions: db.ListOptions{ | ||||
| 				PageSize: setting.UI.User.RepoPagingNum, | ||||
| 				PageSize: pagingNum, | ||||
| 				Page:     page, | ||||
| 			}, | ||||
| 			Actor:              ctx.Doer, | ||||
| @@ -258,7 +271,7 @@ func Profile(ctx *context.Context) { | ||||
| 	default: | ||||
| 		repos, count, err = repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{ | ||||
| 			ListOptions: db.ListOptions{ | ||||
| 				PageSize: setting.UI.User.RepoPagingNum, | ||||
| 				PageSize: pagingNum, | ||||
| 				Page:     page, | ||||
| 			}, | ||||
| 			Actor:              ctx.Doer, | ||||
| @@ -281,12 +294,15 @@ func Profile(ctx *context.Context) { | ||||
| 	ctx.Data["Repos"] = repos | ||||
| 	ctx.Data["Total"] = total | ||||
|  | ||||
| 	pager := context.NewPagination(total, setting.UI.User.RepoPagingNum, page, 5) | ||||
| 	pager := context.NewPagination(total, pagingNum, page, 5) | ||||
| 	pager.SetDefaultParams(ctx) | ||||
| 	pager.AddParam(ctx, "tab", "TabName") | ||||
| 	if tab != "followers" && tab != "following" && tab != "activity" && tab != "projects" { | ||||
| 		pager.AddParam(ctx, "language", "Language") | ||||
| 	} | ||||
| 	if tab == "activity" { | ||||
| 		pager.AddParam(ctx, "date", "Date") | ||||
| 	} | ||||
| 	ctx.Data["Page"] = pager | ||||
| 	ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled | ||||
| 	ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled | ||||
|   | ||||
| @@ -124,3 +124,5 @@ | ||||
| 		<div class="ui divider"></div> | ||||
| 	</div> | ||||
| {{end}} | ||||
|  | ||||
| {{template "base/paginate" .}} | ||||
|   | ||||
| @@ -70,6 +70,8 @@ export default { | ||||
|         params.set('date', clickedDate); | ||||
|       } | ||||
|  | ||||
|       params.delete('page'); | ||||
|  | ||||
|       const newSearch = params.toString(); | ||||
|       window.location.search = newSearch.length ? `?${newSearch}` : ''; | ||||
|     } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user