diff --git a/integrations/api_issue_test.go b/integrations/api_issue_test.go index d742049335c97..1dc9b46affb98 100644 --- a/integrations/api_issue_test.go +++ b/integrations/api_issue_test.go @@ -31,7 +31,17 @@ func TestAPIListIssues(t *testing.T) { DecodeJSON(t, resp, &apiIssues) assert.Len(t, apiIssues, models.GetCount(t, &models.Issue{RepoID: repo.ID})) for _, apiIssue := range apiIssues { - models.AssertExistsAndLoadBean(t, &models.Issue{ID: apiIssue.ID, RepoID: repo.ID}) + issue, err := models.GetIssueByID(apiIssue.ID) + assert.NoError(t, err) + assert.NoError(t, issue.LoadAttributes()) + assert.Equal(t, repo.ID, issue.RepoID) + assert.Equal(t, issue.PosterID, apiIssue.Poster.ID) + assert.Equal(t, int64(issue.UpdatedUnix), apiIssue.Updated.Unix()) + summary := issue.Reactions.Summary() + for i, apiR := range apiIssue.Reactions { + assert.Equal(t, summary[i].Type, apiR.Type) + assert.EqualValues(t, summary[i].Users, apiR.Users) + } } // test milestone filter diff --git a/models/issue.go b/models/issue.go index ee75623f53025..b0b1323811b46 100644 --- a/models/issue.go +++ b/models/issue.go @@ -217,6 +217,11 @@ func (issue *Issue) loadCommentsByType(e Engine, tp CommentType) (err error) { return err } +// LoadReactions loads reactions +func (issue *Issue) LoadReactions() (err error) { + return issue.loadReactions(x) +} + func (issue *Issue) loadReactions(e Engine) (err error) { if issue.Reactions != nil { return nil diff --git a/models/issue_comment.go b/models/issue_comment.go index 726ed7472bcf5..3f347ed531661 100644 --- a/models/issue_comment.go +++ b/models/issue_comment.go @@ -354,15 +354,17 @@ func (c *Comment) PRURL() string { // APIFormat converts a Comment to the api.Comment format func (c *Comment) APIFormat() *api.Comment { + _ = c.LoadReactions return &api.Comment{ - ID: c.ID, - Poster: c.Poster.APIFormat(), - HTMLURL: c.HTMLURL(), - IssueURL: c.IssueURL(), - PRURL: c.PRURL(), - Body: c.Content, - Created: c.CreatedUnix.AsTime(), - Updated: c.UpdatedUnix.AsTime(), + ID: c.ID, + Poster: c.Poster.APIFormat(), + HTMLURL: c.HTMLURL(), + IssueURL: c.IssueURL(), + PRURL: c.PRURL(), + Body: c.Content, + Created: c.CreatedUnix.AsTime(), + Updated: c.UpdatedUnix.AsTime(), + Reactions: c.Reactions.Summary(), } } diff --git a/models/issue_reaction.go b/models/issue_reaction.go index 50b9d6848a533..b82480cceff5f 100644 --- a/models/issue_reaction.go +++ b/models/issue_reaction.go @@ -9,6 +9,7 @@ import ( "fmt" "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" "xorm.io/builder" @@ -254,6 +255,22 @@ func (list ReactionList) HasUser(userID int64) bool { return false } +// Summary return grouped reactions +func (list ReactionList) Summary() []*api.GroupedReaction { + rmap := make(map[string][]string) + var result []*api.GroupedReaction + + for _, r := range list { + rmap[r.Type] = append(rmap[r.Type], r.User.Name) + } + + for k, v := range rmap { + result = append(result, &api.GroupedReaction{Type: k, Users: v}) + } + + return result +} + // GroupByType returns reactions grouped by type func (list ReactionList) GroupByType() map[string]ReactionList { var reactions = make(map[string]ReactionList) diff --git a/modules/convert/issue.go b/modules/convert/issue.go index 724ec8ffcf67f..295c7d423cf36 100644 --- a/modules/convert/issue.go +++ b/modules/convert/issue.go @@ -26,20 +26,25 @@ func ToAPIIssue(issue *models.Issue) *api.Issue { return &api.Issue{} } + if err := issue.LoadReactions(); err != nil { + return &api.Issue{} + } + apiIssue := &api.Issue{ - ID: issue.ID, - URL: issue.APIURL(), - HTMLURL: issue.HTMLURL(), - Index: issue.Index, - Poster: issue.Poster.APIFormat(), - Title: issue.Title, - Body: issue.Content, - Labels: ToLabelList(issue.Labels), - State: issue.State(), - IsLocked: issue.IsLocked, - Comments: issue.NumComments, - Created: issue.CreatedUnix.AsTime(), - Updated: issue.UpdatedUnix.AsTime(), + ID: issue.ID, + URL: issue.APIURL(), + HTMLURL: issue.HTMLURL(), + Index: issue.Index, + Poster: issue.Poster.APIFormat(), + Title: issue.Title, + Body: issue.Content, + Labels: ToLabelList(issue.Labels), + State: issue.State(), + IsLocked: issue.IsLocked, + Comments: issue.NumComments, + Created: issue.CreatedUnix.AsTime(), + Updated: issue.UpdatedUnix.AsTime(), + Reactions: issue.Reactions.Summary(), } apiIssue.Repo = &api.RepositoryMeta{ diff --git a/modules/structs/issue.go b/modules/structs/issue.go index 54b0f31d8a5b3..1a1c66ba1c1ae 100644 --- a/modules/structs/issue.go +++ b/modules/structs/issue.go @@ -38,19 +38,20 @@ type RepositoryMeta struct { // Issue represents an issue in a repository // swagger:model type Issue struct { - ID int64 `json:"id"` - URL string `json:"url"` - HTMLURL string `json:"html_url"` - Index int64 `json:"number"` - Poster *User `json:"user"` - OriginalAuthor string `json:"original_author"` - OriginalAuthorID int64 `json:"original_author_id"` - Title string `json:"title"` - Body string `json:"body"` - Labels []*Label `json:"labels"` - Milestone *Milestone `json:"milestone"` - Assignee *User `json:"assignee"` - Assignees []*User `json:"assignees"` + ID int64 `json:"id"` + URL string `json:"url"` + HTMLURL string `json:"html_url"` + Index int64 `json:"number"` + Poster *User `json:"user"` + OriginalAuthor string `json:"original_author"` + OriginalAuthorID int64 `json:"original_author_id"` + Title string `json:"title"` + Body string `json:"body"` + Labels []*Label `json:"labels"` + Milestone *Milestone `json:"milestone"` + Assignee *User `json:"assignee"` + Assignees []*User `json:"assignees"` + Reactions ReactionSummary `json:"reaction_summary"` // Whether the issue is open or closed // // type: string diff --git a/modules/structs/issue_comment.go b/modules/structs/issue_comment.go index 0c8ac20017fbc..a53500ddbf103 100644 --- a/modules/structs/issue_comment.go +++ b/modules/structs/issue_comment.go @@ -10,14 +10,15 @@ import ( // Comment represents a comment on a commit or issue type Comment struct { - ID int64 `json:"id"` - HTMLURL string `json:"html_url"` - PRURL string `json:"pull_request_url"` - IssueURL string `json:"issue_url"` - Poster *User `json:"user"` - OriginalAuthor string `json:"original_author"` - OriginalAuthorID int64 `json:"original_author_id"` - Body string `json:"body"` + ID int64 `json:"id"` + HTMLURL string `json:"html_url"` + PRURL string `json:"pull_request_url"` + IssueURL string `json:"issue_url"` + Poster *User `json:"user"` + OriginalAuthor string `json:"original_author"` + OriginalAuthorID int64 `json:"original_author_id"` + Body string `json:"body"` + Reactions ReactionSummary `json:"reaction_summary"` // swagger:strfmt date-time Created time.Time `json:"created_at"` // swagger:strfmt date-time diff --git a/modules/structs/issue_reaction.go b/modules/structs/issue_reaction.go index 56408313ee4d3..a728aed5dc2bc 100644 --- a/modules/structs/issue_reaction.go +++ b/modules/structs/issue_reaction.go @@ -20,3 +20,13 @@ type Reaction struct { // swagger:strfmt date-time Created time.Time `json:"created_at"` } + +// ReactionSummary return users who reacted grouped by type +// swagger:model +type ReactionSummary []*GroupedReaction + +// GroupedReaction represents a item of ReactionSummary +type GroupedReaction struct { + Type string `json:"type"` + Users []string `json:"users"` +} diff --git a/routers/api/v1/swagger/issue.go b/routers/api/v1/swagger/issue.go index 0f2f572020805..3c62432c1ee93 100644 --- a/routers/api/v1/swagger/issue.go +++ b/routers/api/v1/swagger/issue.go @@ -119,3 +119,10 @@ type swaggerReactionList struct { // in:body Body []api.Reaction `json:"body"` } + +// ReactionSummary +// swagger:response ReactionSummary +type swaggerReactionSummary struct { + // in:body + Body api.ReactionSummary `json:"body"` +} diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index e7655f02a8ca8..8bce8dde8714a 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -11273,6 +11273,9 @@ "type": "string", "x-go-name": "PRURL" }, + "reaction_summary": { + "$ref": "#/definitions/ReactionSummary" + }, "updated_at": { "type": "string", "format": "date-time", @@ -13461,6 +13464,24 @@ }, "x-go-package": "code.gitea.io/gitea/modules/structs" }, + "GroupedReaction": { + "description": "GroupedReaction represents a item of ReactionSummary", + "type": "object", + "properties": { + "type": { + "type": "string", + "x-go-name": "Type" + }, + "users": { + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "Users" + } + }, + "x-go-package": "code.gitea.io/gitea/modules/structs" + }, "Hook": { "description": "Hook a hook is a web hook when one repository changed", "type": "object", @@ -13621,6 +13642,9 @@ "pull_request": { "$ref": "#/definitions/PullRequestMeta" }, + "reaction_summary": { + "$ref": "#/definitions/ReactionSummary" + }, "repository": { "$ref": "#/definitions/RepositoryMeta" }, @@ -14633,6 +14657,14 @@ }, "x-go-package": "code.gitea.io/gitea/modules/structs" }, + "ReactionSummary": { + "description": "ReactionSummary return users who reacted grouped by type", + "type": "array", + "items": { + "$ref": "#/definitions/GroupedReaction" + }, + "x-go-package": "code.gitea.io/gitea/modules/structs" + }, "Reference": { "type": "object", "title": "Reference represents a Git reference.", @@ -15931,6 +15963,12 @@ } } }, + "ReactionSummary": { + "description": "ReactionSummary", + "schema": { + "$ref": "#/definitions/ReactionSummary" + } + }, "Reference": { "description": "Reference", "schema": {