Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend Notifications API and return pinned notifications by default #12164

Merged
merged 14 commits into from
Jul 11, 2020
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions integrations/api_notification_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func TestAPINotification(t *testing.T) {
assert.EqualValues(t, false, apiNL[2].Pinned)

// -- GET /repos/{owner}/{repo}/notifications --
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/notifications?token=%s", user2.Name, repo1.Name, token))
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/notifications?status-types=unread&token=%s", user2.Name, repo1.Name, token))
resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiNL)

Expand Down Expand Up @@ -92,7 +92,7 @@ func TestAPINotification(t *testing.T) {
assert.True(t, new.New > 0)

// -- mark notifications as read --
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?token=%s", token))
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?status-types=unread&token=%s", token))
resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiNL)
assert.Len(t, apiNL, 2)
Expand All @@ -101,7 +101,7 @@ func TestAPINotification(t *testing.T) {
req = NewRequest(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/notifications?last_read_at=%s&token=%s", user2.Name, repo1.Name, lastReadAt, token))
resp = session.MakeRequest(t, req, http.StatusResetContent)

req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?token=%s", token))
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?status-types=unread&token=%s", token))
resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiNL)
assert.Len(t, apiNL, 1)
Expand Down
4 changes: 2 additions & 2 deletions integrations/eventsource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func TestEventSourceManagerRun(t *testing.T) {
var apiNL []api.NotificationThread

// -- mark notifications as read --
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?token=%s", token))
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?status-types=unread&token=%s", token))
resp := session.MakeRequest(t, req, http.StatusOK)

DecodeJSON(t, resp, &apiNL)
Expand All @@ -69,7 +69,7 @@ func TestEventSourceManagerRun(t *testing.T) {
req = NewRequest(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/notifications?last_read_at=%s&token=%s", user2.Name, repo1.Name, lastReadAt, token))
resp = session.MakeRequest(t, req, http.StatusResetContent)

req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?token=%s", token))
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?token=%s&status-types=unread", token))
resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiNL)
assert.Len(t, apiNL, 1)
Expand Down
12 changes: 9 additions & 3 deletions models/notification.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ type FindNotificationOptions struct {
UserID int64
RepoID int64
IssueID int64
Status NotificationStatus
Status []NotificationStatus
UpdatedAfterUnix int64
UpdatedBeforeUnix int64
}
Expand All @@ -89,8 +89,14 @@ func (opts *FindNotificationOptions) ToCond() builder.Cond {
if opts.IssueID != 0 {
cond = cond.And(builder.Eq{"notification.issue_id": opts.IssueID})
}
if opts.Status != 0 {
cond = cond.And(builder.Eq{"notification.status": opts.Status})
if len(opts.Status) == 1 {
zeripath marked this conversation as resolved.
Show resolved Hide resolved
techknowlogick marked this conversation as resolved.
Show resolved Hide resolved
zeripath marked this conversation as resolved.
Show resolved Hide resolved
cond = cond.And(builder.Eq{"notification.status": opts.Status[0]})
zeripath marked this conversation as resolved.
Show resolved Hide resolved
} else if len(opts.Status) > 1 {
zeripath marked this conversation as resolved.
Show resolved Hide resolved
eqConds := make([]builder.Cond, 0, len(opts.Status))
for _, status := range opts.Status {
eqConds = append(eqConds, builder.Eq{"notification.status": status})
}
cond = cond.And(cond.Or(eqConds...))
}
if opts.UpdatedAfterUnix != 0 {
cond = cond.And(builder.Gte{"notification.updated_unix": opts.UpdatedAfterUnix})
Expand Down
81 changes: 75 additions & 6 deletions routers/api/v1/notify/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,37 @@ import (

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/routers/api/v1/utils"
)

func statusStringToNotificationStatus(status string) models.NotificationStatus {
switch strings.ToLower(strings.TrimSpace(status)) {
case "unread":
return models.NotificationStatusUnread
case "read":
return models.NotificationStatusRead
case "pinned":
return models.NotificationStatusPinned
default:
return 0
}
}

func statusStringsToNotificationStatuses(statuses []string, defaultStatuses []string) []models.NotificationStatus {
if len(statuses) == 0 {
statuses = defaultStatuses
}
results := make([]models.NotificationStatus, 0, len(statuses))
for _, status := range statuses {
notificationStatus := statusStringToNotificationStatus(status)
if notificationStatus > 0 {
results = append(results, notificationStatus)
}
}
return results
}

// ListRepoNotifications list users's notification threads on a specific repo
func ListRepoNotifications(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/notifications notification notifyGetRepoList
Expand All @@ -39,6 +67,14 @@ func ListRepoNotifications(ctx *context.APIContext) {
// description: If true, show notifications marked as read. Default value is false
// type: string
// required: false
// - name: status-types
// in: query
// description: "Show notifications with the provided status types. Options are: unread, read and/or pinned. Defaults to unread & pinned"
// type: array
// collectionFormat: multi
// items:
// type: string
// required: false
// - name: since
// in: query
// description: Only show notifications updated after the given time. This is a timestamp in RFC 3339 format
Expand Down Expand Up @@ -75,9 +111,10 @@ func ListRepoNotifications(ctx *context.APIContext) {
UpdatedBeforeUnix: before,
UpdatedAfterUnix: since,
}
qAll := strings.Trim(ctx.Query("all"), " ")
if qAll != "true" {
opts.Status = models.NotificationStatusUnread

if !ctx.QueryBool("all") {
statuses := ctx.QueryStrings("status-types")
opts.Status = statusStringsToNotificationStatuses(statuses, []string{"unread", "pinned"})
}
nl, err := models.GetNotifications(opts)
if err != nil {
Expand All @@ -97,7 +134,7 @@ func ListRepoNotifications(ctx *context.APIContext) {
func ReadRepoNotifications(ctx *context.APIContext) {
// swagger:operation PUT /repos/{owner}/{repo}/notifications notification notifyReadRepoList
// ---
// summary: Mark notification threads as read on a specific repo
// summary: Mark notification threads as read, pinned or unread on a specific repo
// consumes:
// - application/json
// produces:
Expand All @@ -113,6 +150,24 @@ func ReadRepoNotifications(ctx *context.APIContext) {
// description: name of the repo
// type: string
// required: true
// - name: all
// in: query
// description: If true, mark all notifications on this repo. Default value is false
// type: string
// required: false
// - name: status-types
// in: query
// description: "Mark notifications with the provided status types. Options are: unread, read and/or pinned. Defaults to unread."
// type: array
// collectionFormat: multi
// items:
// type: string
// required: false
// - name: to-status
// in: query
// description: Status to mark notifications as. Defaults to read.
// type: string
// required: false
// - name: last_read_at
// in: query
// description: Describes the last point that notifications were checked. Anything updated since this time will not be updated.
Expand All @@ -135,20 +190,34 @@ func ReadRepoNotifications(ctx *context.APIContext) {
lastRead = tmpLastRead.Unix()
}
}

opts := models.FindNotificationOptions{
UserID: ctx.User.ID,
RepoID: ctx.Repo.Repository.ID,
UpdatedBeforeUnix: lastRead,
Status: models.NotificationStatusUnread,
}

if !ctx.QueryBool("all") {
statuses := ctx.QueryStrings("status-types")
opts.Status = statusStringsToNotificationStatuses(statuses, []string{"unread"})
log.Error("%v", opts.Status)
}
nl, err := models.GetNotifications(opts)
if err != nil {
ctx.InternalServerError(err)
return
}
for _, t := range nl {
lafriks marked this conversation as resolved.
Show resolved Hide resolved
log.Error("found thread: %d %d", t.ID, t.Status)
}
zeripath marked this conversation as resolved.
Show resolved Hide resolved

targetStatus := statusStringToNotificationStatus(ctx.Query("to-status"))
if targetStatus == 0 {
targetStatus = models.NotificationStatusRead
}

for _, n := range nl {
err := models.SetNotificationStatus(n.ID, ctx.User, models.NotificationStatusRead)
err := models.SetNotificationStatus(n.ID, ctx.User, targetStatus)
if err != nil {
ctx.InternalServerError(err)
return
Expand Down
13 changes: 12 additions & 1 deletion routers/api/v1/notify/threads.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ func ReadThread(ctx *context.APIContext) {
// description: id of notification thread
// type: string
// required: true
// - name: to-status
// in: query
// description: Status to mark notifications as
// type: string
// default: read
// required: false
// responses:
// "205":
// "$ref": "#/responses/empty"
Expand All @@ -75,7 +81,12 @@ func ReadThread(ctx *context.APIContext) {
return
}

err := models.SetNotificationStatus(n.ID, ctx.User, models.NotificationStatusRead)
targetStatus := statusStringToNotificationStatus(ctx.Query("to-status"))
if targetStatus == 0 {
targetStatus = models.NotificationStatusRead
}

err := models.SetNotificationStatus(n.ID, ctx.User, targetStatus)
if err != nil {
ctx.InternalServerError(err)
return
Expand Down
48 changes: 41 additions & 7 deletions routers/api/v1/notify/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ func ListNotifications(ctx *context.APIContext) {
// description: If true, show notifications marked as read. Default value is false
// type: string
// required: false
// - name: status-types
// in: query
// description: "Show notifications with the provided status types. Options are: unread, read and/or pinned. Defaults to unread & pinned."
// type: array
// collectionFormat: multi
// items:
// type: string
// required: false
// - name: since
// in: query
// description: Only show notifications updated after the given time. This is a timestamp in RFC 3339 format
Expand Down Expand Up @@ -64,9 +72,9 @@ func ListNotifications(ctx *context.APIContext) {
UpdatedBeforeUnix: before,
UpdatedAfterUnix: since,
}
qAll := strings.Trim(ctx.Query("all"), " ")
if qAll != "true" {
opts.Status = models.NotificationStatusUnread
if !ctx.QueryBool("all") {
statuses := ctx.QueryStrings("status-types")
opts.Status = statusStringsToNotificationStatuses(statuses, []string{"unread", "pinned"})
}
nl, err := models.GetNotifications(opts)
if err != nil {
Expand All @@ -82,11 +90,11 @@ func ListNotifications(ctx *context.APIContext) {
ctx.JSON(http.StatusOK, nl.APIFormat())
}

// ReadNotifications mark notification threads as read
// ReadNotifications mark notification threads as read, unread, or pinned
func ReadNotifications(ctx *context.APIContext) {
// swagger:operation PUT /notifications notification notifyReadList
// ---
// summary: Mark notification threads as read
// summary: Mark notification threads as read, pinned or unread
// consumes:
// - application/json
// produces:
Expand All @@ -98,6 +106,24 @@ func ReadNotifications(ctx *context.APIContext) {
// type: string
// format: date-time
// required: false
// - name: all
// in: query
// description: If true, mark all notifications on this repo. Default value is false
// type: string
// required: false
// - name: status-types
// in: query
// description: "Mark notifications with the provided status types. Options are: unread, read and/or pinned. Defaults to unread."
// type: array
// collectionFormat: multi
// items:
// type: string
// required: false
// - name: to-status
// in: query
// description: Status to mark notifications as, Defaults to read.
// type: string
// required: false
// responses:
// "205":
// "$ref": "#/responses/empty"
Expand All @@ -117,16 +143,24 @@ func ReadNotifications(ctx *context.APIContext) {
opts := models.FindNotificationOptions{
UserID: ctx.User.ID,
UpdatedBeforeUnix: lastRead,
Status: models.NotificationStatusUnread,
}
if !ctx.QueryBool("all") {
statuses := ctx.QueryStrings("status-types")
opts.Status = statusStringsToNotificationStatuses(statuses, []string{"unread"})
}
nl, err := models.GetNotifications(opts)
if err != nil {
ctx.InternalServerError(err)
return
}

targetStatus := statusStringToNotificationStatus(ctx.Query("to-status"))
if targetStatus == 0 {
targetStatus = models.NotificationStatusRead
}

for _, n := range nl {
err := models.SetNotificationStatus(n.ID, ctx.User, models.NotificationStatusRead)
err := models.SetNotificationStatus(n.ID, ctx.User, targetStatus)
if err != nil {
ctx.InternalServerError(err)
return
Expand Down
Loading