mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 21:28:11 +09:00 
			
		
		
		
	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" />
This commit is contained in:
		| @@ -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 | ||||
|   | ||||
| @@ -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" | ||||
| @@ -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: | ||||
| 			labelIDs = append(labelIDs, int64(rv.Float())) | ||||
| 		case reflect.String: | ||||
| 			labelNames = append(labelNames, rv.String()) | ||||
| 		} | ||||
| 	} | ||||
| 	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 | ||||
|   | ||||
							
								
								
									
										7
									
								
								templates/swagger/v1_json.tmpl
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										7
									
								
								templates/swagger/v1_json.tmpl
									
									
									
										generated
									
									
									
								
							| @@ -21897,12 +21897,9 @@ | ||||
|       "type": "object", | ||||
|       "properties": { | ||||
|         "labels": { | ||||
|           "description": "list of label IDs", | ||||
|           "description": "Labels can be a list of integers representing label IDs\nor a list of strings representing label names", | ||||
|           "type": "array", | ||||
|           "items": { | ||||
|             "type": "integer", | ||||
|             "format": "int64" | ||||
|           }, | ||||
|           "items": {}, | ||||
|           "x-go-name": "Labels" | ||||
|         } | ||||
|       }, | ||||
|   | ||||
| @@ -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 | ||||
| @@ -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()) | ||||
|  | ||||
| @@ -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 | ||||
| @@ -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()) | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user