Skip to content

Commit 08609d4

Browse files
qwerty287lafriks6543wxiaoguang
authored
Add pages to view watched repos and subscribed issues/PRs (#17156)
Adds GitHub-like pages to view watched repos and subscribed issues/PRs This is my second try to fix this, but it is better than the first since it doesn't uses a filter option which could be slow when accessing `/issues` or `/pulls` and it shows both pulls and issues (the first try is #17053). Closes #16111 Replaces and closes #17053 ![Screenshot](https://user-images.githubusercontent.com/80460567/134782937-3112f7da-425a-45b6-9511-5c9695aee896.png) Co-authored-by: Lauris BH <[email protected]> Co-authored-by: 6543 <[email protected]> Co-authored-by: wxiaoguang <[email protected]>
1 parent 3b6a7e5 commit 08609d4

File tree

6 files changed

+334
-2
lines changed

6 files changed

+334
-2
lines changed

models/issues/issue.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1186,6 +1186,7 @@ type IssuesOptions struct { //nolint
11861186
PosterID int64
11871187
MentionedID int64
11881188
ReviewRequestedID int64
1189+
SubscriberID int64
11891190
MilestoneIDs []int64
11901191
ProjectID int64
11911192
ProjectBoardID int64
@@ -1299,6 +1300,10 @@ func (opts *IssuesOptions) setupSessionNoLimit(sess *xorm.Session) {
12991300
applyReviewRequestedCondition(sess, opts.ReviewRequestedID)
13001301
}
13011302

1303+
if opts.SubscriberID > 0 {
1304+
applySubscribedCondition(sess, opts.SubscriberID)
1305+
}
1306+
13021307
if len(opts.MilestoneIDs) > 0 {
13031308
sess.In("issue.milestone_id", opts.MilestoneIDs)
13041309
}
@@ -1463,6 +1468,36 @@ func applyReviewRequestedCondition(sess *xorm.Session, reviewRequestedID int64)
14631468
reviewRequestedID, ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest, reviewRequestedID)
14641469
}
14651470

1471+
func applySubscribedCondition(sess *xorm.Session, subscriberID int64) *xorm.Session {
1472+
return sess.And(
1473+
builder.
1474+
NotIn("issue.id",
1475+
builder.Select("issue_id").
1476+
From("issue_watch").
1477+
Where(builder.Eq{"is_watching": false, "user_id": subscriberID}),
1478+
),
1479+
).And(
1480+
builder.Or(
1481+
builder.In("issue.id", builder.
1482+
Select("issue_id").
1483+
From("issue_watch").
1484+
Where(builder.Eq{"is_watching": true, "user_id": subscriberID}),
1485+
),
1486+
builder.In("issue.id", builder.
1487+
Select("issue_id").
1488+
From("comment").
1489+
Where(builder.Eq{"poster_id": subscriberID}),
1490+
),
1491+
builder.Eq{"issue.poster_id": subscriberID},
1492+
builder.In("issue.repo_id", builder.
1493+
Select("id").
1494+
From("watch").
1495+
Where(builder.Eq{"user_id": subscriberID, "mode": true}),
1496+
),
1497+
),
1498+
)
1499+
}
1500+
14661501
// CountIssuesByRepo map from repoID to number of issues matching the options
14671502
func CountIssuesByRepo(opts *IssuesOptions) (map[int64]int64, error) {
14681503
e := db.GetEngine(db.DefaultContext)

options/locale/locale_en-US.ini

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3034,6 +3034,9 @@ pin = Pin notification
30343034
mark_as_read = Mark as read
30353035
mark_as_unread = Mark as unread
30363036
mark_all_as_read = Mark all as read
3037+
subscriptions = Subscriptions
3038+
watching = Watching
3039+
no_subscriptions = No subscriptions
30373040

30383041
[gpg]
30393042
default_key=Signed with default key

routers/web/user/notification.go

Lines changed: 211 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,23 @@ import (
1313
"strings"
1414

1515
activities_model "code.gitea.io/gitea/models/activities"
16+
"code.gitea.io/gitea/models/db"
17+
issues_model "code.gitea.io/gitea/models/issues"
18+
repo_model "code.gitea.io/gitea/models/repo"
1619
"code.gitea.io/gitea/modules/base"
1720
"code.gitea.io/gitea/modules/context"
1821
"code.gitea.io/gitea/modules/log"
1922
"code.gitea.io/gitea/modules/setting"
2023
"code.gitea.io/gitea/modules/structs"
24+
"code.gitea.io/gitea/modules/util"
25+
issue_service "code.gitea.io/gitea/services/issue"
26+
pull_service "code.gitea.io/gitea/services/pull"
2127
)
2228

2329
const (
24-
tplNotification base.TplName = "user/notification/notification"
25-
tplNotificationDiv base.TplName = "user/notification/notification_div"
30+
tplNotification base.TplName = "user/notification/notification"
31+
tplNotificationDiv base.TplName = "user/notification/notification_div"
32+
tplNotificationSubscriptions base.TplName = "user/notification/notification_subscriptions"
2633
)
2734

2835
// GetNotificationCount is the middleware that sets the notification count in the context
@@ -197,6 +204,208 @@ func NotificationPurgePost(c *context.Context) {
197204
c.Redirect(setting.AppSubURL+"/notifications", http.StatusSeeOther)
198205
}
199206

207+
// NotificationSubscriptions returns the list of subscribed issues
208+
func NotificationSubscriptions(c *context.Context) {
209+
page := c.FormInt("page")
210+
if page < 1 {
211+
page = 1
212+
}
213+
214+
sortType := c.FormString("sort")
215+
c.Data["SortType"] = sortType
216+
217+
state := c.FormString("state")
218+
if !util.IsStringInSlice(state, []string{"all", "open", "closed"}, true) {
219+
state = "all"
220+
}
221+
c.Data["State"] = state
222+
var showClosed util.OptionalBool
223+
switch state {
224+
case "all":
225+
showClosed = util.OptionalBoolNone
226+
case "closed":
227+
showClosed = util.OptionalBoolTrue
228+
case "open":
229+
showClosed = util.OptionalBoolFalse
230+
}
231+
232+
var issueTypeBool util.OptionalBool
233+
issueType := c.FormString("issueType")
234+
switch issueType {
235+
case "issues":
236+
issueTypeBool = util.OptionalBoolFalse
237+
case "pulls":
238+
issueTypeBool = util.OptionalBoolTrue
239+
default:
240+
issueTypeBool = util.OptionalBoolNone
241+
}
242+
c.Data["IssueType"] = issueType
243+
244+
var labelIDs []int64
245+
selectedLabels := c.FormString("labels")
246+
c.Data["Labels"] = selectedLabels
247+
if len(selectedLabels) > 0 && selectedLabels != "0" {
248+
var err error
249+
labelIDs, err = base.StringsToInt64s(strings.Split(selectedLabels, ","))
250+
if err != nil {
251+
c.ServerError("StringsToInt64s", err)
252+
return
253+
}
254+
}
255+
256+
count, err := issues_model.CountIssues(&issues_model.IssuesOptions{
257+
SubscriberID: c.Doer.ID,
258+
IsClosed: showClosed,
259+
IsPull: issueTypeBool,
260+
LabelIDs: labelIDs,
261+
})
262+
if err != nil {
263+
c.ServerError("CountIssues", err)
264+
return
265+
}
266+
issues, err := issues_model.Issues(&issues_model.IssuesOptions{
267+
ListOptions: db.ListOptions{
268+
PageSize: setting.UI.IssuePagingNum,
269+
Page: page,
270+
},
271+
SubscriberID: c.Doer.ID,
272+
SortType: sortType,
273+
IsClosed: showClosed,
274+
IsPull: issueTypeBool,
275+
LabelIDs: labelIDs,
276+
})
277+
if err != nil {
278+
c.ServerError("Issues", err)
279+
return
280+
}
281+
282+
commitStatuses, lastStatus, err := pull_service.GetIssuesAllCommitStatus(c, issues)
283+
if err != nil {
284+
c.ServerError("GetIssuesAllCommitStatus", err)
285+
return
286+
}
287+
c.Data["CommitLastStatus"] = lastStatus
288+
c.Data["CommitStatuses"] = commitStatuses
289+
c.Data["Issues"] = issues
290+
291+
c.Data["IssueRefEndNames"], c.Data["IssueRefURLs"] = issue_service.GetRefEndNamesAndURLs(issues, "")
292+
293+
commitStatus, err := pull_service.GetIssuesLastCommitStatus(c, issues)
294+
if err != nil {
295+
c.ServerError("GetIssuesLastCommitStatus", err)
296+
return
297+
}
298+
c.Data["CommitStatus"] = commitStatus
299+
300+
issueList := issues_model.IssueList(issues)
301+
approvalCounts, err := issueList.GetApprovalCounts(c)
302+
if err != nil {
303+
c.ServerError("ApprovalCounts", err)
304+
return
305+
}
306+
c.Data["ApprovalCounts"] = func(issueID int64, typ string) int64 {
307+
counts, ok := approvalCounts[issueID]
308+
if !ok || len(counts) == 0 {
309+
return 0
310+
}
311+
reviewTyp := issues_model.ReviewTypeApprove
312+
if typ == "reject" {
313+
reviewTyp = issues_model.ReviewTypeReject
314+
} else if typ == "waiting" {
315+
reviewTyp = issues_model.ReviewTypeRequest
316+
}
317+
for _, count := range counts {
318+
if count.Type == reviewTyp {
319+
return count.Count
320+
}
321+
}
322+
return 0
323+
}
324+
325+
c.Data["Status"] = 1
326+
c.Data["Title"] = c.Tr("notification.subscriptions")
327+
328+
// redirect to last page if request page is more than total pages
329+
pager := context.NewPagination(int(count), setting.UI.IssuePagingNum, page, 5)
330+
if pager.Paginater.Current() < page {
331+
c.Redirect(fmt.Sprintf("/notifications/subscriptions?page=%d", pager.Paginater.Current()))
332+
return
333+
}
334+
pager.AddParam(c, "sort", "SortType")
335+
pager.AddParam(c, "state", "State")
336+
c.Data["Page"] = pager
337+
338+
c.HTML(http.StatusOK, tplNotificationSubscriptions)
339+
}
340+
341+
// NotificationWatching returns the list of watching repos
342+
func NotificationWatching(c *context.Context) {
343+
page := c.FormInt("page")
344+
if page < 1 {
345+
page = 1
346+
}
347+
348+
var orderBy db.SearchOrderBy
349+
c.Data["SortType"] = c.FormString("sort")
350+
switch c.FormString("sort") {
351+
case "newest":
352+
orderBy = db.SearchOrderByNewest
353+
case "oldest":
354+
orderBy = db.SearchOrderByOldest
355+
case "recentupdate":
356+
orderBy = db.SearchOrderByRecentUpdated
357+
case "leastupdate":
358+
orderBy = db.SearchOrderByLeastUpdated
359+
case "reversealphabetically":
360+
orderBy = db.SearchOrderByAlphabeticallyReverse
361+
case "alphabetically":
362+
orderBy = db.SearchOrderByAlphabetically
363+
case "moststars":
364+
orderBy = db.SearchOrderByStarsReverse
365+
case "feweststars":
366+
orderBy = db.SearchOrderByStars
367+
case "mostforks":
368+
orderBy = db.SearchOrderByForksReverse
369+
case "fewestforks":
370+
orderBy = db.SearchOrderByForks
371+
default:
372+
c.Data["SortType"] = "recentupdate"
373+
orderBy = db.SearchOrderByRecentUpdated
374+
}
375+
376+
repos, count, err := repo_model.SearchRepository(&repo_model.SearchRepoOptions{
377+
ListOptions: db.ListOptions{
378+
PageSize: setting.UI.User.RepoPagingNum,
379+
Page: page,
380+
},
381+
Actor: c.Doer,
382+
Keyword: c.FormTrim("q"),
383+
OrderBy: orderBy,
384+
Private: c.IsSigned,
385+
WatchedByID: c.Doer.ID,
386+
Collaborate: util.OptionalBoolFalse,
387+
TopicOnly: c.FormBool("topic"),
388+
IncludeDescription: setting.UI.SearchRepoDescription,
389+
})
390+
if err != nil {
391+
c.ServerError("ErrSearchRepository", err)
392+
return
393+
}
394+
total := int(count)
395+
c.Data["Total"] = total
396+
c.Data["Repos"] = repos
397+
398+
// redirect to last page if request page is more than total pages
399+
pager := context.NewPagination(total, setting.UI.User.RepoPagingNum, page, 5)
400+
pager.SetDefaultParams(c)
401+
c.Data["Page"] = pager
402+
403+
c.Data["Status"] = 2
404+
c.Data["Title"] = c.Tr("notification.watching")
405+
406+
c.HTML(http.StatusOK, tplNotificationSubscriptions)
407+
}
408+
200409
// NewAvailable returns the notification counts
201410
func NewAvailable(ctx *context.Context) {
202411
ctx.JSON(http.StatusOK, structs.NotificationCount{New: activities_model.CountUnread(ctx, ctx.Doer.ID)})

routers/web/web.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1269,6 +1269,8 @@ func RegisterRoutes(m *web.Route) {
12691269

12701270
m.Group("/notifications", func() {
12711271
m.Get("", user.Notifications)
1272+
m.Get("/subscriptions", user.NotificationSubscriptions)
1273+
m.Get("/watching", user.NotificationWatching)
12721274
m.Post("/status", user.NotificationStatusPost)
12731275
m.Post("/purge", user.NotificationPurgePost)
12741276
m.Get("/new", user.NewAvailable)

templates/base/head_navbar.tmpl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,10 @@
171171
{{.locale.Tr "your_starred"}}
172172
</a>
173173
{{end}}
174+
<a class="item" href="{{AppSubUrl}}/notifications/subscriptions">
175+
{{svg "octicon-bell"}}
176+
{{.locale.Tr "notification.subscriptions"}}<!-- Subscriptions -->
177+
</a>
174178
<a class="{{if .PageIsUserSettings}}active{{end}} item" href="{{AppSubUrl}}/user/settings">
175179
{{svg "octicon-tools"}}
176180
{{.locale.Tr "your_settings"}}<!-- Your settings -->

0 commit comments

Comments
 (0)