Browse Source

[Refactor] move APIFormat() of Issue and Label to convert package (#10423)

* Label: delete .APIFormat() and use convert.ToLabel()

* move issue APIFormat too

* add missing one

* move TEST too

* handle error -> return empty APIIssue

* Apply suggestions from code review

Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com>

Co-authored-by: zeripath <art27@cantab.net>
Co-authored-by: guillep2k <18600385+guillep2k@users.noreply.github.com>
tags/v1.10.5
6543 4 years ago
parent
commit
7e8cdba181
No account linked to committer's email address

+ 5
- 70
models/issue.go View File

return return
} }


// LoadLabels loads labels
func (issue *Issue) LoadLabels() error {
return issue.loadLabels(x)
}

func (issue *Issue) loadLabels(e Engine) (err error) { func (issue *Issue) loadLabels(e Engine) (err error) {
if issue.Labels == nil { if issue.Labels == nil {
issue.Labels, err = getLabelsByIssueID(e, issue.ID) issue.Labels, err = getLabelsByIssueID(e, issue.ID)
return api.StateOpen return api.StateOpen
} }


// APIFormat assumes some fields assigned with values:
// Required - Poster, Labels,
// Optional - Milestone, Assignee, PullRequest
func (issue *Issue) APIFormat() *api.Issue {
return issue.apiFormat(x)
}

func (issue *Issue) apiFormat(e Engine) *api.Issue {
issue.loadLabels(e)
apiLabels := make([]*api.Label, len(issue.Labels))
for i := range issue.Labels {
apiLabels[i] = issue.Labels[i].APIFormat()
}

issue.loadPoster(e)
issue.loadRepo(e)
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: apiLabels,
State: issue.State(),
Comments: issue.NumComments,
Created: issue.CreatedUnix.AsTime(),
Updated: issue.UpdatedUnix.AsTime(),
}

apiIssue.Repo = &api.RepositoryMeta{
ID: issue.Repo.ID,
Name: issue.Repo.Name,
Owner: issue.Repo.OwnerName,
FullName: issue.Repo.FullName(),
}

if issue.ClosedUnix != 0 {
apiIssue.Closed = issue.ClosedUnix.AsTimePtr()
}

issue.loadMilestone(e)
if issue.Milestone != nil {
apiIssue.Milestone = issue.Milestone.APIFormat()
}

issue.loadAssignees(e)
if len(issue.Assignees) > 0 {
for _, assignee := range issue.Assignees {
apiIssue.Assignees = append(apiIssue.Assignees, assignee.APIFormat())
}
apiIssue.Assignee = issue.Assignees[0].APIFormat() // For compatibility, we're keeping the first assignee as `apiIssue.Assignee`
}
if issue.IsPull {
issue.loadPullRequest(e)
apiIssue.PullRequest = &api.PullRequestMeta{
HasMerged: issue.PullRequest.HasMerged,
}
if issue.PullRequest.HasMerged {
apiIssue.PullRequest.Merged = issue.PullRequest.MergedUnix.AsTimePtr()
}
}
if issue.DeadlineUnix != 0 {
apiIssue.Deadline = issue.DeadlineUnix.AsTimePtr()
}

return apiIssue
}

// HashTag returns unique hash tag for issue. // HashTag returns unique hash tag for issue.
func (issue *Issue) HashTag() string { func (issue *Issue) HashTag() string {
return "issue-" + com.ToStr(issue.ID) return "issue-" + com.ToStr(issue.ID)

+ 5
- 0
models/issue_assignees.go View File

IssueID int64 `xorm:"INDEX"` IssueID int64 `xorm:"INDEX"`
} }


// LoadAssignees load assignees of this issue.
func (issue *Issue) LoadAssignees() error {
return issue.loadAssignees(x)
}

// This loads all assignees of an issue // This loads all assignees of an issue
func (issue *Issue) loadAssignees(e Engine) (err error) { func (issue *Issue) loadAssignees(e Engine) (err error) {
// Reset maybe preexisting assignees // Reset maybe preexisting assignees

+ 0
- 12
models/issue_label.go View File

"strconv" "strconv"
"strings" "strings"


api "code.gitea.io/gitea/modules/structs"

"xorm.io/builder" "xorm.io/builder"
"xorm.io/xorm" "xorm.io/xorm"
) )
IsExcluded bool `xorm:"-"` IsExcluded bool `xorm:"-"`
} }


// APIFormat converts a Label to the api.Label format
func (label *Label) APIFormat() *api.Label {
return &api.Label{
ID: label.ID,
Name: label.Name,
Color: strings.TrimLeft(label.Color, "#"),
Description: label.Description,
}
}

// GetLabelTemplateFile loads the label template file by given name, // GetLabelTemplateFile loads the label template file by given name,
// then parses and returns a list of name-color pairs and optionally description. // then parses and returns a list of name-color pairs and optionally description.
func GetLabelTemplateFile(name string) ([][3]string, error) { func GetLabelTemplateFile(name string) ([][3]string, error) {

+ 0
- 12
models/issue_label_test.go View File

"html/template" "html/template"
"testing" "testing"


api "code.gitea.io/gitea/modules/structs"

"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )


// TODO TestGetLabelTemplateFile // TODO TestGetLabelTemplateFile


func TestLabel_APIFormat(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
label := AssertExistsAndLoadBean(t, &Label{ID: 1}).(*Label)
assert.Equal(t, api.Label{
ID: label.ID,
Name: label.Name,
Color: "abcdef",
}, *label.APIFormat())
}

func TestLabel_CalOpenIssues(t *testing.T) { func TestLabel_CalOpenIssues(t *testing.T) {
assert.NoError(t, PrepareTestDatabase()) assert.NoError(t, PrepareTestDatabase())
label := AssertExistsAndLoadBean(t, &Label{ID: 1}).(*Label) label := AssertExistsAndLoadBean(t, &Label{ID: 1}).(*Label)

+ 106
- 1
modules/convert/issue.go View File

package convert package convert


import ( import (
"strings"

"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
) )


// ToAPIIssue converts an Issue to API format
// it assumes some fields assigned with values:
// Required - Poster, Labels,
// Optional - Milestone, Assignee, PullRequest
func ToAPIIssue(issue *models.Issue) *api.Issue {
if err := issue.LoadLabels(); err != nil {
return &api.Issue{}
}
if err := issue.LoadPoster(); err != nil {
return &api.Issue{}
}
if err := issue.LoadRepo(); 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(),
Comments: issue.NumComments,
Created: issue.CreatedUnix.AsTime(),
Updated: issue.UpdatedUnix.AsTime(),
}

apiIssue.Repo = &api.RepositoryMeta{
ID: issue.Repo.ID,
Name: issue.Repo.Name,
Owner: issue.Repo.OwnerName,
FullName: issue.Repo.FullName(),
}

if issue.ClosedUnix != 0 {
apiIssue.Closed = issue.ClosedUnix.AsTimePtr()
}

if err := issue.LoadMilestone(); err != nil {
return &api.Issue{}
}
if issue.Milestone != nil {
apiIssue.Milestone = issue.Milestone.APIFormat()
}

if err := issue.LoadAssignees(); err != nil {
return &api.Issue{}
}
if len(issue.Assignees) > 0 {
for _, assignee := range issue.Assignees {
apiIssue.Assignees = append(apiIssue.Assignees, assignee.APIFormat())
}
apiIssue.Assignee = issue.Assignees[0].APIFormat() // For compatibility, we're keeping the first assignee as `apiIssue.Assignee`
}
if issue.IsPull {
if err := issue.LoadPullRequest(); err != nil {
return &api.Issue{}
}
apiIssue.PullRequest = &api.PullRequestMeta{
HasMerged: issue.PullRequest.HasMerged,
}
if issue.PullRequest.HasMerged {
apiIssue.PullRequest.Merged = issue.PullRequest.MergedUnix.AsTimePtr()
}
}
if issue.DeadlineUnix != 0 {
apiIssue.Deadline = issue.DeadlineUnix.AsTimePtr()
}

return apiIssue
}

// ToAPIIssueList converts an IssueList to API format
func ToAPIIssueList(il models.IssueList) []*api.Issue {
result := make([]*api.Issue, len(il))
for i := range il {
result[i] = ToAPIIssue(il[i])
}
return result
}

// ToTrackedTime converts TrackedTime to API format // ToTrackedTime converts TrackedTime to API format
func ToTrackedTime(t *models.TrackedTime) (apiT *api.TrackedTime) { func ToTrackedTime(t *models.TrackedTime) (apiT *api.TrackedTime) {
apiT = &api.TrackedTime{ apiT = &api.TrackedTime{
Created: t.Created, Created: t.Created,
} }
if t.Issue != nil { if t.Issue != nil {
apiT.Issue = t.Issue.APIFormat()
apiT.Issue = ToAPIIssue(t.Issue)
} }
if t.User != nil { if t.User != nil {
apiT.UserName = t.User.Name apiT.UserName = t.User.Name
} }
return result return result
} }

// ToLabel converts Label to API format
func ToLabel(label *models.Label) *api.Label {
return &api.Label{
ID: label.ID,
Name: label.Name,
Color: strings.TrimLeft(label.Color, "#"),
Description: label.Description,
}
}

// ToLabelList converts list of Label to API format
func ToLabelList(labels []*models.Label) []*api.Label {
result := make([]*api.Label, len(labels))
for i := range labels {
result[i] = ToLabel(labels[i])
}
return result
}

+ 24
- 0
modules/convert/issue_test.go View File

// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package convert

import (
"testing"

"code.gitea.io/gitea/models"
api "code.gitea.io/gitea/modules/structs"

"github.com/stretchr/testify/assert"
)

func TestLabel_ToLabel(t *testing.T) {
assert.NoError(t, models.PrepareTestDatabase())
label := models.AssertExistsAndLoadBean(t, &models.Label{ID: 1}).(*models.Label)
assert.Equal(t, &api.Label{
ID: label.ID,
Name: label.Name,
Color: "abcdef",
}, ToLabel(label))
}

+ 1
- 1
modules/convert/pull.go View File

return nil return nil
} }


apiIssue := pr.Issue.APIFormat()
apiIssue := ToAPIIssue(pr.Issue)
if pr.BaseRepo == nil { if pr.BaseRepo == nil {
pr.BaseRepo, err = models.GetRepositoryByID(pr.BaseRepoID) pr.BaseRepo, err = models.GetRepositoryByID(pr.BaseRepoID)
if err != nil { if err != nil {

+ 11
- 11
modules/notification/webhook/webhook.go View File

err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssues, &api.IssuePayload{ err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssues, &api.IssuePayload{
Action: api.HookIssueLabelCleared, Action: api.HookIssueLabelCleared,
Index: issue.Index, Index: issue.Index,
Issue: issue.APIFormat(),
Issue: convert.ToAPIIssue(issue),
Repository: issue.Repo.APIFormat(mode), Repository: issue.Repo.APIFormat(mode),
Sender: doer.APIFormat(), Sender: doer.APIFormat(),
}) })
mode, _ := models.AccessLevelUnit(doer, issue.Repo, models.UnitTypeIssues) mode, _ := models.AccessLevelUnit(doer, issue.Repo, models.UnitTypeIssues)
apiIssue := &api.IssuePayload{ apiIssue := &api.IssuePayload{
Index: issue.Index, Index: issue.Index,
Issue: issue.APIFormat(),
Issue: convert.ToAPIIssue(issue),
Repository: issue.Repo.APIFormat(mode), Repository: issue.Repo.APIFormat(mode),
Sender: doer.APIFormat(), Sender: doer.APIFormat(),
} }
From: oldTitle, From: oldTitle,
}, },
}, },
Issue: issue.APIFormat(),
Issue: convert.ToAPIIssue(issue),
Repository: issue.Repo.APIFormat(mode), Repository: issue.Repo.APIFormat(mode),
Sender: issue.Poster.APIFormat(), Sender: issue.Poster.APIFormat(),
}) })
} else { } else {
apiIssue := &api.IssuePayload{ apiIssue := &api.IssuePayload{
Index: issue.Index, Index: issue.Index,
Issue: issue.APIFormat(),
Issue: convert.ToAPIIssue(issue),
Repository: issue.Repo.APIFormat(mode), Repository: issue.Repo.APIFormat(mode),
Sender: doer.APIFormat(), Sender: doer.APIFormat(),
} }
if err := webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssues, &api.IssuePayload{ if err := webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssues, &api.IssuePayload{
Action: api.HookIssueOpened, Action: api.HookIssueOpened,
Index: issue.Index, Index: issue.Index,
Issue: issue.APIFormat(),
Issue: convert.ToAPIIssue(issue),
Repository: issue.Repo.APIFormat(mode), Repository: issue.Repo.APIFormat(mode),
Sender: issue.Poster.APIFormat(), Sender: issue.Poster.APIFormat(),
}); err != nil { }); err != nil {
From: oldContent, From: oldContent,
}, },
}, },
Issue: issue.APIFormat(),
Issue: convert.ToAPIIssue(issue),
Repository: issue.Repo.APIFormat(mode), Repository: issue.Repo.APIFormat(mode),
Sender: doer.APIFormat(), Sender: doer.APIFormat(),
}) })
mode, _ := models.AccessLevel(doer, c.Issue.Repo) mode, _ := models.AccessLevel(doer, c.Issue.Repo)
if err := webhook_module.PrepareWebhooks(c.Issue.Repo, models.HookEventIssueComment, &api.IssueCommentPayload{ if err := webhook_module.PrepareWebhooks(c.Issue.Repo, models.HookEventIssueComment, &api.IssueCommentPayload{
Action: api.HookIssueCommentEdited, Action: api.HookIssueCommentEdited,
Issue: c.Issue.APIFormat(),
Issue: convert.ToAPIIssue(c.Issue),
Comment: c.APIFormat(), Comment: c.APIFormat(),
Changes: &api.ChangesPayload{ Changes: &api.ChangesPayload{
Body: &api.ChangesFromPayload{ Body: &api.ChangesFromPayload{
mode, _ := models.AccessLevel(doer, repo) mode, _ := models.AccessLevel(doer, repo)
if err := webhook_module.PrepareWebhooks(repo, models.HookEventIssueComment, &api.IssueCommentPayload{ if err := webhook_module.PrepareWebhooks(repo, models.HookEventIssueComment, &api.IssueCommentPayload{
Action: api.HookIssueCommentCreated, Action: api.HookIssueCommentCreated,
Issue: issue.APIFormat(),
Issue: convert.ToAPIIssue(issue),
Comment: comment.APIFormat(), Comment: comment.APIFormat(),
Repository: repo.APIFormat(mode), Repository: repo.APIFormat(mode),
Sender: doer.APIFormat(), Sender: doer.APIFormat(),


if err := webhook_module.PrepareWebhooks(comment.Issue.Repo, models.HookEventIssueComment, &api.IssueCommentPayload{ if err := webhook_module.PrepareWebhooks(comment.Issue.Repo, models.HookEventIssueComment, &api.IssueCommentPayload{
Action: api.HookIssueCommentDeleted, Action: api.HookIssueCommentDeleted,
Issue: comment.Issue.APIFormat(),
Issue: convert.ToAPIIssue(comment.Issue),
Comment: comment.APIFormat(), Comment: comment.APIFormat(),
Repository: comment.Issue.Repo.APIFormat(mode), Repository: comment.Issue.Repo.APIFormat(mode),
Sender: doer.APIFormat(), Sender: doer.APIFormat(),
err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssues, &api.IssuePayload{ err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssues, &api.IssuePayload{
Action: api.HookIssueLabelUpdated, Action: api.HookIssueLabelUpdated,
Index: issue.Index, Index: issue.Index,
Issue: issue.APIFormat(),
Issue: convert.ToAPIIssue(issue),
Repository: issue.Repo.APIFormat(mode), Repository: issue.Repo.APIFormat(mode),
Sender: doer.APIFormat(), Sender: doer.APIFormat(),
}) })
err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssues, &api.IssuePayload{ err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssues, &api.IssuePayload{
Action: hookAction, Action: hookAction,
Index: issue.Index, Index: issue.Index,
Issue: issue.APIFormat(),
Issue: convert.ToAPIIssue(issue),
Repository: issue.Repo.APIFormat(mode), Repository: issue.Repo.APIFormat(mode),
Sender: doer.APIFormat(), Sender: doer.APIFormat(),
}) })

+ 6
- 15
routers/api/v1/repo/issue.go View File



"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/convert"
issue_indexer "code.gitea.io/gitea/modules/indexer/issues" issue_indexer "code.gitea.io/gitea/modules/indexer/issues"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
return return
} }


apiIssues := make([]*api.Issue, len(issues))
for i := range issues {
apiIssues[i] = issues[i].APIFormat()
}

ctx.SetLinkHeader(issueCount, setting.UI.IssuePagingNum) ctx.SetLinkHeader(issueCount, setting.UI.IssuePagingNum)
ctx.JSON(http.StatusOK, &apiIssues)
ctx.JSON(http.StatusOK, convert.ToAPIIssueList(issues))
} }


// ListIssues list the issues of a repository // ListIssues list the issues of a repository
return return
} }


apiIssues := make([]*api.Issue, len(issues))
for i := range issues {
apiIssues[i] = issues[i].APIFormat()
}

ctx.SetLinkHeader(ctx.Repo.Repository.NumIssues, listOptions.PageSize) ctx.SetLinkHeader(ctx.Repo.Repository.NumIssues, listOptions.PageSize)
ctx.JSON(http.StatusOK, &apiIssues)
ctx.JSON(http.StatusOK, convert.ToAPIIssueList(issues))
} }


// GetIssue get an issue of a repository // GetIssue get an issue of a repository
} }
return return
} }
ctx.JSON(http.StatusOK, issue.APIFormat())
ctx.JSON(http.StatusOK, convert.ToAPIIssue(issue))
} }


// CreateIssue create an issue of a repository // CreateIssue create an issue of a repository
ctx.Error(http.StatusInternalServerError, "GetIssueByID", err) ctx.Error(http.StatusInternalServerError, "GetIssueByID", err)
return return
} }
ctx.JSON(http.StatusCreated, issue.APIFormat())
ctx.JSON(http.StatusCreated, convert.ToAPIIssue(issue))
} }


// EditIssue modify an issue of a repository // EditIssue modify an issue of a repository
ctx.InternalServerError(err) ctx.InternalServerError(err)
return return
} }
ctx.JSON(http.StatusCreated, issue.APIFormat())
ctx.JSON(http.StatusCreated, convert.ToAPIIssue(issue))
} }


// UpdateIssueDeadline updates an issue deadline // UpdateIssueDeadline updates an issue deadline

+ 4
- 15
routers/api/v1/repo/issue_label.go View File



"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/convert"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
issue_service "code.gitea.io/gitea/services/issue" issue_service "code.gitea.io/gitea/services/issue"
) )
return return
} }


apiLabels := make([]*api.Label, len(issue.Labels))
for i := range issue.Labels {
apiLabels[i] = issue.Labels[i].APIFormat()
}
ctx.JSON(http.StatusOK, &apiLabels)
ctx.JSON(http.StatusOK, convert.ToLabelList(issue.Labels))
} }


// AddIssueLabels add labels for an issue // AddIssueLabels add labels for an issue
return return
} }


apiLabels := make([]*api.Label, len(labels))
for i := range labels {
apiLabels[i] = labels[i].APIFormat()
}
ctx.JSON(http.StatusOK, &apiLabels)
ctx.JSON(http.StatusOK, convert.ToLabelList(labels))
} }


// DeleteIssueLabel delete a label for an issue // DeleteIssueLabel delete a label for an issue
return return
} }


apiLabels := make([]*api.Label, len(labels))
for i := range labels {
apiLabels[i] = labels[i].APIFormat()
}
ctx.JSON(http.StatusOK, &apiLabels)
ctx.JSON(http.StatusOK, convert.ToLabelList(labels))
} }


// ClearIssueLabels delete all the labels for an issue // ClearIssueLabels delete all the labels for an issue

+ 5
- 8
routers/api/v1/repo/label.go View File



"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/convert"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/routers/api/v1/utils" "code.gitea.io/gitea/routers/api/v1/utils"
) )
return return
} }


apiLabels := make([]*api.Label, len(labels))
for i := range labels {
apiLabels[i] = labels[i].APIFormat()
}
ctx.JSON(http.StatusOK, &apiLabels)
ctx.JSON(http.StatusOK, convert.ToLabelList(labels))
} }


// GetLabel get label by repository and label id // GetLabel get label by repository and label id
return return
} }


ctx.JSON(http.StatusOK, label.APIFormat())
ctx.JSON(http.StatusOK, convert.ToLabel(label))
} }


// CreateLabel create a label for a repository // CreateLabel create a label for a repository
ctx.Error(http.StatusInternalServerError, "NewLabel", err) ctx.Error(http.StatusInternalServerError, "NewLabel", err)
return return
} }
ctx.JSON(http.StatusCreated, label.APIFormat())
ctx.JSON(http.StatusCreated, convert.ToLabel(label))
} }


// EditLabel modify a label for a repository // EditLabel modify a label for a repository
ctx.ServerError("UpdateLabel", err) ctx.ServerError("UpdateLabel", err)
return return
} }
ctx.JSON(http.StatusOK, label.APIFormat())
ctx.JSON(http.StatusOK, convert.ToLabel(label))
} }


// DeleteLabel delete a label for a repository // DeleteLabel delete a label for a repository

Loading…
Cancel
Save