mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 16:40:24 +08:00 
			
		
		
		
	Add API to get issue/pull comments and events (timeline) (#17403)
* Add API to get issue/pull comments and events (timeline) Adds an API to get both comments and events in one endpoint with all required data. Closes go-gitea/gitea#13250 * Fix swagger * Don't show code comments (use review api instead) * fmt * Fix comment * Time -> TrackedTime * Use var directly * Add logger * Fix lint * Fix test * Add comments * fmt * [test] get issue directly by ID * Update test * Add description for changed refs * Fix build issues + lint * Fix build * Use string enums * Update swagger * Support `page` and `limit` params * fmt + swagger * Use global slices Co-authored-by: zeripath <art27@cantab.net> Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
This commit is contained in:
		@@ -180,3 +180,25 @@ func TestAPIDeleteComment(t *testing.T) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	unittest.AssertNotExistsBean(t, &models.Comment{ID: comment.ID})
 | 
						unittest.AssertNotExistsBean(t, &models.Comment{ID: comment.ID})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestAPIListIssueTimeline(t *testing.T) {
 | 
				
			||||||
 | 
						defer prepareTestEnv(t)()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// load comment
 | 
				
			||||||
 | 
						issue := unittest.AssertExistsAndLoadBean(t, &models.Issue{ID: 1}).(*models.Issue)
 | 
				
			||||||
 | 
						repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}).(*repo_model.Repository)
 | 
				
			||||||
 | 
						repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}).(*user_model.User)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// make request
 | 
				
			||||||
 | 
						session := loginUser(t, repoOwner.Name)
 | 
				
			||||||
 | 
						req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/%d/timeline",
 | 
				
			||||||
 | 
							repoOwner.Name, repo.Name, issue.Index)
 | 
				
			||||||
 | 
						resp := session.MakeRequest(t, req, http.StatusOK)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// check if lens of list returned by API and
 | 
				
			||||||
 | 
						// lists extracted directly from DB are the same
 | 
				
			||||||
 | 
						var comments []*api.TimelineComment
 | 
				
			||||||
 | 
						DecodeJSON(t, resp, &comments)
 | 
				
			||||||
 | 
						expectedCount := unittest.GetCount(t, &models.Comment{IssueID: issue.ID})
 | 
				
			||||||
 | 
						assert.EqualValues(t, expectedCount, len(comments))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -110,6 +110,47 @@ const (
 | 
				
			|||||||
	CommentTypeChangeIssueRef
 | 
						CommentTypeChangeIssueRef
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var commentStrings = []string{
 | 
				
			||||||
 | 
						"comment",
 | 
				
			||||||
 | 
						"reopen",
 | 
				
			||||||
 | 
						"close",
 | 
				
			||||||
 | 
						"issue_ref",
 | 
				
			||||||
 | 
						"commit_ref",
 | 
				
			||||||
 | 
						"comment_ref",
 | 
				
			||||||
 | 
						"pull_ref",
 | 
				
			||||||
 | 
						"label",
 | 
				
			||||||
 | 
						"milestone",
 | 
				
			||||||
 | 
						"assignees",
 | 
				
			||||||
 | 
						"change_title",
 | 
				
			||||||
 | 
						"delete_branch",
 | 
				
			||||||
 | 
						"start_tracking",
 | 
				
			||||||
 | 
						"stop_tracking",
 | 
				
			||||||
 | 
						"add_time_manual",
 | 
				
			||||||
 | 
						"cancel_tracking",
 | 
				
			||||||
 | 
						"added_deadline",
 | 
				
			||||||
 | 
						"modified_deadline",
 | 
				
			||||||
 | 
						"removed_deadline",
 | 
				
			||||||
 | 
						"add_dependency",
 | 
				
			||||||
 | 
						"remove_dependency",
 | 
				
			||||||
 | 
						"code",
 | 
				
			||||||
 | 
						"review",
 | 
				
			||||||
 | 
						"lock",
 | 
				
			||||||
 | 
						"unlock",
 | 
				
			||||||
 | 
						"change_target_branch",
 | 
				
			||||||
 | 
						"delete_time_manual",
 | 
				
			||||||
 | 
						"review_request",
 | 
				
			||||||
 | 
						"merge_pull",
 | 
				
			||||||
 | 
						"pull_push",
 | 
				
			||||||
 | 
						"project",
 | 
				
			||||||
 | 
						"project_board",
 | 
				
			||||||
 | 
						"dismiss_review",
 | 
				
			||||||
 | 
						"change_issue_ref",
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (t CommentType) String() string {
 | 
				
			||||||
 | 
						return commentStrings[t]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// RoleDescriptor defines comment tag type
 | 
					// RoleDescriptor defines comment tag type
 | 
				
			||||||
type RoleDescriptor int
 | 
					type RoleDescriptor int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,9 @@ package convert
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"code.gitea.io/gitea/models"
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
 | 
						repo_model "code.gitea.io/gitea/models/repo"
 | 
				
			||||||
 | 
						user_model "code.gitea.io/gitea/models/user"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/log"
 | 
				
			||||||
	api "code.gitea.io/gitea/modules/structs"
 | 
						api "code.gitea.io/gitea/modules/structs"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -22,3 +25,143 @@ func ToComment(c *models.Comment) *api.Comment {
 | 
				
			|||||||
		Updated:  c.UpdatedUnix.AsTime(),
 | 
							Updated:  c.UpdatedUnix.AsTime(),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ToTimelineComment converts a models.Comment to the api.TimelineComment format
 | 
				
			||||||
 | 
					func ToTimelineComment(c *models.Comment, doer *user_model.User) *api.TimelineComment {
 | 
				
			||||||
 | 
						err := c.LoadMilestone()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Error("LoadMilestone: %v", err)
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = c.LoadAssigneeUserAndTeam()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Error("LoadAssigneeUserAndTeam: %v", err)
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = c.LoadResolveDoer()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Error("LoadResolveDoer: %v", err)
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = c.LoadDepIssueDetails()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Error("LoadDepIssueDetails: %v", err)
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = c.LoadTime()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Error("LoadTime: %v", err)
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = c.LoadLabel()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Error("LoadLabel: %v", err)
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						comment := &api.TimelineComment{
 | 
				
			||||||
 | 
							ID:       c.ID,
 | 
				
			||||||
 | 
							Type:     c.Type.String(),
 | 
				
			||||||
 | 
							Poster:   ToUser(c.Poster, nil),
 | 
				
			||||||
 | 
							HTMLURL:  c.HTMLURL(),
 | 
				
			||||||
 | 
							IssueURL: c.IssueURL(),
 | 
				
			||||||
 | 
							PRURL:    c.PRURL(),
 | 
				
			||||||
 | 
							Body:     c.Content,
 | 
				
			||||||
 | 
							Created:  c.CreatedUnix.AsTime(),
 | 
				
			||||||
 | 
							Updated:  c.UpdatedUnix.AsTime(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							OldProjectID: c.OldProjectID,
 | 
				
			||||||
 | 
							ProjectID:    c.ProjectID,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							OldTitle: c.OldTitle,
 | 
				
			||||||
 | 
							NewTitle: c.NewTitle,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							OldRef: c.OldRef,
 | 
				
			||||||
 | 
							NewRef: c.NewRef,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							RefAction:    c.RefAction.String(),
 | 
				
			||||||
 | 
							RefCommitSHA: c.CommitSHA,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							ReviewID: c.ReviewID,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							RemovedAssignee: c.RemovedAssignee,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if c.OldMilestone != nil {
 | 
				
			||||||
 | 
							comment.OldMilestone = ToAPIMilestone(c.OldMilestone)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if c.Milestone != nil {
 | 
				
			||||||
 | 
							comment.Milestone = ToAPIMilestone(c.Milestone)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if c.Time != nil {
 | 
				
			||||||
 | 
							comment.TrackedTime = ToTrackedTime(c.Time)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if c.RefIssueID != 0 {
 | 
				
			||||||
 | 
							issue, err := models.GetIssueByID(c.RefIssueID)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Error("GetIssueByID(%d): %v", c.RefIssueID, err)
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							comment.RefIssue = ToAPIIssue(issue)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if c.RefCommentID != 0 {
 | 
				
			||||||
 | 
							com, err := models.GetCommentByID(c.RefCommentID)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Error("GetCommentByID(%d): %v", c.RefCommentID, err)
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							err = com.LoadPoster()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Error("LoadPoster: %v", err)
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							comment.RefComment = ToComment(com)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if c.Label != nil {
 | 
				
			||||||
 | 
							var org *user_model.User
 | 
				
			||||||
 | 
							var repo *repo_model.Repository
 | 
				
			||||||
 | 
							if c.Label.BelongsToOrg() {
 | 
				
			||||||
 | 
								var err error
 | 
				
			||||||
 | 
								org, err = user_model.GetUserByID(c.Label.OrgID)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									log.Error("GetUserByID(%d): %v", c.Label.OrgID, err)
 | 
				
			||||||
 | 
									return nil
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if c.Label.BelongsToRepo() {
 | 
				
			||||||
 | 
								var err error
 | 
				
			||||||
 | 
								repo, err = repo_model.GetRepositoryByID(c.Label.RepoID)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									log.Error("GetRepositoryByID(%d): %v", c.Label.RepoID, err)
 | 
				
			||||||
 | 
									return nil
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							comment.Label = ToLabel(c.Label, repo, org)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if c.Assignee != nil {
 | 
				
			||||||
 | 
							comment.Assignee = ToUser(c.Assignee, nil)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if c.AssigneeTeam != nil {
 | 
				
			||||||
 | 
							comment.AssigneeTeam = ToTeam(c.AssigneeTeam)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if c.ResolveDoer != nil {
 | 
				
			||||||
 | 
							comment.ResolveDoer = ToUser(c.ResolveDoer, nil)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if c.DependentIssue != nil {
 | 
				
			||||||
 | 
							comment.DependentIssue = ToAPIIssue(c.DependentIssue)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return comment
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -49,6 +49,13 @@ var (
 | 
				
			|||||||
	giteaHostInit         sync.Once
 | 
						giteaHostInit         sync.Once
 | 
				
			||||||
	giteaHost             string
 | 
						giteaHost             string
 | 
				
			||||||
	giteaIssuePullPattern *regexp.Regexp
 | 
						giteaIssuePullPattern *regexp.Regexp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						actionStrings = []string{
 | 
				
			||||||
 | 
							"none",
 | 
				
			||||||
 | 
							"closes",
 | 
				
			||||||
 | 
							"reopens",
 | 
				
			||||||
 | 
							"neutered",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// XRefAction represents the kind of effect a cross reference has once is resolved
 | 
					// XRefAction represents the kind of effect a cross reference has once is resolved
 | 
				
			||||||
@@ -65,6 +72,10 @@ const (
 | 
				
			|||||||
	XRefActionNeutered // 3
 | 
						XRefActionNeutered // 3
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (a XRefAction) String() string {
 | 
				
			||||||
 | 
						return actionStrings[a]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// IssueReference contains an unverified cross-reference to a local issue or pull request
 | 
					// IssueReference contains an unverified cross-reference to a local issue or pull request
 | 
				
			||||||
type IssueReference struct {
 | 
					type IssueReference struct {
 | 
				
			||||||
	Index   int64
 | 
						Index   int64
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,3 +35,48 @@ type EditIssueCommentOption struct {
 | 
				
			|||||||
	// required: true
 | 
						// required: true
 | 
				
			||||||
	Body string `json:"body" binding:"Required"`
 | 
						Body string `json:"body" binding:"Required"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TimelineComment represents a timeline comment (comment of any type) on a commit or issue
 | 
				
			||||||
 | 
					type TimelineComment struct {
 | 
				
			||||||
 | 
						ID   int64  `json:"id"`
 | 
				
			||||||
 | 
						Type string `json:"type"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						HTMLURL  string `json:"html_url"`
 | 
				
			||||||
 | 
						PRURL    string `json:"pull_request_url"`
 | 
				
			||||||
 | 
						IssueURL string `json:"issue_url"`
 | 
				
			||||||
 | 
						Poster   *User  `json:"user"`
 | 
				
			||||||
 | 
						Body     string `json:"body"`
 | 
				
			||||||
 | 
						// swagger:strfmt date-time
 | 
				
			||||||
 | 
						Created time.Time `json:"created_at"`
 | 
				
			||||||
 | 
						// swagger:strfmt date-time
 | 
				
			||||||
 | 
						Updated time.Time `json:"updated_at"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						OldProjectID int64        `json:"old_project_id"`
 | 
				
			||||||
 | 
						ProjectID    int64        `json:"project_id"`
 | 
				
			||||||
 | 
						OldMilestone *Milestone   `json:"old_milestone"`
 | 
				
			||||||
 | 
						Milestone    *Milestone   `json:"milestone"`
 | 
				
			||||||
 | 
						TrackedTime  *TrackedTime `json:"tracked_time"`
 | 
				
			||||||
 | 
						OldTitle     string       `json:"old_title"`
 | 
				
			||||||
 | 
						NewTitle     string       `json:"new_title"`
 | 
				
			||||||
 | 
						OldRef       string       `json:"old_ref"`
 | 
				
			||||||
 | 
						NewRef       string       `json:"new_ref"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						RefIssue   *Issue   `json:"ref_issue"`
 | 
				
			||||||
 | 
						RefComment *Comment `json:"ref_comment"`
 | 
				
			||||||
 | 
						RefAction  string   `json:"ref_action"`
 | 
				
			||||||
 | 
						// commit SHA where issue/PR was referenced
 | 
				
			||||||
 | 
						RefCommitSHA string `json:"ref_commit_sha"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ReviewID int64 `json:"review_id"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Label *Label `json:"label"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Assignee     *User `json:"assignee"`
 | 
				
			||||||
 | 
						AssigneeTeam *Team `json:"assignee_team"`
 | 
				
			||||||
 | 
						// whether the assignees were removed or added
 | 
				
			||||||
 | 
						RemovedAssignee bool `json:"removed_assignee"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ResolveDoer *User `json:"resolve_doer"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						DependentIssue *Issue `json:"dependent_issue"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -842,6 +842,7 @@ func Routes(sessioner func(http.Handler) http.Handler) *web.Route {
 | 
				
			|||||||
							m.Combo("/{id}", reqToken()).Patch(bind(api.EditIssueCommentOption{}), repo.EditIssueCommentDeprecated).
 | 
												m.Combo("/{id}", reqToken()).Patch(bind(api.EditIssueCommentOption{}), repo.EditIssueCommentDeprecated).
 | 
				
			||||||
								Delete(repo.DeleteIssueCommentDeprecated)
 | 
													Delete(repo.DeleteIssueCommentDeprecated)
 | 
				
			||||||
						})
 | 
											})
 | 
				
			||||||
 | 
											m.Get("/timeline", repo.ListIssueCommentsAndTimeline)
 | 
				
			||||||
						m.Group("/labels", func() {
 | 
											m.Group("/labels", func() {
 | 
				
			||||||
							m.Combo("").Get(repo.ListIssueLabels).
 | 
												m.Combo("").Get(repo.ListIssueLabels).
 | 
				
			||||||
								Post(reqToken(), bind(api.IssueLabelsOption{}), repo.AddIssueLabels).
 | 
													Post(reqToken(), bind(api.IssueLabelsOption{}), repo.AddIssueLabels).
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,6 +10,8 @@ import (
 | 
				
			|||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/models"
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
 | 
						repo_model "code.gitea.io/gitea/models/repo"
 | 
				
			||||||
 | 
						user_model "code.gitea.io/gitea/models/user"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/context"
 | 
						"code.gitea.io/gitea/modules/context"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/convert"
 | 
						"code.gitea.io/gitea/modules/convert"
 | 
				
			||||||
	api "code.gitea.io/gitea/modules/structs"
 | 
						api "code.gitea.io/gitea/modules/structs"
 | 
				
			||||||
@@ -102,6 +104,115 @@ func ListIssueComments(ctx *context.APIContext) {
 | 
				
			|||||||
	ctx.JSON(http.StatusOK, &apiComments)
 | 
						ctx.JSON(http.StatusOK, &apiComments)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ListIssueCommentsAndTimeline list all the comments and events of an issue
 | 
				
			||||||
 | 
					func ListIssueCommentsAndTimeline(ctx *context.APIContext) {
 | 
				
			||||||
 | 
						// swagger:operation GET /repos/{owner}/{repo}/issues/{index}/timeline issue issueGetCommentsAndTimeline
 | 
				
			||||||
 | 
						// ---
 | 
				
			||||||
 | 
						// summary: List all comments and events on an issue
 | 
				
			||||||
 | 
						// produces:
 | 
				
			||||||
 | 
						// - application/json
 | 
				
			||||||
 | 
						// parameters:
 | 
				
			||||||
 | 
						// - name: owner
 | 
				
			||||||
 | 
						//   in: path
 | 
				
			||||||
 | 
						//   description: owner of the repo
 | 
				
			||||||
 | 
						//   type: string
 | 
				
			||||||
 | 
						//   required: true
 | 
				
			||||||
 | 
						// - name: repo
 | 
				
			||||||
 | 
						//   in: path
 | 
				
			||||||
 | 
						//   description: name of the repo
 | 
				
			||||||
 | 
						//   type: string
 | 
				
			||||||
 | 
						//   required: true
 | 
				
			||||||
 | 
						// - name: index
 | 
				
			||||||
 | 
						//   in: path
 | 
				
			||||||
 | 
						//   description: index of the issue
 | 
				
			||||||
 | 
						//   type: integer
 | 
				
			||||||
 | 
						//   format: int64
 | 
				
			||||||
 | 
						//   required: true
 | 
				
			||||||
 | 
						// - name: since
 | 
				
			||||||
 | 
						//   in: query
 | 
				
			||||||
 | 
						//   description: if provided, only comments updated since the specified time are returned.
 | 
				
			||||||
 | 
						//   type: string
 | 
				
			||||||
 | 
						//   format: date-time
 | 
				
			||||||
 | 
						// - name: page
 | 
				
			||||||
 | 
						//   in: query
 | 
				
			||||||
 | 
						//   description: page number of results to return (1-based)
 | 
				
			||||||
 | 
						//   type: integer
 | 
				
			||||||
 | 
						// - name: limit
 | 
				
			||||||
 | 
						//   in: query
 | 
				
			||||||
 | 
						//   description: page size of results
 | 
				
			||||||
 | 
						//   type: integer
 | 
				
			||||||
 | 
						// - name: before
 | 
				
			||||||
 | 
						//   in: query
 | 
				
			||||||
 | 
						//   description: if provided, only comments updated before the provided time are returned.
 | 
				
			||||||
 | 
						//   type: string
 | 
				
			||||||
 | 
						//   format: date-time
 | 
				
			||||||
 | 
						// responses:
 | 
				
			||||||
 | 
						//   "200":
 | 
				
			||||||
 | 
						//     "$ref": "#/responses/TimelineList"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						before, since, err := utils.GetQueryBeforeSince(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							ctx.Error(http.StatusInternalServerError, "GetRawIssueByIndex", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						issue.Repo = ctx.Repo.Repository
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						opts := &models.FindCommentsOptions{
 | 
				
			||||||
 | 
							ListOptions: utils.GetListOptions(ctx),
 | 
				
			||||||
 | 
							IssueID:     issue.ID,
 | 
				
			||||||
 | 
							Since:       since,
 | 
				
			||||||
 | 
							Before:      before,
 | 
				
			||||||
 | 
							Type:        models.CommentTypeUnknown,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						comments, err := models.FindComments(opts)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							ctx.Error(http.StatusInternalServerError, "FindComments", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := models.CommentList(comments).LoadPosters(); err != nil {
 | 
				
			||||||
 | 
							ctx.Error(http.StatusInternalServerError, "LoadPosters", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var apiComments []*api.TimelineComment
 | 
				
			||||||
 | 
						for _, comment := range comments {
 | 
				
			||||||
 | 
							if comment.Type != models.CommentTypeCode && isXRefCommentAccessible(ctx.User, comment, issue.RepoID) {
 | 
				
			||||||
 | 
								comment.Issue = issue
 | 
				
			||||||
 | 
								apiComments = append(apiComments, convert.ToTimelineComment(comment, ctx.User))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx.SetTotalCountHeader(int64(len(apiComments)))
 | 
				
			||||||
 | 
						ctx.JSON(http.StatusOK, &apiComments)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func isXRefCommentAccessible(user *user_model.User, c *models.Comment, issueRepoID int64) bool {
 | 
				
			||||||
 | 
						// Remove comments that the user has no permissions to see
 | 
				
			||||||
 | 
						if models.CommentTypeIsRef(c.Type) && c.RefRepoID != issueRepoID && c.RefRepoID != 0 {
 | 
				
			||||||
 | 
							var err error
 | 
				
			||||||
 | 
							// Set RefRepo for description in template
 | 
				
			||||||
 | 
							c.RefRepo, err = repo_model.GetRepositoryByID(c.RefRepoID)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							perm, err := models.GetUserRepoPermission(c.RefRepo, user)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if !perm.CanReadIssuesOrPulls(c.RefIsPull) {
 | 
				
			||||||
 | 
								return false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ListRepoIssueComments returns all issue-comments for a repo
 | 
					// ListRepoIssueComments returns all issue-comments for a repo
 | 
				
			||||||
func ListRepoIssueComments(ctx *context.APIContext) {
 | 
					func ListRepoIssueComments(ctx *context.APIContext) {
 | 
				
			||||||
	// swagger:operation GET /repos/{owner}/{repo}/issues/comments issue issueGetRepoComments
 | 
						// swagger:operation GET /repos/{owner}/{repo}/issues/comments issue issueGetRepoComments
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -36,6 +36,13 @@ type swaggerResponseCommentList struct {
 | 
				
			|||||||
	Body []api.Comment `json:"body"`
 | 
						Body []api.Comment `json:"body"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TimelineList
 | 
				
			||||||
 | 
					// swagger:response TimelineList
 | 
				
			||||||
 | 
					type swaggerResponseTimelineList struct {
 | 
				
			||||||
 | 
						// in:body
 | 
				
			||||||
 | 
						Body []api.TimelineComment `json:"body"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Label
 | 
					// Label
 | 
				
			||||||
// swagger:response Label
 | 
					// swagger:response Label
 | 
				
			||||||
type swaggerResponseLabel struct {
 | 
					type swaggerResponseLabel struct {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6057,6 +6057,73 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "/repos/{owner}/{repo}/issues/{index}/timeline": {
 | 
				
			||||||
 | 
					      "get": {
 | 
				
			||||||
 | 
					        "produces": [
 | 
				
			||||||
 | 
					          "application/json"
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "tags": [
 | 
				
			||||||
 | 
					          "issue"
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "summary": "List all comments and events on an issue",
 | 
				
			||||||
 | 
					        "operationId": "issueGetCommentsAndTimeline",
 | 
				
			||||||
 | 
					        "parameters": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "type": "string",
 | 
				
			||||||
 | 
					            "description": "owner of the repo",
 | 
				
			||||||
 | 
					            "name": "owner",
 | 
				
			||||||
 | 
					            "in": "path",
 | 
				
			||||||
 | 
					            "required": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "type": "string",
 | 
				
			||||||
 | 
					            "description": "name of the repo",
 | 
				
			||||||
 | 
					            "name": "repo",
 | 
				
			||||||
 | 
					            "in": "path",
 | 
				
			||||||
 | 
					            "required": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "type": "integer",
 | 
				
			||||||
 | 
					            "format": "int64",
 | 
				
			||||||
 | 
					            "description": "index of the issue",
 | 
				
			||||||
 | 
					            "name": "index",
 | 
				
			||||||
 | 
					            "in": "path",
 | 
				
			||||||
 | 
					            "required": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "type": "string",
 | 
				
			||||||
 | 
					            "format": "date-time",
 | 
				
			||||||
 | 
					            "description": "if provided, only comments updated since the specified time are returned.",
 | 
				
			||||||
 | 
					            "name": "since",
 | 
				
			||||||
 | 
					            "in": "query"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "type": "integer",
 | 
				
			||||||
 | 
					            "description": "page number of results to return (1-based)",
 | 
				
			||||||
 | 
					            "name": "page",
 | 
				
			||||||
 | 
					            "in": "query"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "type": "integer",
 | 
				
			||||||
 | 
					            "description": "page size of results",
 | 
				
			||||||
 | 
					            "name": "limit",
 | 
				
			||||||
 | 
					            "in": "query"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "type": "string",
 | 
				
			||||||
 | 
					            "format": "date-time",
 | 
				
			||||||
 | 
					            "description": "if provided, only comments updated before the provided time are returned.",
 | 
				
			||||||
 | 
					            "name": "before",
 | 
				
			||||||
 | 
					            "in": "query"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "responses": {
 | 
				
			||||||
 | 
					          "200": {
 | 
				
			||||||
 | 
					            "$ref": "#/responses/TimelineList"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "/repos/{owner}/{repo}/issues/{index}/times": {
 | 
					    "/repos/{owner}/{repo}/issues/{index}/times": {
 | 
				
			||||||
      "get": {
 | 
					      "get": {
 | 
				
			||||||
        "produces": [
 | 
					        "produces": [
 | 
				
			||||||
@@ -17396,6 +17463,126 @@
 | 
				
			|||||||
      "format": "int64",
 | 
					      "format": "int64",
 | 
				
			||||||
      "x-go-package": "code.gitea.io/gitea/modules/timeutil"
 | 
					      "x-go-package": "code.gitea.io/gitea/modules/timeutil"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "TimelineComment": {
 | 
				
			||||||
 | 
					      "description": "TimelineComment represents a timeline comment (comment of any type) on a commit or issue",
 | 
				
			||||||
 | 
					      "type": "object",
 | 
				
			||||||
 | 
					      "properties": {
 | 
				
			||||||
 | 
					        "assignee": {
 | 
				
			||||||
 | 
					          "$ref": "#/definitions/User"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "assignee_team": {
 | 
				
			||||||
 | 
					          "$ref": "#/definitions/Team"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "body": {
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "x-go-name": "Body"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "created_at": {
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "format": "date-time",
 | 
				
			||||||
 | 
					          "x-go-name": "Created"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "dependent_issue": {
 | 
				
			||||||
 | 
					          "$ref": "#/definitions/Issue"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "html_url": {
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "x-go-name": "HTMLURL"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "id": {
 | 
				
			||||||
 | 
					          "type": "integer",
 | 
				
			||||||
 | 
					          "format": "int64",
 | 
				
			||||||
 | 
					          "x-go-name": "ID"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "issue_url": {
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "x-go-name": "IssueURL"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "label": {
 | 
				
			||||||
 | 
					          "$ref": "#/definitions/Label"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "milestone": {
 | 
				
			||||||
 | 
					          "$ref": "#/definitions/Milestone"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "new_ref": {
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "x-go-name": "NewRef"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "new_title": {
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "x-go-name": "NewTitle"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "old_milestone": {
 | 
				
			||||||
 | 
					          "$ref": "#/definitions/Milestone"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "old_project_id": {
 | 
				
			||||||
 | 
					          "type": "integer",
 | 
				
			||||||
 | 
					          "format": "int64",
 | 
				
			||||||
 | 
					          "x-go-name": "OldProjectID"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "old_ref": {
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "x-go-name": "OldRef"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "old_title": {
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "x-go-name": "OldTitle"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "project_id": {
 | 
				
			||||||
 | 
					          "type": "integer",
 | 
				
			||||||
 | 
					          "format": "int64",
 | 
				
			||||||
 | 
					          "x-go-name": "ProjectID"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "pull_request_url": {
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "x-go-name": "PRURL"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "ref_action": {
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "x-go-name": "RefAction"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "ref_comment": {
 | 
				
			||||||
 | 
					          "$ref": "#/definitions/Comment"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "ref_commit_sha": {
 | 
				
			||||||
 | 
					          "description": "commit SHA where issue/PR was referenced",
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "x-go-name": "RefCommitSHA"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "ref_issue": {
 | 
				
			||||||
 | 
					          "$ref": "#/definitions/Issue"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "removed_assignee": {
 | 
				
			||||||
 | 
					          "description": "whether the assignees were removed or added",
 | 
				
			||||||
 | 
					          "type": "boolean",
 | 
				
			||||||
 | 
					          "x-go-name": "RemovedAssignee"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "resolve_doer": {
 | 
				
			||||||
 | 
					          "$ref": "#/definitions/User"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "review_id": {
 | 
				
			||||||
 | 
					          "type": "integer",
 | 
				
			||||||
 | 
					          "format": "int64",
 | 
				
			||||||
 | 
					          "x-go-name": "ReviewID"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "tracked_time": {
 | 
				
			||||||
 | 
					          "$ref": "#/definitions/TrackedTime"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "type": {
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "x-go-name": "Type"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "updated_at": {
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "format": "date-time",
 | 
				
			||||||
 | 
					          "x-go-name": "Updated"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "user": {
 | 
				
			||||||
 | 
					          "$ref": "#/definitions/User"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "x-go-package": "code.gitea.io/gitea/modules/structs"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "TopicName": {
 | 
					    "TopicName": {
 | 
				
			||||||
      "description": "TopicName a list of repo topic names",
 | 
					      "description": "TopicName a list of repo topic names",
 | 
				
			||||||
      "type": "object",
 | 
					      "type": "object",
 | 
				
			||||||
@@ -18525,6 +18712,15 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "TimelineList": {
 | 
				
			||||||
 | 
					      "description": "TimelineList",
 | 
				
			||||||
 | 
					      "schema": {
 | 
				
			||||||
 | 
					        "type": "array",
 | 
				
			||||||
 | 
					        "items": {
 | 
				
			||||||
 | 
					          "$ref": "#/definitions/TimelineComment"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "TopicListResponse": {
 | 
					    "TopicListResponse": {
 | 
				
			||||||
      "description": "TopicListResponse",
 | 
					      "description": "TopicListResponse",
 | 
				
			||||||
      "schema": {
 | 
					      "schema": {
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user