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

Support using label names when changing issue labels #30943

Merged
merged 6 commits into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 3 additions & 2 deletions modules/structs/issue_label.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,9 @@ type EditLabelOption struct {

// IssueLabelsOption a collection of labels
type IssueLabelsOption struct {
// list of label IDs
Labels []int64 `json:"labels"`
// Labels can be a list of integers representing label IDs
// or a list of strings representing label names
Labels []any `json:"labels"`
}

// LabelTemplate info of a Label template
Expand Down
29 changes: 28 additions & 1 deletion routers/api/v1/repo/issue_label.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
package repo

import (
"fmt"
"net/http"
"reflect"

issues_model "code.gitea.io/gitea/models/issues"
api "code.gitea.io/gitea/modules/structs"
Expand Down Expand Up @@ -317,7 +319,32 @@ func prepareForReplaceOrAdd(ctx *context.APIContext, form api.IssueLabelsOption)
return nil, nil, err
}

labels, err := issues_model.GetLabelsByIDs(ctx, form.Labels, "id", "repo_id", "org_id", "name", "exclusive")
var (
labelIDs []int64
labelNames []string
)
for _, label := range form.Labels {
rv := reflect.ValueOf(label)
switch rv.Kind() {
case reflect.Float64:
lunny marked this conversation as resolved.
Show resolved Hide resolved
labelIDs = append(labelIDs, int64(rv.Float()))
case reflect.String:
labelNames = append(labelNames, rv.String())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should handle the default case for strange input like [[1]].

}
}
if len(labelIDs) > 0 && len(labelNames) > 0 {
ctx.Error(http.StatusBadRequest, "InvalidLabels", "labels should be an array of strings or integers")
return nil, nil, fmt.Errorf("invalid labels")
}
if len(labelNames) > 0 {
labelIDs, err = issues_model.GetLabelIDsInRepoByNames(ctx, ctx.Repo.Repository.ID, labelNames)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetLabelIDsInRepoByNames", err)
return nil, nil, err
}
}

labels, err := issues_model.GetLabelsByIDs(ctx, labelIDs, "id", "repo_id", "org_id", "name", "exclusive")
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetLabelsByIDs", err)
return nil, nil, err
Expand Down
7 changes: 2 additions & 5 deletions templates/swagger/v1_json.tmpl

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

53 changes: 51 additions & 2 deletions tests/integration/api_issue_label_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ func TestAPIAddIssueLabels(t *testing.T) {
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/labels",
repo.OwnerName, repo.Name, issue.Index)
req := NewRequestWithJSON(t, "POST", urlStr, &api.IssueLabelsOption{
Labels: []int64{1, 2},
Labels: []any{1, 2},
}).AddTokenAuth(token)
resp := MakeRequest(t, req, http.StatusOK)
var apiLabels []*api.Label
Expand All @@ -114,6 +114,32 @@ func TestAPIAddIssueLabels(t *testing.T) {
unittest.AssertExistsAndLoadBean(t, &issues_model.IssueLabel{IssueID: issue.ID, LabelID: 2})
}

func TestAPIAddIssueLabelsWithLabelNames(t *testing.T) {
assert.NoError(t, unittest.LoadFixtures())

repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: repo.ID})
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})

session := loginUser(t, owner.Name)
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue)
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/labels",
repo.OwnerName, repo.Name, issue.Index)
req := NewRequestWithJSON(t, "POST", urlStr, &api.IssueLabelsOption{
Labels: []any{"label1", "label2"},
}).AddTokenAuth(token)
resp := MakeRequest(t, req, http.StatusOK)
var apiLabels []*api.Label
DecodeJSON(t, resp, &apiLabels)
assert.Len(t, apiLabels, unittest.GetCount(t, &issues_model.IssueLabel{IssueID: issue.ID}))

var apiLabelNames []string
for _, label := range apiLabels {
apiLabelNames = append(apiLabelNames, label.Name)
}
assert.ElementsMatch(t, apiLabelNames, []string{"label1", "label2"})
}

func TestAPIReplaceIssueLabels(t *testing.T) {
assert.NoError(t, unittest.LoadFixtures())

Expand All @@ -127,7 +153,7 @@ func TestAPIReplaceIssueLabels(t *testing.T) {
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/labels",
owner.Name, repo.Name, issue.Index)
req := NewRequestWithJSON(t, "PUT", urlStr, &api.IssueLabelsOption{
Labels: []int64{label.ID},
Labels: []any{label.ID},
}).AddTokenAuth(token)
resp := MakeRequest(t, req, http.StatusOK)
var apiLabels []*api.Label
Expand All @@ -140,6 +166,29 @@ func TestAPIReplaceIssueLabels(t *testing.T) {
unittest.AssertExistsAndLoadBean(t, &issues_model.IssueLabel{IssueID: issue.ID, LabelID: label.ID})
}

func TestAPIReplaceIssueLabelsWithLabelNames(t *testing.T) {
assert.NoError(t, unittest.LoadFixtures())

repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: repo.ID})
label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{RepoID: repo.ID})
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})

session := loginUser(t, owner.Name)
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue)
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/labels",
owner.Name, repo.Name, issue.Index)
req := NewRequestWithJSON(t, "PUT", urlStr, &api.IssueLabelsOption{
Labels: []any{label.Name},
}).AddTokenAuth(token)
resp := MakeRequest(t, req, http.StatusOK)
var apiLabels []*api.Label
DecodeJSON(t, resp, &apiLabels)
if assert.Len(t, apiLabels, 1) {
assert.EqualValues(t, label.Name, apiLabels[0].Name)
}
}

func TestAPIModifyOrgLabels(t *testing.T) {
assert.NoError(t, unittest.LoadFixtures())

Expand Down