Skip to content

Commit f4920c9

Browse files
authored
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.
1 parent 740a5ec commit f4920c9

File tree

9 files changed

+81
-32
lines changed

9 files changed

+81
-32
lines changed

models/activities/action.go

+8-8
Original file line numberDiff line numberDiff line change
@@ -380,14 +380,14 @@ type GetFeedsOptions struct {
380380
}
381381

382382
// GetFeeds returns actions according to the provided options
383-
func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, error) {
383+
func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, error) {
384384
if opts.RequestedUser == nil && opts.RequestedTeam == nil && opts.RequestedRepo == nil {
385-
return nil, fmt.Errorf("need at least one of these filters: RequestedUser, RequestedTeam, RequestedRepo")
385+
return nil, 0, fmt.Errorf("need at least one of these filters: RequestedUser, RequestedTeam, RequestedRepo")
386386
}
387387

388388
cond, err := activityQueryCondition(opts)
389389
if err != nil {
390-
return nil, err
390+
return nil, 0, err
391391
}
392392

393393
sess := db.GetEngine(ctx).Where(cond).
@@ -398,16 +398,16 @@ func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, error) {
398398
sess = db.SetSessionPagination(sess, &opts)
399399

400400
actions := make([]*Action, 0, opts.PageSize)
401-
402-
if err := sess.Desc("`action`.created_unix").Find(&actions); err != nil {
403-
return nil, fmt.Errorf("Find: %w", err)
401+
count, err := sess.Desc("`action`.created_unix").FindAndCount(&actions)
402+
if err != nil {
403+
return nil, 0, fmt.Errorf("FindAndCount: %w", err)
404404
}
405405

406406
if err := ActionList(actions).loadAttributes(ctx); err != nil {
407-
return nil, fmt.Errorf("LoadAttributes: %w", err)
407+
return nil, 0, fmt.Errorf("LoadAttributes: %w", err)
408408
}
409409

410-
return actions, nil
410+
return actions, count, nil
411411
}
412412

413413
// ActivityReadable return whether doer can read activities of user

models/activities/action_test.go

+18-9
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func TestGetFeeds(t *testing.T) {
4444
assert.NoError(t, unittest.PrepareTestDatabase())
4545
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
4646

47-
actions, err := activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{
47+
actions, count, err := activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{
4848
RequestedUser: user,
4949
Actor: user,
5050
IncludePrivate: true,
@@ -56,15 +56,17 @@ func TestGetFeeds(t *testing.T) {
5656
assert.EqualValues(t, 1, actions[0].ID)
5757
assert.EqualValues(t, user.ID, actions[0].UserID)
5858
}
59+
assert.Equal(t, int64(1), count)
5960

60-
actions, err = activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{
61+
actions, count, err = activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{
6162
RequestedUser: user,
6263
Actor: user,
6364
IncludePrivate: false,
6465
OnlyPerformedBy: false,
6566
})
6667
assert.NoError(t, err)
6768
assert.Len(t, actions, 0)
69+
assert.Equal(t, int64(0), count)
6870
}
6971

7072
func TestGetFeedsForRepos(t *testing.T) {
@@ -74,38 +76,42 @@ func TestGetFeedsForRepos(t *testing.T) {
7476
pubRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 8})
7577

7678
// private repo & no login
77-
actions, err := activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{
79+
actions, count, err := activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{
7880
RequestedRepo: privRepo,
7981
IncludePrivate: true,
8082
})
8183
assert.NoError(t, err)
8284
assert.Len(t, actions, 0)
85+
assert.Equal(t, int64(0), count)
8386

8487
// public repo & no login
85-
actions, err = activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{
88+
actions, count, err = activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{
8689
RequestedRepo: pubRepo,
8790
IncludePrivate: true,
8891
})
8992
assert.NoError(t, err)
9093
assert.Len(t, actions, 1)
94+
assert.Equal(t, int64(1), count)
9195

9296
// private repo and login
93-
actions, err = activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{
97+
actions, count, err = activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{
9498
RequestedRepo: privRepo,
9599
IncludePrivate: true,
96100
Actor: user,
97101
})
98102
assert.NoError(t, err)
99103
assert.Len(t, actions, 1)
104+
assert.Equal(t, int64(1), count)
100105

101106
// public repo & login
102-
actions, err = activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{
107+
actions, count, err = activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{
103108
RequestedRepo: pubRepo,
104109
IncludePrivate: true,
105110
Actor: user,
106111
})
107112
assert.NoError(t, err)
108113
assert.Len(t, actions, 1)
114+
assert.Equal(t, int64(1), count)
109115
}
110116

111117
func TestGetFeeds2(t *testing.T) {
@@ -114,7 +120,7 @@ func TestGetFeeds2(t *testing.T) {
114120
org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
115121
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
116122

117-
actions, err := activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{
123+
actions, count, err := activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{
118124
RequestedUser: org,
119125
Actor: user,
120126
IncludePrivate: true,
@@ -127,8 +133,9 @@ func TestGetFeeds2(t *testing.T) {
127133
assert.EqualValues(t, 2, actions[0].ID)
128134
assert.EqualValues(t, org.ID, actions[0].UserID)
129135
}
136+
assert.Equal(t, int64(1), count)
130137

131-
actions, err = activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{
138+
actions, count, err = activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{
132139
RequestedUser: org,
133140
Actor: user,
134141
IncludePrivate: false,
@@ -137,6 +144,7 @@ func TestGetFeeds2(t *testing.T) {
137144
})
138145
assert.NoError(t, err)
139146
assert.Len(t, actions, 0)
147+
assert.Equal(t, int64(0), count)
140148
}
141149

142150
func TestActivityReadable(t *testing.T) {
@@ -224,13 +232,14 @@ func TestGetFeedsCorrupted(t *testing.T) {
224232
RepoID: 1700,
225233
})
226234

227-
actions, err := activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{
235+
actions, count, err := activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{
228236
RequestedUser: user,
229237
Actor: user,
230238
IncludePrivate: true,
231239
})
232240
assert.NoError(t, err)
233241
assert.Len(t, actions, 0)
242+
assert.Equal(t, int64(0), count)
234243
}
235244

236245
func TestConsistencyUpdateAction(t *testing.T) {

models/activities/user_heatmap_test.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ func TestGetUserHeatmapDataByUser(t *testing.T) {
7373
}
7474

7575
// get the action for comparison
76-
actions, err := activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{
76+
actions, count, err := activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{
7777
RequestedUser: user,
7878
Actor: doer,
7979
IncludePrivate: true,
@@ -90,6 +90,7 @@ func TestGetUserHeatmapDataByUser(t *testing.T) {
9090
}
9191
assert.NoError(t, err)
9292
assert.Len(t, actions, contributions, "invalid action count: did the test data became too old?")
93+
assert.Equal(t, count, int64(contributions))
9394
assert.Equal(t, tc.CountResult, contributions, fmt.Sprintf("testcase '%s'", tc.desc))
9495

9596
// Test JSON rendering

routers/web/feed/profile.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ func ShowUserFeedAtom(ctx *context.Context) {
2626
func showUserFeed(ctx *context.Context, formatType string) {
2727
includePrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == ctx.ContextUser.ID)
2828

29-
actions, err := activities_model.GetFeeds(ctx, activities_model.GetFeedsOptions{
29+
actions, _, err := activities_model.GetFeeds(ctx, activities_model.GetFeedsOptions{
3030
RequestedUser: ctx.ContextUser,
3131
Actor: ctx.Doer,
3232
IncludePrivate: includePrivate,

routers/web/feed/repo.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import (
1515

1616
// ShowRepoFeed shows user activity on the repo as RSS / Atom feed
1717
func ShowRepoFeed(ctx *context.Context, repo *repo_model.Repository, formatType string) {
18-
actions, err := activities_model.GetFeeds(ctx, activities_model.GetFeedsOptions{
18+
actions, _, err := activities_model.GetFeeds(ctx, activities_model.GetFeedsOptions{
1919
RequestedRepo: repo,
2020
Actor: ctx.Doer,
2121
IncludePrivate: true,

routers/web/user/home.go

+22-3
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,23 @@ func Dashboard(ctx *context.Context) {
7272
return
7373
}
7474

75+
var (
76+
date = ctx.FormString("date")
77+
page = ctx.FormInt("page")
78+
)
79+
80+
// Make sure page number is at least 1. Will be posted to ctx.Data.
81+
if page <= 1 {
82+
page = 1
83+
}
84+
7585
ctx.Data["Title"] = ctxUser.DisplayName() + " - " + ctx.Tr("dashboard")
7686
ctx.Data["PageIsDashboard"] = true
7787
ctx.Data["PageIsNews"] = true
7888
cnt, _ := organization.GetOrganizationCount(ctx, ctxUser)
7989
ctx.Data["UserOrgsCount"] = cnt
8090
ctx.Data["MirrorsEnabled"] = setting.Mirror.Enabled
91+
ctx.Data["Date"] = date
8192

8293
var uid int64
8394
if ctxUser != nil {
@@ -98,22 +109,30 @@ func Dashboard(ctx *context.Context) {
98109
ctx.Data["HeatmapData"] = data
99110
}
100111

101-
var err error
102-
ctx.Data["Feeds"], err = activities_model.GetFeeds(ctx, activities_model.GetFeedsOptions{
112+
feeds, count, err := activities_model.GetFeeds(ctx, activities_model.GetFeedsOptions{
103113
RequestedUser: ctxUser,
104114
RequestedTeam: ctx.Org.Team,
105115
Actor: ctx.Doer,
106116
IncludePrivate: true,
107117
OnlyPerformedBy: false,
108118
IncludeDeleted: false,
109119
Date: ctx.FormString("date"),
110-
ListOptions: db.ListOptions{PageSize: setting.UI.FeedPagingNum},
120+
ListOptions: db.ListOptions{
121+
Page: page,
122+
PageSize: setting.UI.FeedPagingNum,
123+
},
111124
})
112125
if err != nil {
113126
ctx.ServerError("GetFeeds", err)
114127
return
115128
}
116129

130+
ctx.Data["Feeds"] = feeds
131+
132+
pager := context.NewPagination(int(count), setting.UI.FeedPagingNum, page, 5)
133+
pager.AddParam(ctx, "date", "Date")
134+
ctx.Data["Page"] = pager
135+
117136
ctx.HTML(http.StatusOK, tplDashboard)
118137
}
119138

routers/web/user/profile.go

+25-9
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,11 @@ func Profile(ctx *context.Context) {
119119
page = 1
120120
}
121121

122+
pagingNum := setting.UI.User.RepoPagingNum
123+
if tab == "activity" {
124+
pagingNum = setting.UI.FeedPagingNum
125+
}
126+
122127
topicOnly := ctx.FormBool("topic")
123128

124129
var (
@@ -164,7 +169,7 @@ func Profile(ctx *context.Context) {
164169
switch tab {
165170
case "followers":
166171
items, count, err := user_model.GetUserFollowers(ctx, ctx.ContextUser, ctx.Doer, db.ListOptions{
167-
PageSize: setting.UI.User.RepoPagingNum,
172+
PageSize: pagingNum,
168173
Page: page,
169174
})
170175
if err != nil {
@@ -176,7 +181,7 @@ func Profile(ctx *context.Context) {
176181
total = int(count)
177182
case "following":
178183
items, count, err := user_model.GetUserFollowing(ctx, ctx.ContextUser, ctx.Doer, db.ListOptions{
179-
PageSize: setting.UI.User.RepoPagingNum,
184+
PageSize: pagingNum,
180185
Page: page,
181186
})
182187
if err != nil {
@@ -187,24 +192,32 @@ func Profile(ctx *context.Context) {
187192

188193
total = int(count)
189194
case "activity":
190-
ctx.Data["Feeds"], err = activities_model.GetFeeds(ctx, activities_model.GetFeedsOptions{
195+
date := ctx.FormString("date")
196+
items, count, err := activities_model.GetFeeds(ctx, activities_model.GetFeedsOptions{
191197
RequestedUser: ctx.ContextUser,
192198
Actor: ctx.Doer,
193199
IncludePrivate: showPrivate,
194200
OnlyPerformedBy: true,
195201
IncludeDeleted: false,
196-
Date: ctx.FormString("date"),
197-
ListOptions: db.ListOptions{PageSize: setting.UI.FeedPagingNum},
202+
Date: date,
203+
ListOptions: db.ListOptions{
204+
PageSize: pagingNum,
205+
Page: page,
206+
},
198207
})
199208
if err != nil {
200209
ctx.ServerError("GetFeeds", err)
201210
return
202211
}
212+
ctx.Data["Feeds"] = items
213+
ctx.Data["Date"] = date
214+
215+
total = int(count)
203216
case "stars":
204217
ctx.Data["PageIsProfileStarList"] = true
205218
repos, count, err = repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{
206219
ListOptions: db.ListOptions{
207-
PageSize: setting.UI.User.RepoPagingNum,
220+
PageSize: pagingNum,
208221
Page: page,
209222
},
210223
Actor: ctx.Doer,
@@ -236,7 +249,7 @@ func Profile(ctx *context.Context) {
236249
case "watching":
237250
repos, count, err = repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{
238251
ListOptions: db.ListOptions{
239-
PageSize: setting.UI.User.RepoPagingNum,
252+
PageSize: pagingNum,
240253
Page: page,
241254
},
242255
Actor: ctx.Doer,
@@ -258,7 +271,7 @@ func Profile(ctx *context.Context) {
258271
default:
259272
repos, count, err = repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{
260273
ListOptions: db.ListOptions{
261-
PageSize: setting.UI.User.RepoPagingNum,
274+
PageSize: pagingNum,
262275
Page: page,
263276
},
264277
Actor: ctx.Doer,
@@ -281,12 +294,15 @@ func Profile(ctx *context.Context) {
281294
ctx.Data["Repos"] = repos
282295
ctx.Data["Total"] = total
283296

284-
pager := context.NewPagination(total, setting.UI.User.RepoPagingNum, page, 5)
297+
pager := context.NewPagination(total, pagingNum, page, 5)
285298
pager.SetDefaultParams(ctx)
286299
pager.AddParam(ctx, "tab", "TabName")
287300
if tab != "followers" && tab != "following" && tab != "activity" && tab != "projects" {
288301
pager.AddParam(ctx, "language", "Language")
289302
}
303+
if tab == "activity" {
304+
pager.AddParam(ctx, "date", "Date")
305+
}
290306
ctx.Data["Page"] = pager
291307
ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled
292308
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled

templates/user/dashboard/feeds.tmpl

+2
Original file line numberDiff line numberDiff line change
@@ -124,3 +124,5 @@
124124
<div class="ui divider"></div>
125125
</div>
126126
{{end}}
127+
128+
{{template "base/paginate" .}}

web_src/js/components/ActivityHeatmap.vue

+2
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ export default {
7070
params.set('date', clickedDate);
7171
}
7272
73+
params.delete('page');
74+
7375
const newSearch = params.toString();
7476
window.location.search = newSearch.length ? `?${newSearch}` : '';
7577
}

0 commit comments

Comments
 (0)