Procházet zdrojové kódy

Extend Notifications API and return pinned notifications by default (#12164)

* Extend notifications API and return pinned notifications in notifications list

Signed-off-by: Andrew Thornton <art27@cantab.net>

* fix swagger

Signed-off-by: Andrew Thornton <art27@cantab.net>

* Fix swagger again

Signed-off-by: Andrew Thornton <art27@cantab.net>

* fix test

Signed-off-by: Andrew Thornton <art27@cantab.net>

* remove spurious debugs

* as per @6543

Signed-off-by: Andrew Thornton <art27@cantab.net>

* Update models/notification.go

* as per @6543

Signed-off-by: Andrew Thornton <art27@cantab.net>

Co-authored-by: techknowlogick <techknowlogick@gitea.io>
tags/v1.13.0-rc1
zeripath před 3 roky
rodič
revize
63591016b3
Žádný účet není propojen s e-mailovou adresou tvůrce revize

+ 3
- 3
integrations/api_notification_test.go Zobrazit soubor

assert.EqualValues(t, false, apiNL[2].Pinned) assert.EqualValues(t, false, apiNL[2].Pinned)


// -- GET /repos/{owner}/{repo}/notifications -- // -- 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) resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiNL) DecodeJSON(t, resp, &apiNL)


assert.True(t, new.New > 0) assert.True(t, new.New > 0)


// -- mark notifications as read -- // -- 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) resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiNL) DecodeJSON(t, resp, &apiNL)
assert.Len(t, apiNL, 2) assert.Len(t, apiNL, 2)
req = NewRequest(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/notifications?last_read_at=%s&token=%s", user2.Name, repo1.Name, lastReadAt, token)) 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) 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) resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiNL) DecodeJSON(t, resp, &apiNL)
assert.Len(t, apiNL, 1) assert.Len(t, apiNL, 1)

+ 2
- 2
integrations/eventsource_test.go Zobrazit soubor

var apiNL []api.NotificationThread var apiNL []api.NotificationThread


// -- mark notifications as read -- // -- 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) resp := session.MakeRequest(t, req, http.StatusOK)


DecodeJSON(t, resp, &apiNL) DecodeJSON(t, resp, &apiNL)
req = NewRequest(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/notifications?last_read_at=%s&token=%s", user2.Name, repo1.Name, lastReadAt, token)) 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) 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) resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiNL) DecodeJSON(t, resp, &apiNL)
assert.Len(t, apiNL, 1) assert.Len(t, apiNL, 1)

+ 3
- 3
models/notification.go Zobrazit soubor

UserID int64 UserID int64
RepoID int64 RepoID int64
IssueID int64 IssueID int64
Status NotificationStatus
Status []NotificationStatus
UpdatedAfterUnix int64 UpdatedAfterUnix int64
UpdatedBeforeUnix int64 UpdatedBeforeUnix int64
} }
if opts.IssueID != 0 { if opts.IssueID != 0 {
cond = cond.And(builder.Eq{"notification.issue_id": opts.IssueID}) 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) > 0 {
cond = cond.And(builder.In("notification.status", opts.Status))
} }
if opts.UpdatedAfterUnix != 0 { if opts.UpdatedAfterUnix != 0 {
cond = cond.And(builder.Gte{"notification.updated_unix": opts.UpdatedAfterUnix}) cond = cond.And(builder.Gte{"notification.updated_unix": opts.UpdatedAfterUnix})

+ 72
- 6
routers/api/v1/notify/repo.go Zobrazit soubor



"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/routers/api/v1/utils" "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 // ListRepoNotifications list users's notification threads on a specific repo
func ListRepoNotifications(ctx *context.APIContext) { func ListRepoNotifications(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/notifications notification notifyGetRepoList // swagger:operation GET /repos/{owner}/{repo}/notifications notification notifyGetRepoList
// description: If true, show notifications marked as read. Default value is false // description: If true, show notifications marked as read. Default value is false
// type: string // type: string
// required: false // 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 // - name: since
// in: query // in: query
// description: Only show notifications updated after the given time. This is a timestamp in RFC 3339 format // description: Only show notifications updated after the given time. This is a timestamp in RFC 3339 format
UpdatedBeforeUnix: before, UpdatedBeforeUnix: before,
UpdatedAfterUnix: since, 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) nl, err := models.GetNotifications(opts)
if err != nil { if err != nil {
func ReadRepoNotifications(ctx *context.APIContext) { func ReadRepoNotifications(ctx *context.APIContext) {
// swagger:operation PUT /repos/{owner}/{repo}/notifications notification notifyReadRepoList // 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: // consumes:
// - application/json // - application/json
// produces: // produces:
// description: name of the repo // description: name of the repo
// type: string // type: string
// required: true // 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 // - name: last_read_at
// in: query // in: query
// description: Describes the last point that notifications were checked. Anything updated since this time will not be updated. // description: Describes the last point that notifications were checked. Anything updated since this time will not be updated.
lastRead = tmpLastRead.Unix() lastRead = tmpLastRead.Unix()
} }
} }

opts := models.FindNotificationOptions{ opts := models.FindNotificationOptions{
UserID: ctx.User.ID, UserID: ctx.User.ID,
RepoID: ctx.Repo.Repository.ID, RepoID: ctx.Repo.Repository.ID,
UpdatedBeforeUnix: lastRead, 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) nl, err := models.GetNotifications(opts)
if err != nil { if err != nil {
return return
} }


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

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

+ 12
- 1
routers/api/v1/notify/threads.go Zobrazit soubor

// description: id of notification thread // description: id of notification thread
// type: string // type: string
// required: true // required: true
// - name: to-status
// in: query
// description: Status to mark notifications as
// type: string
// default: read
// required: false
// responses: // responses:
// "205": // "205":
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"
return 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 { if err != nil {
ctx.InternalServerError(err) ctx.InternalServerError(err)
return return

+ 41
- 7
routers/api/v1/notify/user.go Zobrazit soubor

// description: If true, show notifications marked as read. Default value is false // description: If true, show notifications marked as read. Default value is false
// type: string // type: string
// required: false // 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 // - name: since
// in: query // in: query
// description: Only show notifications updated after the given time. This is a timestamp in RFC 3339 format // description: Only show notifications updated after the given time. This is a timestamp in RFC 3339 format
UpdatedBeforeUnix: before, UpdatedBeforeUnix: before,
UpdatedAfterUnix: since, 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) nl, err := models.GetNotifications(opts)
if err != nil { if err != nil {
ctx.JSON(http.StatusOK, nl.APIFormat()) 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) { func ReadNotifications(ctx *context.APIContext) {
// swagger:operation PUT /notifications notification notifyReadList // swagger:operation PUT /notifications notification notifyReadList
// --- // ---
// summary: Mark notification threads as read
// summary: Mark notification threads as read, pinned or unread
// consumes: // consumes:
// - application/json // - application/json
// produces: // produces:
// type: string // type: string
// format: date-time // format: date-time
// required: false // 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: // responses:
// "205": // "205":
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"
opts := models.FindNotificationOptions{ opts := models.FindNotificationOptions{
UserID: ctx.User.ID, UserID: ctx.User.ID,
UpdatedBeforeUnix: lastRead, 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) nl, err := models.GetNotifications(opts)
if err != nil { if err != nil {
return return
} }


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

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

+ 73
- 2
templates/swagger/v1_json.tmpl Zobrazit soubor

"name": "all", "name": "all",
"in": "query" "in": "query"
}, },
{
"type": "array",
"items": {
"type": "string"
},
"collectionFormat": "multi",
"description": "Show notifications with the provided status types. Options are: unread, read and/or pinned. Defaults to unread \u0026 pinned.",
"name": "status-types",
"in": "query"
},
{ {
"type": "string", "type": "string",
"format": "date-time", "format": "date-time",
"tags": [ "tags": [
"notification" "notification"
], ],
"summary": "Mark notification threads as read",
"summary": "Mark notification threads as read, pinned or unread",
"operationId": "notifyReadList", "operationId": "notifyReadList",
"parameters": [ "parameters": [
{ {
"description": "Describes the last point that notifications were checked. Anything updated since this time will not be updated.", "description": "Describes the last point that notifications were checked. Anything updated since this time will not be updated.",
"name": "last_read_at", "name": "last_read_at",
"in": "query" "in": "query"
},
{
"type": "string",
"description": "If true, mark all notifications on this repo. Default value is false",
"name": "all",
"in": "query"
},
{
"type": "array",
"items": {
"type": "string"
},
"collectionFormat": "multi",
"description": "Mark notifications with the provided status types. Options are: unread, read and/or pinned. Defaults to unread.",
"name": "status-types",
"in": "query"
},
{
"type": "string",
"description": "Status to mark notifications as, Defaults to read.",
"name": "to-status",
"in": "query"
} }
], ],
"responses": { "responses": {
"name": "id", "name": "id",
"in": "path", "in": "path",
"required": true "required": true
},
{
"type": "string",
"default": "read",
"description": "Status to mark notifications as",
"name": "to-status",
"in": "query"
} }
], ],
"responses": { "responses": {
"name": "all", "name": "all",
"in": "query" "in": "query"
}, },
{
"type": "array",
"items": {
"type": "string"
},
"collectionFormat": "multi",
"description": "Show notifications with the provided status types. Options are: unread, read and/or pinned. Defaults to unread \u0026 pinned",
"name": "status-types",
"in": "query"
},
{ {
"type": "string", "type": "string",
"format": "date-time", "format": "date-time",
"tags": [ "tags": [
"notification" "notification"
], ],
"summary": "Mark notification threads as read on a specific repo",
"summary": "Mark notification threads as read, pinned or unread on a specific repo",
"operationId": "notifyReadRepoList", "operationId": "notifyReadRepoList",
"parameters": [ "parameters": [
{ {
"in": "path", "in": "path",
"required": true "required": true
}, },
{
"type": "string",
"description": "If true, mark all notifications on this repo. Default value is false",
"name": "all",
"in": "query"
},
{
"type": "array",
"items": {
"type": "string"
},
"collectionFormat": "multi",
"description": "Mark notifications with the provided status types. Options are: unread, read and/or pinned. Defaults to unread.",
"name": "status-types",
"in": "query"
},
{
"type": "string",
"description": "Status to mark notifications as. Defaults to read.",
"name": "to-status",
"in": "query"
},
{ {
"type": "string", "type": "string",
"format": "date-time", "format": "date-time",

Načítá se…
Zrušit
Uložit