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

Add API to get issue/pull comments and events (timeline) #17403

Merged
merged 47 commits into from
Jan 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
0918b4c
Add API to get issue/pull comments and events (timeline)
qwerty287 Oct 22, 2021
330ea4d
Fix swagger
qwerty287 Oct 22, 2021
59ae4d1
Merge branch 'main' into timeline-api
qwerty287 Oct 22, 2021
a96bcc7
Don't show code comments (use review api instead)
qwerty287 Oct 22, 2021
2df41c1
fmt
qwerty287 Oct 22, 2021
6811c3a
Fix comment
qwerty287 Oct 22, 2021
ea097a6
Time -> TrackedTime
qwerty287 Oct 22, 2021
7fec3c7
Use var directly
qwerty287 Oct 22, 2021
8a443f0
Add logger
qwerty287 Oct 22, 2021
f13aa96
Merge branch 'main' into timeline-api
qwerty287 Oct 22, 2021
b4bcf1f
Fix lint
qwerty287 Oct 22, 2021
f6df41e
Fix test
qwerty287 Oct 23, 2021
577b222
Merge branch 'main' into timeline-api
qwerty287 Oct 23, 2021
a6a85b7
Merge branch 'timeline-api' of github.com:qwerty287/gitea into timeli…
qwerty287 Oct 23, 2021
de9a364
Merge branch 'main' into timeline-api
qwerty287 Oct 26, 2021
3daf4ff
Merge branch 'main' into timeline-api
qwerty287 Oct 28, 2021
6315db4
Merge branch 'main' into timeline-api
zeripath Oct 28, 2021
ffcfe38
Add comments
qwerty287 Oct 29, 2021
efdb224
fmt
qwerty287 Oct 29, 2021
90439b8
Merge branch 'main' into timeline-api
qwerty287 Oct 29, 2021
fabe8eb
[test] get issue directly by ID
qwerty287 Oct 30, 2021
6f2d140
Merge branch 'main' into timeline-api
qwerty287 Oct 30, 2021
7f1df1c
Merge branch 'main' into timeline-api
qwerty287 Nov 1, 2021
e827bfd
Merge branch 'main' into timeline-api
lunny Nov 5, 2021
ce2cbf0
Merge branch 'main' into timeline-api
qwerty287 Nov 7, 2021
4a04a73
Merge branch 'main' into timeline-api
qwerty287 Nov 12, 2021
13a7019
Merge branch 'main' into timeline-api
qwerty287 Nov 15, 2021
9cbb709
Merge branch 'main' into timeline-api
qwerty287 Nov 16, 2021
c2017dc
Update test
qwerty287 Nov 16, 2021
eca8416
Merge branch 'main' into timeline-api
qwerty287 Nov 18, 2021
7e3172b
Merge branch 'main' into timeline-api
qwerty287 Nov 21, 2021
f835685
Add description for changed refs
qwerty287 Nov 22, 2021
34919d4
Merge branch 'main' into timeline-api
qwerty287 Nov 23, 2021
d2be482
Merge branch 'main' into timeline-api
qwerty287 Nov 26, 2021
7a8b1f1
Fix build issues + lint
qwerty287 Nov 27, 2021
71eecfc
Merge branch 'main' into timeline-api
qwerty287 Nov 29, 2021
bf15f47
Merge branch 'main' into timeline-api
qwerty287 Dec 5, 2021
126f9fb
Merge branch 'main' into timeline-api
qwerty287 Dec 8, 2021
648513d
Merge branch 'main' into timeline-api
qwerty287 Dec 12, 2021
df62eed
Fix build
qwerty287 Dec 12, 2021
e8b65b3
Merge branch 'main' into timeline-api
qwerty287 Dec 21, 2021
4dd46ef
Use string enums
qwerty287 Dec 22, 2021
dd3e9c4
Update swagger
qwerty287 Dec 22, 2021
06db66a
Support `page` and `limit` params
qwerty287 Dec 30, 2021
a6ac7bd
fmt + swagger
qwerty287 Jan 1, 2022
dfd0e65
Use global slices
qwerty287 Jan 1, 2022
ab221a7
Merge branch 'main' into timeline-api
qwerty287 Jan 1, 2022
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
22 changes: 22 additions & 0 deletions integrations/api_comment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,3 +180,25 @@ func TestAPIDeleteComment(t *testing.T) {

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))
}
41 changes: 41 additions & 0 deletions models/issue_comment.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,47 @@ const (
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
type RoleDescriptor int

Expand Down
143 changes: 143 additions & 0 deletions modules/convert/issue_comment.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ package convert

import (
"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"
)

Expand All @@ -22,3 +25,143 @@ func ToComment(c *models.Comment) *api.Comment {
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
qwerty287 marked this conversation as resolved.
Show resolved Hide resolved
}

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
}
11 changes: 11 additions & 0 deletions modules/references/references.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ var (
giteaHostInit sync.Once
giteaHost string
giteaIssuePullPattern *regexp.Regexp

actionStrings = []string{
"none",
"closes",
"reopens",
"neutered",
}
)

// XRefAction represents the kind of effect a cross reference has once is resolved
Expand All @@ -65,6 +72,10 @@ const (
XRefActionNeutered // 3
)

func (a XRefAction) String() string {
return actionStrings[a]
}

// IssueReference contains an unverified cross-reference to a local issue or pull request
type IssueReference struct {
Index int64
Expand Down
45 changes: 45 additions & 0 deletions modules/structs/issue_comment.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,48 @@ type EditIssueCommentOption struct {
// required: true
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"`
}
1 change: 1 addition & 0 deletions routers/api/v1/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,7 @@ func Routes(sessioner func(http.Handler) http.Handler) *web.Route {
m.Combo("/{id}", reqToken()).Patch(bind(api.EditIssueCommentOption{}), repo.EditIssueCommentDeprecated).
Delete(repo.DeleteIssueCommentDeprecated)
})
m.Get("/timeline", repo.ListIssueCommentsAndTimeline)
m.Group("/labels", func() {
m.Combo("").Get(repo.ListIssueLabels).
Post(reqToken(), bind(api.IssueLabelsOption{}), repo.AddIssueLabels).
Expand Down
Loading