Skip to content

Commit

Permalink
Add pages to view watched repos and subscribed issues/PRs (#17156)
Browse files Browse the repository at this point in the history
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 <lauris@nix.lv>
Co-authored-by: 6543 <6543@obermui.de>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
  • Loading branch information
4 people authored Sep 29, 2022
1 parent 3b6a7e5 commit 08609d4
Show file tree
Hide file tree
Showing 6 changed files with 334 additions and 2 deletions.
35 changes: 35 additions & 0 deletions models/issues/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -1186,6 +1186,7 @@ type IssuesOptions struct { //nolint
PosterID int64
MentionedID int64
ReviewRequestedID int64
SubscriberID int64
MilestoneIDs []int64
ProjectID int64
ProjectBoardID int64
Expand Down Expand Up @@ -1299,6 +1300,10 @@ func (opts *IssuesOptions) setupSessionNoLimit(sess *xorm.Session) {
applyReviewRequestedCondition(sess, opts.ReviewRequestedID)
}

if opts.SubscriberID > 0 {
applySubscribedCondition(sess, opts.SubscriberID)
}

if len(opts.MilestoneIDs) > 0 {
sess.In("issue.milestone_id", opts.MilestoneIDs)
}
Expand Down Expand Up @@ -1463,6 +1468,36 @@ func applyReviewRequestedCondition(sess *xorm.Session, reviewRequestedID int64)
reviewRequestedID, ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest, reviewRequestedID)
}

func applySubscribedCondition(sess *xorm.Session, subscriberID int64) *xorm.Session {
return sess.And(
builder.
NotIn("issue.id",
builder.Select("issue_id").
From("issue_watch").
Where(builder.Eq{"is_watching": false, "user_id": subscriberID}),
),
).And(
builder.Or(
builder.In("issue.id", builder.
Select("issue_id").
From("issue_watch").
Where(builder.Eq{"is_watching": true, "user_id": subscriberID}),
),
builder.In("issue.id", builder.
Select("issue_id").
From("comment").
Where(builder.Eq{"poster_id": subscriberID}),
),
builder.Eq{"issue.poster_id": subscriberID},
builder.In("issue.repo_id", builder.
Select("id").
From("watch").
Where(builder.Eq{"user_id": subscriberID, "mode": true}),
),
),
)
}

// CountIssuesByRepo map from repoID to number of issues matching the options
func CountIssuesByRepo(opts *IssuesOptions) (map[int64]int64, error) {
e := db.GetEngine(db.DefaultContext)
Expand Down
3 changes: 3 additions & 0 deletions options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -3034,6 +3034,9 @@ pin = Pin notification
mark_as_read = Mark as read
mark_as_unread = Mark as unread
mark_all_as_read = Mark all as read
subscriptions = Subscriptions
watching = Watching
no_subscriptions = No subscriptions

[gpg]
default_key=Signed with default key
Expand Down
213 changes: 211 additions & 2 deletions routers/web/user/notification.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,23 @@ import (
"strings"

activities_model "code.gitea.io/gitea/models/activities"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
issue_service "code.gitea.io/gitea/services/issue"
pull_service "code.gitea.io/gitea/services/pull"
)

const (
tplNotification base.TplName = "user/notification/notification"
tplNotificationDiv base.TplName = "user/notification/notification_div"
tplNotification base.TplName = "user/notification/notification"
tplNotificationDiv base.TplName = "user/notification/notification_div"
tplNotificationSubscriptions base.TplName = "user/notification/notification_subscriptions"
)

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

// NotificationSubscriptions returns the list of subscribed issues
func NotificationSubscriptions(c *context.Context) {
page := c.FormInt("page")
if page < 1 {
page = 1
}

sortType := c.FormString("sort")
c.Data["SortType"] = sortType

state := c.FormString("state")
if !util.IsStringInSlice(state, []string{"all", "open", "closed"}, true) {
state = "all"
}
c.Data["State"] = state
var showClosed util.OptionalBool
switch state {
case "all":
showClosed = util.OptionalBoolNone
case "closed":
showClosed = util.OptionalBoolTrue
case "open":
showClosed = util.OptionalBoolFalse
}

var issueTypeBool util.OptionalBool
issueType := c.FormString("issueType")
switch issueType {
case "issues":
issueTypeBool = util.OptionalBoolFalse
case "pulls":
issueTypeBool = util.OptionalBoolTrue
default:
issueTypeBool = util.OptionalBoolNone
}
c.Data["IssueType"] = issueType

var labelIDs []int64
selectedLabels := c.FormString("labels")
c.Data["Labels"] = selectedLabels
if len(selectedLabels) > 0 && selectedLabels != "0" {
var err error
labelIDs, err = base.StringsToInt64s(strings.Split(selectedLabels, ","))
if err != nil {
c.ServerError("StringsToInt64s", err)
return
}
}

count, err := issues_model.CountIssues(&issues_model.IssuesOptions{
SubscriberID: c.Doer.ID,
IsClosed: showClosed,
IsPull: issueTypeBool,
LabelIDs: labelIDs,
})
if err != nil {
c.ServerError("CountIssues", err)
return
}
issues, err := issues_model.Issues(&issues_model.IssuesOptions{
ListOptions: db.ListOptions{
PageSize: setting.UI.IssuePagingNum,
Page: page,
},
SubscriberID: c.Doer.ID,
SortType: sortType,
IsClosed: showClosed,
IsPull: issueTypeBool,
LabelIDs: labelIDs,
})
if err != nil {
c.ServerError("Issues", err)
return
}

commitStatuses, lastStatus, err := pull_service.GetIssuesAllCommitStatus(c, issues)
if err != nil {
c.ServerError("GetIssuesAllCommitStatus", err)
return
}
c.Data["CommitLastStatus"] = lastStatus
c.Data["CommitStatuses"] = commitStatuses
c.Data["Issues"] = issues

c.Data["IssueRefEndNames"], c.Data["IssueRefURLs"] = issue_service.GetRefEndNamesAndURLs(issues, "")

commitStatus, err := pull_service.GetIssuesLastCommitStatus(c, issues)
if err != nil {
c.ServerError("GetIssuesLastCommitStatus", err)
return
}
c.Data["CommitStatus"] = commitStatus

issueList := issues_model.IssueList(issues)
approvalCounts, err := issueList.GetApprovalCounts(c)
if err != nil {
c.ServerError("ApprovalCounts", err)
return
}
c.Data["ApprovalCounts"] = func(issueID int64, typ string) int64 {
counts, ok := approvalCounts[issueID]
if !ok || len(counts) == 0 {
return 0
}
reviewTyp := issues_model.ReviewTypeApprove
if typ == "reject" {
reviewTyp = issues_model.ReviewTypeReject
} else if typ == "waiting" {
reviewTyp = issues_model.ReviewTypeRequest
}
for _, count := range counts {
if count.Type == reviewTyp {
return count.Count
}
}
return 0
}

c.Data["Status"] = 1
c.Data["Title"] = c.Tr("notification.subscriptions")

// redirect to last page if request page is more than total pages
pager := context.NewPagination(int(count), setting.UI.IssuePagingNum, page, 5)
if pager.Paginater.Current() < page {
c.Redirect(fmt.Sprintf("/notifications/subscriptions?page=%d", pager.Paginater.Current()))
return
}
pager.AddParam(c, "sort", "SortType")
pager.AddParam(c, "state", "State")
c.Data["Page"] = pager

c.HTML(http.StatusOK, tplNotificationSubscriptions)
}

// NotificationWatching returns the list of watching repos
func NotificationWatching(c *context.Context) {
page := c.FormInt("page")
if page < 1 {
page = 1
}

var orderBy db.SearchOrderBy
c.Data["SortType"] = c.FormString("sort")
switch c.FormString("sort") {
case "newest":
orderBy = db.SearchOrderByNewest
case "oldest":
orderBy = db.SearchOrderByOldest
case "recentupdate":
orderBy = db.SearchOrderByRecentUpdated
case "leastupdate":
orderBy = db.SearchOrderByLeastUpdated
case "reversealphabetically":
orderBy = db.SearchOrderByAlphabeticallyReverse
case "alphabetically":
orderBy = db.SearchOrderByAlphabetically
case "moststars":
orderBy = db.SearchOrderByStarsReverse
case "feweststars":
orderBy = db.SearchOrderByStars
case "mostforks":
orderBy = db.SearchOrderByForksReverse
case "fewestforks":
orderBy = db.SearchOrderByForks
default:
c.Data["SortType"] = "recentupdate"
orderBy = db.SearchOrderByRecentUpdated
}

repos, count, err := repo_model.SearchRepository(&repo_model.SearchRepoOptions{
ListOptions: db.ListOptions{
PageSize: setting.UI.User.RepoPagingNum,
Page: page,
},
Actor: c.Doer,
Keyword: c.FormTrim("q"),
OrderBy: orderBy,
Private: c.IsSigned,
WatchedByID: c.Doer.ID,
Collaborate: util.OptionalBoolFalse,
TopicOnly: c.FormBool("topic"),
IncludeDescription: setting.UI.SearchRepoDescription,
})
if err != nil {
c.ServerError("ErrSearchRepository", err)
return
}
total := int(count)
c.Data["Total"] = total
c.Data["Repos"] = repos

// redirect to last page if request page is more than total pages
pager := context.NewPagination(total, setting.UI.User.RepoPagingNum, page, 5)
pager.SetDefaultParams(c)
c.Data["Page"] = pager

c.Data["Status"] = 2
c.Data["Title"] = c.Tr("notification.watching")

c.HTML(http.StatusOK, tplNotificationSubscriptions)
}

// NewAvailable returns the notification counts
func NewAvailable(ctx *context.Context) {
ctx.JSON(http.StatusOK, structs.NotificationCount{New: activities_model.CountUnread(ctx, ctx.Doer.ID)})
Expand Down
2 changes: 2 additions & 0 deletions routers/web/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -1269,6 +1269,8 @@ func RegisterRoutes(m *web.Route) {

m.Group("/notifications", func() {
m.Get("", user.Notifications)
m.Get("/subscriptions", user.NotificationSubscriptions)
m.Get("/watching", user.NotificationWatching)
m.Post("/status", user.NotificationStatusPost)
m.Post("/purge", user.NotificationPurgePost)
m.Get("/new", user.NewAvailable)
Expand Down
4 changes: 4 additions & 0 deletions templates/base/head_navbar.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,10 @@
{{.locale.Tr "your_starred"}}
</a>
{{end}}
<a class="item" href="{{AppSubUrl}}/notifications/subscriptions">
{{svg "octicon-bell"}}
{{.locale.Tr "notification.subscriptions"}}<!-- Subscriptions -->
</a>
<a class="{{if .PageIsUserSettings}}active{{end}} item" href="{{AppSubUrl}}/user/settings">
{{svg "octicon-tools"}}
{{.locale.Tr "your_settings"}}<!-- Your settings -->
Expand Down
Loading

0 comments on commit 08609d4

Please sign in to comment.