Переглянути джерело

[API] Fix inconsistent label color format (#10129)

* update and use labelColorPattern

* add TestCases

* fix lint

* # optional for templates

* fix typo

* some more

* fix lint of **master**
tags/v1.10.5
6543 4 роки тому
джерело
коміт
e273817154
Аккаунт користувача з таким Email не знайдено

+ 71
- 0
integrations/api_issue_label_test.go Переглянути файл

import ( import (
"fmt" "fmt"
"net/http" "net/http"
"strings"
"testing" "testing"


"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )


func TestAPIModifyLabels(t *testing.T) {
assert.NoError(t, models.LoadFixtures())

repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 2}).(*models.Repository)
owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User)
session := loginUser(t, owner.Name)
token := getTokenForLoggedInUser(t, session)
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/labels?token=%s", owner.Name, repo.Name, token)

// CreateLabel
req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateLabelOption{
Name: "TestL 1",
Color: "abcdef",
Description: "test label",
})
resp := session.MakeRequest(t, req, http.StatusCreated)
apiLabel := new(api.Label)
DecodeJSON(t, resp, &apiLabel)
dbLabel := models.AssertExistsAndLoadBean(t, &models.Label{ID: apiLabel.ID, RepoID: repo.ID}).(*models.Label)
assert.EqualValues(t, dbLabel.Name, apiLabel.Name)
assert.EqualValues(t, strings.TrimLeft(dbLabel.Color, "#"), apiLabel.Color)

req = NewRequestWithJSON(t, "POST", urlStr, &api.CreateLabelOption{
Name: "TestL 2",
Color: "#123456",
Description: "jet another test label",
})
session.MakeRequest(t, req, http.StatusCreated)
req = NewRequestWithJSON(t, "POST", urlStr, &api.CreateLabelOption{
Name: "WrongTestL",
Color: "#12345g",
})
session.MakeRequest(t, req, http.StatusUnprocessableEntity)

//ListLabels
req = NewRequest(t, "GET", urlStr)
resp = session.MakeRequest(t, req, http.StatusOK)
var apiLabels []*api.Label
DecodeJSON(t, resp, &apiLabels)
assert.Len(t, apiLabels, 2)

//GetLabel
singleURLStr := fmt.Sprintf("/api/v1/repos/%s/%s/labels/%d?token=%s", owner.Name, repo.Name, dbLabel.ID, token)
req = NewRequest(t, "GET", singleURLStr)
resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiLabel)
assert.EqualValues(t, strings.TrimLeft(dbLabel.Color, "#"), apiLabel.Color)

//EditLabel
newName := "LabelNewName"
newColor := "09876a"
newColorWrong := "09g76a"
req = NewRequestWithJSON(t, "PATCH", singleURLStr, &api.EditLabelOption{
Name: &newName,
Color: &newColor,
})
resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiLabel)
assert.EqualValues(t, newColor, apiLabel.Color)
req = NewRequestWithJSON(t, "PATCH", singleURLStr, &api.EditLabelOption{
Color: &newColorWrong,
})
session.MakeRequest(t, req, http.StatusUnprocessableEntity)

//DeleteLabel
req = NewRequest(t, "DELETE", singleURLStr)
resp = session.MakeRequest(t, req, http.StatusNoContent)

}

func TestAPIAddIssueLabels(t *testing.T) { func TestAPIAddIssueLabels(t *testing.T) {
assert.NoError(t, models.LoadFixtures()) assert.NoError(t, models.LoadFixtures())



+ 47
- 33
models/issue_label.go Переглянути файл

"xorm.io/xorm" "xorm.io/xorm"
) )


var labelColorPattern = regexp.MustCompile("#([a-fA-F0-9]{6})")
// LabelColorPattern is a regexp witch can validate LabelColor
var LabelColorPattern = regexp.MustCompile("^#[0-9a-fA-F]{6}$")

// Label represents a label of repository for issues.
type Label struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"INDEX"`
Name string
Description string
Color string `xorm:"VARCHAR(7)"`
NumIssues int
NumClosedIssues int
NumOpenIssues int `xorm:"-"`
IsChecked bool `xorm:"-"`
QueryString string `xorm:"-"`
IsSelected 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.
return nil, fmt.Errorf("line is malformed: %s", line) return nil, fmt.Errorf("line is malformed: %s", line)
} }


if !labelColorPattern.MatchString(fields[0]) {
color := strings.Trim(fields[0], " ")
if len(color) == 6 {
color = "#" + color
}
if !LabelColorPattern.MatchString(color) {
return nil, fmt.Errorf("bad HTML color code in line: %s", line) return nil, fmt.Errorf("bad HTML color code in line: %s", line)
} }


} }


fields[1] = strings.TrimSpace(fields[1]) fields[1] = strings.TrimSpace(fields[1])
list = append(list, [3]string{fields[1], fields[0], description})
list = append(list, [3]string{fields[1], color, description})
} }


return list, nil return list, nil
} }


// Label represents a label of repository for issues.
type Label struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"INDEX"`
Name string
Description string
Color string `xorm:"VARCHAR(7)"`
NumIssues int
NumClosedIssues int
NumOpenIssues int `xorm:"-"`
IsChecked bool `xorm:"-"`
QueryString string `xorm:"-"`
IsSelected 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,
}
}

// CalOpenIssues calculates the open issues of label. // CalOpenIssues calculates the open issues of label.
func (label *Label) CalOpenIssues() { func (label *Label) CalOpenIssues() {
label.NumOpenIssues = label.NumIssues - label.NumClosedIssues label.NumOpenIssues = label.NumIssues - label.NumClosedIssues
return strings.Join(labels, ", "), err return strings.Join(labels, ", "), err
} }


func initalizeLabels(e Engine, repoID int64, labelTemplate string) error {
func initializeLabels(e Engine, repoID int64, labelTemplate string) error {
list, err := GetLabelTemplateFile(labelTemplate) list, err := GetLabelTemplateFile(labelTemplate)
if err != nil { if err != nil {
return ErrIssueLabelTemplateLoad{labelTemplate, err} return ErrIssueLabelTemplateLoad{labelTemplate, err}
return nil return nil
} }


// InitalizeLabels adds a label set to a repository using a template
func InitalizeLabels(ctx DBContext, repoID int64, labelTemplate string) error {
return initalizeLabels(ctx.e, repoID, labelTemplate)
// InitializeLabels adds a label set to a repository using a template
func InitializeLabels(ctx DBContext, repoID int64, labelTemplate string) error {
return initializeLabels(ctx.e, repoID, labelTemplate)
} }


func newLabel(e Engine, label *Label) error { func newLabel(e Engine, label *Label) error {


// NewLabel creates a new label for a repository // NewLabel creates a new label for a repository
func NewLabel(label *Label) error { func NewLabel(label *Label) error {
if !LabelColorPattern.MatchString(label.Color) {
return fmt.Errorf("bad color code: %s", label.Color)
}
return newLabel(x, label) return newLabel(x, label)
} }


return err return err
} }
for _, label := range labels { for _, label := range labels {
if !LabelColorPattern.MatchString(label.Color) {
return fmt.Errorf("bad color code: %s", label.Color)
}
if err := newLabel(sess, label); err != nil { if err := newLabel(sess, label); err != nil {
return err return err
} }


// UpdateLabel updates label information. // UpdateLabel updates label information.
func UpdateLabel(l *Label) error { func UpdateLabel(l *Label) error {
if !LabelColorPattern.MatchString(l.Color) {
return fmt.Errorf("bad color code: %s", l.Color)
}
return updateLabel(x, l) return updateLabel(x, l)
} }



+ 4
- 1
models/issue_label_test.go Переглянути файл

assert.NoError(t, PrepareTestDatabase()) assert.NoError(t, PrepareTestDatabase())
labels := []*Label{ labels := []*Label{
{RepoID: 2, Name: "labelName2", Color: "#123456"}, {RepoID: 2, Name: "labelName2", Color: "#123456"},
{RepoID: 3, Name: "labelName3", Color: "#234567"},
{RepoID: 3, Name: "labelName3", Color: "#23456F"},
} }
assert.Error(t, NewLabel(&Label{RepoID: 3, Name: "invalid Color", Color: ""}))
assert.Error(t, NewLabel(&Label{RepoID: 3, Name: "invalid Color", Color: "123456"}))
assert.Error(t, NewLabel(&Label{RepoID: 3, Name: "invalid Color", Color: "#12345G"}))
for _, label := range labels { for _, label := range labels {
AssertNotExistsBean(t, label) AssertNotExistsBean(t, label)
} }

+ 1
- 1
models/user.go Переглянути файл

} else if isExist { } else if isExist {
return ErrUserAlreadyExist{newUserName} return ErrUserAlreadyExist{newUserName}
} }
sess := x.NewSession() sess := x.NewSession()
defer sess.Close() defer sess.Close()
if err = sess.Begin(); err != nil { if err = sess.Begin(); err != nil {

+ 3
- 3
modules/repository/create.go Переглянути файл



// Initialize Issue Labels if selected // Initialize Issue Labels if selected
if len(opts.IssueLabels) > 0 { if len(opts.IssueLabels) > 0 {
if err = models.InitalizeLabels(ctx, repo.ID, opts.IssueLabels); err != nil {
return fmt.Errorf("initalizeLabels: %v", err)
if err = models.InitializeLabels(ctx, repo.ID, opts.IssueLabels); err != nil {
return fmt.Errorf("InitializeLabels: %v", err)
} }
} }


if stdout, err := git.NewCommand("update-server-info"). if stdout, err := git.NewCommand("update-server-info").
SetDescription(fmt.Sprintf("CreateRepository(git update-server-info): %s", repoPath)). SetDescription(fmt.Sprintf("CreateRepository(git update-server-info): %s", repoPath)).
RunInDir(repoPath); err != nil { RunInDir(repoPath); err != nil {
log.Error("CreateRepitory(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err)
log.Error("CreateRepository(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err)
return fmt.Errorf("CreateRepository(git update-server-info): %v", err) return fmt.Errorf("CreateRepository(git update-server-info): %v", err)
} }
} }

+ 1
- 1
modules/structs/issue_label.go Переглянути файл

Name string `json:"name" binding:"Required"` Name string `json:"name" binding:"Required"`
// required:true // required:true
// example: #00aabb // example: #00aabb
Color string `json:"color" binding:"Required;Size(7)"`
Color string `json:"color" binding:"Required"`
Description string `json:"description"` Description string `json:"description"`
} }



+ 23
- 1
routers/api/v1/repo/label.go Переглянути файл

package repo package repo


import ( import (
"fmt"
"net/http" "net/http"
"strconv" "strconv"
"strings"


"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
// responses: // responses:
// "201": // "201":
// "$ref": "#/responses/Label" // "$ref": "#/responses/Label"
// "422":
// "$ref": "#/responses/validationError"

form.Color = strings.Trim(form.Color, " ")
if len(form.Color) == 6 {
form.Color = "#" + form.Color
}
if !models.LabelColorPattern.MatchString(form.Color) {
ctx.Error(http.StatusUnprocessableEntity, "ColorPattern", fmt.Errorf("bad color code: %s", form.Color))
return
}


label := &models.Label{ label := &models.Label{
Name: form.Name, Name: form.Name,
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/Label" // "$ref": "#/responses/Label"
// "422":
// "$ref": "#/responses/validationError"


label, err := models.GetLabelInRepoByID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id")) label, err := models.GetLabelInRepoByID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id"))
if err != nil { if err != nil {
label.Name = *form.Name label.Name = *form.Name
} }
if form.Color != nil { if form.Color != nil {
label.Color = *form.Color
label.Color = strings.Trim(*form.Color, " ")
if len(label.Color) == 6 {
label.Color = "#" + label.Color
}
if !models.LabelColorPattern.MatchString(label.Color) {
ctx.Error(http.StatusUnprocessableEntity, "ColorPattern", fmt.Errorf("bad color code: %s", label.Color))
return
}
} }
if form.Description != nil { if form.Description != nil {
label.Description = *form.Description label.Description = *form.Description

+ 2
- 2
routers/repo/issue_label.go Переглянути файл

return return
} }


if err := models.InitalizeLabels(models.DefaultDBContext(), ctx.Repo.Repository.ID, form.TemplateName); err != nil {
if err := models.InitializeLabels(models.DefaultDBContext(), ctx.Repo.Repository.ID, form.TemplateName); err != nil {
if models.IsErrIssueLabelTemplateLoad(err) { if models.IsErrIssueLabelTemplateLoad(err) {
originalErr := err.(models.ErrIssueLabelTemplateLoad).OriginalError originalErr := err.(models.ErrIssueLabelTemplateLoad).OriginalError
ctx.Flash.Error(ctx.Tr("repo.issues.label_templates.fail_to_load_file", form.TemplateName, originalErr)) ctx.Flash.Error(ctx.Tr("repo.issues.label_templates.fail_to_load_file", form.TemplateName, originalErr))
ctx.Redirect(ctx.Repo.RepoLink + "/labels") ctx.Redirect(ctx.Repo.RepoLink + "/labels")
return return
} }
ctx.ServerError("InitalizeLabels", err)
ctx.ServerError("InitializeLabels", err)
return return
} }
ctx.Redirect(ctx.Repo.RepoLink + "/labels") ctx.Redirect(ctx.Repo.RepoLink + "/labels")

+ 6
- 0
templates/swagger/v1_json.tmpl Переглянути файл

"responses": { "responses": {
"201": { "201": {
"$ref": "#/responses/Label" "$ref": "#/responses/Label"
},
"422": {
"$ref": "#/responses/validationError"
} }
} }
} }
"responses": { "responses": {
"200": { "200": {
"$ref": "#/responses/Label" "$ref": "#/responses/Label"
},
"422": {
"$ref": "#/responses/validationError"
} }
} }
} }

Завантаження…
Відмінити
Зберегти