diff --git a/models/activities/notification.go b/models/activities/notification.go index ef263ef735c3d..317e4fb28035a 100644 --- a/models/activities/notification.go +++ b/models/activities/notification.go @@ -50,6 +50,8 @@ const ( NotificationSourceCommit // NotificationSourceRepository is a notification for a repository NotificationSourceRepository + // NotificationSourceRepository is a notification for a release + NotificationSourceRelease ) // Notification represents a notification @@ -64,6 +66,7 @@ type Notification struct { IssueID int64 `xorm:"INDEX NOT NULL"` CommitID string `xorm:"INDEX"` CommentID int64 + ReleaseID int64 `xorm:"INDEX"` UpdatedBy int64 `xorm:"INDEX NOT NULL"` @@ -71,6 +74,7 @@ type Notification struct { Repository *repo_model.Repository `xorm:"-"` Comment *issues_model.Comment `xorm:"-"` User *user_model.User `xorm:"-"` + Release *repo_model.Release `xorm:"-"` CreatedUnix timeutil.TimeStamp `xorm:"created INDEX NOT NULL"` UpdatedUnix timeutil.TimeStamp `xorm:"updated INDEX NOT NULL"` @@ -260,7 +264,7 @@ func createOrUpdateIssueNotifications(ctx context.Context, issueID, commentID, n continue } - if notificationExists(notifications, issue.ID, userID) { + if issueNotificationExists(notifications, issue.ID, userID) { if err = updateIssueNotification(ctx, userID, issue.ID, commentID, notificationAuthorID); err != nil { return err } @@ -280,7 +284,7 @@ func getNotificationsByIssueID(ctx context.Context, issueID int64) (notification return notifications, err } -func notificationExists(notifications []*Notification, issueID, userID int64) bool { +func issueNotificationExists(notifications []*Notification, issueID, userID int64) bool { for _, notification := range notifications { if notification.IssueID == issueID && notification.UserID == userID { return true @@ -341,6 +345,91 @@ func GetIssueNotification(ctx context.Context, userID, issueID int64) (*Notifica return notification, err } +// CreateOrUpdateIssueNotifications creates an release notification +// for each watcher, or updates it if already exists +// this function called from a queue, so we can't pass a context +func CreateOrUpdateReleaseNotifications(releaseID, receiverID int64) error { + ctx, committer, err := db.TxContext(db.DefaultContext) + if err != nil { + return err + } + defer committer.Close() + + release, err := repo_model.GetReleaseByID(ctx, releaseID) + if err != nil { + return err + } + + notifications, err := getNotificationsByReleaseID(ctx, release.ID) + if err != nil { + return err + } + + if releaseNotificationExists(notifications, release.ID, receiverID) { + err = updateReleaseNotification(ctx, receiverID, release) + } else { + err = createReleaseNotification(ctx, receiverID, release) + } + if err != nil { + return err + } + + return committer.Commit() +} + +func getNotificationsByReleaseID(ctx context.Context, releaseID int64) (notifications []*Notification, err error) { + err = db.GetEngine(ctx). + Where("release_id = ?", releaseID). + Find(¬ifications) + return notifications, err +} + +func releaseNotificationExists(notifications []*Notification, releaseID, userID int64) bool { + for _, notification := range notifications { + if notification.ReleaseID == releaseID && notification.UserID == userID { + return true + } + } + + return false +} + +func createReleaseNotification(ctx context.Context, userID int64, release *repo_model.Release) error { + notification := &Notification{ + UserID: userID, + RepoID: release.RepoID, + Status: NotificationStatusUnread, + ReleaseID: release.ID, + Source: NotificationSourceRelease, + UpdatedBy: release.PublisherID, + } + + return db.Insert(ctx, notification) +} + +func updateReleaseNotification(ctx context.Context, userID int64, release *repo_model.Release) error { + notification, err := GetReleaseNotification(ctx, userID, release.ID) + if err != nil { + return err + } + + notification.Status = NotificationStatusUnread + notification.UpdatedBy = release.PublisherID + + _, err = db.GetEngine(ctx).ID(notification.ID).Cols([]string{"status", "update_by"}...).Update(notification) + return err +} + +// GetIssueNotification return the notification about an release +func GetReleaseNotification(ctx context.Context, userID, releaseID int64) (*Notification, error) { + notification := new(Notification) + _, err := db.GetEngine(ctx). + Where("user_id = ?", userID). + And("release_id = ?", releaseID). + Get(notification) + return notification, err +} + // NotificationsForUser returns notifications for a given user and status func NotificationsForUser(ctx context.Context, user *user_model.User, statuses []NotificationStatus, page, perPage int) (notifications NotificationList, err error) { if len(statuses) == 0 { @@ -384,6 +473,9 @@ func (n *Notification) LoadAttributes(ctx context.Context) (err error) { if err = n.loadComment(ctx); err != nil { return err } + if err = n.loadRelease(ctx); err != nil { + return err + } return err } @@ -434,6 +526,24 @@ func (n *Notification) loadUser(ctx context.Context) (err error) { return nil } +func (n *Notification) loadRelease(ctx context.Context) (err error) { + if n.Source != NotificationSourceRelease || n.Release != nil { + return nil + } + + n.Release, err = repo_model.GetReleaseByID(ctx, n.ReleaseID) + if err != nil { + return err + } + + err = n.Release.LoadAttributes(ctx) + if err != nil { + return err + } + + return nil +} + // GetRepo returns the repo of the notification func (n *Notification) GetRepo() (*repo_model.Repository, error) { return n.Repository, n.loadRepo(db.DefaultContext) @@ -472,6 +582,8 @@ func (n *Notification) Link() string { return n.Repository.Link() + "/commit/" + url.PathEscape(n.CommitID) case NotificationSourceRepository: return n.Repository.Link() + case NotificationSourceRelease: + return n.Release.Link() } return "" } @@ -717,6 +829,31 @@ func (nl NotificationList) LoadComments(ctx context.Context) ([]int, error) { return failures, nil } +func (nl NotificationList) LoadReleases(ctx context.Context) ([]int, error) { + if len(nl) == 0 { + return []int{}, nil + } + + failures := []int{} + for i, notification := range nl { + if notification.Source != NotificationSourceRelease { + continue + } + + release, err := repo_model.GetReleaseByID(ctx, notification.ReleaseID) + if err != nil { + if repo_model.IsErrReleaseNotExist(err) { + failures = append(failures, i) + } else { + return nil, err + } + } + release.Repo = notification.Repository + notification.Release = release + } + return failures, nil +} + // GetNotificationCount returns the notification count for user func GetNotificationCount(ctx context.Context, user *user_model.User, status NotificationStatus) (count int64, err error) { count, err = db.GetEngine(ctx). @@ -767,6 +904,23 @@ func setIssueNotificationStatusReadIfUnread(ctx context.Context, userID, issueID return err } +// SetReleaseReadBy sets release to be read by given user. +func SetReleaseReadBy(ctx context.Context, releaseID, userID int64) error { + notification, err := GetReleaseNotification(ctx, userID, releaseID) + if err != nil { + return err + } + + if notification.Status != NotificationStatusUnread { + return nil + } + + notification.Status = NotificationStatusRead + + _, err = db.GetEngine(ctx).ID(notification.ID).Cols("status").Update(notification) + return err +} + // SetRepoReadBy sets repo to be visited by given user. func SetRepoReadBy(ctx context.Context, userID, repoID int64) error { _, err := db.GetEngine(ctx).Where(builder.Eq{ diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index f0a8b05d5337d..cc34c45837f8e 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -532,6 +532,8 @@ var migrations = []Migration{ NewMigration("Add Actions artifacts expiration date", v1_21.AddExpiredUnixColumnInActionArtifactTable), // v275 -> v276 NewMigration("Add ScheduleID for ActionRun", v1_21.AddScheduleIDForActionRun), + // v276 -> v277 + NewMigration("Add ReleaseID to Notification", v1_21.AddReleaseIDToNotification), } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/v1_21/v276.go b/models/migrations/v1_21/v276.go new file mode 100644 index 0000000000000..6785cb3a12a12 --- /dev/null +++ b/models/migrations/v1_21/v276.go @@ -0,0 +1,15 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_21 //nolint + +import ( + "xorm.io/xorm" +) + +func AddReleaseIDToNotification(x *xorm.Engine) error { + type Notification struct { + ReleaseID int64 `xorm:"INDEX"` + } + return x.Sync(new(Notification)) +} diff --git a/modules/structs/notifications.go b/modules/structs/notifications.go index 7fbf4cb46d8a7..20c0c02b4c3dd 100644 --- a/modules/structs/notifications.go +++ b/modules/structs/notifications.go @@ -46,4 +46,6 @@ const ( NotifySubjectCommit NotifySubjectType = "Commit" // NotifySubjectRepository an repository is subject of an notification NotifySubjectRepository NotifySubjectType = "Repository" + // NotifySubjectRelease an release is subject of an notification + NotifySubjectRelease NotifySubjectType = "Release" ) diff --git a/routers/api/v1/notify/notifications.go b/routers/api/v1/notify/notifications.go index b22ea8a771579..57f6d2047a238 100644 --- a/routers/api/v1/notify/notifications.go +++ b/routers/api/v1/notify/notifications.go @@ -60,6 +60,8 @@ func subjectToSource(value []string) (result []activities_model.NotificationSour result = append(result, activities_model.NotificationSourceCommit) case "repository": result = append(result, activities_model.NotificationSourceRepository) + case "release": + result = append(result, activities_model.NotificationSourceRelease) } } return result diff --git a/routers/api/v1/notify/repo.go b/routers/api/v1/notify/repo.go index e16c54a2c0bb4..2cd85b43943d7 100644 --- a/routers/api/v1/notify/repo.go +++ b/routers/api/v1/notify/repo.go @@ -80,7 +80,7 @@ func ListRepoNotifications(ctx *context.APIContext) { // collectionFormat: multi // items: // type: string - // enum: [issue,pull,commit,repository] + // enum: [issue,pull,commit,repository,release] // - name: since // in: query // description: Only show notifications updated after the given time. This is a timestamp in RFC 3339 format diff --git a/routers/api/v1/notify/user.go b/routers/api/v1/notify/user.go index a9c6b4361794d..263ad73e406a3 100644 --- a/routers/api/v1/notify/user.go +++ b/routers/api/v1/notify/user.go @@ -41,7 +41,7 @@ func ListNotifications(ctx *context.APIContext) { // collectionFormat: multi // items: // type: string - // enum: [issue,pull,commit,repository] + // enum: [issue,pull,commit,repository,release] // - name: since // in: query // description: Only show notifications updated after the given time. This is a timestamp in RFC 3339 format diff --git a/routers/web/repo/release.go b/routers/web/repo/release.go index 138df45857606..7aba5e57918ff 100644 --- a/routers/web/repo/release.go +++ b/routers/web/repo/release.go @@ -11,6 +11,7 @@ import ( "strings" "code.gitea.io/gitea/models" + activities_model "code.gitea.io/gitea/models/activities" "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" @@ -291,6 +292,14 @@ func SingleRelease(ctx *context.Context) { return } + if ctx.IsSigned && !release.IsTag { + err = activities_model.SetReleaseReadBy(ctx, release.ID, ctx.Doer.ID) + if err != nil { + ctx.ServerError("SetReleaseReadBy", err) + return + } + } + ctx.Data["Releases"] = []*repo_model.Release{release} ctx.HTML(http.StatusOK, tplReleasesList) } diff --git a/routers/web/user/notification.go b/routers/web/user/notification.go index 579287ffac65d..474e21e61d80e 100644 --- a/routers/web/user/notification.go +++ b/routers/web/user/notification.go @@ -139,6 +139,14 @@ func getNotifications(ctx *context.Context) { notifications = notifications.Without(failures) failCount += len(failures) + failures, err = notifications.LoadReleases(ctx) + if err != nil { + ctx.ServerError("LoadReleases", err) + return + } + notifications = notifications.Without(failures) + failCount += len(failures) + if failCount > 0 { ctx.Flash.Error(fmt.Sprintf("ERROR: %d notifications were removed due to missing parts - check the logs", failCount)) } diff --git a/services/convert/notification.go b/services/convert/notification.go index 3906fa9b388a2..aff1ed3f2d3ff 100644 --- a/services/convert/notification.go +++ b/services/convert/notification.go @@ -82,6 +82,13 @@ func ToNotificationThread(n *activities_model.Notification) *api.NotificationThr URL: n.Repository.Link(), HTMLURL: n.Repository.HTMLURL(), } + case activities_model.NotificationSourceRelease: + result.Subject = &api.NotificationSubject{Type: api.NotifySubjectRelease} + if n.Release != nil { + result.Subject.Title = n.Release.Title + result.Subject.URL = n.Release.APIURL() + result.Subject.HTMLURL = n.Release.HTMLURL() + } } return result diff --git a/services/uinotification/notify.go b/services/uinotification/notify.go index 9cf4fe73d78d2..19a1ecce22b9b 100644 --- a/services/uinotification/notify.go +++ b/services/uinotification/notify.go @@ -18,17 +18,31 @@ import ( notify_service "code.gitea.io/gitea/services/notify" ) +type ( + // NotificationQueType is the type of the notification + NotificationQueueType uint8 +) + +const ( + // NotificationQueIssue is a notification of an issue + NotificationQueueIssue NotificationQueueType = iota + 1 + // NotificationSourceRepository is a notification for a release + NotificationQueueRelease +) + type ( notificationService struct { notify_service.NullNotifier - issueQueue *queue.WorkerPoolQueue[issueNotificationOpts] + notificationQueue *queue.WorkerPoolQueue[notificationOpts] } - issueNotificationOpts struct { + notificationOpts struct { IssueID int64 CommentID int64 NotificationAuthorID int64 ReceiverID int64 // 0 -- ALL Watcher + QueueType NotificationQueueType + ReleaseID int64 } ) @@ -43,39 +57,50 @@ var _ notify_service.Notifier = ¬ificationService{} // NewNotifier create a new notificationService notifier func NewNotifier() notify_service.Notifier { ns := ¬ificationService{} - ns.issueQueue = queue.CreateSimpleQueue(graceful.GetManager().ShutdownContext(), "notification-service", handler) - if ns.issueQueue == nil { + ns.notificationQueue = queue.CreateSimpleQueue(graceful.GetManager().ShutdownContext(), "notification-service", handler) + if ns.notificationQueue == nil { log.Fatal("Unable to create notification-service queue") } return ns } -func handler(items ...issueNotificationOpts) []issueNotificationOpts { +func handler(items ...notificationOpts) []notificationOpts { for _, opts := range items { - if err := activities_model.CreateOrUpdateIssueNotifications(opts.IssueID, opts.CommentID, opts.NotificationAuthorID, opts.ReceiverID); err != nil { - log.Error("Was unable to create issue notification: %v", err) + switch opts.QueueType { + case NotificationQueueIssue: + if err := activities_model.CreateOrUpdateIssueNotifications(opts.IssueID, opts.CommentID, opts.NotificationAuthorID, opts.ReceiverID); err != nil { + log.Error("Was unable to create issue notification: %v", err) + } + case NotificationQueueRelease: + if err := activities_model.CreateOrUpdateReleaseNotifications(opts.ReleaseID, opts.ReceiverID); err != nil { + log.Error("Was unable to create release notification: %v", err) + } + default: + log.Error("Unknown notification queue type") } } return nil } func (ns *notificationService) Run() { - go graceful.GetManager().RunWithCancel(ns.issueQueue) // TODO: using "go" here doesn't seem right, just leave it as old code + go graceful.GetManager().RunWithCancel(ns.notificationQueue) // TODO: using "go" here doesn't seem right, just leave it as old code } func (ns *notificationService) CreateIssueComment(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, issue *issues_model.Issue, comment *issues_model.Comment, mentions []*user_model.User, ) { - opts := issueNotificationOpts{ + opts := notificationOpts{ + QueueType: NotificationQueueIssue, IssueID: issue.ID, NotificationAuthorID: doer.ID, } if comment != nil { opts.CommentID = comment.ID } - _ = ns.issueQueue.Push(opts) + _ = ns.notificationQueue.Push(opts) for _, mention := range mentions { - opts := issueNotificationOpts{ + opts := notificationOpts{ + QueueType: NotificationQueueIssue, IssueID: issue.ID, NotificationAuthorID: doer.ID, ReceiverID: mention.ID, @@ -83,17 +108,19 @@ func (ns *notificationService) CreateIssueComment(ctx context.Context, doer *use if comment != nil { opts.CommentID = comment.ID } - _ = ns.issueQueue.Push(opts) + _ = ns.notificationQueue.Push(opts) } } func (ns *notificationService) NewIssue(ctx context.Context, issue *issues_model.Issue, mentions []*user_model.User) { - _ = ns.issueQueue.Push(issueNotificationOpts{ + _ = ns.notificationQueue.Push(notificationOpts{ + QueueType: NotificationQueueIssue, IssueID: issue.ID, NotificationAuthorID: issue.Poster.ID, }) for _, mention := range mentions { - _ = ns.issueQueue.Push(issueNotificationOpts{ + _ = ns.notificationQueue.Push(notificationOpts{ + QueueType: NotificationQueueIssue, IssueID: issue.ID, NotificationAuthorID: issue.Poster.ID, ReceiverID: mention.ID, @@ -102,7 +129,8 @@ func (ns *notificationService) NewIssue(ctx context.Context, issue *issues_model } func (ns *notificationService) IssueChangeStatus(ctx context.Context, doer *user_model.User, commitID string, issue *issues_model.Issue, actionComment *issues_model.Comment, isClosed bool) { - _ = ns.issueQueue.Push(issueNotificationOpts{ + _ = ns.notificationQueue.Push(notificationOpts{ + QueueType: NotificationQueueIssue, IssueID: issue.ID, NotificationAuthorID: doer.ID, CommentID: actionComment.ID, @@ -115,7 +143,8 @@ func (ns *notificationService) IssueChangeTitle(ctx context.Context, doer *user_ return } if issue.IsPull && issues_model.HasWorkInProgressPrefix(oldTitle) && !issue.PullRequest.IsWorkInProgress() { - _ = ns.issueQueue.Push(issueNotificationOpts{ + _ = ns.notificationQueue.Push(notificationOpts{ + QueueType: NotificationQueueIssue, IssueID: issue.ID, NotificationAuthorID: doer.ID, }) @@ -123,7 +152,8 @@ func (ns *notificationService) IssueChangeTitle(ctx context.Context, doer *user_ } func (ns *notificationService) MergePullRequest(ctx context.Context, doer *user_model.User, pr *issues_model.PullRequest) { - _ = ns.issueQueue.Push(issueNotificationOpts{ + _ = ns.notificationQueue.Push(notificationOpts{ + QueueType: NotificationQueueIssue, IssueID: pr.Issue.ID, NotificationAuthorID: doer.ID, }) @@ -160,7 +190,8 @@ func (ns *notificationService) NewPullRequest(ctx context.Context, pr *issues_mo toNotify.Add(mention.ID) } for receiverID := range toNotify { - _ = ns.issueQueue.Push(issueNotificationOpts{ + _ = ns.notificationQueue.Push(notificationOpts{ + QueueType: NotificationQueueIssue, IssueID: pr.Issue.ID, NotificationAuthorID: pr.Issue.PosterID, ReceiverID: receiverID, @@ -169,16 +200,18 @@ func (ns *notificationService) NewPullRequest(ctx context.Context, pr *issues_mo } func (ns *notificationService) PullRequestReview(ctx context.Context, pr *issues_model.PullRequest, r *issues_model.Review, c *issues_model.Comment, mentions []*user_model.User) { - opts := issueNotificationOpts{ + opts := notificationOpts{ + QueueType: NotificationQueueIssue, IssueID: pr.Issue.ID, NotificationAuthorID: r.Reviewer.ID, } if c != nil { opts.CommentID = c.ID } - _ = ns.issueQueue.Push(opts) + _ = ns.notificationQueue.Push(opts) for _, mention := range mentions { - opts := issueNotificationOpts{ + opts := notificationOpts{ + QueueType: NotificationQueueIssue, IssueID: pr.Issue.ID, NotificationAuthorID: r.Reviewer.ID, ReceiverID: mention.ID, @@ -186,13 +219,14 @@ func (ns *notificationService) PullRequestReview(ctx context.Context, pr *issues if c != nil { opts.CommentID = c.ID } - _ = ns.issueQueue.Push(opts) + _ = ns.notificationQueue.Push(opts) } } func (ns *notificationService) PullRequestCodeComment(ctx context.Context, pr *issues_model.PullRequest, c *issues_model.Comment, mentions []*user_model.User) { for _, mention := range mentions { - _ = ns.issueQueue.Push(issueNotificationOpts{ + _ = ns.notificationQueue.Push(notificationOpts{ + QueueType: NotificationQueueIssue, IssueID: pr.Issue.ID, NotificationAuthorID: c.Poster.ID, CommentID: c.ID, @@ -202,26 +236,29 @@ func (ns *notificationService) PullRequestCodeComment(ctx context.Context, pr *i } func (ns *notificationService) PullRequestPushCommits(ctx context.Context, doer *user_model.User, pr *issues_model.PullRequest, comment *issues_model.Comment) { - opts := issueNotificationOpts{ + opts := notificationOpts{ + QueueType: NotificationQueueIssue, IssueID: pr.IssueID, NotificationAuthorID: doer.ID, CommentID: comment.ID, } - _ = ns.issueQueue.Push(opts) + _ = ns.notificationQueue.Push(opts) } func (ns *notificationService) PullReviewDismiss(ctx context.Context, doer *user_model.User, review *issues_model.Review, comment *issues_model.Comment) { - opts := issueNotificationOpts{ + opts := notificationOpts{ + QueueType: NotificationQueueIssue, IssueID: review.IssueID, NotificationAuthorID: doer.ID, CommentID: comment.ID, } - _ = ns.issueQueue.Push(opts) + _ = ns.notificationQueue.Push(opts) } func (ns *notificationService) IssueChangeAssignee(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, assignee *user_model.User, removed bool, comment *issues_model.Comment) { if !removed && doer.ID != assignee.ID { - opts := issueNotificationOpts{ + opts := notificationOpts{ + QueueType: NotificationQueueIssue, IssueID: issue.ID, NotificationAuthorID: doer.ID, ReceiverID: assignee.ID, @@ -231,13 +268,14 @@ func (ns *notificationService) IssueChangeAssignee(ctx context.Context, doer *us opts.CommentID = comment.ID } - _ = ns.issueQueue.Push(opts) + _ = ns.notificationQueue.Push(opts) } } func (ns *notificationService) PullRequestReviewRequest(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, reviewer *user_model.User, isRequest bool, comment *issues_model.Comment) { if isRequest { - opts := issueNotificationOpts{ + opts := notificationOpts{ + QueueType: NotificationQueueIssue, IssueID: issue.ID, NotificationAuthorID: doer.ID, ReceiverID: reviewer.ID, @@ -247,7 +285,7 @@ func (ns *notificationService) PullRequestReviewRequest(ctx context.Context, doe opts.CommentID = comment.ID } - _ = ns.issueQueue.Push(opts) + _ = ns.notificationQueue.Push(opts) } } @@ -259,3 +297,20 @@ func (ns *notificationService) RepoPendingTransfer(ctx context.Context, doer, ne log.Error("CreateRepoTransferNotification: %v", err) } } + +func (ns *notificationService) NewRelease(ctx context.Context, rel *repo_model.Release) { + repoWatchers, err := repo_model.GetRepoWatchersIDs(ctx, rel.RepoID) + if err != nil { + log.Error("GetRepoWatchersIDs: %v", err) + return + } + + for _, watcher := range repoWatchers { + if watcher != rel.PublisherID { + err = ns.notificationQueue.Push(notificationOpts{QueueType: NotificationQueueRelease, ReleaseID: rel.ID, ReceiverID: watcher}) + if err != nil { + log.Error("NotificationQueuePushRelease: %v", err) + } + } + } +} diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 88dc9ea1ce551..fbd888df40881 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -1176,7 +1176,8 @@ "issue", "pull", "commit", - "repository" + "repository", + "release" ], "type": "string" }, @@ -9962,7 +9963,8 @@ "issue", "pull", "commit", - "repository" + "repository", + "release" ], "type": "string" }, diff --git a/templates/user/notification/notification_div.tmpl b/templates/user/notification/notification_div.tmpl index 415051ae957ef..e0d9644d39f5f 100644 --- a/templates/user/notification/notification_div.tmpl +++ b/templates/user/notification/notification_div.tmpl @@ -40,6 +40,8 @@
{{if .Issue}} {{template "shared/issueicon" .Issue}} + {{else if .Release}} + {{svg "octicon-tag" 16 "text grey"}} {{else}} {{svg "octicon-repo" 16 "text grey"}} {{end}} @@ -55,6 +57,8 @@ {{if .Issue}} {{.Issue.Title | RenderEmoji $.Context | RenderCodeBlock}} + {{else if .Release}} + {{.Release.Title}} {{else}} {{.Repository.FullName}} {{end}} @@ -64,6 +68,8 @@
{{if .Issue}} {{TimeSinceUnix .Issue.UpdatedUnix $locale}} + {{else if .Release}} + {{TimeSinceUnix .Release.CreatedUnix $locale}} {{else}} {{TimeSinceUnix .UpdatedUnix $locale}} {{end}}