diff --git a/models/fixtures/label.yml b/models/fixtures/label.yml index 2242b90dcd..acfac74968 100644 --- a/models/fixtures/label.yml +++ b/models/fixtures/label.yml @@ -96,3 +96,14 @@ num_issues: 0 num_closed_issues: 0 archived_unix: 0 + +- + id: 10 + repo_id: 3 + org_id: 0 + name: repo3label1 + color: '#112233' + exclusive: false + num_issues: 0 + num_closed_issues: 0 + archived_unix: 0 diff --git a/models/issues/label.go b/models/issues/label.go index d80578193e..b9d24bbe99 100644 --- a/models/issues/label.go +++ b/models/issues/label.go @@ -349,6 +349,17 @@ func GetLabelIDsInRepoByNames(ctx context.Context, repoID int64, labelNames []st Find(&labelIDs) } +// GetLabelIDsInOrgByNames returns a list of labelIDs by names in a given org. +func GetLabelIDsInOrgByNames(ctx context.Context, orgID int64, labelNames []string) ([]int64, error) { + labelIDs := make([]int64, 0, len(labelNames)) + return labelIDs, db.GetEngine(ctx).Table("label"). + Where("org_id = ?", orgID). + In("name", labelNames). + Asc("name"). + Cols("id"). + Find(&labelIDs) +} + // BuildLabelNamesIssueIDsCondition returns a builder where get issue ids match label names func BuildLabelNamesIssueIDsCondition(labelNames []string) *builder.Builder { return builder.Select("issue_label.issue_id"). diff --git a/routers/api/v1/repo/issue_label.go b/routers/api/v1/repo/issue_label.go index cc517619e9..6507864a3c 100644 --- a/routers/api/v1/repo/issue_label.go +++ b/routers/api/v1/repo/issue_label.go @@ -335,6 +335,9 @@ func prepareForReplaceOrAdd(ctx *context.APIContext, form api.IssueLabelsOption) labelIDs = append(labelIDs, int64(rv.Float())) case reflect.String: labelNames = append(labelNames, rv.String()) + default: + ctx.Error(http.StatusBadRequest, "InvalidLabel", "a label must be an integer or a string") + return nil, nil, fmt.Errorf("invalid label") } } if len(labelIDs) > 0 && len(labelNames) > 0 { @@ -342,11 +345,20 @@ func prepareForReplaceOrAdd(ctx *context.APIContext, form api.IssueLabelsOption) return nil, nil, fmt.Errorf("invalid labels") } if len(labelNames) > 0 { - labelIDs, err = issues_model.GetLabelIDsInRepoByNames(ctx, ctx.Repo.Repository.ID, labelNames) + repoLabelIDs, err := issues_model.GetLabelIDsInRepoByNames(ctx, ctx.Repo.Repository.ID, labelNames) if err != nil { ctx.Error(http.StatusInternalServerError, "GetLabelIDsInRepoByNames", err) return nil, nil, err } + labelIDs = append(labelIDs, repoLabelIDs...) + if ctx.Repo.Owner.IsOrganization() { + orgLabelIDs, err := issues_model.GetLabelIDsInOrgByNames(ctx, ctx.Repo.Owner.ID, labelNames) + if err != nil { + ctx.Error(http.StatusInternalServerError, "GetLabelIDsInOrgByNames", err) + return nil, nil, err + } + labelIDs = append(labelIDs, orgLabelIDs...) + } } labels, err := issues_model.GetLabelsByIDs(ctx, labelIDs, "id", "repo_id", "org_id", "name", "exclusive") diff --git a/tests/integration/api_issue_label_test.go b/tests/integration/api_issue_label_test.go index 0e4cd8243b..c9cdd46b9a 100644 --- a/tests/integration/api_issue_label_test.go +++ b/tests/integration/api_issue_label_test.go @@ -117,27 +117,33 @@ func TestAPIAddIssueLabels(t *testing.T) { 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}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 6, RepoID: repo.ID}) owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + repoLabel := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 10, RepoID: repo.ID}) + orgLabel := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 4, OrgID: owner.ID}) - 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) + user1Session := loginUser(t, "user1") + token := getTokenForLoggedInUser(t, user1Session, auth_model.AccessTokenScopeWriteIssue) + + // add the org label and the repo label to the issue + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/labels", owner.Name, repo.Name, issue.Index) req := NewRequestWithJSON(t, "POST", urlStr, &api.IssueLabelsOption{ - Labels: []any{"label1", "label2"}, + Labels: []any{repoLabel.Name, orgLabel.Name}, }).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"}) + assert.ElementsMatch(t, apiLabelNames, []string{repoLabel.Name, orgLabel.Name}) + + // delete labels + req = NewRequest(t, "DELETE", urlStr).AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) } func TestAPIReplaceIssueLabels(t *testing.T) {