Skip to content

Commit b3beaed

Browse files
authored
Support using label names when changing issue labels (#30943)
Resolve #30917 Make the APIs for adding labels and replacing labels support both label IDs and label names so the [`actions/labeler`](https://github.com/actions/labeler) action can work in Gitea. <img width="600px" src="https://github.com/go-gitea/gitea/assets/15528715/7835c771-f637-4c57-9ce5-e4fbf56fa0d3" />
1 parent 2442ead commit b3beaed

File tree

4 files changed

+84
-10
lines changed

4 files changed

+84
-10
lines changed

modules/structs/issue_label.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,9 @@ type EditLabelOption struct {
4747

4848
// IssueLabelsOption a collection of labels
4949
type IssueLabelsOption struct {
50-
// list of label IDs
51-
Labels []int64 `json:"labels"`
50+
// Labels can be a list of integers representing label IDs
51+
// or a list of strings representing label names
52+
Labels []any `json:"labels"`
5253
}
5354

5455
// LabelTemplate info of a Label template

routers/api/v1/repo/issue_label.go

+28-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
package repo
66

77
import (
8+
"fmt"
89
"net/http"
10+
"reflect"
911

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

320-
labels, err := issues_model.GetLabelsByIDs(ctx, form.Labels, "id", "repo_id", "org_id", "name", "exclusive")
322+
var (
323+
labelIDs []int64
324+
labelNames []string
325+
)
326+
for _, label := range form.Labels {
327+
rv := reflect.ValueOf(label)
328+
switch rv.Kind() {
329+
case reflect.Float64:
330+
labelIDs = append(labelIDs, int64(rv.Float()))
331+
case reflect.String:
332+
labelNames = append(labelNames, rv.String())
333+
}
334+
}
335+
if len(labelIDs) > 0 && len(labelNames) > 0 {
336+
ctx.Error(http.StatusBadRequest, "InvalidLabels", "labels should be an array of strings or integers")
337+
return nil, nil, fmt.Errorf("invalid labels")
338+
}
339+
if len(labelNames) > 0 {
340+
labelIDs, err = issues_model.GetLabelIDsInRepoByNames(ctx, ctx.Repo.Repository.ID, labelNames)
341+
if err != nil {
342+
ctx.Error(http.StatusInternalServerError, "GetLabelIDsInRepoByNames", err)
343+
return nil, nil, err
344+
}
345+
}
346+
347+
labels, err := issues_model.GetLabelsByIDs(ctx, labelIDs, "id", "repo_id", "org_id", "name", "exclusive")
321348
if err != nil {
322349
ctx.Error(http.StatusInternalServerError, "GetLabelsByIDs", err)
323350
return nil, nil, err

templates/swagger/v1_json.tmpl

+2-5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/integration/api_issue_label_test.go

+51-2
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ func TestAPIAddIssueLabels(t *testing.T) {
104104
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/labels",
105105
repo.OwnerName, repo.Name, issue.Index)
106106
req := NewRequestWithJSON(t, "POST", urlStr, &api.IssueLabelsOption{
107-
Labels: []int64{1, 2},
107+
Labels: []any{1, 2},
108108
}).AddTokenAuth(token)
109109
resp := MakeRequest(t, req, http.StatusOK)
110110
var apiLabels []*api.Label
@@ -114,6 +114,32 @@ func TestAPIAddIssueLabels(t *testing.T) {
114114
unittest.AssertExistsAndLoadBean(t, &issues_model.IssueLabel{IssueID: issue.ID, LabelID: 2})
115115
}
116116

117+
func TestAPIAddIssueLabelsWithLabelNames(t *testing.T) {
118+
assert.NoError(t, unittest.LoadFixtures())
119+
120+
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
121+
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: repo.ID})
122+
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
123+
124+
session := loginUser(t, owner.Name)
125+
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue)
126+
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/labels",
127+
repo.OwnerName, repo.Name, issue.Index)
128+
req := NewRequestWithJSON(t, "POST", urlStr, &api.IssueLabelsOption{
129+
Labels: []any{"label1", "label2"},
130+
}).AddTokenAuth(token)
131+
resp := MakeRequest(t, req, http.StatusOK)
132+
var apiLabels []*api.Label
133+
DecodeJSON(t, resp, &apiLabels)
134+
assert.Len(t, apiLabels, unittest.GetCount(t, &issues_model.IssueLabel{IssueID: issue.ID}))
135+
136+
var apiLabelNames []string
137+
for _, label := range apiLabels {
138+
apiLabelNames = append(apiLabelNames, label.Name)
139+
}
140+
assert.ElementsMatch(t, apiLabelNames, []string{"label1", "label2"})
141+
}
142+
117143
func TestAPIReplaceIssueLabels(t *testing.T) {
118144
assert.NoError(t, unittest.LoadFixtures())
119145

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

169+
func TestAPIReplaceIssueLabelsWithLabelNames(t *testing.T) {
170+
assert.NoError(t, unittest.LoadFixtures())
171+
172+
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
173+
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: repo.ID})
174+
label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{RepoID: repo.ID})
175+
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
176+
177+
session := loginUser(t, owner.Name)
178+
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue)
179+
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/labels",
180+
owner.Name, repo.Name, issue.Index)
181+
req := NewRequestWithJSON(t, "PUT", urlStr, &api.IssueLabelsOption{
182+
Labels: []any{label.Name},
183+
}).AddTokenAuth(token)
184+
resp := MakeRequest(t, req, http.StatusOK)
185+
var apiLabels []*api.Label
186+
DecodeJSON(t, resp, &apiLabels)
187+
if assert.Len(t, apiLabels, 1) {
188+
assert.EqualValues(t, label.Name, apiLabels[0].Name)
189+
}
190+
}
191+
143192
func TestAPIModifyOrgLabels(t *testing.T) {
144193
assert.NoError(t, unittest.LoadFixtures())
145194

0 commit comments

Comments
 (0)